Flutter + arduino nano 33 BLE sense = очень простой BLE sensor
В этой статье я хочу рассказать как сделать очень простую bluetooth метеостанцию (куда уж без нее:)) и написать мобильное приложение на Flutter для нее.
В начале рассмотрим сам сенсор.
Для повторения понадобится плата Arduino nano 33 BLE sense
Плата построена на nrf52840. Устанавливаем ее через менеджер плат в ардуино.
Сразу же установим необходимые библиотеки:
Эти библиотеки необходимы для сенсоров, которые уже распаяны на самой плате.
Немного теории, а дальше рассмотрим практическую реализацию.
Основная идея была в том, чтобы не делать подключаемое устройство, а реализовать широковещательные посылки с включением в них всей необходимой информации.
Использовался режим обычной bluetooth метки, но с модификацией ManufacturerData. Этот пакет можно передавать в каждой advertise посылке.
Общий размер advertise посылки 31 байт. Сюда входит вся необходимая информация: имя устройства, системные данные, пользовательские данные. В чистом виде пользователю в ManufacturerData остается около 20 байт. Этого хватает на передачу данных метеостанции.
Преимущества такого способа передачи данных в более низком энергопотреблении, отсутствии необходимости держать постоянный коннект с устройством, широковещательная рассылка. Такое сообщение может поймать неограниченное количество приемников в радиусе приема.
ManufacturerData устанавливается перед стартом адвертайзинга.
А теперь практическая часть:
В коде ардуино указываем тип работы BLE части и задаем стартовый ManufacturerData
Также для удобства я указываю имя устройства, так его проще искать в приложении.
BLE.setLocalName("nrf52840.ru");
BLE.setConnectable(false);
byte data[8] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
BLE.setManufacturerData(data, 8);
// start advertising
BLE.advertise();
Начальные данные заданы как набор байтов, чтобы заполнить массив.
Теперь в основном коде делаем остановку advertising, останавливаем радио производим замер необходимых данных и заполняем ManufacturerData уже реальными данными, после этого запускаем вещание обратно. Эта операция производится каждые 2 сек.
BLE.stopAdvertise();
// read all the sensor values
---------------------
читаем данные с сенсоров и записываем их значения
---------------------
byte data[8] = { 0x00, 0x01, t1, t2, h1, h2, p1, p2}; // t - температура (2 байта), h - влажность (2 байта), p - давление (2 байта)
BLE.setManufacturerData(data, 8);
BLE.advertise();
// wait 2 second to print again
delay(2000);
На этом работа с датчиком закончена. Вещать датчик будет каждые 100 мс и каждые 2сек обновлять данные на актуальные. Получился очень простой по коду и реализации метео сенсор.
Теперь рассмотрим мобильное приложение
Сразу сделаю оговорку: я не разработчик мобильных приложений.
Для работы я использовал VSCode с плагином Flutter. Эта среда кажется проще, чем Android Studio, как мне показалось. Для работы с BLE использовалась библиотека Flutter_blue, которая сильно упростила подключение устройства.
Логика работы приложения тоже достаточно простая. Наш сенсор вещает в режиме обычного Beacon, поэтому нужно выполнить всего пару действий:
- Просканировать эфир — найти устройство с заданным именем,
- Разобрать его ManufacturerData, чтобы отобразить данные на экране.
Давайте посмотрим как это сделано.
После старта приложения регулярно запускается таймер, который сканирует Bluetooth устройства каждые 10 сек в течение 2 сек. Чаще и дольше сканировать смысла нет, расход батареи увеличится, а датчик вещает вообще каждые 100 мс.
DeviceScanner() {
_subscribeToScanEvents();
_timer = new Timer.periodic(const Duration(seconds: 10), startScan);
}
void startScan(Timer timer) {
FlutterBlue.instance.startScan(timeout: Duration(seconds: 2));
}
Дальше начинаем разбирать результаты сканирования, проверяем есть ли в списке сканирования устройство с заданным именем, и, если есть, то разбираем его посылку, и выводим результат пользователю.
void _subscribeToScanEvents() {
FlutterBlue.instance.scanResults.listen((scanResults) {
for (ScanResult scanResult in scanResults) {
if (scanResult.device.name.toString() == "nrf52840.ru") {
final int rssi = scanResult.rssi;
final String name = scanResult.device.name;
final String mac = scanResult.device.id.toString();
final double temp = scanResult.advertisementData.manufacturerData[256]
[0] +
scanResult.advertisementData.manufacturerData[256][1] * 0.01;
final double humm = scanResult.advertisementData.manufacturerData[256]
[2] +
scanResult.advertisementData.manufacturerData[256][3] * 0.01;
final double press =
scanResult.advertisementData.manufacturerData[256][4] +
scanResult.advertisementData.manufacturerData[256][5] * 0.01;
final SensorData sensorData = new SensorData(
name: name,
rssi: rssi,
mac: mac,
temperature: temp,
humidity: humm,
pressure: press);
_streamController.add(sensorData);
print(
'Manufacturer data ${scanResult.advertisementData.manufacturerData}');
FlutterBlue.instance.stopScan();
}
print(
'${scanResult.device.name} found! mac: ${scanResult.device.id} rssi: ${scanResult.rssi}');
}
});
}
Небольшой нюанс. Bluetooth библиотека для Flutter может показаться странной, данные она получает в виде массива int, а в ардуино мы формируем посылку из байтов, поэтому в сенсоре я формировал посылку чтобы упростить ее анализ. Температура 25.85 градусов разбивается на два значения 25 и 85, которые отправляются в виде отдельных byte значений, и точно так же собираются обратно.
В конечном результате получилось вот такое приложение.
Исходный код проекта можно скачать с Github