Skip to content
This repository was archived by the owner on Nov 9, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
10 changes: 4 additions & 6 deletions flora/config.h.example
Original file line number Diff line number Diff line change
@@ -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
Expand Down
161 changes: 122 additions & 39 deletions flora/flora.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 (...) {
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand All @@ -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 (...) {
Expand All @@ -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;
Expand All @@ -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 ");
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -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<deviceCount; i++) {
int tryCount = 0;
char* deviceMacAddress = FLORA_DEVICES[i];
BLEAddress floraAddress(deviceMacAddress);
// check if battery status should be read - based on boot count
bool readBattery = ((bootCount % BATTERY_INTERVAL) == 0);
if (readBattery) Serial.println("Battery will be read during this run");

while (tryCount < RETRY) {
tryCount++;
if (processFloraDevice(floraAddress, deviceMacAddress, readBattery, tryCount)) {
break;
// process devices
for (int i = 0; i < floraScanner.getDeviceCount(); i++) {
int tryCount = 0;
BLEAddress floraAddress(floraScanner.getDeviceAddress(i));

while (tryCount < RETRY) {
tryCount++;
if (processFloraDevice(floraAddress, readBattery, tryCount)) {
break;
}
delay(1000);
}
delay(1000);
delay(1500);
}
delay(1500);
}

// disconnect wifi and mqtt
disconnectWifi();
disconnectMqtt();
// disconnect wifi and mqtt
disconnectWifi();
disconnectMqtt();
}

// delete emergency hibernate task
vTaskDelete(hibernateTaskHandle);
Expand Down