Compare commits

..

2 Commits

Author SHA1 Message Date
Siwat Sirichai 4c77474a94 fixed climate card mqtt crash bug 2023-12-31 23:29:40 +07:00
Siwat Sirichai 9cd5b7132b mqtt_dbg 2023-12-31 20:49:22 +07:00
8 changed files with 147 additions and 50 deletions

View File

@ -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() {

View File

@ -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) {

View File

@ -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<void(char *, char *)> 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
// 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<void(char *, char *)> 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<void(void)> 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)
@ -454,7 +488,8 @@ MqttConfig *ESPMegaIoT::getMqttConfig()
bool ESPMegaIoT::mqttConnected()
{
return mqtt_connected;
//return mqtt_connected;
return mqtt.connected();
}
bool ESPMegaIoT::networkConnected()

View File

@ -13,6 +13,10 @@
#include <ETH.h>
#include <WiFi.h>
#include <FRAM.h>
#include <map>
#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<void(char *, char *)> callback);
void unregisterMqttCallback(uint8_t handler);
uint8_t registerRelativeMqttCallback(std::function<void(char *, char *)> callback);
void unregisterRelativeMqttCallback(uint8_t handler);
uint8_t registerSubscribeCallback(std::function<void(void)> 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<uint8_t, std::function<void(char*, char*)>> mqtt_callbacks;
std::map<uint8_t, std::function<void(char*, char*)>> mqtt_relative_callbacks;
std::map<uint8_t, std::function<void(void)>> 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

View File

@ -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() {

View File

@ -1,6 +1,7 @@
#pragma once
#include <ExpansionCard.hpp>
#include <PubSubClient.h>
#include <esp_log.h>
class IoTComponent {
public:
virtual bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);

View File

@ -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
build_flags = -DCORE_DEBUG_LEVEL=5

View File

@ -4,10 +4,18 @@
#include <ClimateCard.hpp>
// #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"};
@ -29,6 +37,7 @@ AirConditioner ac = {
.getInfraredCode = &getInfraredCode};
ClimateCard climateCard = ClimateCard(14, ac);
#endif
void input_change_callback(uint8_t pin, uint8_t value)
{
@ -38,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,
@ -68,6 +78,7 @@ void setMqttConfig()
espmega.iot->setMqttConfig(config);
espmega.iot->saveMqttConfig();
}
#endif
void setup()
{
@ -79,20 +90,31 @@ void setup()
ETH.begin();
ESP_LOGI("Initializer", "Binding Ethernet to IOT module");
espmega.iot->bindEthernetInterface(&ETH);
#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");
@ -101,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()
@ -122,4 +149,21 @@ void loop()
espmega.dumpFRAMtoSerialASCII(0, 500);
}
#endif
// Every 5 seconds, publish "I'm alive" to MQTT
#ifdef MQTT_DEBUG
static uint32_t last_mqtt_publish = 0;
if (millis() - last_mqtt_publish >= 5000)
{
last_mqtt_publish = millis();
espmega.iot->publish("/espmegai/alive", "true");
}
static uint32_t last_mqtt_status = 0;
if (millis() - last_mqtt_status >= 1000)
{
last_mqtt_status = millis();
Serial.print("MQTT Status: ");
Serial.println(espmega.iot->mqttConnected() ? "Connected" : "Disconnected");
}
#endif
}