From 1a74fd96e7d68c4fbd5560b0ef5518f2f9cb2eb0 Mon Sep 17 00:00:00 2001 From: Djebouh <41395186+Djebouh@users.noreply.github.com> Date: Sat, 4 May 2019 13:29:07 +0200 Subject: [PATCH 1/3] Add files via upload --- flora/flora.ino | 161 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 122 insertions(+), 39 deletions(-) diff --git a/flora/flora.ino b/flora/flora.ino index 1cda8a5..25d5294 100644 --- a/flora/flora.ino +++ b/flora/flora.ino @@ -4,9 +4,9 @@ See https://github.com/nkolban/esp32-snippets/blob/master/Documentation/BLE%20C%2B%2B%20Guide.pdf on how bluetooth low energy and the library used are working. - See https://github.com/ChrisScheffler/miflora/wiki/The-Basics for details on how the + See https://github.com/ChrisScheffler/miflora/wiki/The-Basics for details on how the protocol is working. - + MIT License Copyright (c) 2017 Sven Henkel @@ -40,8 +40,8 @@ // boot count used to check if battery status should be read RTC_DATA_ATTR int bootCount = 0; -// device count -static int deviceCount = sizeof FLORA_DEVICES / sizeof FLORA_DEVICES[0]; +// Root service for Flora Devices +static BLEUUID rootServiceDataUUID((uint16_t) 0xfe95); // the remote service we wish to connect to static BLEUUID serviceUUID("00001204-0000-1000-8000-00805f9b34fb"); @@ -101,7 +101,8 @@ BLEClient* getFloraClient(BLEAddress floraAddress) { BLEClient* floraClient = BLEDevice::createClient(); if (!floraClient->connect(floraAddress)) { - Serial.println("- Connection failed, skipping"); + Serial.print("- Connection failed, skipping "); + Serial.println(floraAddress.toString().c_str()); return nullptr; } @@ -130,7 +131,7 @@ BLERemoteService* getFloraService(BLEClient* floraClient) { bool forceFloraServiceDataMode(BLERemoteService* floraService) { BLERemoteCharacteristic* floraCharacteristic; - + // get device mode characteristic, needs to be changed to read data Serial.println("- Force device in data mode"); floraCharacteristic = nullptr; @@ -172,7 +173,7 @@ bool readFloraDataCharacteristic(BLERemoteService* floraService, String baseTopi // read characteristic value Serial.println("- Read value from characteristic"); std::string value; - try{ + try { value = floraCharacteristic->readValue(); } catch (...) { @@ -201,7 +202,7 @@ bool readFloraDataCharacteristic(BLERemoteService* floraService, String baseTopi int light = val[3] + val[4] * 256; Serial.print("-- Light: "); Serial.println(light); - + int conductivity = val[8] + val[9] * 256; Serial.print("-- Conductivity: "); Serial.println(conductivity); @@ -214,13 +215,15 @@ bool readFloraDataCharacteristic(BLERemoteService* floraService, String baseTopi char buffer[64]; snprintf(buffer, 64, "%f", temperature); - client.publish((baseTopic + "temperature").c_str(), buffer); - snprintf(buffer, 64, "%d", moisture); - client.publish((baseTopic + "moisture").c_str(), buffer); + client.publish((baseTopic + "temperature").c_str(), buffer, true); + snprintf(buffer, 64, "%d", moisture); + client.publish((baseTopic + "moisture").c_str(), buffer, true); snprintf(buffer, 64, "%d", light); - client.publish((baseTopic + "light").c_str(), buffer); + client.publish((baseTopic + "light").c_str(), buffer, true); snprintf(buffer, 64, "%d", conductivity); - client.publish((baseTopic + "conductivity").c_str(), buffer); + client.publish((baseTopic + "conductivity").c_str(), buffer, true); + + Serial.println("MQTT pub for topic: " + baseTopic); return true; } @@ -244,7 +247,7 @@ bool readFloraBatteryCharacteristic(BLERemoteService* floraService, String baseT // read characteristic value Serial.println("- Read value from characteristic"); std::string value; - try{ + try { value = floraCharacteristic->readValue(); } catch (...) { @@ -260,12 +263,12 @@ bool readFloraBatteryCharacteristic(BLERemoteService* floraService, String baseT Serial.print("-- Battery: "); Serial.println(battery); snprintf(buffer, 64, "%d", battery); - client.publish((baseTopic + "battery").c_str(), buffer); + client.publish((baseTopic + "battery").c_str(), buffer, true); return true; } -bool processFloraService(BLERemoteService* floraService, char* deviceMacAddress, bool readBattery) { +bool processFloraService(BLERemoteService* floraService, const char* deviceMacAddress, bool readBattery) { // set device in data mode if (!forceFloraServiceDataMode(floraService)) { return false; @@ -282,7 +285,7 @@ bool processFloraService(BLERemoteService* floraService, char* deviceMacAddress, return dataSuccess && batterySuccess; } -bool processFloraDevice(BLEAddress floraAddress, char* deviceMacAddress, bool getBattery, int tryCount) { +bool processFloraDevice(BLEAddress floraAddress, bool getBattery, int tryCount) { Serial.print("Processing Flora device at "); Serial.print(floraAddress.toString().c_str()); Serial.print(" (try "); @@ -303,7 +306,7 @@ bool processFloraDevice(BLEAddress floraAddress, char* deviceMacAddress, bool ge } // process devices data - bool success = processFloraService(floraService, deviceMacAddress, getBattery); + bool success = processFloraService(floraService, floraAddress.toString().c_str(), getBattery); // disconnect from device floraClient->disconnect(); @@ -319,11 +322,86 @@ void hibernate() { } void delayedHibernate(void *parameter) { - delay(EMERGENCY_HIBERNATE*1000); // delay for five minutes + delay(EMERGENCY_HIBERNATE * 1000); // delay for five minutes Serial.println("Something got stuck, entering emergency hibernate..."); hibernate(); } +// before setup() +static void my_gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param) { + ESP_LOGW(LOG_TAG, "custom gattc event handler, event: %d", (uint8_t)event); + if (event == ESP_GATTC_DISCONNECT_EVT) { + Serial.print("Disconnect reason: "); + Serial.println((int)param->disconnect.reason); + } +} + + +class FloraDevicesScanner { + public: + // Scan BLE and return true if flora devices are found + bool scan(); + + int getDeviceCount() const { + return _deviceCount; + } + + std::string getDeviceAddress(int i) const { + if (i < _deviceCount) + return _devices[i]; + else + return std::string(); + } + + private: + std::string _devices[MAX_DEVICES]; + int _deviceCount = 0; + + void registerDevice(BLEAdvertisedDevice& advertisedDevice) { + std::string deviceAddress(advertisedDevice.getAddress().toString()); + Serial.print("Flora device found at address "); + Serial.println(deviceAddress.c_str()); + + if (_deviceCount < MAX_DEVICES) + _devices[_deviceCount++] = deviceAddress; + else + Serial.println("can't register device, no remaining slot"); + } + +}; + +bool FloraDevicesScanner::scan() { + Serial.println("Scan BLE, looking for Flora Devices"); + + // detect and register Flora devices during BLE scan + class FloraDevicesBLEDetector: public BLEAdvertisedDeviceCallbacks { + public: + FloraDevicesBLEDetector(FloraDevicesScanner &floraScanner) : _floraScanner(floraScanner) { } + + void onResult(BLEAdvertisedDevice advertisedDevice) + { + if (advertisedDevice.haveServiceUUID()) { + BLEUUID service = advertisedDevice.getServiceUUID(); + if (service.equals(rootServiceDataUUID)) + _floraScanner.registerDevice(advertisedDevice); + } + } + + private: + FloraDevicesScanner& _floraScanner; + }; + + BLEScan* scan = BLEDevice::getScan(); + FloraDevicesBLEDetector floraDetector(*this); + scan->setAdvertisedDeviceCallbacks(&floraDetector); + scan->start(BLE_SCAN_DURATION); + + Serial.print("Number of Flora devices detected: "); + Serial.println(_deviceCount); + return (_deviceCount > 0); +} + + void setup() { // all action is done when device is woken up Serial.begin(115200); @@ -336,35 +414,40 @@ void setup() { xTaskCreate(delayedHibernate, "hibernate", 4096, NULL, 1, &hibernateTaskHandle); Serial.println("Initialize BLE client..."); + // BLEDevice::setCustomGattcHandler(my_gattc_event_handler); // before BLEDevice::init(); BLEDevice::init(""); BLEDevice::setPower(ESP_PWR_LVL_P7); - // connecting wifi and mqtt server - connectWifi(); - connectMqtt(); + FloraDevicesScanner floraScanner; + if (floraScanner.scan()) { - // check if battery status should be read - based on boot count - bool readBattery = ((bootCount % BATTERY_INTERVAL) == 0); + // connecting wifi and mqtt server + connectWifi(); + connectMqtt(); - // process devices - for (int i=0; i Date: Sat, 4 May 2019 13:30:43 +0200 Subject: [PATCH 2/3] Update config.h.example --- flora/config.h.example | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/flora/config.h.example b/flora/config.h.example index 38ca55f..07666c3 100644 --- a/flora/config.h.example +++ b/flora/config.h.example @@ -1,10 +1,8 @@ +// Max Number of devices monitored +const int MAX_DEVICES = 20; -// array of different xiaomi flora MAC addresses -char* FLORA_DEVICES[] = { - "C4:7C:8D:67:11:11", - "C4:7C:8D:67:22:22", - "C4:7C:8D:67:33:33" -}; +// Max duration of BLE scan (in seconds) +const int BLE_SCAN_DURATION = 10; // sleep between to runs in seconds #define SLEEP_DURATION 30 * 60 From 9c6de394bc5abfd3e001d04d7812ee797bd72ae3 Mon Sep 17 00:00:00 2001 From: Djebouh <41395186+Djebouh@users.noreply.github.com> Date: Sat, 4 May 2019 13:35:36 +0200 Subject: [PATCH 3/3] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb8992f..14591a3 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,19 @@ Software: 1) Copy config.h.example into config.h and update seetings according to your environment: - WLAN SSID and password - MQTT Server address -- MAC address(es) of your Xiaomi Mi Plant sensor(s) 2) Open ino sketch in Arduino, compile & upload. ## Measuring interval -The ESP32 will perform a single connection attempt to the Xiaomi Mi Plant sensor, read the sensor data & push it to the MQTT server. The ESP32 will enter deep sleep mode after all sensors have been read and sleep for X minutes before repeating the exercise... +The ESP32 will scan BLE devices to find Xiaomi Mi Plant sensors. Then it perform a single connection attempt to the Xiaomi Mi Plant sensor, read the sensor data & push it to the MQTT server. The ESP32 will enter deep sleep mode after all sensors have been read and sleep for X minutes before repeating the exercise... Battery level is read every Xth wakeup. Up to X attempst per sensor are performed when reading the data fails. ## Configuration +- MAX_DEVICES - the maximum number of devices that can be scanned at once +- BLE_SCAN_DURATION - duration of the initial BLE scan used to look for Xiaomi Mi Plant sensors - SLEEP_DURATION - how long should the device sleep between sensor reads? - EMERGENCY_HIBERNATE - how long after wakeup should the device forcefully go to sleep (e.g. when something gets stuck)? - BATTERY_INTERVAL - how ofter should the battery status be read?