Differences

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

Link to this comparison view

iothings:laboratoare:2025:lab2 [2025/09/28 17:47]
dan.tudose created
iothings:laboratoare:2025:lab2 [2025/09/28 18:16] (current)
dan.tudose [Sparrow BLE Service]
Line 197: Line 197:
    User config    User config
    ​============================ */    ​============================ */
-static const char* DEVICE_NAME = "​ESP32-C6 Env"; ​  // shows in scan response +static const char* DEVICE_NAME = "​ESP32-C6 Env";​ 
-#define I2C_SDA_PIN 21                             // adjust for your board+#define I2C_SDA_PIN 21
 #define I2C_SCL_PIN 22 #define I2C_SCL_PIN 22
-#define BME680_I2C_ADDR 0x77                      // try 0x76 if needed +#define BME680_I2C_ADDR 0x77 
-static const uint32_t MEAS_INTERVAL_MS = 2000;    // update ​period+static const uint32_t MEAS_INTERVAL_MS ​  ​= 2000;  // env update 
 +static const uint32_t BATTERY_INTERVAL_MS = 60000; // battery update 
 + 
 +/* Device Information strings */ 
 +static const char* DIS_MANUFACTURER = "​YourBrand";​ 
 +static const char* DIS_MODEL ​       = "​ESP32-C6 EnvSense";​ 
 +static const char* DIS_FW_REV ​      = "​1.0.0";​ 
 +static const char* DIS_SW_REV ​      = "​1.0.0";​ 
 +static const char* DIS_SN ​          = "​SN-000001";​ 
 +static const char* DIS_HW ​          = "​RevA";​
  
 /* ============================ /* ============================
-   BLE UUIDs (Environmental Sensing)+   UUIDs
    ​============================ */    ​============================ */
-static NimBLEUUID ESS_UUID((uint16_t)0x181A);​ +// Environmental Sensing 
-static NimBLEUUID TEMP_UUID((uint16_t)0x2A6E); ​ // Temperature +static NimBLEUUID ESS_UUID ((uint16_t)0x181A);​ 
-static NimBLEUUID PRES_UUID((uint16_t)0x2A6D); ​ // Pressure +static NimBLEUUID TEMP_UUID((uint16_t)0x2A6E);​ 
-static NimBLEUUID HUM_UUID ((uint16_t)0x2A6F); ​ // Humidity+static NimBLEUUID PRES_UUID((uint16_t)0x2A6D);​ 
 +static NimBLEUUID HUM_UUID ((uint16_t)0x2A6F);​ 
 + 
 +// Battery Service 
 +static NimBLEUUID BATT_SVC_UUID((uint16_t)0x180F);​ 
 +static NimBLEUUID BATT_LVL_UUID((uint16_t)0x2A19);​ 
 + 
 +// Device Information Service 
 +static NimBLEUUID DIS_UUID ​     ((uint16_t)0x180A);​ 
 +static NimBLEUUID DIS_MFR_UUID ​ ((uint16_t)0x2A29);​ 
 +static NimBLEUUID DIS_MODEL_UUID((uint16_t)0x2A24);​ 
 +static NimBLEUUID DIS_FW_UUID ​  ​((uint16_t)0x2A26);​ 
 +static NimBLEUUID DIS_SW_UUID ​  ​((uint16_t)0x2A28);​ 
 +static NimBLEUUID DIS_PNP_UUID ​ ((uint16_t)0x2A50);​
  
 /* ============================ /* ============================
    ​Globals    ​Globals
    ​============================ */    ​============================ */
-NimBLEServer* ​        ​gServer ​  ​= nullptr; +Adafruit_BME680 bme; // I2C 
-NimBLEService* ​       ​gService  ​= nullptr;+ 
 +NimBLEServer* gServer = nullptr; 
 + 
 +/* Environmental Sensing */ 
 +NimBLEService* ​       ​gESS      ​= nullptr;
 NimBLECharacteristic* gTempChar = nullptr; NimBLECharacteristic* gTempChar = nullptr;
 NimBLECharacteristic* gPresChar = nullptr; NimBLECharacteristic* gPresChar = nullptr;
 NimBLECharacteristic* gHumChar ​ = nullptr; NimBLECharacteristic* gHumChar ​ = nullptr;
  
