#include "BLEDevice.h" #include #include // array of different xiaomi flora MAC addresses char* FLORA_DEVICES[] = { "XXX" }; // sleep between to runs in seconds #define SLEEP_DURATION 30 * 60 // emergency hibernate countdown in seconds #define EMERGENCY_HIBERNATE 3 * 60 // how often should a device be retried in a run when something fails #define RETRY 3 const char* WIFI_SSID = "XXX"; const char* WIFI_PASSWORD = "XXX"; char wifiHostname[ ] = "ESP32 PlantPi"; // Konfigurationsvariablen für Ein- und Ausgangspins const int waterSensorPin = 16; // Eingangspin für den Wasserstandssensor const char* properties[] = {"light", "dir", "motor", "pumpe", "nn"}; const int ausgangspins[] = {5, 17, 18, 19, 21}; // MQTT topic gets defined by "//" // where MAC_ADDRESS is one of the values from FLORA_DEVICES array // property is either temperature, moisture, conductivity, light or battery IPAddress MQTT_HOST(192, 168, x, x); const int MQTT_PORT = 1883; const char* MQTT_CLIENTID = "miflora-client"; const char* MQTT_USERNAME = "xxx"; const char* MQTT_PASSWORD = "xxx"; const String MQTT_BASE_TOPIC = "beet0"; const int MQTT_RETRY_WAIT = 5000; // 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]; // the remote service we wish to connect to static BLEUUID serviceUUID("00001204-0000-1000-8000-00805f9b34fb"); // the characteristic of the remote service we are interested in static BLEUUID uuid_version_battery("00001a02-0000-1000-8000-00805f9b34fb"); static BLEUUID uuid_sensor_data("00001a01-0000-1000-8000-00805f9b34fb"); static BLEUUID uuid_write_mode("00001a00-0000-1000-8000-00805f9b34fb"); // ESP32 MAC address char macAddr[18]; //TaskHandle_t hibernateTaskHandle = NULL; WiFiClient espClient; PubSubClient client(espClient); void connectWifi() { Serial.println("Connecting to WiFi..."); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi connected"); byte ar[6]; WiFi.macAddress(ar); sprintf(macAddr,"%02X:%02X:%02X:%02X:%02X:%02X",ar[0],ar[1],ar[2],ar[3],ar[4],ar[5]); } void disconnectWifi() { WiFi.disconnect(true); Serial.println("WiFi disonnected"); } void connectMqtt() { Serial.println("Connecting to MQTT..."); client.setServer(MQTT_HOST, MQTT_PORT); while (!client.connected()) { if (!client.connect(MQTT_CLIENTID, MQTT_USERNAME, MQTT_PASSWORD)) { Serial.print("MQTT connection failed:"); Serial.print(client.state()); Serial.println("Retrying..."); delay(MQTT_RETRY_WAIT); } } Serial.println("MQTT connected"); } void disconnectMqtt() { client.disconnect(); Serial.println("MQTT disconnected"); } BLEClient* getFloraClient(BLEAddress floraAddress) { BLEClient* floraClient = BLEDevice::createClient(); if (!floraClient->connect(floraAddress)) { Serial.println("- Connection failed, skipping"); return nullptr; } Serial.println("- Connection successful"); return floraClient; } BLERemoteService* getFloraService(BLEClient* floraClient) { BLERemoteService* floraService = nullptr; try { floraService = floraClient->getService(serviceUUID); } catch (...) { // something went wrong } if (floraService == nullptr) { Serial.println("- Failed to find data service"); } else { Serial.println("- Found data service"); } return floraService; } 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; try { floraCharacteristic = floraService->getCharacteristic(uuid_write_mode); } catch (...) { // something went wrong } if (floraCharacteristic == nullptr) { Serial.println("-- Failed, skipping device"); return false; } // write the magic data uint8_t buf[2] = {0xA0, 0x1F}; floraCharacteristic->writeValue(buf, 2, true); delay(500); return true; } bool readFloraDataCharacteristic(BLERemoteService* floraService, String baseTopic) { BLERemoteCharacteristic* floraCharacteristic = nullptr; // get the main device data characteristic try { floraCharacteristic = floraService->getCharacteristic(uuid_sensor_data); } catch (...) { // something went wrong } if (floraCharacteristic == nullptr) { Serial.println("-- Failed, skipping device"); return false; } // read characteristic value Serial.println("- Read value from characteristic"); std::string value; try { value = floraCharacteristic->readValue(); } catch (...) { // something went wrong Serial.println("-- Failed, skipping device"); return false; } const char *val = value.c_str(); Serial.print("Hex: "); for (int i = 0; i < 16; i++) { Serial.print((int)val[i], HEX); Serial.print(" "); } Serial.println(" "); char buffer[64]; int16_t* temp_raw = (int16_t*)val; float temperature = (*temp_raw) / ((float)10.0); snprintf(buffer, 64, "%2.1f", temperature); if (client.publish((baseTopic + "temperature").c_str(), buffer)) { Serial.println(" >> Published"); } int moisture = val[7]; snprintf(buffer, 64, "%d", moisture); if (client.publish((baseTopic + "moisture").c_str(), buffer)) { Serial.println(" >> Published"); } int light = val[3] + val[4] * 256; snprintf(buffer, 64, "%d", light); if (client.publish((baseTopic + "light").c_str(), buffer)) { Serial.println(" >> Published"); } int conductivity = val[8] + val[9] * 256; snprintf(buffer, 64, "%d", conductivity); if (client.publish((baseTopic + "conductivity").c_str(), buffer)) { Serial.println(" >> Published"); } return true; } bool readFloraBatteryCharacteristic(BLERemoteService* floraService, String baseTopic) { BLERemoteCharacteristic* floraCharacteristic = nullptr; // get the device battery characteristic try { floraCharacteristic = floraService->getCharacteristic(uuid_version_battery); } catch (...) { // something went wrong } if (floraCharacteristic == nullptr) { return false; } // read characteristic value std::string value; try { value = floraCharacteristic->readValue(); } catch (...) { // something went wrong return false; } const char *val2 = value.c_str(); int battery = val2[0]; char buffer[64]; snprintf(buffer, 64, "%d", battery); client.publish((baseTopic + "battery").c_str(), buffer); return true; } bool processFloraService(BLERemoteService* floraService, char* deviceMacAddress, bool readBattery) { // set device in data mode if (!forceFloraServiceDataMode(floraService)) { return false; } String baseTopic = MQTT_BASE_TOPIC + "/" + deviceMacAddress + "/"; bool dataSuccess = readFloraDataCharacteristic(floraService, baseTopic); bool batterySuccess = true; if (readBattery) { batterySuccess = readFloraBatteryCharacteristic(floraService, baseTopic); } return dataSuccess && batterySuccess; } bool processFloraDevice(BLEAddress floraAddress, char* deviceMacAddress, bool getBattery, int tryCount) { // connect to flora ble server BLEClient* floraClient = getFloraClient(floraAddress); if (floraClient == nullptr) { return false; } // connect data service BLERemoteService* floraService = getFloraService(floraClient); if (floraService == nullptr) { floraClient->disconnect(); return false; } // process devices data bool success = processFloraService(floraService, deviceMacAddress, getBattery); // disconnect from device floraClient->disconnect(); return success; } void splitString(String input, char delimiter, String* parts, int maxParts) { int partIndex = 0; int startIndex = 0; int endIndex = 0; while (endIndex >= 0 && partIndex < maxParts - 1) { endIndex = input.indexOf(delimiter, startIndex); if (endIndex >= 0) { parts[partIndex++] = input.substring(startIndex, endIndex); startIndex = endIndex + 1; } } // Letzten Teil hinzufügen if (startIndex < input.length()) { parts[partIndex] = input.substring(startIndex); } } void MiFloraRead(bool readBattery) { Serial.println("Reading MiFlora"); for (int i = 0; i < deviceCount; i++) { int tryCount = 0; char* deviceMacAddress = FLORA_DEVICES[i]; BLEAddress floraAddress(deviceMacAddress); while (tryCount < RETRY) { tryCount++; if (processFloraDevice(floraAddress, deviceMacAddress, readBattery, tryCount)) { break; } delay(1000); } delay(1500); } } void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Message arrived ["); Serial.print(topic); Serial.print("] "); for (int i = 0; i < length; i++) { Serial.print((char)payload[i]); } Serial.println(); char topicTail[35]; // Annahme: Ziel-Array für den extrahierten Teil // Finden des ersten '/' in topic char* delimiterPosition = strchr(topic, '/'); // Überprüfen, ob '/' gefunden wurde if (delimiterPosition != NULL) { // Kopieren des Inhalts nach dem gefundenen '/' in topicTail strcpy(topicTail, delimiterPosition + 1); // Ausgabe des extrahierten Teils Serial.print("topicTail: "); Serial.println(topicTail); } else { // '/' wurde nicht gefunden Serial.println("Kein '/' gefunden."); } if (strcmp(topicTail, "MiFloraRead") == 0) { Serial.println("Read MiFlora..."); MiFloraRead(0); //return; } else if (strcmp(topicTail, "MiFloraBatt") == 0) { Serial.println("Read MiFlora with battery status..."); MiFloraRead(1); //return; } else { for (int i = 0; i < sizeof(ausgangspins) / sizeof(ausgangspins[0]); i++) { Serial.println("Running loop"); if (strcmp(topicTail, properties[i]) == 0) { if ((char)payload[0] == 'T' || (char)payload[0] == 't') { digitalWrite(ausgangspins[i], HIGH); Serial.println("--Pin set HIGH; returning to main programm"); //return; } else if ((char)payload[0] == 'F' || (char)payload[0] == 'f') { digitalWrite(ausgangspins[i], LOW); Serial.println("--Pin set LOW; returning to main programm"); //return; } else { Serial.println("!! unexpected char for payload. Expected T/t or F/f on [0]"); } } } } } void subscribePins() { Serial.println("-- Subscribe to properties"); client.setCallback(callback); for (int i = 0; i < sizeof(properties) / sizeof(properties[0]); i++) { if (client.connected()) { String topic = String(MQTT_BASE_TOPIC) + "/" + String(properties[i]); if (client.subscribe(topic.c_str())) { Serial.println("Subscribed to topic: " + topic); } else { Serial.println("Failed to subscribe to topic: " + topic); } } } String topic1 = String(MQTT_BASE_TOPIC) + "/MiFloraRead"; if (client.subscribe(topic1.c_str())) { Serial.println("Subscribed to topic: " + topic1); } else { Serial.println("Failed to subscribe to topic: " + topic1); } String topic2 = String(MQTT_BASE_TOPIC) + "/MiFloraRead"; if (client.subscribe(topic2.c_str())) { Serial.println("Subscribed to topic: " + topic2); } else { Serial.println("Failed to subscribe to topic: " + topic2); } } void setupPins() { for (int i = 0; i < sizeof(ausgangspins) / sizeof(ausgangspins[0]); i++) { pinMode(ausgangspins[i], OUTPUT); digitalWrite(ausgangspins[i], LOW); } pinMode(waterSensorPin, INPUT); // Eingangspin für Wasserstandssensor //attachInterrupt(digitalPinToInterrupt(waterSensorPin), sendWaterLevel, CHANGE); } void sendWaterLevel() { // Überprüfe den Wasserstand Serial.println ("-- read Water Level"); bool wasserstand = digitalRead(waterSensorPin); Serial.print("Level is "); if (wasserstand) { Serial.println("HIGH"); } else { Serial.println("LOW"); } client.publish((MQTT_BASE_TOPIC + "/wasser").c_str(), wasserstand ? "1" : "0"); Serial.println(" >> published"); } void setup() { Serial.begin(115200); delay(1000); //Serial.flush(); setupPins(); // connecting wifi and mqtt server connectWifi(); connectMqtt(); // subscribe to topics subscribePins(); // starting BLE Serial.println("Initialize BLE client..."); BLEDevice::init(""); BLEDevice::setPower(ESP_PWR_LVL_P7); Serial.println("--- setup complete ---"); } void loop() { // check MQTT connection if (!client.connected()) { Serial.println("!Not connected >> reconnect"); connectMqtt(); subscribePins(); } else { client.loop(); if (bootCount >200000) { bootCount = 0; Serial.println("-- connection alive, connect.loop sent"); } else { bootCount ++; } } //sendWaterLevel(); }