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:59]
dan.tudose [Sparrow BLE Service]
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 123: Line 189:
  
 <code C> <code C>
-#include <BLEDevice.h> +#include <Arduino.h> 
-#include <BLEServer.h> +#include <Wire.h> 
-#include <BLEUtils.h> +#include <Adafruit_BME680.h> 
-#include <BLE2902.h> +#include <NimBLEDevice.h>
-#include "​Zanshin_BME680.h" ​+
  
-//BLE server name +/* ============================ 
-#define bleServerName ​"Sparrow_BME680"+   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
  
-// Default UUID for Environmental Sensing ​Service +/* ============================ 
-// https://www.bluetooth.com/​specifications/​assigned-numbers+   BLE UUIDs (Environmental Sensing) 
-#define SERVICE_UUID (BLEUUID((uint16_t)0x181A))+   ============================ */ 
 +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
  
-// Temperature Characteristic and Descriptor (default UUID) +/* ============================ 
-// Check the default UUIDs here: https://​www.bluetooth.com/​specifications/​assigned-numbers/​ +   Globals 
-BLECharacteristic temperatureCharacteristic(BLEUUID((uint16_t)0x2A6E),​ BLECharacteristic::​PROPERTY_NOTIFY)+   ​============================ *
-BLEDescriptor temperatureDescriptor(BLEUUID((uint16_t)0x2902));+NimBLEServer* ​        ​gServer ​  = nullptr; 
 +NimBLEService* ​       gService ​ = nullptr; 
 +NimBLECharacteristic* gTempChar = nullptr; 
 +NimBLECharacteristic* gPresChar = nullptr
 +NimBLECharacteristic* gHumChar ​ = nullptr;
  
-// Humidity Characteristic and Descriptor (default UUID) +Adafruit_BME680 bme; // I2C 
-BLECharacteristic humidityCharacteristic(BLEUUID((uint16_t)0x2A6F),​ BLECharacteristic::​PROPERTY_NOTIFY);​ +uint32_t lastMeas = 0;
-BLEDescriptor humidityDescriptor(BLEUUID((uint16_t)0x2902));+
  
-// Pressure Characteristic and Descriptor ​(default UUID+/* ============================ 
-BLECharacteristic pressureCharacteristic(BLEUUID((uint16_t)0x2A6D), BLECharacteristic::​PROPERTY_NOTIFY); +   ​Helpers:​ encode per SIG formats (little-endian) 
-BLEDescriptor pressureDescriptor(BLEUUID((uint16_t)0x2902));+   ​============================ */ 
 +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 < -32768raw = -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));​ 
 +}
  
-// Create a sensor object +static void setHumidityCentiPct(NimBLECharacteristic* ch, float rh) { 
-BME680_Class BME680;+  ​// 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));​ 
 +}
  
-bool deviceConnected ​false;+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));​ 
 +}
  
-//Setup callbacks onConnect and onDisconnect +/* Add Characteristic Presentation Format (0x2904) *
-class MyServerCallbacks:​ public BLEServerCallbacks ​+static void addCPF(NimBLECharacteristic* ch, uint8_t format, int8_t exponent, uint16_t unit) 
-  ​void onConnect(BLEServer* pServer) { +  ​// 7 bytes: format, exponent, unit(LE), namespace(1=1),​ description(2=0) 
-    ​deviceConnected = true; +  uint8_t cpf[7] = 
-    ​Serial.println("​Device Connected"​);+    ​format, (uint8_t)exponent,​ 
 +    (uint8_t)(unit & 0xFF), (uint8_t)((unit >> 8) & 0xFF), 
 +    0x01, 0x00, 0x00
   };   };