-Adafruit_BME680 bme; // I2C +/* Battery */ 
-uint32_t ​lastMeas ​= 0;+NimBLEService* ​       gBattSvc ​ = nullptr; 
 +NimBLECharacteristic* gBattChar = nullptr; 
 + 
 +/* DIS */ 
 +NimBLEService* ​       gDIS      = nullptr; 
 + 
 +uint32_t lastEnv = 0; 
 +uint32_t ​lastBatt ​= 0;
  
 /* ============================ /* ============================
-   ​Helpers: ​encode per SIG formats (little-endian)+   ​Helpers: ​GATT encoders
    ​============================ */    ​============================ */
 static void setTempCentiDeg(NimBLECharacteristic* ch, float celsius) { static void setTempCentiDeg(NimBLECharacteristic* ch, float celsius) {
-  ​// sint16, 0.01 °C +  int32_t raw = lroundf(celsius * 100.0f); ​     // sint16, 0.01 °C 
-  ​int32_t raw = lroundf(celsius * 100.0f); +  if (raw > 32767) raw = 32767;
-  if (raw >  32767) raw =  32767;+
   if (raw < -32768) raw = -32768;   if (raw < -32768) raw = -32768;
   int16_t v = (int16_t)raw;​   int16_t v = (int16_t)raw;​
Line 237: Line 269:
  
 static void setHumidityCentiPct(NimBLECharacteristic* ch, float rh) { static void setHumidityCentiPct(NimBLECharacteristic* ch, float rh) {
-  ​// uint16, 0.01 %RH (clip 0..100%) +  if (rh < 0) rh = 0; if (rh > 100) rh = 100;   // uint16, 0.01 %RH
-  ​if (rh < 0)   ​rh = 0; +
-  ​if (rh > 100) rh = 100;+
   uint32_t raw = lroundf(rh * 100.0f);   uint32_t raw = lroundf(rh * 100.0f);
   if (raw > 0xFFFF) raw = 0xFFFF;   if (raw > 0xFFFF) raw = 0xFFFF;
Line 248: Line 278:
  
 static void setPressureDeciPa(NimBLECharacteristic* ch, float pascals) { static void setPressureDeciPa(NimBLECharacteristic* ch, float pascals) {
-  ​// uint32, 0.1 Pa +  if (pascals < 0) pascals = 0;                 // uint32, 0.1 Pa
-  ​if (pascals < 0) pascals = 0;+
   uint64_t raw = llroundf(pascals * 10.0f);   uint64_t raw = llroundf(pascals * 10.0f);
   if (raw > 0xFFFFFFFFULL) raw = 0xFFFFFFFFULL;​   if (raw > 0xFFFFFFFFULL) raw = 0xFFFFFFFFULL;​
   uint32_t v = (uint32_t)raw;​   uint32_t v = (uint32_t)raw;​
   uint8_t buf[4] = {   uint8_t buf[4] = {
-    (uint8_t)(v & 0xFF), +    (uint8_t)(v & 0xFF), (uint8_t)((v >> 8) & 0xFF), 
-    ​(uint8_t)((v >> 8) & 0xFF), +    (uint8_t)((v >> 16) & 0xFF), (uint8_t)((v >> 24) & 0xFF)
-    (uint8_t)((v >> 16) & 0xFF), +
-    ​(uint8_t)((v >> 24) & 0xFF)+
   };   };
   ch->​setValue(buf,​ sizeof(buf));​   ch->​setValue(buf,​ sizeof(buf));​
 } }
  
-/* Add Characteristic ​Presentation Format ​(0x2904) ​*/+/* 0x2904 ​Presentation Format ​descriptor ​*/
 static void addCPF(NimBLECharacteristic* ch, uint8_t format, int8_t exponent, uint16_t unit) { 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)   // 7 bytes: format, exponent, unit(LE), namespace(1=1),​ description(2=0)
Line 274: Line 301:
  
 /* ============================ /* ============================
-   BLE setup (keeps your adv/scan-response style)+   Battery level source ​(stub) 
 +   ​Replace with ADC/PMIC reading if available. 
 +   ​============================ */ 
 +static uint8_t getBatteryPercent(
 +  // TODO: read your actual battery voltage & map to 0..100 
 +  return 100; // placeholder 
 +
 + 
 +/* ============================ 
 +   BLE setup
    ​============================ */    ​============================ */
 void startBLE() { void startBLE() {
   NimBLEDevice::​init(DEVICE_NAME);​   NimBLEDevice::​init(DEVICE_NAME);​
-  NimBLEDevice::​setPower(ESP_PWR_LVL_P9); ​ // strong TX for gateways; lower if needed +  NimBLEDevice::​setPower(ESP_PWR_LVL_P9);​ 
-  NimBLEDevice::​setSecurityAuth(false,​ false, true); // no bonding; SC on+  NimBLEDevice::​setSecurityAuth(false,​ false, true); // no bonding; SC=true
  
-  gServer ​ = NimBLEDevice::​createServer(); +  gServer = NimBLEDevice::​createServer();​
-  gService = gServer->​createService(ESS_UUID);+
  
-  // READ + NOTIFY for all three characteristics +  /* --- Environmental Sensing Service --- */ 
-  auto props = (NIMBLE_PROPERTY::​READ | NIMBLE_PROPERTY::​NOTIFY); +  gESS = gServer->​createService(ESS_UUID);​ 
-  gTempChar = gService->​createCharacteristic(TEMP_UUID,​ props); +  auto propsRN ​= (NIMBLE_PROPERTY::​READ | NIMBLE_PROPERTY::​NOTIFY);​
-  gPresChar = gService->​createCharacteristic(PRES_UUID,​ props); +
-  gHumChar ​ = gService->​createCharacteristic(HUM_UUID, ​ props);+
  
-  // Presentation formats+  ​gTempChar = gESS->​createCharacteristic(TEMP_UUID,​ propsRN); 
 +  gPresChar = gESS->​createCharacteristic(PRES_UUID,​ propsRN); 
 +  gHumChar ​ = gESS->​createCharacteristic(HUM_UUID, ​ propsRN); 
 + 
 +  ​// CPF descriptors for units
   const uint8_t GATT_FORMAT_SINT16 = 0x0E;   const uint8_t GATT_FORMAT_SINT16 = 0x0E;
   const uint8_t GATT_FORMAT_UINT16 = 0x0D;   const uint8_t GATT_FORMAT_UINT16 = 0x0D;
   const uint8_t GATT_FORMAT_UINT32 = 0x10;   const uint8_t GATT_FORMAT_UINT32 = 0x10;
   addCPF(gTempChar,​ GATT_FORMAT_SINT16,​ -2, 0x272F); // Celsius   addCPF(gTempChar,​ GATT_FORMAT_SINT16,​ -2, 0x272F); // Celsius
-  addCPF(gPresChar,​ GATT_FORMAT_UINT32,​ -1, 0x2724); // Pascal, 0.1 exponent +  addCPF(gPresChar,​ GATT_FORMAT_UINT32,​ -1, 0x2724); // Pascal, 0.1 
-  addCPF(gHumChar, ​ GATT_FORMAT_UINT16,​ -2, 0x27AD); // Percentage+  addCPF(gHumChar, ​ GATT_FORMAT_UINT16,​ -2, 0x27AD); // %
  
-  // Initialize with placeholder ​values ​so READ works before first measurement+  // Initial ​values
   setTempCentiDeg(gTempChar,​ 0.0f);   setTempCentiDeg(gTempChar,​ 0.0f);
   setPressureDeciPa(gPresChar,​ 101325.0f);   setPressureDeciPa(gPresChar,​ 101325.0f);
   setHumidityCentiPct(gHumChar,​ 0.0f);   setHumidityCentiPct(gHumChar,​ 0.0f);
  
-  ​gService->​start();​+  ​gESS->​start();​ 
 + 
 +  /* --- Battery Service --- */ 
 +  gBattSvc ​ = gServer->​createService(BATT_SVC_UUID);​ 
 +  gBattChar = gBattSvc->​createCharacteristic(BATT_LVL_UUID,​ propsRN); 
 +  // Optional CPF for % (uint8, exponent 0) 
 +  // format for uint8 = 0x04 
 +  addCPF(gBattChar,​ 0x04, 0, 0x27AD); 
 +  uint8_t lvl = getBatteryPercent();​ 
 +  gBattChar->​setValue(&​lvl,​ 1); 
 +  gBattSvc->​start();​ 
 + 
 +  /* --- Device Information Service (all READ-only) --- */ 
 +  gDIS = gServer->​createService(DIS_UUID);​ 
 +  gDIS->​createCharacteristic(DIS_MFR_UUID, ​  ​NIMBLE_PROPERTY::​READ)->​setValue(DIS_MANUFACTURER);​ 
 +  gDIS->​createCharacteristic(DIS_MODEL_UUID,​ NIMBLE_PROPERTY::​READ)->​setValue(DIS_MODEL);​ 
 +  gDIS->​createCharacteristic(DIS_FW_UUID, ​   NIMBLE_PROPERTY::​READ)->​setValue(DIS_FW_REV);​ 
 +  gDIS->​createCharacteristic(DIS_SW_UUID, ​   NIMBLE_PROPERTY::​READ)->​setValue(DIS_SW_REV);​ 
 +  gDIS->​createCharacteristic((uint16_t)0x2A25,​ NIMBLE_PROPERTY::​READ)->​setValue(DIS_SN); ​ // Serial Number 
 +  gDIS->​createCharacteristic((uint16_t)0x2A27,​ NIMBLE_PROPERTY::​READ)->​setValue(DIS_HW); ​ // Hardware Rev 
 + 
 +  // PnP ID (7 bytes): Vendor ID Source (1=Bluetooth,​ 2=USB), Vendor ID (LE16), Product ID (LE16), Product Version (LE16) 
 +  uint8_t pnp[7] = { 0x02, 0x34, 0x12, 0x78, 0x56, 0x00, 0x01 }; // USB, VID 0x1234, PID 0x5678, ver 0x0100 
 +  gDIS->​createCharacteristic(DIS_PNP_UUID,​ NIMBLE_PROPERTY::​READ)->​setValue(pnp,​ sizeof(pnp));​ 
 + 
 +  gDIS->​start();​
  
 +  /* --- Advertising (keeps your explicit payload approach) --- */
   NimBLEAdvertising* adv = NimBLEDevice::​getAdvertising();​   NimBLEAdvertising* adv = NimBLEDevice::​getAdvertising();​
  
-  // Advertise ​the standard Environmental Sensing Service UUID+  // Advertise ​primary services (ESS + Battery). DIS is usually not advertised.
   adv->​addServiceUUID(ESS_UUID);​   adv->​addServiceUUID(ESS_UUID);​
 +  adv->​addServiceUUID(BATT_SVC_UUID);​
  
-  // Your explicit adv + scan-response payloads 
   NimBLEAdvertisementData advData;   NimBLEAdvertisementData advData;
   advData.setFlags(0x06);​ // LE General Discoverable + BR/EDR Not Supported   advData.setFlags(0x06);​ // LE General Discoverable + BR/EDR Not Supported
Line 316: Line 379:
  
   NimBLEAdvertisementData scanData;   NimBLEAdvertisementData scanData;
-  scanData.setName(DEVICE_NAME);​+  scanData.setName(DEVICE_NAME); ​// put name in scan response
   adv->​setScanResponseData(scanData);​   adv->​setScanResponseData(scanData);​
  
-  // Appearance: Generic Thermometer (0x0300)+  // Appearance: Generic Thermometer (helps some UIs)
   adv->​setAppearance(0x0300);​   adv->​setAppearance(0x0300);​
  
   NimBLEDevice::​startAdvertising();​   NimBLEDevice::​startAdvertising();​
-  Serial.println("​Advertising ​Environmental Sensing Service. Open nRF Connect -> Scan."​);​+  Serial.println("​Advertising: ESS + Battery + DIS (readable). Open nRF Connect -> Scan."​);​
 } }
  
 /* ============================ /* ============================
-   BME680 ​setup+   Sensor ​setup
    ​============================ */    ​============================ */
 bool startBME680() { bool startBME680() {
Line 334: Line 397:
     Serial.println("​[BME680] Not found on 0x77, trying 0x76..."​);​     Serial.println("​[BME680] Not found on 0x77, trying 0x76..."​);​
     if (!bme.begin(0x76)) {     if (!bme.begin(0x76)) {
-      Serial.println("​[BME680] Sensor not found. Check wiring ​and power."​);​+      Serial.println("​[BME680] Sensor not found. Check wiring/power."​);​
       return false;       return false;
     }     }
   }   }
- 
-  // Oversampling & filter for stable readings 
   bme.setTemperatureOversampling(BME680_OS_8X);​   bme.setTemperatureOversampling(BME680_OS_8X);​
   bme.setHumidityOversampling(BME680_OS_2X);​   bme.setHumidityOversampling(BME680_OS_2X);​
   bme.setPressureOversampling(BME680_OS_4X);​   bme.setPressureOversampling(BME680_OS_4X);​
   bme.setIIRFilterSize(BME680_FILTER_SIZE_3);​   bme.setIIRFilterSize(BME680_FILTER_SIZE_3);​
- +  ​bme.setGasHeater(0,​ 0); // off
-  // Gas heater off (not needed for T/RH/P) +
-  ​bme.setGasHeater(0,​ 0);+
   return true;   return true;
 } }
  
 /* ============================ /* ============================
-   Measurement + GATT update+   Updates
    ​============================ */    ​============================ */
-void updateMeasurements() {+void updateEnv() {
   if (!bme.performReading()) {   if (!bme.performReading()) {
     Serial.println("​[BME680] performReading() failed"​);​     Serial.println("​[BME680] performReading() failed"​);​
     return;     return;
   }   }
- +  ​float c = bme.temperature;​ 
-  ​float c = bme.temperature; ​ // °C +  float h = bme.humidity;​ 
-  float h = bme.humidity; ​    // %RH +  float p = bme.pressure;​
-  float p = bme.pressure; ​    // Pa+
  
   setTempCentiDeg(gTempChar,​ c);   setTempCentiDeg(gTempChar,​ c);
Line 367: Line 425:
   setPressureDeciPa(gPresChar,​ p);   setPressureDeciPa(gPresChar,​ p);
  
-  ​// Send notifications to any subscribed client +  gTempChar->​notify();​ 
-  ​gTempChar->​notify(true); +  gHumChar->​notify();​ 
-  gHumChar->​notify(true); +  gPresChar->​notify();​
-  gPresChar->​notify(true);+
  
-  Serial.printf("​[DATA] T=%.2f°C ​ RH=%.2f%% ​ P=%.1f Pa\n", c, h, p);+  Serial.printf("​[ENV] T=%.2f°C ​ RH=%.2f%% ​ P=%.1f Pa\n", c, h, p); 
 +
 + 
 +void updateBattery() { 
 +  uint8_t lvl = getBatteryPercent();​ 
 +  gBattChar->​setValue(&​lvl,​ 1); 
 +  gBattChar->​notify();​ 
 +  Serial.printf("​[BATT] %u%%\n",​ lvl);
 } }
  
Line 383: Line 447:
  
   if (!startBME680()) {   if (!startBME680()) {
-    // We still start BLE so you can see/​connect ​even if sensor is missing+    // Proceed with BLE even if sensor is missing, so you can still connect
   }   }
- 
   startBLE();   startBLE();
 } }
Line 391: Line 454:
 void loop() { void loop() {
   uint32_t now = millis();   uint32_t now = millis();
-  ​if (now - lastMeas ​>= MEAS_INTERVAL_MS) { + 
-    ​lastMeas ​= now; +  ​if (now - lastEnv ​>= MEAS_INTERVAL_MS) { 
-    ​updateMeasurements();+    ​lastEnv ​= now; 
 +    ​updateEnv();
   }   }
 +
 +  if (now - lastBatt >= BATTERY_INTERVAL_MS) {
 +    lastBatt = now;
 +    updateBattery();​
 +  }
 +
   delay(10);   delay(10);
 } }
Line 409: Line 479:
  
 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. 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.
 +
 +You can also see a Battery Service and a Device Information Service. Battery is hardcoded for now at 100% and device information is also hardcoded.
  
 ===== Web BLE Application ===== ===== Web BLE Application =====
iothings/laboratoare/2025/lab2.1759070836.txt.gz · Last modified: 2025/09/28 17:47 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