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:33]
dan.tudose [Mobile App]
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 105: Line 171:
 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. 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 (App Store). 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.+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.1700508781.txt.gz · Last modified: 2023/11/20 21:33 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