-  void onDisconnect(BLEServerpServer) { +  ​ch->​createDescriptor("​2904"​)->​setValue(cpf,​ sizeof(cpf));​ 
-    ​deviceConnected = false+
-    Serial.println("​Device Disconnected");+ 
 +/* ============================ 
 +   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();​ 
 + 
 +  NimBLEAdvertisingadv = 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; 
 +    }
   }   }
-}; 
  
-void setup() { +  // Oversampling & filter for stable readings 
-  ​// Start serial communication ​ +  bme.setTemperatureOversampling(BME680_OS_8X); 
-  ​Serial.begin(115200);+  ​bme.setHumidityOversampling(BME680_OS_2X);​ 
 +  bme.setPressureOversampling(BME680_OS_4X);​ 
 +  ​bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  
-  // Start BME sensor +  // Gas heater off (not needed ​for T/RH/P
-  while (!BME680.begin(I2C_STANDARD_MODE)) {  // Start BME680 using I2C, use first device found +  ​bme.setGasHeater(00); 
-    Serial.print(F("​- ​ Unable to find BME680. Trying again in 5 seconds.\n"​));​ +  ​return true
-    delay(5000);​ +}
-  }  // of loop until device is located +
-  Serial.print(F("​- Setting 16x oversampling ​for all sensors\n"​));​ +
-  BME680.setOversampling(TemperatureSensor,​ Oversample16);  ​// Use enumerated type values +
-  BME680.setOversampling(HumiditySensor,​ Oversample16);     // Use enumerated type values +
-  ​BME680.setOversampling(PressureSensorOversample16);     // Use enumerated type values +
-  ​Serial.print(F("​- Setting IIR filter to a value of 4 samples\n"​))+
-  ​BME680.setIIRFilter(IIR4); ​ // Use enumerated type values +
-  Serial.print(F("​- Setting gas measurement to 320\xC2\xB0\x43 for 150ms\n"​)); ​ // "​degC"​ symbols +
-  BME680.setGas(320,​ 150);  // 320 deg.C for 150 milliseconds+
  
-  // Create the BLE Device +/* ============================ 
-  ​BLEDevice::​init(bleServerName);+   ​Measurement + GATT update 
 +   ​============================ */ 
 +void updateMeasurements() { 
 +  ​if (!bme.performReading()) { 
 +    Serial.println("​[BME680] performReading() failed"​); 
 +    return; 
 +  }
  
-  // Create the BLE Server +  ​float c = bme.temperature;  ​// °C 
-  ​BLEServer *pServer ​BLEDevice::​createServer()+  ​float h bme.humidity    // %RH 
-  ​pServer->​setCallbacks(new MyServerCallbacks());+  ​float p = bme.pressure    // Pa
  
-  ​// Create the BLE Service +  ​setTempCentiDeg(gTempChar,​ c); 
-  ​BLEService *bmeService = pServer->​createService(SERVICE_UUID);+  setHumidityCentiPct(gHumChar,​ h); 
 +  ​setPressureDeciPa(gPresChar, p);
  
-  // Create BLE Characteristics and corresponding Descriptors +  // Send notifications to any subscribed client 
-  ​bmeService->addCharacteristic(&​temperatureCharacteristic); +  ​gTempChar->notify(true); 
-  ​temperatureCharacteristic.addDescriptor(&​temperatureDescriptor); +  ​gHumChar->​notify(true); 
-  ​ +  ​gPresChar->notify(true);
-  bmeService->addCharacteristic(&​humidityCharacteristic);​ +
-  humidityCharacteristic.addDescriptor(&​humidityDescriptor);+
  
-  ​bmeService->​addCharacteristic(&​pressureCharacteristic);​ +  ​Serial.printf("​[DATA] T=%.2f°C ​ RH=%.2f%%  P=%.1f Pa\n", c, h, p); 
-  pressureCharacteristic.addDescriptor(&​pressureDescriptor); +}
-   +
-  // Start the service +
-  bmeService->​start();​+
  
-  ​// Start advertising +/* ============================ 
-  ​pServer->​getAdvertising()->start(); +   ​Arduino entry points 
-  Serial.println("​Waiting a client connection to notify..."​);+   ​============================ *
 +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() { void loop() {
-  ​if (deviceConnected) { +  ​uint32_t now millis(); 
-     +  ​if (now - lastMeas >MEAS_INTERVAL_MS{ 
-  static int32_t ​ temp, hum, pres, gas;  // BME readings +    ​lastMeas ​now
-   +    ​updateMeasurements();
-  BME680.getSensorData(temp,​ hum, pres, gas);  // Get readings +
-   +
-  float t = (float)((int8_t)(temp / 100)); +
-  ​float h = (float)((int8_t)(humidity / 1000)); +
-  float p (float)((int16_t)(pressure / 100)); +
-  +
-     +
-    //Notify temperature reading +
-    ​uint16_t temperature ​(uint16_t)t+
-    ​//Set temperature Characteristic value and notify connected client +
-    temperatureCharacteristic.setValue(temperature);​ +
-    temperatureCharacteristic.notify();​ +
-    Serial.print("​Temperature Celsius: "); +
-    Serial.print(t);​ +
-    Serial.println("​ ºC"​);​ +
-    +
-    //Notify humidity reading +
-    uint16_t humidity = (uint16_t)h;​ +
-    //Set humidity Characteristic value and notify connected client +
-    humidityCharacteristic.setValue(humidity);​ +
-    humidityCharacteristic.notify(); ​   +
-    Serial.print("​Humidity:​ "); +
-    Serial.print(h);​ +
-    Serial.println("​ %"); +
- +
-    //Notify pressure reading +
-    uint16_t pressure = (uint16_t)p;​ +
-    //Set humidity Characteristic value and notify connected client +
-    pressureCharacteristic.setValue(pressure);​ +
-    pressureCharacteristic.notify(); ​   +
-    Serial.print("​Pressure:​ "); +
-    Serial.print(p);​ +
-    Serial.println("​ hPa"​);​ +
-     +
-    delay(10000);+
   }   }
 +  delay(10);
 } }
 +
 </​code>​ </​code>​
  
 Upload the code to your board. After uploading, open the Serial Monitor, and restart the Sparrow by pressing the RST/EN button. ​ 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 //"​Waiting ​client connection ​to notify..."// ​message ​in the Serial Monitor.+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 device called **ESP32-C6 Env**, this is the BLE server name you defined earlier. 
 + 
 +Connect ​to itYou’ll see that it displays the Environmental Sensing service with the temperature,​ humidity, and pressure characteristicsClick 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.1700510383.txt.gz · Last modified: 2023/11/20 21:59 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