From 4c77474a94190fb22ed259f7cecc7d4b54344ca5 Mon Sep 17 00:00:00 2001 From: Siwat Sirichai Date: Sun, 31 Dec 2023 23:29:40 +0700 Subject: [PATCH] fixed climate card mqtt crash bug --- .../lib/ESPMegaPRO/ClimateIoT.cpp | 6 ++ .../lib/ESPMegaPRO/DigitalInputIoT.cpp | 4 +- .../lib/ESPMegaPRO/ESPMegaIoT.cpp | 98 +++++++++++++------ .../lib/ESPMegaPRO/ESPMegaIoT.hpp | 25 +++-- .../lib/ESPMegaPRO/IoTComponent.cpp | 6 +- .../lib/ESPMegaPRO/IoTComponent.hpp | 1 + ESPMegaPRO-firmware/platformio.ini | 2 +- ESPMegaPRO-firmware/src/main.cpp | 36 ++++++- 8 files changed, 128 insertions(+), 50 deletions(-) diff --git a/ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.cpp b/ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.cpp index 613dc76..54db2f7 100644 --- a/ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.cpp +++ b/ESPMegaPRO-firmware/lib/ESPMegaPRO/ClimateIoT.cpp @@ -9,12 +9,16 @@ ClimateIoT::~ClimateIoT() { } bool ClimateIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) { + this->mqtt = mqtt; + this->base_topic = base_topic; + this->card_id = card_id; this->card = (ClimateCard *)card; // Reister Callbacks auto bindedSensorCallback = std::bind(&ClimateIoT::handleSensorUpdate, this, std::placeholders::_1, std::placeholders::_2); this->card->registerSensorCallback(bindedSensorCallback); auto bindedAirConditionerCallback = std::bind(&ClimateIoT::handleAirConditionerUpdate, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); this->card->registerChangeCallback(bindedAirConditionerCallback); + ESP_LOGD("ClimateIoT", "Climate IoT Component initialized"); return true; } @@ -83,9 +87,11 @@ void ClimateIoT::publishReport() { } void ClimateIoT::subscribe() { + ESP_LOGD("ClimateIoT", " topics"); this->subscribeRelative(AC_TEMPERATURE_SET_TOPIC); this->subscribeRelative(AC_MODE_SET_TOPIC); this->subscribeRelative(AC_FAN_SPEED_SET_TOPIC); + ESP_LOGD("ClimateIoT", "Subscribed to topics"); } void ClimateIoT::loop() { diff --git a/ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.cpp b/ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.cpp index 6349803..138f91b 100644 --- a/ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.cpp +++ b/ESPMegaPRO-firmware/lib/ESPMegaPRO/DigitalInputIoT.cpp @@ -13,9 +13,7 @@ bool DigitalInputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient * } void DigitalInputIoT::subscribe() { - char topic[64]; - sprintf(topic, "%s/%d/%s", this->base_topic, this->card_id, PUBLISH_ENABLE_TOPIC); - this->subscribeRelative(topic); + this->subscribeRelative(PUBLISH_ENABLE_TOPIC); } void DigitalInputIoT::handleMqttMessage(char *topic, char *payload) { diff --git a/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.cpp b/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.cpp index 995351d..24a649c 100644 --- a/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.cpp +++ b/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.cpp @@ -3,7 +3,7 @@ ESPMegaIoT::ESPMegaIoT() : mqtt(tcpClient) { - tcpClient.setTimeout(1); + tcpClient.setTimeout(TCP_TIMEOUT_SEC); // Initialize the components array for (int i = 0; i < 255; i++) { @@ -11,9 +11,6 @@ ESPMegaIoT::ESPMegaIoT() : mqtt(tcpClient) } active = false; mqtt_connected = false; - this->user_mqtt_callback = nullptr; - this->user_relative_mqtt_callback = nullptr; - this->user_subscribe_callback = nullptr; } ESPMegaIoT::~ESPMegaIoT() @@ -26,14 +23,14 @@ void ESPMegaIoT::mqttCallback(char *topic, byte *payload, unsigned int length) memcpy(payload_buffer, payload, length); payload_buffer[length] = '\0'; // Remove the base topic from the topic - char *topic_without_base = topic + strlen(base_topic) + 1; - if (user_relative_mqtt_callback != NULL) + char *topic_without_base = topic + strlen(this->mqtt_config.base_topic) + 1; + for (const auto &callback : mqtt_relative_callbacks) { - user_relative_mqtt_callback(topic_without_base + 3, payload_buffer); + callback.second(topic_without_base + 3, payload_buffer); } - if (user_mqtt_callback != NULL) + for (const auto &callback : mqtt_callbacks) { - user_mqtt_callback(topic, payload_buffer); + callback.second(topic, payload_buffer); } // Call the respective card's mqtt callback // Note that after the base topic, there should be the card id @@ -51,8 +48,8 @@ void ESPMegaIoT::mqttCallback(char *topic, byte *payload, unsigned int length) void ESPMegaIoT::setBaseTopic(char *base_topic) { - strcpy(this->base_topic, base_topic); - base_topic_length = strlen(base_topic); + strcpy(this->mqtt_config.base_topic, base_topic); + base_topic_length = strlen(this->mqtt_config.base_topic); } void ESPMegaIoT::intr_begin(ExpansionCard *cards[]) @@ -91,7 +88,7 @@ void ESPMegaIoT::registerCard(uint8_t card_id) { case CARD_TYPE_ANALOG: components[card_id] = new AnalogIoT(); - components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic); + components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); @@ -100,16 +97,22 @@ void ESPMegaIoT::registerCard(uint8_t card_id) break; case CARD_TYPE_DIGITAL_INPUT: components[card_id] = new DigitalInputIoT(); - components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic); + components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { + Serial.println("Subscribing to Input Topics"); components[card_id]->subscribe(); + mqtt.loop(); + Serial.println(mqtt.connected()? "MQTT Connected" : "MQTT Not Connected"); + Serial.println("Publishing Input report"); components[card_id]->publishReport(); + mqtt.loop(); + Serial.println(mqtt.connected()? "MQTT Connected" : "MQTT Not Connected"); } break; case CARD_TYPE_DIGITAL_OUTPUT: components[card_id] = new DigitalOutputIoT(); - components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic); + components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); @@ -118,7 +121,7 @@ void ESPMegaIoT::registerCard(uint8_t card_id) break; case CARD_TYPE_CLIMATE: components[card_id] = new ClimateIoT(); - components[card_id]->begin(card_id, cards[card_id], &mqtt, base_topic); + components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); @@ -238,27 +241,33 @@ void ESPMegaIoT::disconnectFromMqtt() { mqtt.disconnect(); } -void ESPMegaIoT::publishToTopic(char *topic, char *payload) +void ESPMegaIoT::publish(const char *topic, const char *payload) { mqtt.publish(topic, payload); } -void ESPMegaIoT::registerMqttCallback(void (*callback)(char *, char *)) + +uint8_t ESPMegaIoT::registerMqttCallback(std::function callback) { - user_mqtt_callback = callback; + mqtt_callbacks[mqtt_callbacks_handler_index] = callback; + return mqtt_callbacks_handler_index++; } + +void ESPMegaIoT::unregisterMqttCallback(uint8_t handler) +{ + mqtt_callbacks.erase(handler); +} + void ESPMegaIoT::mqttSubscribe() { ESP_LOGD("ESPMegaIoT", "Begin MQTT Subscription"); - if (user_subscribe_callback != nullptr) + for (const auto &callback : subscribe_callbacks) { - ESP_LOGD("ESPMegaIoT", "Subscribing user callback"); - user_subscribe_callback(); + callback.second(); mqtt.loop(); } // Subscribe to all topics for (int i = 0; i < 255; i++) { - ESP_LOGV("ESPMegaIoT","Scanning component %d", i); if (components[i] != NULL) { ESP_LOGD("ESPMegaIoT","Subscribing component %d", i); @@ -271,7 +280,7 @@ void ESPMegaIoT::mqttSubscribe() void ESPMegaIoT::publishRelative(uint8_t card_id, char *topic, char *payload) { char absolute_topic[100]; - sprintf(absolute_topic, "%s/%d/%s", base_topic, card_id, topic); + sprintf(absolute_topic, "%s/%d/%s", this->mqtt_config.base_topic, card_id, topic); mqtt.publish(absolute_topic, payload); } @@ -289,28 +298,45 @@ bool ESPMegaIoT::mqttReconnect() void ESPMegaIoT::sessionKeepAlive() { + // This reconnect the MQTT if it disconnect. + // If a disconnect happens, this will reconnect the MQTT within 1 second. + // A connection attempt will be made at most once every MQTT_RECONNECT_INTERVAL + // This have the effect of reconnecting to the server immediately if the connection is lost + // and the connection was previously stable for at least MQTT_RECONNECT_INTERVAL + // But will not reconnect if the connection was unstable and the connection was lost static unsigned long lastSessionKeepAlive = 0; - if (millis() - lastSessionKeepAlive > 30000) + static unsigned long lastConnectionAttempt = 0; + if (millis() - lastSessionKeepAlive > 1000) { lastSessionKeepAlive = millis(); // Check if mqtt is connected if (!mqtt.connected()) { - // Try to reconnect - mqtt_connected = mqttReconnect(); + // Try to reconnect if lastConnectionAttempt exceed MQTT_RECONNECT_INTERVAL + if (millis() - lastConnectionAttempt > MQTT_RECONNECT_INTERVAL) + { + lastConnectionAttempt = millis(); + mqtt_connected = mqttReconnect(); + } } } } -void ESPMegaIoT::registerRelativeMqttCallback(void (*callback)(char *, char *)) +uint8_t ESPMegaIoT::registerRelativeMqttCallback(std::function callback) { - user_relative_mqtt_callback = callback; + mqtt_relative_callbacks[mqtt_relative_callbacks_handler_index] = callback; + return mqtt_relative_callbacks_handler_index++; +} + +void ESPMegaIoT::unregisterRelativeMqttCallback(uint8_t handler) +{ + mqtt_relative_callbacks.erase(handler); } void ESPMegaIoT::publishRelative(char *topic, char *payload) { char absolute_topic[100]; - sprintf(absolute_topic, "%s/%s", base_topic, topic); + sprintf(absolute_topic, "%s/%s", this->mqtt_config.base_topic, topic); mqtt.publish(absolute_topic, payload); mqtt.loop(); } @@ -318,14 +344,20 @@ void ESPMegaIoT::publishRelative(char *topic, char *payload) void ESPMegaIoT::subscribeRelative(char *topic) { char absolute_topic[100]; - sprintf(absolute_topic, "%s/%s", base_topic, topic); + sprintf(absolute_topic, "%s/%s", this->mqtt_config.base_topic, topic); mqtt.subscribe(absolute_topic); mqtt.loop(); } -void ESPMegaIoT::registerSubscribeCallback(void (*callback)(void)) +uint8_t ESPMegaIoT::registerSubscribeCallback(std::function callback) { - user_subscribe_callback = callback; + subscribe_callbacks[subscribe_callbacks_handler_index] = callback; + return subscribe_callbacks_handler_index++; +} + +void ESPMegaIoT::unregisterSubscribeCallback(uint8_t handler) +{ + subscribe_callbacks.erase(handler); } void ESPMegaIoT::setNetworkConfig(NetworkConfig network_config) @@ -380,6 +412,7 @@ void ESPMegaIoT::loadMqttConfig() fram->read(IOT_FRAM_ADDRESS + 194, (uint8_t *)mqtt_config.mqtt_password, 32); mqtt_config.mqtt_useauth = fram->read8(IOT_FRAM_ADDRESS + 226); fram->read(IOT_FRAM_ADDRESS + 227, (uint8_t *)mqtt_config.base_topic, 32); + this->base_topic_length = strlen(mqtt_config.base_topic); } void ESPMegaIoT::saveMqttConfig() @@ -430,6 +463,7 @@ void ESPMegaIoT::connectNetwork() void ESPMegaIoT::setMqttConfig(MqttConfig mqtt_config) { this->mqtt_config = mqtt_config; + this->base_topic_length = strlen(mqtt_config.base_topic); } void ESPMegaIoT::bindEthernetInterface(ETHClass *ethernetIface) diff --git a/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.hpp b/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.hpp index b624057..9d59862 100644 --- a/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.hpp +++ b/ESPMegaPRO-firmware/lib/ESPMegaPRO/ESPMegaIoT.hpp @@ -13,6 +13,10 @@ #include #include #include +#include + +#define TCP_TIMEOUT_SEC 5 +#define MQTT_RECONNECT_INTERVAL 30000 // FRAM Address for ESPMegaPROIoT // Starts from 34 @@ -80,10 +84,13 @@ public: bool connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port); bool mqttConnected(); void disconnectFromMqtt(); - void publishToTopic(char *topic, char *payload); - void registerMqttCallback(void (*callback)(char *, char *)); - void registerRelativeMqttCallback(void (*callback)(char *, char *)); - void registerSubscribeCallback(void (*callback)(void)); + void publish(const char *topic, const char *payload); + uint8_t registerMqttCallback(std::function callback); + void unregisterMqttCallback(uint8_t handler); + uint8_t registerRelativeMqttCallback(std::function callback); + void unregisterRelativeMqttCallback(uint8_t handler); + uint8_t registerSubscribeCallback(std::function callback); + void unregisterSubscribeCallback(uint8_t handler); void setBaseTopic(char *base_topic); void bindEthernetInterface(ETHClass *ethernetIface); bool networkConnected(); @@ -104,15 +111,17 @@ private: void wifiReconnect(); void mqttSubscribe(); void mqttCallback(char *topic, byte *payload, unsigned int length); - void (*user_mqtt_callback)(char *, char *); - void (*user_relative_mqtt_callback)(char *, char *); - void (*user_subscribe_callback)(void); + uint8_t mqtt_callbacks_handler_index; + uint8_t mqtt_relative_callbacks_handler_index; + uint8_t subscribe_callbacks_handler_index; + std::map> mqtt_callbacks; + std::map> mqtt_relative_callbacks; + std::map> subscribe_callbacks; void publishRelative(uint8_t card_id, char *topic, char *payload); bool active; PubSubClient mqtt; IoTComponent *components[255]; char payload_buffer[200]; - char base_topic[100]; uint8_t base_topic_length; ExpansionCard **cards; // Points to card array in ESPMegaPRO Core // MQTT Connection Parameters diff --git a/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.cpp b/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.cpp index 38479e7..d4fa956 100644 --- a/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.cpp +++ b/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.cpp @@ -5,15 +5,19 @@ void IoTComponent::setMqttClient(PubSubClient *mqtt) { } void IoTComponent::publishRelative(const char *topic, const char *payload) { - char absolute_topic[100]; + static char absolute_topic[100]; sprintf(absolute_topic, "%s/%02d/%s", base_topic, card_id, topic); mqtt->publish(absolute_topic, payload); + mqtt->loop(); + delay(50); } void IoTComponent::subscribeRelative(const char *topic) { char absolute_topic[50]; sprintf(absolute_topic, "%s/%02d/%s", base_topic, card_id, topic); + ESP_LOGD("IoTComponent", "Subscribing to %s", absolute_topic); mqtt->subscribe(absolute_topic); + mqtt->loop(); } void IoTComponent::loop() { diff --git a/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.hpp b/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.hpp index 947bc79..c075cdc 100644 --- a/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.hpp +++ b/ESPMegaPRO-firmware/lib/ESPMegaPRO/IoTComponent.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include class IoTComponent { public: virtual bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic); diff --git a/ESPMegaPRO-firmware/platformio.ini b/ESPMegaPRO-firmware/platformio.ini index 1fe009e..a17692c 100644 --- a/ESPMegaPRO-firmware/platformio.ini +++ b/ESPMegaPRO-firmware/platformio.ini @@ -31,4 +31,4 @@ lib_deps = adafruit/Adafruit PWM Servo Driver Library@^2.4.1 https://github.com/me-no-dev/ESPAsyncWebServer.git #esphome/ESPAsyncWebServer-esphome@^3.1.0 monitor_speed = 115200 -build_flags = -DCORE_DEBUG_LEVEL=1 \ No newline at end of file +build_flags = -DCORE_DEBUG_LEVEL=5 \ No newline at end of file diff --git a/ESPMegaPRO-firmware/src/main.cpp b/ESPMegaPRO-firmware/src/main.cpp index 887c441..b2c358e 100644 --- a/ESPMegaPRO-firmware/src/main.cpp +++ b/ESPMegaPRO-firmware/src/main.cpp @@ -5,10 +5,17 @@ // #define FRAM_DEBUG #define MQTT_DEBUG +// #define WRITE_DEFAULT_NETCONF +#define CLIMATE_CARD_ENABLE +#define MQTT_CARD_REGISTER +#define DISPLAY_ENABLE // Demo PLC firmware using the ESPMegaPRO OOP library ESPMegaPRO espmega = ESPMegaPRO(); + +#ifdef CLIMATE_CARD_ENABLE +// Climate Card const uint16_t irCode[15][4][4][1] = {0}; const char *mode_names[] = {"Off", "Fan-only", "Cool"}; const char *fan_speed_names[] = {"Auto", "Low", "Medium", "High"}; @@ -30,6 +37,7 @@ AirConditioner ac = { .getInfraredCode = &getInfraredCode}; ClimateCard climateCard = ClimateCard(14, ac); +#endif void input_change_callback(uint8_t pin, uint8_t value) { @@ -39,14 +47,15 @@ void input_change_callback(uint8_t pin, uint8_t value) Serial.println(value); } +#ifdef WRITE_DEFAULT_NETCONF void setNetworkConfig() { NetworkConfig config = { - .ip = {192, 168, 0, 11}, - .gateway = {192, 168, 0, 1}, + .ip = {10, 16, 6, 213}, + .gateway = {10, 16, 6, 1}, .subnet = {255, 255, 255, 0}, - .dns1 = {192, 168, 0, 1}, - .dns2 = {192, 168, 0, 1}, + .dns1 = {10, 192, 1, 1}, + .dns2 = {10, 192, 1, 1}, .useStaticIp = true, .useWifi = false, .wifiUseAuth = false, @@ -69,6 +78,7 @@ void setMqttConfig() espmega.iot->setMqttConfig(config); espmega.iot->saveMqttConfig(); } +#endif void setup() { @@ -80,20 +90,31 @@ void setup() ETH.begin(); ESP_LOGI("Initializer", "Binding Ethernet to IOT module"); espmega.iot->bindEthernetInterface(Ð); + #ifdef WRITE_DEFAULT_NETCONF + setNetworkConfig(); + #else ESP_LOGI("Initializer", "Loading network config"); espmega.iot->loadNetworkConfig(); + #endif ESP_LOGI("Initializer", "Connecting to network"); espmega.iot->connectNetwork(); + #ifdef WRITE_DEFAULT_NETCONF + setMqttConfig(); + #else ESP_LOGI("Initializer", "Loading MQTT config"); espmega.iot->loadMqttConfig(); + #endif ESP_LOGI("Initializer", "Connecting to MQTT"); espmega.iot->connectToMqtt(); + #ifdef MQTT_CARD_REGISTER ESP_LOGI("Initializer", "Registering cards 0"); espmega.iot->registerCard(0); ESP_LOGI("Initializer", "Registering cards 1"); espmega.iot->registerCard(1); + #endif ESP_LOGI("Initializer", "Registering Input change callback"); espmega.inputs.registerCallback(input_change_callback); + #ifdef CLIMATE_CARD_ENABLE ESP_LOGI("Initializer", "Installing climate card"); espmega.installCard(2, &climateCard); ESP_LOGI("Initializer", "Binding climate card to FRAM"); @@ -102,10 +123,15 @@ void setup() climateCard.loadStateFromFRAM(); ESP_LOGI("Initializer", "Enabling climate card FRAM autosave"); climateCard.setFRAMAutoSave(true); + ESP_LOGI("Initializer", "Registering cards 2"); + espmega.iot->registerCard(2); + #endif + #ifdef DISPLAY_ENABLE ESP_LOGI("Initializer", "Enabling internal display"); espmega.enableInternalDisplay(&Serial); ESP_LOGI("Initializer", "Binding climate card to internal display"); espmega.display->bindClimateCard(&climateCard); + #endif } void loop() @@ -130,7 +156,7 @@ void loop() if (millis() - last_mqtt_publish >= 5000) { last_mqtt_publish = millis(); - espmega.iot->publishToTopic("/espmegai/alive", "true"); + espmega.iot->publish("/espmegai/alive", "true"); } static uint32_t last_mqtt_status = 0; if (millis() - last_mqtt_status >= 1000)