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