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 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.1700508515.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