Optimal BLE connection/broadcast/disconnect cycle with Python Bleak and ArduinoBLE.h library

I am creating my first project with Arduino. The goal is to read sensor data with Arduino, and broadcast them with BLE to my PC. I would like to keep the energy consumptions as low as I can. I've managed to get the broadcasting and receiving the sensor values working, but I would like to have some input on how optimal the solution is, as I am not familiar with Bluetooth or any other wireless technology.

With optimal solution, I mean that I would like to avoid overhead that unnecessary connect/advertise/broadcast/disconnect events might cause.

Here is the current flow of work:
-> A user opens up a browser on /v1/sensor endpoint
-> React frontend requests the data from Python Fast API backend
-> Python Fast API initialises a BLE-service class and calls for the data via BLE-service class' method
-> BLE-service class uses Bleak library to create a connection with Arduino
-> BLE-service class reads multiple GATT characteristics, disconnects and returns the sensor values to Python Fast API
-> A user sees the sensor values on the browser
-> While user is at /v1/sensor endpoint, the sensor values keep updating via server sent events, ie Fast API loops the BLE-service class method that creates the connection, reads characteristics and disconnects. This is the point I think could cause overhead

I've understood, that reading GATT characteristics is better suited for an application that does not need real-time updates, which is why I went with it. However, I've noticed that it takes at least 2 seconds to get the sensor values because establishing the connection seems to be the most resource demanding and takes most of the time (I could be wrong). Therefore I am worried that reading values every 10s or even 30s might still be too frequent for reading GATT characteristics at least with the current solution:

Arduino

#include <Arduino.h>
#include <ArduinoBLE.h>

BLEService myService("180F");
BLEFloatCharacteristic pm1("123-1", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic pm25("123-2", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic pm4("123-3", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic pm10("123-4", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic hum("123-5", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic temp("123-6", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic voc("123-7", BLERead | BLENotify | BLEBroadcast);
BLEFloatCharacteristic nox("123-8", BLERead | BLENotify | BLEBroadcast);

void loop()
{
  BLEDevice central = BLE.central();

  delay(1000);

  // Read Measurement
  float massConcentrationPm1p0;
  float massConcentrationPm2p5;
  float massConcentrationPm4p0;
  float massConcentrationPm10p0;
  float ambientHumidity;
  float ambientTemperature;
  float vocIndex;
  float noxIndex;

  if (central)
  {
    Serial.print("Connected to central: ");
    // print the central's BT address:
    Serial.println(central.address());
    // turn on the LED to indicate the connection:
    digitalWrite(LED_BUILTIN, HIGH);

    while (central.connected())
    {
      pm1.writeValue(massConcentrationPm1p0);
      pm25.writeValue(massConcentrationPm2p5);
      pm4.writeValue(massConcentrationPm4p0);
      pm10.writeValue(massConcentrationPm10p0);
      hum.writeValue(ambientHumidity);
      temp.writeValue(ambientTemperature);
      voc.writeValue(vocIndex);
      nox.writeValue(noxIndex);
    }

    // when the central disconnects, turn off the LED:
    digitalWrite(LED_BUILTIN, LOW);
    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
  }
}

BLE-Service class (Python Bleak)

import struct

from bleak import BleakClient

PM1_CHAR_UUID = "123-1"
PM25_CHAR_UUID = "123-2"
PM4_CHAR_UUID = "123-3"
PM10_CHAR_UUID = "123-4"
HUMIDITY_CHAR_UUID = "123-5"
TEMPERATURE_CHAR_UUID = "123-6"
VOC_CHAR_UUID = "123-7"
NOX_CHAR_UUID = "123-8"

class BLEService:
    def __init__(self):
         pass

     # this method is looped indef
    async def read_characteristic(self):
        async with BleakClient('my-address-123') as client:
            #while True:
                # Connect to the BLE device
                await client.connect()

                # Read the value of the specified characteristic
                pm1_value = round(struct.unpack('<f', (await client.read_gatt_char(PM1_CHAR_UUID)))[0], 2)
                pm25_value = round(struct.unpack('<f', (await client.read_gatt_char(PM25_CHAR_UUID)))[0], 2)
                pm4_value = round(struct.unpack('<f', (await client.read_gatt_char(PM4_CHAR_UUID)))[0], 2)
                pm10_value = round(struct.unpack('<f', (await client.read_gatt_char(PM10_CHAR_UUID)))[0], 2)
                humidity_value = round(struct.unpack('<f', (await client.read_gatt_char(HUMIDITY_CHAR_UUID)))[0], 2)
                temperature_value = round(struct.unpack('<f', (await client.read_gatt_char(TEMPERATURE_CHAR_UUID)))[0], 2)
                voc_value = struct.unpack('<f', (await client.read_gatt_char(VOC_CHAR_UUID)))[0]
                nox_value = struct.unpack('<f', (await client.read_gatt_char(NOX_CHAR_UUID)))[0]
                print(f"Value of Characteristic: {pm1_value}, {pm25_value}, {pm4_value}, {pm10_value}, {humidity_value}, {temperature_value}, {voc_value}, {nox_value}")

                # Disconnect from the BLE device
                await client.disconnect()
                return(voc_value)

Python Fast API class

from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from app.cors import setup_cors
from app.BLEService.sensor_data import BLEService
from sse_starlette.sse import EventSourceResponse

import logging
import asyncio

logger = logging.getLogger(__name__)

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.ble_service_instance = BLEService()
    yield

app = FastAPI(lifespan=lifespan)
setup_cors(app)

@app.get("/sensor")
async def get_sensor_data(request: Request):
    logger.info("requesting sensor endpoint")

    ble_service = request.app.state.ble_service_instance
    return EventSourceResponse(data_retrieve(ble_service))

async def data_retrieve(ble_service):
    try:
        while True:
            sensor_data = await ble_service.read_characteristic()
            yield sensor_data
            await asyncio.sleep(1)
    except asyncio.CancelledError as e:
        logger.info("Disconnected from client")
        raise e

When it comes to BLE using Python I have had many problems with delays in connection and communication. I would sleep for a little more than 1 as I'm assuming this isn't a sensor that will be changing drastically over time.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.