Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:laboratoare:2022:lab7 [2023/11/20 21:28]
dan.tudose [Project Overview]
iothings:laboratoare:2022:lab7 [2025/09/28 17:46] (current)
dan.tudose [UUID]
Line 72: Line 72:
 In essence, the UUID serves the purpose of uniquely identifying information. For example, it can distinguish a specific service provided by a Bluetooth device. In essence, the UUID serves the purpose of uniquely identifying information. For example, it can distinguish a specific service provided by a Bluetooth device.
  
 +===== Advertise on BLE  =====
 +
 +Build the example below, which advertises the board on BLE. Install on your phone an app that scans nearby Bluetooth devices, such as [[https://​play.google.com/​store/​apps/​details?​id=no.nordicsemi.android.mcp&​hl=en&​pli=1| nRF Connect]]. Check if your device is in the list.
 +
 +<code C main.cpp>​
 +#include <​Arduino.h>​
 +#include <​NimBLEDevice.h>​
 +
 +static const char* DEVICE_NAME = "​ESP32-C6 Demo";
 +static NimBLEUUID SERVICE_UUID("​6E400001-B5A3-F393-E0A9-E50E24DCCA9E"​);​
 +static NimBLEUUID CHAR_UUID ​  ​("​6E400002-B5A3-F393-E0A9-E50E24DCCA9E"​);​
 +
 +NimBLEServer* ​        ​gServer = nullptr;
 +NimBLEService* ​       gService = nullptr;
 +NimBLECharacteristic* gChar = nullptr;
 +
 +void startBLE() {
 +  NimBLEDevice::​init(DEVICE_NAME);​
 +
 +  gServer ​ = NimBLEDevice::​createServer();​
 +  gService = gServer->​createService(SERVICE_UUID);​
 +
 +  gChar = gService->​createCharacteristic(
 +    CHAR_UUID,
 +    NIMBLE_PROPERTY::​READ
 +  );
 +  gChar->​setValue("​Hello from ESP32-C6!"​);​
 +  gService->​start();​
 +
 +  NimBLEAdvertising* adv = NimBLEDevice::​getAdvertising();​
 +
 +  // Advertise our service UUID
 +  adv->​addServiceUUID(SERVICE_UUID);​
 +
 +  // (v2.x) Build advertising + scan-response payloads explicitly
 +  NimBLEAdvertisementData advData;
 +  advData.setFlags(0x06);​ // LE General Discoverable + BR/EDR Not Supported
 +
 +  NimBLEAdvertisementData scanData;
 +  scanData.setName(DEVICE_NAME);​ // put the name in scan response
 +  // you can also add manufacturer data here if you want:
 +  // std::string mfg = "​\x34\x12C6";​ scanData.setManufacturerData(mfg);​
 +
 +  adv->​setAdvertisementData(advData);​
 +  adv->​setScanResponseData(scanData);​
 +
 +  // Appearance is still supported
 +  adv->​setAppearance(0x0200);​ // Generic Tag
 +
 +  NimBLEDevice::​startAdvertising();​
 +}
 +
 +void setup() {
 +  Serial.begin(115200);​
 +  while (!Serial) { delay(10); }
 +  startBLE();
 +  Serial.println("​Advertising as ESP32-C6 Demo. Open nRF Connect -> Scan."​);​
 +}
 +
 +void loop() {
 +  delay(1000);​
 +}
 +
 +</​code>​
 +
 +{{:​iothings:​laboratoare:​lab1-ble-scanner.jpg?​300|}}
  
 ===== Project Overview ===== ===== Project Overview =====
Line 83: Line 149:
 We will adhere to the default UUIDs designated for the Environmental Sensing Profile and its associated characteristics. We will adhere to the default UUIDs designated for the Environmental Sensing Profile and its associated characteristics.
  
-To access the default assigned UUID numbers, visit [[https://​www.bluetooth.com/​specifications/​assigned-numbers/​| this page]] and refer to the Assigned Numbers Document (PDF). By searching for the Environmental Sensing Service within the document, you can explore all the authorized characteristics applicable to this service. It's evident that the Environmental Sensing Service supports temperature,​ humidity, and pressure readings.+To access the default assigned UUID numbers, visit [[https://​www.bluetooth.com/​specifications/​assigned-numbers/​| this page]] and refer to the [[https://​btprodspecificationrefs.blob.core.windows.net/​assigned-numbers/​Assigned%20Number%20Types/​Assigned_Numbers.pdf| ​Assigned Numbers Document (PDF)]]. By searching for the Environmental Sensing Service within the document, you can explore all the authorized characteristics applicable to this service. It's evident that the Environmental Sensing Service supports temperature,​ humidity, and pressure readings.
  
 {{ :​iothings:​laboratoare:​2022:​environmental-sensing-service-permitted-characteristics.png?​600 |}} {{ :​iothings:​laboratoare:​2022:​environmental-sensing-service-permitted-characteristics.png?​600 |}}
Line 94: Line 160:
   * humidity: **0x246F**   * humidity: **0x246F**
  
 +==== Mobile App ====
 +
 +{{ :​iothings:​laboratoare:​2022:​nrf_connect.png?​150|}}
 +
 +
 +To verify the proper creation of the BLE Server and to receive notifications for temperature,​ humidity, and pressure, we'll utilize a smartphone application.
 +
 +Most contemporary smartphones come equipped with BLE capabilities. You can check your smartphone'​s specifications to confirm its BLE compatibility.
 +
 +Note: The smartphone can function as either a client or a server. In this context, it will act as the client, establishing a connection with the Sparrow BLE server.
 +
 +For our testing purposes, we'll employ a free application named nRF Connect for Mobile, developed by Nordic Semi. This app is available on both Android ([[https://​play.google.com/​store/​apps/​details?​id=no.nordicsemi.android.mcp&​hl=en_US| Google Play Store]]) and iOS. To install the app, simply go to the Google Play Store or App Store, search for "nRF Connect for Mobile,"​ and proceed with the installation.
 +
 +==== Sparrow BLE Service ====
 +
 +Here are the steps to create an BLE peripheral with an Environmental Sensing BLE service with temperature,​ humidity, and pressure, characteristics:​
 +
 +  - Create a BLE device (server) with a name of your choice (we’ll call it ESP32_BME680,​ but you can call it any other name).
 +  - Create an Environmental Sensing service (UUID: 0x181A).
 +  - Add characteristics to that service: pressure (0x2A6D), temperature (0x2A6E) and humidity (0x246F)
 +  - Add descriptors to the characteristics.
 +  - Start the BLE server.  ​
 +  - Start advertising so BLE clients can connect and read the characteristics.
 +  - Once a connection is established with a client, it will write new values on the characteristics and will notify the client, every time there’s a change.
 +
 +
 +Copy the following code to the Arduino IDE, modify it and upload it to your board.
 +
 +<code C>
 +#include <​Arduino.h>​
 +#include <​Wire.h>​
 +#include <​Adafruit_BME680.h>​
 +#include <​NimBLEDevice.h>​
 +
 +/* ============================
 +   User config
 +   ​============================ */
 +static const char* DEVICE_NAME = "​ESP32-C6 Env"; ​  // shows in scan response
 +#define I2C_SDA_PIN 21                             // adjust for your board
 +#define I2C_SCL_PIN 22
 +#define BME680_I2C_ADDR 0x77                      // try 0x76 if needed
 +static const uint32_t MEAS_INTERVAL_MS = 2000;    // update period
 +
 +/* ============================
 +   BLE UUIDs (Environmental Sensing)
 +   ​============================ */
 +static NimBLEUUID ESS_UUID((uint16_t)0x181A);​
 +static NimBLEUUID TEMP_UUID((uint16_t)0x2A6E); ​ // Temperature
 +static NimBLEUUID PRES_UUID((uint16_t)0x2A6D); ​ // Pressure
 +static NimBLEUUID HUM_UUID ((uint16_t)0x2A6F); ​ // Humidity
 +
 +/* ============================
 +   ​Globals
 +   ​============================ */
 +NimBLEServer* ​        ​gServer ​  = nullptr;
 +NimBLEService* ​       gService ​ = nullptr;
 +NimBLECharacteristic* gTempChar = nullptr;
 +NimBLECharacteristic* gPresChar = nullptr;
 +NimBLECharacteristic* gHumChar ​ = nullptr;
 +
 +Adafruit_BME680 bme; // I2C
 +uint32_t lastMeas = 0;
 +
 +/* ============================
 +   ​Helpers:​ encode per SIG formats (little-endian)
 +   ​============================ */
 +static void setTempCentiDeg(NimBLECharacteristic* ch, float celsius) {
 +  // sint16, 0.01 °C
 +  int32_t raw = lroundf(celsius * 100.0f);
 +  if (raw >  32767) raw =  32767;
 +  if (raw < -32768) raw = -32768;
 +  int16_t v = (int16_t)raw;​
 +  uint8_t buf[2] = { (uint8_t)(v & 0xFF), (uint8_t)((v >> 8) & 0xFF) };
 +  ch->​setValue(buf,​ sizeof(buf));​
 +}
 +
 +static void setHumidityCentiPct(NimBLECharacteristic* ch, float rh) {
 +  // uint16, 0.01 %RH (clip 0..100%)
 +  if (rh < 0)   rh = 0;
 +  if (rh > 100) rh = 100;
 +  uint32_t raw = lroundf(rh * 100.0f);
 +  if (raw > 0xFFFF) raw = 0xFFFF;
 +  uint16_t v = (uint16_t)raw;​
 +  uint8_t buf[2] = { (uint8_t)(v & 0xFF), (uint8_t)((v >> 8) & 0xFF) };
 +  ch->​setValue(buf,​ sizeof(buf));​
 +}
 +
 +static void setPressureDeciPa(NimBLECharacteristic* ch, float pascals) {
 +  // uint32, 0.1 Pa
 +  if (pascals < 0) pascals = 0;
 +  uint64_t raw = llroundf(pascals * 10.0f);
 +  if (raw > 0xFFFFFFFFULL) raw = 0xFFFFFFFFULL;​
 +  uint32_t v = (uint32_t)raw;​
 +  uint8_t buf[4] = {
 +    (uint8_t)(v & 0xFF),
 +    (uint8_t)((v >> 8) & 0xFF),
 +    (uint8_t)((v >> 16) & 0xFF),
 +    (uint8_t)((v >> 24) & 0xFF)
 +  };
 +  ch->​setValue(buf,​ sizeof(buf));​
 +}
 +
 +/* Add Characteristic Presentation Format (0x2904) */
 +static void addCPF(NimBLECharacteristic* ch, uint8_t format, int8_t exponent, uint16_t unit) {
 +  // 7 bytes: format, exponent, unit(LE), namespace(1=1),​ description(2=0)
 +  uint8_t cpf[7] = {
 +    format, (uint8_t)exponent,​
 +    (uint8_t)(unit & 0xFF), (uint8_t)((unit >> 8) & 0xFF),
 +    0x01, 0x00, 0x00
 +  };
 +  ch->​createDescriptor("​2904"​)->​setValue(cpf,​ sizeof(cpf));​
 +}
 +
 +/* ============================
 +   BLE setup (keeps your adv/​scan-response style)
 +   ​============================ */
 +void startBLE() {
 +  NimBLEDevice::​init(DEVICE_NAME);​
 +  NimBLEDevice::​setPower(ESP_PWR_LVL_P9); ​ // strong TX for gateways; lower if needed
 +  NimBLEDevice::​setSecurityAuth(false,​ false, true); // no bonding; SC on
 +
 +  gServer ​ = NimBLEDevice::​createServer();​
 +  gService = gServer->​createService(ESS_UUID);​
 +
 +  // READ + NOTIFY for all three characteristics
 +  auto props = (NIMBLE_PROPERTY::​READ | NIMBLE_PROPERTY::​NOTIFY);​
 +  gTempChar = gService->​createCharacteristic(TEMP_UUID,​ props);
 +  gPresChar = gService->​createCharacteristic(PRES_UUID,​ props);
 +  gHumChar ​ = gService->​createCharacteristic(HUM_UUID, ​ props);
 +
 +  // Presentation formats
 +  const uint8_t GATT_FORMAT_SINT16 = 0x0E;
 +  const uint8_t GATT_FORMAT_UINT16 = 0x0D;
 +  const uint8_t GATT_FORMAT_UINT32 = 0x10;
 +  addCPF(gTempChar,​ GATT_FORMAT_SINT16,​ -2, 0x272F); // Celsius
 +  addCPF(gPresChar,​ GATT_FORMAT_UINT32,​ -1, 0x2724); // Pascal, 0.1 exponent
 +  addCPF(gHumChar, ​ GATT_FORMAT_UINT16,​ -2, 0x27AD); // Percentage
 +
 +  // Initialize with placeholder values so READ works before first measurement
 +  setTempCentiDeg(gTempChar,​ 0.0f);
 +  setPressureDeciPa(gPresChar,​ 101325.0f);
 +  setHumidityCentiPct(gHumChar,​ 0.0f);
 +
 +  gService->​start();​
 +
 +  NimBLEAdvertising* adv = NimBLEDevice::​getAdvertising();​
 +
 +  // Advertise the standard Environmental Sensing Service UUID
 +  adv->​addServiceUUID(ESS_UUID);​
 +
 +  // Your explicit adv + scan-response payloads
 +  NimBLEAdvertisementData advData;
 +  advData.setFlags(0x06);​ // LE General Discoverable + BR/EDR Not Supported
 +  adv->​setAdvertisementData(advData);​
 +
 +  NimBLEAdvertisementData scanData;
 +  scanData.setName(DEVICE_NAME);​
 +  adv->​setScanResponseData(scanData);​
 +
 +  // Appearance: Generic Thermometer (0x0300)
 +  adv->​setAppearance(0x0300);​
 +
 +  NimBLEDevice::​startAdvertising();​
 +  Serial.println("​Advertising Environmental Sensing Service. Open nRF Connect -> Scan."​);​
 +}
 +
 +/* ============================
 +   ​BME680 setup
 +   ​============================ */
 +bool startBME680() {
 +  Wire.begin(I2C_SDA_PIN,​ I2C_SCL_PIN);​
 +  if (!bme.begin(BME680_I2C_ADDR)) {
 +    Serial.println("​[BME680] Not found on 0x77, trying 0x76..."​);​
 +    if (!bme.begin(0x76)) {
 +      Serial.println("​[BME680] Sensor not found. Check wiring and power."​);​
 +      return false;
 +    }
 +  }
 +
 +  // Oversampling & filter for stable readings
 +  bme.setTemperatureOversampling(BME680_OS_8X);​
 +  bme.setHumidityOversampling(BME680_OS_2X);​
 +  bme.setPressureOversampling(BME680_OS_4X);​
 +  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);​
 +
 +  // Gas heater off (not needed for T/RH/P)
 +  bme.setGasHeater(0,​ 0);
 +  return true;
 +}
 +
 +/* ============================
 +   ​Measurement + GATT update
 +   ​============================ */
 +void updateMeasurements() {
 +  if (!bme.performReading()) {
 +    Serial.println("​[BME680] performReading() failed"​);​
 +    return;
 +  }
 +
 +  float c = bme.temperature; ​ // °C
 +  float h = bme.humidity; ​    // %RH
 +  float p = bme.pressure; ​    // Pa
 +
 +  setTempCentiDeg(gTempChar,​ c);
 +  setHumidityCentiPct(gHumChar,​ h);
 +  setPressureDeciPa(gPresChar,​ p);
 +
 +  // Send notifications to any subscribed client
 +  gTempChar->​notify(true);​
 +  gHumChar->​notify(true);​
 +  gPresChar->​notify(true);​
 +
 +  Serial.printf("​[DATA] T=%.2f°C ​ RH=%.2f%% ​ P=%.1f Pa\n", c, h, p);
 +}
 +
 +/* ============================
 +   ​Arduino entry points
 +   ​============================ */
 +void setup() {
 +  Serial.begin(115200);​
 +  while (!Serial) { delay(10); }
 +
 +  if (!startBME680()) {
 +    // We still start BLE so you can see/connect even if sensor is missing
 +  }
 +
 +  startBLE();
 +}
 +
 +void loop() {
 +  uint32_t now = millis();
 +  if (now - lastMeas >= MEAS_INTERVAL_MS) {
 +    lastMeas = now;
 +    updateMeasurements();​
 +  }
 +  delay(10);
 +}
 +
 +</​code>​
 +
 +Upload the code to your board. After uploading, open the Serial Monitor, and restart the Sparrow by pressing the RST/EN button. ​
 +
 +You should get a data reading messages in the Serial Monitor.
 +
 +Then, go to your smartphone, open the nRF Connect app from Nordic, and start scanning for new devices. You should find a device called **ESP32-C6 Env**, this is the BLE server name you defined earlier.
 +
 +Connect to it. You’ll see that it displays the Environmental Sensing service with the temperature,​ humidity, and pressure characteristics. Click on the down arrows to activate the notifications.
 +
 +Then, click on the second icon (the one that looks like a " mark) at the left to change the format. You can change to unsigned int for all characteristics. You’ll start seeing the temperature,​ humidity, and pressure values being reported every 2 seconds.
 +
 +===== Web BLE Application =====
 +
 +Follow the tutorial [[https://​randomnerdtutorials.com/​esp32-web-bluetooth/​| here]] to learn how to create a Web application that connects directly to your Sparrow ESP32 board. You can use the web app just like a normal phone application to send and receive information over BLE from your device. ​
  
 +<note important>​Web BLE is not currently supported by iOS phones </​note>​
  
 +Build the application in the tutorial and deploy the web page in your GitHub account. ​
  
 +=== Assignment ===
 +<​note>​ Modify the web page and the BLE app to display the BME680 sensor data (temperature,​ pressure and humidity). </​note>​
iothings/laboratoare/2022/lab7.1700508486.txt.gz · Last modified: 2023/11/20 21:28 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0