#include #include ESPMegaIoT::ESPMegaIoT() : mqtt(tcpClient) { tcpClient.setTimeout(TCP_TIMEOUT_SEC); // Initialize the components array for (int i = 0; i < 255; i++) { components[i] = NULL; } active = false; mqtt_connected = false; } ESPMegaIoT::~ESPMegaIoT() { } void ESPMegaIoT::mqttCallback(char *topic, byte *payload, unsigned int length) { // Create a null terminated string from the payload memcpy(payload_buffer, payload, length); payload_buffer[length] = '\0'; // Remove the base topic from the topic char *topic_without_base = topic + strlen(this->mqtt_config.base_topic) + 1; for (const auto &callback : mqtt_relative_callbacks) { callback.second(topic_without_base + 3, payload_buffer); } for (const auto &callback : mqtt_callbacks) { callback.second(topic, payload_buffer); } // Call the respective card's mqtt callback // Note that after the base topic, there should be the card id // /base_topic/card_id/... // First, get the card id in integer form char *card_id_str = strtok(topic_without_base, "/"); uint8_t card_id = atoi(card_id_str); // Check if the card is registered if (components[card_id] == NULL) { return; } components[card_id]->handleMqttMessage(topic_without_base + 3, payload_buffer); } void ESPMegaIoT::setBaseTopic(char *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[]) { this->cards = cards; active = true; } void ESPMegaIoT::loop() { if (!active) return; // Call each component's loop function for (int i = 0; i < 255; i++) { if (components[i] != NULL) { components[i]->loop(); } } mqtt.loop(); sessionKeepAlive(); } // Register Existing Card for use with IoT void ESPMegaIoT::registerCard(uint8_t card_id) { // Check if the card is already registered if (components[card_id] != NULL) { return; } // Get the card type uint8_t card_type = cards[card_id]->getType(); // Create the respective IoT component switch (card_type) { case CARD_TYPE_ANALOG: components[card_id] = new AnalogIoT(); components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); components[card_id]->publishReport(); } break; case CARD_TYPE_DIGITAL_INPUT: components[card_id] = new DigitalInputIoT(); components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); components[card_id]->publishReport(); } break; case CARD_TYPE_DIGITAL_OUTPUT: components[card_id] = new DigitalOutputIoT(); components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); components[card_id]->publishReport(); } break; case CARD_TYPE_CLIMATE: components[card_id] = new ClimateIoT(); components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic); if (mqtt_connected) { components[card_id]->subscribe(); components[card_id]->publishReport(); } break; default: ESP_LOGE("ESPMegaIoT", "Registering card %d failed: Unknown card", card_id); return; } } void ESPMegaIoT::unregisterCard(uint8_t card_id) { // Check if the card is registered if (components[card_id] == NULL) { return; } // Delete the IoT component delete components[card_id]; components[card_id] = NULL; } void ESPMegaIoT::publishCard(uint8_t card_id) { // Check if the card is registered if (components[card_id] == NULL) { return; } // Publish the card components[card_id]->publishReport(); } void ESPMegaIoT::subscribeToTopic(char *topic) { mqtt.subscribe(topic); } void ESPMegaIoT::unsubscribeFromTopic(char *topic) { mqtt.unsubscribe(topic); } void ESPMegaIoT::connectToWifi(char *ssid, char *password) { WiFi.begin(ssid, password); } void ESPMegaIoT::connectToWifi(char *ssid) { WiFi.begin(ssid); } void ESPMegaIoT::disconnectFromWifi() { WiFi.disconnect(); } bool ESPMegaIoT::wifiConnected() { return WiFi.status() == WL_CONNECTED; } bool ESPMegaIoT::connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port, char *mqtt_user, char *mqtt_password) { mqtt.setServer(mqtt_server, mqtt_port); auto boundCallback = std::bind(&ESPMegaIoT::mqttCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); mqtt.setCallback(boundCallback); if (mqtt_user == nullptr || mqtt_password == nullptr || strlen(mqtt_user) == 0 || strlen(mqtt_password) == 0) { mqtt_connected = false; ESP_LOGE("ESPMegaIoT", "MQTT Connection failed: Username or password not set but MQTT use_auth is true"); return false; } if (mqtt.connect(client_id, mqtt_user, mqtt_password)) { sessionKeepAlive(); mqttSubscribe(); // Publish all cards for (int i = 0; i < 255; i++) { if (components[i] != NULL) { components[i]->publishReport(); } } mqtt_connected = true; return true; } mqtt_connected = false; return false; } bool ESPMegaIoT::connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port) { ESP_LOGD("ESPMegaIoT", "Setting MQTT server to %s:%d", mqtt_server, mqtt_port); mqtt.setServer(mqtt_server, mqtt_port); auto boundCallback = std::bind(&ESPMegaIoT::mqttCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); ESP_LOGD("ESPMegaIoT", "Binding MQTT callback"); mqtt.setCallback(boundCallback); if (mqtt.connect(client_id)) { ESP_LOGD("ESPMegaIoT", "MQTT Connected, Calling session keep alive"); sessionKeepAlive(); ESP_LOGD("ESPMegaIoT", "Subscribing to topics"); mqttSubscribe(); ESP_LOGD("ESPMegaIoT", "Publishing reports"); // Publish all cards for (int i = 0; i < 255; i++) { if (components[i] != NULL) { components[i]->publishReport(); } } ESP_LOGI("ESPMegaIoT", "MQTT Connected OK."); mqtt_connected = true; return true; } ESP_LOGW("ESPMegaIoT", "MQTT Connection failed: %d", mqtt.state()); mqtt_connected = false; return false; } void ESPMegaIoT::disconnectFromMqtt() { mqtt.disconnect(); } void ESPMegaIoT::publish(const char *topic, const char *payload) { mqtt.publish(topic, payload); } uint8_t ESPMegaIoT::registerMqttCallback(std::function 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"); for (const auto &callback : subscribe_callbacks) { callback.second(); mqtt.loop(); } // Subscribe to all topics for (int i = 0; i < 255; i++) { if (components[i] != NULL) { ESP_LOGD("ESPMegaIoT","Subscribing component %d", i); components[i]->subscribe(); mqtt.loop(); } } } void ESPMegaIoT::publishRelative(uint8_t card_id, char *topic, char *payload) { char absolute_topic[100]; sprintf(absolute_topic, "%s/%d/%s", this->mqtt_config.base_topic, card_id, topic); mqtt.publish(absolute_topic, payload); } bool ESPMegaIoT::mqttReconnect() { if (this->mqtt_config.mqtt_useauth) { return this->connectToMqtt(this->network_config.hostname, this->mqtt_config.mqtt_server, this->mqtt_config.mqtt_port, this->mqtt_config.mqtt_user, this->mqtt_config.mqtt_password); } else { return this->connectToMqtt(this->network_config.hostname, this->mqtt_config.mqtt_server, this->mqtt_config.mqtt_port); } } 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; static unsigned long lastConnectionAttempt = 0; if (millis() - lastSessionKeepAlive > 1000) { lastSessionKeepAlive = millis(); // Check if mqtt is connected if (!mqtt.connected()) { // Try to reconnect if lastConnectionAttempt exceed MQTT_RECONNECT_INTERVAL if (millis() - lastConnectionAttempt > MQTT_RECONNECT_INTERVAL) { lastConnectionAttempt = millis(); mqtt_connected = mqttReconnect(); } } } } uint8_t ESPMegaIoT::registerRelativeMqttCallback(std::function 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", this->mqtt_config.base_topic, topic); mqtt.publish(absolute_topic, payload); mqtt.loop(); } void ESPMegaIoT::subscribeRelative(char *topic) { char absolute_topic[100]; sprintf(absolute_topic, "%s/%s", this->mqtt_config.base_topic, topic); mqtt.subscribe(absolute_topic); mqtt.loop(); } uint8_t ESPMegaIoT::registerSubscribeCallback(std::function 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) { this->network_config = network_config; } void ESPMegaIoT::loadNetworkConfig() { // Load the network config from FRAM network_config.ip = fram->read32(IOT_FRAM_ADDRESS); network_config.gateway = fram->read32(IOT_FRAM_ADDRESS + 4); network_config.subnet = fram->read32(IOT_FRAM_ADDRESS + 8); network_config.dns1 = fram->read32(IOT_FRAM_ADDRESS + 12); network_config.dns2 = fram->read32(IOT_FRAM_ADDRESS + 16); fram->read(IOT_FRAM_ADDRESS + 20, (uint8_t *)network_config.hostname, 32); network_config.useStaticIp = fram->read8(IOT_FRAM_ADDRESS + 52); network_config.useWifi = fram->read8(IOT_FRAM_ADDRESS + 53); network_config.wifiUseAuth = fram->read8(IOT_FRAM_ADDRESS + 54); fram->read(IOT_FRAM_ADDRESS + 55, (uint8_t *)network_config.ssid, 32); fram->read(IOT_FRAM_ADDRESS + 87, (uint8_t *)network_config.password, 32); } void ESPMegaIoT::saveNetworkConfig() { // Save the network config to FRAM fram->write32(IOT_FRAM_ADDRESS, network_config.ip); fram->write32(IOT_FRAM_ADDRESS + 4, network_config.gateway); fram->write32(IOT_FRAM_ADDRESS + 8, network_config.subnet); fram->write32(IOT_FRAM_ADDRESS + 12, network_config.dns1); fram->write32(IOT_FRAM_ADDRESS + 16, network_config.dns2); fram->write(IOT_FRAM_ADDRESS + 20, (uint8_t *)network_config.hostname, 32); fram->write8(IOT_FRAM_ADDRESS + 52, network_config.useStaticIp); fram->write8(IOT_FRAM_ADDRESS + 53, network_config.useWifi); fram->write8(IOT_FRAM_ADDRESS + 54, network_config.wifiUseAuth); fram->write(IOT_FRAM_ADDRESS + 55, (uint8_t *)network_config.ssid, 32); fram->write(IOT_FRAM_ADDRESS + 87, (uint8_t *)network_config.password, 32); } void ESPMegaIoT::ethernetBegin() { ethernetIface->setHostname(network_config.hostname); } void ESPMegaIoT::loadMqttConfig() { // Load the mqtt config from FRAM // We skip bytes 119-127 because they are reserved for the network config mqtt_config.mqtt_port = fram->read16(IOT_FRAM_ADDRESS + 128); fram->read(IOT_FRAM_ADDRESS + 130, (uint8_t *)mqtt_config.mqtt_server, 32); fram->read(IOT_FRAM_ADDRESS + 162, (uint8_t *)mqtt_config.mqtt_user, 32); 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() { fram->write16(IOT_FRAM_ADDRESS + 128, mqtt_config.mqtt_port); fram->write(IOT_FRAM_ADDRESS + 130, (uint8_t *)mqtt_config.mqtt_server, 32); fram->write(IOT_FRAM_ADDRESS + 162, (uint8_t *)mqtt_config.mqtt_user, 32); fram->write(IOT_FRAM_ADDRESS + 194, (uint8_t *)mqtt_config.mqtt_password, 32); fram->write8(IOT_FRAM_ADDRESS + 226, mqtt_config.mqtt_useauth); fram->write(IOT_FRAM_ADDRESS + 227, (uint8_t *)mqtt_config.base_topic, 32); } void ESPMegaIoT::connectToMqtt() { if (mqtt_config.mqtt_useauth) { ESP_LOGD("ESPMegaIoT", "Connecting to MQTT with auth"); this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port, mqtt_config.mqtt_user, mqtt_config.mqtt_password); } else { ESP_LOGD("ESPMegaIoT", "Connecting to MQTT without auth"); this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port); } } void ESPMegaIoT::connectNetwork() { if (network_config.useWifi) { if (network_config.wifiUseAuth) this->connectToWifi(network_config.ssid, network_config.password); else this->connectToWifi(network_config.ssid); if (network_config.useStaticIp) WiFi.config(network_config.ip, network_config.gateway, network_config.subnet); else WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); } else { this->ethernetBegin(); if (network_config.useStaticIp) ethernetIface->config(network_config.ip, network_config.gateway, network_config.subnet, network_config.dns1, network_config.dns2); } } 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) { this->ethernetIface = ethernetIface; } IoTComponent *ESPMegaIoT::getComponent(uint8_t card_id) { return components[card_id]; } NetworkConfig *ESPMegaIoT::getNetworkConfig() { return &network_config; } MqttConfig *ESPMegaIoT::getMqttConfig() { return &mqtt_config; } bool ESPMegaIoT::mqttConnected() { //return mqtt_connected; return mqtt.connected(); } bool ESPMegaIoT::networkConnected() { if (network_config.useWifi) return WiFi.status() == WL_CONNECTED; else return ethernetIface->linkUp(); } void ESPMegaIoT::bindFRAM(FRAM *fram) { this->fram = fram; }