#include #include ESPMegaIoT::ESPMegaIoT() : mqtt(tcpClient) { tcpClient.setTimeout(1); // 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(base_topic) + 1; if (user_relative_mqtt_callback != NULL) { user_relative_mqtt_callback(topic_without_base + 3, payload_buffer); } if (user_mqtt_callback != NULL) { user_mqtt_callback(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->base_topic, base_topic); base_topic_length = strlen(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, 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, 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, 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, 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::deregisterCard(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.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) { 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.connect(client_id)) { sessionKeepAlive(); mqttSubscribe(); // Publish all cards for (int i = 0; i < 255; i++) { if (components[i] != NULL) { components[i]->publishReport(); } } ESP_LOGI("ESPMegaIoT", "MQTT Connected"); 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::publishToTopic(char *topic, char *payload) { mqtt.publish(topic, payload); } void ESPMegaIoT::registerMqttCallback(void (*callback)(char *, char *)) { user_mqtt_callback = callback; } void ESPMegaIoT::mqttSubscribe() { if (user_subscribe_callback != NULL) { user_subscribe_callback(); } // Subscribe to all topics for (int i = 0; i < 255; i++) { if (components[i] != NULL) { components[i]->subscribe(); } } } 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); 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() { static unsigned long lastSessionKeepAlive = 0; if (millis() - lastSessionKeepAlive > 30000) { lastSessionKeepAlive = millis(); // Check if mqtt is connected if (!mqtt.connected()) { // Try to reconnect mqtt_connected = mqttReconnect(); } } } void ESPMegaIoT::registerRelativeMqttCallback(void (*callback)(char *, char *)) { user_relative_mqtt_callback = callback; } void ESPMegaIoT::publishRelative(char *topic, char *payload) { char absolute_topic[100]; sprintf(absolute_topic, "%s/%s", base_topic, topic); mqtt.publish(absolute_topic, payload); } void ESPMegaIoT::subscribeRelative(char *topic) { char absolute_topic[100]; sprintf(absolute_topic, "%s/%s", base_topic, topic); mqtt.subscribe(absolute_topic); } void ESPMegaIoT::registerSubscribeCallback(void (*callback)(void)) { user_subscribe_callback = callback; } 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); } 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) this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port, mqtt_config.mqtt_user, mqtt_config.mqtt_password); else 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; } 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; } bool ESPMegaIoT::networkConnected() { if (network_config.useWifi) return WiFi.status() == WL_CONNECTED; else return ethernetIface->linkUp(); } void ESPMegaIoT::bindFRAM(FRAM *fram) { this->fram = fram; }