diff --git a/CO2_Gadget.ino b/CO2_Gadget.ino index af3a8a15..76e45f4b 100644 --- a/CO2_Gadget.ino +++ b/CO2_Gadget.ino @@ -18,6 +18,7 @@ // Next data always defined to be able to configure in menu String hostName = UNITHOSTNAME; String rootTopic = UNITHOSTNAME; +String discoveryTopic = MQTT_DISCOVERY_PREFIX; String mqttClientId = UNITHOSTNAME; String mqttBroker = MQTT_BROKER_SERVER; String mqttUser = ""; @@ -25,6 +26,7 @@ String mqttPass = ""; String wifiSSID = WIFI_SSID_CREDENTIALS; String wifiPass = WIFI_PW_CREDENTIALS; String mDNSName = "Unset"; +String MACAddress = "Unset"; // String peerESPNow = ESPNOW_PEER_MAC_ADDRESS; uint8_t peerESPNowAddress[] = ESPNOW_PEER_MAC_ADDRESS; @@ -41,6 +43,7 @@ uint64_t timeToRetryTroubledWIFI = 300; // Time in seconds to retry WIFI connec uint64_t timeToRetryTroubledMQTT = 900; // Time in seconds to retry MQTT connection after it is troubled (no need to retry so often as it retries automatically after WiFi is connected) uint16_t WiFiConnectionRetries = 0; uint16_t maxWiFiConnectionRetries = 5; +bool mqttDiscoverySent = false; // Display and menu options uint32_t DisplayBrightness = 100; @@ -63,6 +66,7 @@ uint16_t boardIdESPNow = 0; // Variables for Battery reading float battery_voltage = 0; +uint8_t battery_level = 0; uint16_t timeBetweenBatteryRead = 15; uint64_t lastTimeBatteryRead = 0; // Time of last battery reading @@ -229,6 +233,7 @@ uint16_t batteryFullyChargedMillivolts = 4200; // Voltage of battery when it is /********* SETUP PUSH BUTTONS FUNCTIONALITY *********/ /********* *********/ /*****************************************************************************************************/ +#include "Arduino.h" #include "CO2_Gadget_Buttons.h" /*****************************************************************************************************/ @@ -366,6 +371,15 @@ void displayLoop() { } } +void batteryLoop() { + const float lastBatteryVoltage = battery_voltage; + readBatteryVoltage(); + if (abs(lastBatteryVoltage - battery_voltage) >= 0.1) { // If battery voltage changed by at least 0.1, update battery level + battery_level = getBatteryPercentage(); + Serial.printf("-->[BATT] Battery Level: %d%%\n", battery.level()); + } +} + void utilityLoop() { if (battery_voltage > 4.5) { setCpuFrequencyMhz(240); // High CPU frecuency when working on USB power @@ -411,6 +425,7 @@ void setup() { } void loop() { + batteryLoop(); wifiClientLoop(); mqttClientLoop(); sensorsLoop(); @@ -426,4 +441,4 @@ void loop() { #ifdef SUPPORT_BLE BLELoop(); #endif -} +} \ No newline at end of file diff --git a/CO2_Gadget_Battery.h b/CO2_Gadget_Battery.h index 4f1d75f8..64bb45b9 100644 --- a/CO2_Gadget_Battery.h +++ b/CO2_Gadget_Battery.h @@ -41,7 +41,6 @@ float readBatteryVoltage() { } uint8_t getBatteryPercentage() { - Serial.printf("-->[BATT] Battery Level: %d%%\n", battery.level()); return battery.level(); } diff --git a/CO2_Gadget_MQTT.h b/CO2_Gadget_MQTT.h index b4481cf1..e0193062 100644 --- a/CO2_Gadget_MQTT.h +++ b/CO2_Gadget_MQTT.h @@ -113,16 +113,130 @@ void publishFloatMQTT(String topic, float payload) { void publishStrMQTT(String topic, String payload) { #ifdef SUPPORT_MQTT - payload.toCharArray(charPublish, payload.length()); topic = rootTopic + topic; if (!inMenu) { - Serial.printf("-->[MQTT] Publishing %s to ", payload); + Serial.printf("-->[MQTT] Publishing %s to ", payload.c_str()); Serial.println("topic: " + topic); - mqttClient.publish((topic).c_str(), charPublish); + mqttClient.publish(topic.c_str(), payload.c_str()); + } +#endif +} + +void publishStrDiscoveryMQTT(String topic, String payload, int qos) { +#ifdef SUPPORT_MQTT + if (!inMenu) { + Serial.printf("-->[MQTT] Publishing %s to ", payload.c_str()); + Serial.println("topic: " + topic); + mqttClient.publish(topic.c_str(), payload.c_str(), true); } #endif } +bool sendMQTTDiscoveryTopic(String deviceClass, String stateClass, String entityCategory, + String group, String field, String name, String icon, String unit, + int qos) { + String version = String(CO2_GADGET_VERSION) + String(CO2_GADGET_REV) + " (" + String(FLAVOUR) + ")"; + String hw_version = String(FLAVOUR); + + String maintopic = String(rootTopic); + + String topicFull; + String configTopic; + String payload; + + configTopic = field; + + if (field == "problem") { // Special binary sensor which is based on error topic + topicFull = discoveryTopic + "binary_sensor/" + maintopic + "/" + configTopic + "/config"; + } else { + topicFull = discoveryTopic + "sensor/" + maintopic + "/" + configTopic + "/config"; + } + + /* See https://www.home-assistant.io/docs/mqtt/discovery/ */ + payload = String("{") + + "\"~\": \"" + maintopic + "\"," + + "\"unique_id\": \"" + maintopic + "-" + configTopic + "\"," + + "\"object_id\": \"" + maintopic + "_" + configTopic + "\"," + + "\"name\": \"" + name + "\"," + + "\"icon\": \"mdi:" + icon + "\"," + + "\"unit_of_measurement\": \"" + unit + "\","; + + if (field == "problem") { // Special binary sensor which is based on error topic + payload += "\"state_topic\": \"~/error\","; + payload += "\"value_template\": \"{{ 'OFF' if 'no error' in value else 'ON'}}\","; + } else { + payload += "\"state_topic\": \"~/" + field + "\","; + } + + if (deviceClass != "") { + payload += "\"device_class\": \"" + deviceClass + "\","; + } + + if (stateClass != "") { + payload += "\"state_class\": \"" + stateClass + "\","; + } + + if (entityCategory != "") { + payload += "\"entity_category\": \"" + entityCategory + "\","; + } + + payload += String("\"device\": {") + + "\"identifiers\": [\"" + maintopic + "\"]," + + "\"name\": \"" + maintopic + "\"," + + "\"model\": \"CO2 Gadget\"," + + "\"manufacturer\": \"emariete.com\"," + + "\"hw_version\": \"" + hw_version + "\"," + + "\"sw_version\": \"" + version + "\"," + + "\"configuration_url\": \"http://" + WiFi.localIP().toString() + "\"" + + "}" + + "}"; + + // Replace the following line with your MQTT publish function + // return MQTTPublish(topicFull, payload, qos, true); + Serial.print("MQTT Publish Topic: "); + Serial.println(topicFull); + Serial.print("MQTT Publish Payload: "); + Serial.println(payload); + // topicFull = "Test"; + // payload = "Test"; + publishStrDiscoveryMQTT(topicFull, payload, qos); + return true; +} + +bool publishMQTTDiscovery(int qos) { + bool allSendsSuccessed = false; + + if (!mqttClient.connected()) { + Serial.println("Unable to send MQTT Discovery Topics, we are not connected to the MQTT broker!"); + return false; + } + + // clang-format off + // TO-DO: Add MAC Address, Hostname, IP and Status to discovery. Don't know why they are not working (home assistant doesn't show them) + // + // Device Class | State Class | Entity Category | Group | Field | User Friendly Name | Icon | Unit + allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "uptime", "Uptime", "clock-time-eight-outline", "s", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "MAC", "MAC Address", "network-outline", "", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "hostname", "Hostname", "network-outline", "", qos); + allSendsSuccessed |= sendMQTTDiscoveryTopic("", "measurement", "diagnostic", "", "freeMem", "Free Memory", "memory", "B", qos); + allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "wifiRSSI", "Wi-Fi RSSI", "wifi", "dBm", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "IP", "IP", "network-outline", "", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "status", "Status", "list-status", "", qos); + allSendsSuccessed |= sendMQTTDiscoveryTopic("", "measurement", "diagnostic", "", "battery", "Battery", "", "%", qos); + allSendsSuccessed |= sendMQTTDiscoveryTopic("", "measurement", "diagnostic", "", "voltage", "Voltage", "", "V", qos); + + allSendsSuccessed |= sendMQTTDiscoveryTopic("carbon_dioxide", "", "", "", "co2", "CO2", "molecule-co2", "ppm", qos); + allSendsSuccessed |= sendMQTTDiscoveryTopic("temperature", "", "", "", "temp", "Temperature", "temperature-celsius", "°C", qos); + allSendsSuccessed |= sendMQTTDiscoveryTopic("humidity", "", "", "", "humi", "Humidity", "water-percent", "%", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "error", "Error", "alert-circle-outline", "", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "diagnostic", "", "json", "JSON", "code-json", "", qos); + // allSendsSuccessed |= sendMQTTDiscoveryTopic("", "", "", "", "problem", "Problem", "alert-outline", "", qos); // Special binary sensor which is based on error topic + // clang-format on + + Serial.println("Successfully published all MQTT Discovery topics"); + return allSendsSuccessed; +} + void initMQTT() { #ifdef SUPPORT_MQTT if (activeMQTT) { @@ -136,6 +250,7 @@ void initMQTT() { Serial.printf("-->[MQTT] Initializing MQTT to broker IP: %s\n", mqttBroker.c_str()); mqttClient.setServer(mqttBroker.c_str(), 1883); mqttClient.setCallback(callbackMQTT); + mqttClient.setBufferSize(1024); mqttReconnect(); } #endif @@ -170,15 +285,32 @@ void publishMQTTAlarms() { } } +void publishMQTTSystemData() { + publishIntMQTT("/uptime", millis() / 1000); + publishFloatMQTT("/voltage", battery_voltage); + publishIntMQTT("/battery", battery_level); + publishIntMQTT("/freeMem", ESP.getFreeHeap()); + publishIntMQTT("/wifiRSSI", WiFi.RSSI()); + publishStrMQTT("/IP", WiFi.localIP().toString()); + publishStrMQTT("/MAC", WiFi.macAddress()); + publishStrMQTT("/hostname", hostName); + publishStrMQTT("/status", "OK"); +} + +void publishMeasurementsMQTT() { + publishIntMQTT("/co2", co2); + publishFloatMQTT("/temp", temp); + publishFloatMQTT("/humi", hum); +} + void publishMQTT() { #ifdef SUPPORT_MQTT if (activeMQTT) { if ((WiFi.status() == WL_CONNECTED) && (mqttClient.connected())) { if (millis() - lastTimeMQTTPublished >= timeBetweenMQTTPublish * 1000) { - publishIntMQTT("/co2", co2); - publishFloatMQTT("/temp", temp); - publishFloatMQTT("/humi", hum); + publishMeasurementsMQTT(); publishMQTTAlarms(); + publishMQTTSystemData(); lastTimeMQTTPublished = millis(); } // Serial.print("-->[MQTT] Free heap: "); @@ -201,6 +333,12 @@ void mqttClientLoop() { mqttClient.loop(); } } + + if (!mqttDiscoverySent && mqttClient.connected()) { + Serial.printf("-->[MQTT] Connected to broker. Sending discovery...\n"); + publishMQTTDiscovery(0); + mqttDiscoverySent = true; + } #endif } diff --git a/CO2_Gadget_WIFI.h b/CO2_Gadget_WIFI.h index 5ac4f445..39c99ffe 100644 --- a/CO2_Gadget_WIFI.h +++ b/CO2_Gadget_WIFI.h @@ -25,6 +25,20 @@ void onWifiSettingsChanged(std::string ssid, std::string password) { WiFi.begin(ssid.c_str(), password.c_str()); } +String getMACAddressAsString() { + byte mac[6]; + WiFi.macAddress(mac); + + String macAddress = String(mac[5], HEX) + ":" + + String(mac[4], HEX) + ":" + + String(mac[3], HEX) + ":" + + String(mac[2], HEX) + ":" + + String(mac[1], HEX) + ":" + + String(mac[0], HEX); + + return macAddress; +} + void printWiFiStatus() { // Print wifi status on serial monitor // Get current status @@ -78,7 +92,8 @@ void printWiFiStatus() { // Print wifi status on serial monitor // Print your WiFi shield's MAC address: Serial.print("-->[WiFi] MAC Address: "); - Serial.println(WiFi.macAddress()); + MACAddress = getMACAddressAsString(); + Serial.println(MACAddress); // Print the received signal strength: Serial.print("-->[WiFi] Signal strength (RSSI):"); @@ -234,23 +249,6 @@ String processor(const String &var) { return String(); } -void serialPrintMACAddress() { - byte mac[6]; - WiFi.macAddress(mac); - Serial.print("-->[WiFi] MAC: "); - Serial.print(mac[5], HEX); - Serial.print(":"); - Serial.print(mac[4], HEX); - Serial.print(":"); - Serial.print(mac[3], HEX); - Serial.print(":"); - Serial.print(mac[2], HEX); - Serial.print(":"); - Serial.print(mac[1], HEX); - Serial.print(":"); - Serial.println(mac[0], HEX); -} - bool checkStringIsNumerical(String myString) { uint16_t Numbers = 0; @@ -375,7 +373,8 @@ void initWifi() { } } Serial.println(""); - serialPrintMACAddress(); + Serial.print("-->[WiFi] MAC: "); + Serial.println(MACAddress); Serial.print("-->[WiFi] WiFi connected - IP = "); Serial.println(WiFi.localIP()); #ifdef SUPPORT_MDNS diff --git a/README.md b/README.md index 91e3166e..42f08acd 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This repository is mainly addressed at developers. If you are an end user willin - WIFI connection - Sending of data via MQTT - Receiving remote commands via MQTT +- MQTT Discovery protocol for Home Assistant (and others supporting it as HomeSeer with mcsMQTT) - ESP-NOW communications protocol from Espressif for long range and low power consuption ([more info here](https://emariete.com/en/gateway-esp-now-mqtt/)) - Over the air updates OTA - Support for Neopixel (WS2812B) addressable LEDs (RGB, GBR and RGBW) diff --git a/platformio.ini b/platformio.ini index dc2beb0b..0fcc0fed 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,8 +50,8 @@ build_flags = -D MQTT_BROKER_SERVER="\"192.168.1.145"\" -D CO2_GADGET_VERSION="\"0.7."\" - -D CO2_GADGET_REV="\"008"\" - -D CORE_DEBUG_LEVEL=0 + -D CO2_GADGET_REV="\"010"\" + -D CORE_DEBUG_LEVEL=0 -DNEOPIXEL_PIN=26 ; Pinnumber for button for down/next and back / exit actions -DNEOPIXEL_COUNT=16 ; How many neopixels to control -DENABLE_PIN=27 ; Reserved for the future to enable the sensor @@ -72,6 +72,9 @@ build_flags = ; -DSUPPORT_OTA ; Needs SUPPORT_WIFI ; -DSUPPORT_MDNS ; Needs SUPPORT_WIFI -DSUPPORT_MQTT ; Needs SUPPORT_WIFI + -DSUPPORT_MQTT_DISCOVERY + -DMQTT_DISCOVERY_PREFIX="\"homeassistant/\"" + -DSUPPORT_ESPNOW -DESPNOW_PEER_MAC_ADDRESS="{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}" ; MAC Address of the ESP-NOW receiver (STA MAC). For unicast use peer address, as: {0xE8, 0x68, 0xE7, 0x0F, 0x08, 0x90} -DESPNOW_WIFI_CH=1 ; ESP-NOW WiFi Channel. Must be same as gateway @@ -260,4 +263,4 @@ build_flags = -DFLAVOUR="\"ESP32 OLED OTA"\" -USUPPORT_BLE -DSUPPORT_OTA - -DSUPPORT_MDNS \ No newline at end of file + -DSUPPORT_MDNS