diff --git a/src/app_config.cpp b/src/app_config.cpp index 080e6211..dd9a642e 100644 --- a/src/app_config.cpp +++ b/src/app_config.cpp @@ -354,8 +354,8 @@ void config_changed(String name) timeManager.setTimeZone(time_zone); } else if(name == "flags") { divert.setMode((config_divert_enabled() && 1 == config_charge_mode()) ? DivertMode::Eco : DivertMode::Normal); - if(mqtt_connected() != config_mqtt_enabled()) { - mqtt_restart(); + if(mqtt.isConnected() != config_mqtt_enabled()) { + mqtt.restartConnection(); } if(emoncms_connected != config_emoncms_enabled()) { emoncms_updated = true; @@ -364,7 +364,7 @@ void config_changed(String name) OcppTask::notifyConfigChanged(); evse.setSleepForDisable(!config_pause_uses_disabled()); } else if(name.startsWith("mqtt_")) { - mqtt_restart(); + mqtt.restartConnection(); } else if(name.startsWith("ocpp_")) { OcppTask::notifyConfigChanged(); } else if(name.startsWith("emoncms_")) { diff --git a/src/app_config_mode.h b/src/app_config_mode.h index e67bef47..34ccf3c0 100644 --- a/src/app_config_mode.h +++ b/src/app_config_mode.h @@ -4,7 +4,6 @@ #include #include -#include "mqtt.h" #include "app_config.h" class ConfigOptVirtualChargeMode : public ConfigOpt diff --git a/src/app_config_mqtt.h b/src/app_config_mqtt.h index 334fc112..dd7fac44 100644 --- a/src/app_config_mqtt.h +++ b/src/app_config_mqtt.h @@ -4,7 +4,6 @@ #include #include -#include "mqtt.h" #include "app_config.h" class ConfigOptVirtualMqttProtocol : public ConfigOpt diff --git a/src/current_shaper.h b/src/current_shaper.h index eee7ab28..efa749f3 100644 --- a/src/current_shaper.h +++ b/src/current_shaper.h @@ -17,7 +17,6 @@ #include "emonesp.h" #include #include "evse_man.h" -#include "mqtt.h" #include "app_config.h" #include "http_update.h" #include "input.h" diff --git a/src/main.cpp b/src/main.cpp index cf77d485..d7138398 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -182,6 +182,8 @@ void setup() input_setup(); + mqtt.begin(); + ocpp.begin(evse, lcd, eventLog, rfid); DBUGF("After ocpp.begin: %d", ESPAL.getFreeHeap()); @@ -237,8 +239,6 @@ loop() { teslaClient.loop(); } - mqtt_loop(); - // ------------------------------------------------------------------- // Do these things once every 30 seconds // ------------------------------------------------------------------- @@ -300,7 +300,7 @@ void event_send(JsonDocument &event) #endif web_server_event(event); yield(); - mqtt_publish(event); + mqtt.publishData(event); yield(); } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 03d6ec46..1c872a21 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -2,717 +2,575 @@ #undef ENABLE_DEBUG #endif -#include "emonesp.h" #include "mqtt.h" #include "app_config.h" +#include "openevse.h" #include "divert.h" #include "input.h" #include "espal.h" #include "net_manager.h" #include "web_server.h" -#include "event.h" #include "manual.h" #include "scheduler.h" -#include "certificates.h" - -#include "openevse.h" #include "current_shaper.h" -#include -#include -#include +Mqtt mqtt(evse); // global instance + +Mqtt::Mqtt(EvseManager &evseManager) : + MicroTasks::Task(), + _evse(&evseManager) +{ +} + +Mqtt::~Mqtt() { + if (_mqttclient.connected()) { + _mqttclient.disconnect(); + } +} + +void Mqtt::begin() { + MicroTask.startTask(this); +} -#include +void Mqtt::setup() { + DBUGLN("Mqtt::setup called"); + // Initialization that needs to run once when the task is set up. -MongooseMqttClient mqttclient; -EvseProperties claim_props; -EvseProperties override_props; -LimitProperties limit_props; -DynamicJsonDocument mqtt_doc(4096); + _configVersion = INITIAL_CONFIG_VERSION -1; // Force initial publish + // Initialize versions to force publish on first connect + _claimsVersion = evse.getClaimsVersion() == 0 ? 1 : evse.getClaimsVersion() -1; // ensure different + _overrideVersion = manual.getVersion() == 0 ? 1 : manual.getVersion() -1; + _scheduleVersion = scheduler.getVersion() == 0 ? 1 : scheduler.getVersion() -1; + _limitVersion = limit.getVersion() == 0 ? 1 : limit.getVersion() -1; -static long nextMqttReconnectAttempt = 0; -static unsigned long mqttRestartTime = 0; -static bool connecting = false; -uint8_t claimsVersion = 0; -uint8_t overrideVersion = 0; -uint8_t scheduleVersion = 0; -uint8_t limitVersion = 0; -uint32_t configVersion = 0; + // Setup MQTT client callbacks + _mqttclient.onMessage([this](MongooseString topic, MongooseString payload) { + this->handleMqttMessage(topic, payload); + }); -String lastWill = ""; + _mqttclient.onError([this](int err) { + DBUGF("MQTT error %d", err); + this->onMqttDisconnect(err, + MG_EV_MQTT_CONNACK_UNACCEPTABLE_VERSION == err ? "CONNACK_UNACCEPTABLE_VERSION" : + MG_EV_MQTT_CONNACK_IDENTIFIER_REJECTED == err ? "CONNACK_IDENTIFIER_REJECTED" : + MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE == err ? "CONNACK_SERVER_UNAVAILABLE" : + MG_EV_MQTT_CONNACK_BAD_AUTH == err ? "CONNACK_BAD_AUTH" : + MG_EV_MQTT_CONNACK_NOT_AUTHORIZED == err ? "CONNACK_NOT_AUTHORIZED" : + strerror(err) + ); + _error_time = millis(); + }); -int loop_timer = 0; -unsigned long error_time = 0; + _mqttclient.onClose([this]() { + DBUGLN("MQTT connection closed"); + if (_error_time + 100 < millis()) { // Avoid double event if error just occurred + this->onMqttDisconnect(-1, "CLOSED"); + } + }); +} -#ifndef MQTT_CONNECT_TIMEOUT -#define MQTT_CONNECT_TIMEOUT (5 * 1000) -#endif // !MQTT_CONNECT_TIMEOUT +unsigned long Mqtt::loop(MicroTasks::WakeReason reason) { + Profile_Start(Mqtt_loop); -// ------------------------------------------------------------------- -// MQTT msg Received callback function: -// Function to be called when msg is received on MQTT subscribed topic -// Used to receive RAPI commands via MQTT -// //e.g to set current to 13A: /rapi/$SC 13 -// ------------------------------------------------------------------- -void mqttmsg_callback(MongooseString topic, MongooseString payload) { + // Handle MQTT restart requests + if (_mqttRestartTime > 0 && millis() > _mqttRestartTime) { + _mqttRestartTime = 0; + if (_mqttclient.connected()) { + DBUGLN("Disconnecting MQTT for restart"); + _mqttclient.disconnect(); // This should trigger onClose and then onMqttDisconnect + } + _nextMqttReconnectAttempt = 0; // Force immediate reconnect attempt + _connecting = false; // Reset connecting flag + } - String topic_string = topic.toString(); - // print received MQTT to debug + // Manage connection state + if (config_mqtt_enabled() && !_mqttclient.connected() && !_connecting) { + long now = millis(); + if (now > _nextMqttReconnectAttempt) { + _nextMqttReconnectAttempt = now + MQTT_CONNECT_TIMEOUT; + attemptConnection(); + } + } - DBUGLN("MQTT received:"); - DBUGLN("Topic: " + topic_string); + // If connected, perform periodic checks + if (_mqttclient.connected()) { + if (millis() - _loop_timer > MQTT_LOOP_INTERVAL) { + _loop_timer = millis(); + checkAndPublishUpdates(); + } + } + + + Profile_End(Mqtt_loop, 5); + return MQTT_LOOP_INTERVAL; +} + +void Mqtt::attemptConnection() { + if (!config_mqtt_enabled() || _connecting || _mqttclient.connected()) { + return; + } + _connecting = true; + DBUGLN("Mqtt attempting connection..."); + + String mqtt_host = mqtt_server + ":" + String(mqtt_port); + DBUGF("MQTT Connecting to... %s://%s", MQTT_MQTT == config_mqtt_protocol() ? "mqtt" : "mqtts", mqtt_host.c_str()); + + DynamicJsonDocument willDoc(JSON_OBJECT_SIZE(3) + 60); + willDoc["state"] = "disconnected"; + willDoc["id"] = ESPAL.getLongId(); + willDoc["name"] = esp_hostname; + _lastWill = ""; + serializeJson(willDoc, _lastWill); + + if (!config_mqtt_reject_unauthorized()) { + DEBUG.println("WARNING: Certificate verification disabled"); + } + + _mqttclient.setCredentials(mqtt_user, mqtt_pass); + _mqttclient.setLastWillAndTestimment(mqtt_announce_topic, _lastWill, true); + _mqttclient.setRejectUnauthorized(config_mqtt_reject_unauthorized()); + + if (mqtt_certificate_id != "") { + uint64_t cert_id = std::stoull(mqtt_certificate_id.c_str(), nullptr, 16); + const char *cert = certs.getCertificate(cert_id); + const char *key = certs.getKey(cert_id); + if (NULL != cert && NULL != key) { + _mqttclient.setCertificate(cert, key); + } + } + + _connecting = _mqttclient.connect((MongooseMqttProtocol)config_mqtt_protocol(), mqtt_host, esp_hostname, [this]() { + this->onMqttConnect(); + }); + + if(!_connecting) { // if connect call itself failed immediately + DBUGLN("MQTT immediate connection attempt failed."); + // onError or onClose should handle the detailed reason if it gets to that stage. + // If connect() returns false, it means it couldn't even start the attempt. + onMqttDisconnect(-100, "Initial connection failed"); // Custom error + } +} + +void Mqtt::onMqttConnect() { + DBUGLN("MQTT connected"); + _connecting = false; + _nextMqttReconnectAttempt = 0; // Reset reconnect timer + + DynamicJsonDocument doc(JSON_OBJECT_SIZE(5) + 200); + doc["state"] = "connected"; + doc["id"] = ESPAL.getLongId(); + doc["name"] = esp_hostname; + doc["mqtt"] = mqtt_topic; + doc["http"] = "http://" + net.getIp() + "/"; + + String announce = ""; + serializeJson(doc, announce); + _mqttclient.publish(mqtt_announce_topic, announce, true); + doc.clear(); + doc["mqtt_connected"] = 1; + event_send(doc); + + subscribeTopics(); + publishInitialState(); // Publish current states like config, claims, etc. +} + +void Mqtt::onMqttDisconnect(int err, const char *reason) { + DBUGLN("MQTT disconnected"); + _connecting = false; + // _nextMqttReconnectAttempt is handled by the main loop to retry. + + DynamicJsonDocument doc(JSON_OBJECT_SIZE(3) + 70); + doc["mqtt_connected"] = 0; + doc["mqtt_close_code"] = err; + doc["mqtt_close_reason"] = reason; + event_send(doc); +} + +void Mqtt::subscribeTopics() { + String mqtt_sub_topic; + + // RAPI commands + mqtt_sub_topic = mqtt_topic + "/rapi/in/#"; + _mqttclient.subscribe(mqtt_sub_topic); + yield(); + + // Divert mode related + if (config_divert_enabled()) { + if (divert_type == DIVERT_TYPE_SOLAR && mqtt_solar != "") { + _mqttclient.subscribe(mqtt_solar); yield(); + } + if (divert_type == DIVERT_TYPE_GRID && mqtt_grid_ie != "") { + _mqttclient.subscribe(mqtt_grid_ie); yield(); + } + } + + // Current shaper related + if (config_current_shaper_enabled()) { + if (mqtt_live_pwr != "" && mqtt_live_pwr != mqtt_grid_ie) { + _mqttclient.subscribe(mqtt_live_pwr); yield(); + } + } + + // Vehicle data + if (mqtt_vehicle_soc != "") { _mqttclient.subscribe(mqtt_vehicle_soc); yield(); } + if (mqtt_vehicle_range != "") { _mqttclient.subscribe(mqtt_vehicle_range); yield(); } + if (mqtt_vehicle_eta != "") { _mqttclient.subscribe(mqtt_vehicle_eta); yield(); } + if (mqtt_vrms != "") { _mqttclient.subscribe(mqtt_vrms); yield(); } + + // Settable topics + _mqttclient.subscribe(mqtt_topic + "/divertmode/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/shaper/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/override/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/claim/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/schedule/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/schedule/clear"); yield(); + _mqttclient.subscribe(mqtt_topic + "/limit/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/config/set"); yield(); + _mqttclient.subscribe(mqtt_topic + "/restart"); yield(); + + DBUGLN("MQTT Subscriptions complete"); +} + +void Mqtt::publishInitialState() { + DBUGLN("MQTT Publishing initial state"); + // Force publish everything on new connection + _configVersion = INITIAL_CONFIG_VERSION -1; + _claimsVersion = evse.getClaimsVersion() == 0 ? 1 : evse.getClaimsVersion() -1; + _overrideVersion = manual.getVersion() == 0 ? 1 : manual.getVersion() -1; + _scheduleVersion = scheduler.getVersion() == 0 ? 1 : scheduler.getVersion() -1; + _limitVersion = limit.getVersion() == 0 ? 1 : limit.getVersion() -1; + + checkAndPublishUpdates(); // This will now publish everything +} + + +void Mqtt::checkAndPublishUpdates() { + if (!isConnected()) return; + + if (_claimsVersion != _evse->getClaimsVersion()) { + publishClaim(); + DBUGLN("Claims has changed, publishing to MQTT"); + _claimsVersion = _evse->getClaimsVersion(); + } + + if (_overrideVersion != manual.getVersion()) { + publishOverride(); + DBUGLN("Override has changed, publishing to MQTT"); + _overrideVersion = manual.getVersion(); + } + + if (_scheduleVersion != scheduler.getVersion()) { + publishSchedule(); + DBUGLN("Schedule has changed, publishing to MQTT"); + _scheduleVersion = scheduler.getVersion(); + } + if (_limitVersion != limit.getVersion()) { + publishLimit(); + DBUGLN("Limit has changed, publishing to MQTT"); + _limitVersion = limit.getVersion(); + } + + if (_configVersion != config_version()) { + publishConfig(); // publishConfig now returns void + DBUGLN("Config has changed, publishing to MQTT"); + _configVersion = config_version(); + } +} + +void Mqtt::handleMqttMessage(MongooseString topic, MongooseString payload) { + String topic_string = topic.toString(); String payload_str = payload.toString(); + + DBUGLN("Mqtt received:"); + DBUGLN("Topic: " + topic_string); DBUGLN("Payload: " + payload_str); - // If MQTT message is solar PV + // Logic from old mqttmsg_callback if (topic_string == mqtt_solar){ solar = payload_str.toInt(); DBUGF("solar:%dW", solar); divert.update_state(); - //recalculate shaper if (shaper.getState()) { shaper.shapeCurrent(); } } - else if (topic_string == mqtt_grid_ie) - { + else if (topic_string == mqtt_grid_ie) { grid_ie = payload_str.toInt(); DBUGF("grid:%dW", grid_ie); divert.update_state(); - - // if shaper use the same topic as grid_ie if (mqtt_live_pwr == mqtt_grid_ie) { shaper.setLivePwr(grid_ie); } } - else if (topic_string == mqtt_live_pwr) - { + else if (topic_string == mqtt_live_pwr) { shaper.setLivePwr(payload_str.toInt()); DBUGF("shaper: Live Pwr:%dW", shaper.getLivePwr()); } - else if (topic_string == mqtt_vrms) - { - // TODO: The voltage is no longer a global, need to do something so we don't have - // to read back from the EVSE + else if (topic_string == mqtt_vrms) { double volts = payload_str.toFloat(); DBUGF("voltage:%.1f", volts); - evse.setVoltage(volts); + _evse->setVoltage(volts); } - else if (topic_string == mqtt_vehicle_soc && vehicle_data_src == VEHICLE_DATA_SRC_MQTT) - { + else if (topic_string == mqtt_vehicle_soc && vehicle_data_src == VEHICLE_DATA_SRC_MQTT) { int vehicle_soc = payload_str.toInt(); - DBUGF("vehicle_soc:%d%%", vehicle_soc); - evse.setVehicleStateOfCharge(vehicle_soc); - - StaticJsonDocument<128> event; - event["battery_level"] = vehicle_soc; - event["vehicle_state_update"] = 0; - event_send(event); + _evse->setVehicleStateOfCharge(vehicle_soc); + StaticJsonDocument<128> event; event["battery_level"] = vehicle_soc; event_send(event); } - else if (topic_string == mqtt_vehicle_range && vehicle_data_src == VEHICLE_DATA_SRC_MQTT) - { + else if (topic_string == mqtt_vehicle_range && vehicle_data_src == VEHICLE_DATA_SRC_MQTT) { int vehicle_range = payload_str.toInt(); - DBUGF("vehicle_range:%dKM", vehicle_range); - evse.setVehicleRange(vehicle_range); - - StaticJsonDocument<128> event; - event["battery_range"] = vehicle_range; - event["vehicle_state_update"] = 0; - event_send(event); + _evse->setVehicleRange(vehicle_range); + StaticJsonDocument<128> event; event["battery_range"] = vehicle_range; event_send(event); } - else if (topic_string == mqtt_vehicle_eta && vehicle_data_src == VEHICLE_DATA_SRC_MQTT) - { + else if (topic_string == mqtt_vehicle_eta && vehicle_data_src == VEHICLE_DATA_SRC_MQTT) { int vehicle_eta = payload_str.toInt(); - DBUGF("vehicle_eta:%d", vehicle_eta); - evse.setVehicleEta(vehicle_eta); - - StaticJsonDocument<128> event; - event["time_to_full_charge"] = vehicle_eta; - event["vehicle_state_update"] = 0; - event_send(event); + _evse->setVehicleEta(vehicle_eta); + StaticJsonDocument<128> event; event["time_to_full_charge"] = vehicle_eta; event_send(event); } - // Divert Mode - else if (topic_string == mqtt_topic + "/divertmode/set") - { + else if (topic_string == mqtt_topic + "/divertmode/set") { byte newdivert = payload_str.toInt(); if ((newdivert==1) || (newdivert==2)) { divert.setMode((DivertMode)newdivert); } } - else if (topic_string == mqtt_topic + "/shaper/set") - { + else if (topic_string == mqtt_topic + "/shaper/set") { byte newshaper = payload_str.toInt(); - if (newshaper==0) { - shaper.setState(false); - } else if (newshaper==1) { - shaper.setState(true); - } + if (newshaper==0) shaper.setState(false); else if (newshaper==1) shaper.setState(true); } - // Manual Override else if (topic_string == mqtt_topic + "/override/set") { if (payload_str.equals("clear")) { if (manual.release()) { - override_props.clear(); - mqtt_publish_override(); - } - } - else if (payload_str.equals("toggle")) { - if (manual.toggle()) { - mqtt_publish_override(); + _override_props.clear(); + publishOverride(); } + } else if (payload_str.equals("toggle")) { + if (manual.toggle()) publishOverride(); + } else if (_override_props.deserialize(payload_str)) { + setClaim(true, _override_props); } - else if (override_props.deserialize(payload_str)) { - mqtt_set_claim(true, override_props); - } } - - // Claim else if (topic_string == mqtt_topic + "/claim/set") { if (payload_str.equals("release")) { - if(evse.release(EvseClient_OpenEVSE_MQTT)) { - claim_props.clear(); - mqtt_publish_claim(); + if (_evse->release(EvseClient_OpenEVSE_MQTT)) { + _claim_props.clear(); + publishClaim(); } - } - else if (claim_props.deserialize(payload_str)) { - mqtt_set_claim(false, claim_props); + } else if (_claim_props.deserialize(payload_str)) { + setClaim(false, _claim_props); } } - - //Schedule else if (topic_string == mqtt_topic + "/schedule/set") { - mqtt_set_schedule(payload_str); + setSchedule(payload_str); // Calls scheduler.deserialize and publishSchedule } else if (topic_string == mqtt_topic + "/schedule/clear") { - mqtt_clear_schedule(payload_str.toInt()); + clearSchedule(payload_str.toInt()); // Calls scheduler.removeEvent and publishSchedule } - else if (topic_string == mqtt_topic + "/limit/set") { if (payload_str.equals("clear")) { - DBUGLN("clearing limits"); limit.clear(); - } - else if (limit_props.deserialize(payload_str)) { - mqtt_set_limit(limit_props); + publishLimit(); // Need to ensure limit publishes its state. + } else if (_limit_props.deserialize(payload_str)) { + setLimit(_limit_props); } } - else if (topic_string == mqtt_topic + "/config/set") { - const size_t capacity = JSON_OBJECT_SIZE(128) + 1024; - DynamicJsonDocument doc(capacity); + DynamicJsonDocument doc(4096); // Sufficiently large buffer DeserializationError error = deserializeJson(doc, payload_str); - if(!error) - { - bool config_modified = config_deserialize(doc); - if(config_modified) - { + if(!error) { + if(config_deserialize(doc)) { config_commit(false); - DBUGLN("Config updated"); + DBUGLN("Config updated via MQTT"); + // publishConfig() will be called by checkAndPublishUpdates due to configVersion change } } } - - // Restart else if (topic_string == mqtt_topic + "/restart") { - mqtt_restart_device(payload_str); + // This logic can reuse the existing mqtt_restart_device logic by making it a static helper or part of this class + const size_t capacity = JSON_OBJECT_SIZE(1) + 16; + DynamicJsonDocument doc(capacity); + DeserializationError error = deserializeJson(doc, payload_str); + if(!error && doc.containsKey("device")){ + if (strcmp(doc["device"], "gateway") == 0 ) restart_system(); + else if (strcmp(doc["device"], "evse") == 0) _evse->restartEvse(); + } } - - else - { - // If MQTT message is RAPI command - // Detect if MQTT message is a RAPI command e.g to set 13A /rapi/$SC 13 - // Locate '$' character in the MQTT message to identify RAPI command + else { // RAPI commands int rapi_character_index = topic_string.indexOf('$'); - DBUGVAR(rapi_character_index); if (rapi_character_index > 1) { - DBUGF("Processing as RAPI"); - // Print RAPI command from mqtt-sub topic e.g $SC - // ASSUME RAPI COMMANDS ARE ALWAYS PREFIX BY $ AND TWO CHARACTERS LONG) String cmd = topic_string.substring(rapi_character_index); - if (payload.length() > 0) - { - // If MQTT msg contains a payload e.g $SC 13. Not all rapi commands have a payload e.g. $GC - cmd += " "+payload_str; - } + if (payload.length() > 0) cmd += " " + payload_str; - if(!evse.isRapiCommandBlocked(cmd)) - { - rapiSender.sendCmd(cmd, [](int ret) - { - if (RAPI_RESPONSE_OK == ret || RAPI_RESPONSE_NK == ret) - { + if(!_evse->isRapiCommandBlocked(cmd)) { // Use EvseManager instance + rapiSender.sendCmd(cmd, [this](int ret) { // Capture 'this' only + if (RAPI_RESPONSE_OK == ret || RAPI_RESPONSE_NK == ret) { String rapiString = rapiSender.getResponse(); - String mqtt_data = rapiString; String mqtt_sub_topic = mqtt_topic + "/rapi/out"; - mqttclient.publish(mqtt_sub_topic, mqtt_data); + _mqttclient.publish(mqtt_sub_topic, rapiString); } }); } } } -} //end call back - -static void mqtt_disconnected(int err, const char *reason) -{ - connecting = false; - DynamicJsonDocument doc(JSON_OBJECT_SIZE(1) + 60); - doc["mqtt_connected"] = 0; - doc["mqtt_close_code"] = err; - doc["mqtt_close_reason"] = reason; - event_send(doc); } -// ------------------------------------------------------------------- -// MQTT Connect -// ------------------------------------------------------------------- -boolean -mqtt_connect() -{ - DBUGVAR(mqttclient.connected()); - DBUGVAR(connecting); - if(connecting) { - return false; - } - connecting = true; - - mqttclient.onMessage(mqttmsg_callback); //function to be called when mqtt msg is received on subscribed topic - mqttclient.onError([](int err) - { - DBUGF("MQTT error %d", err); - // This is a bit of a hack to get around the fact that the Mongoose MQTT client - // munges together two sets of error codes, one for the TCP connection and one - // for the MQTT protocol. - mqtt_disconnected(err, - MG_EV_MQTT_CONNACK_UNACCEPTABLE_VERSION == err ? "CONNACK_UNACCEPTABLE_VERSION" : - MG_EV_MQTT_CONNACK_IDENTIFIER_REJECTED == err ? "CONNACK_IDENTIFIER_REJECTED" : - MG_EV_MQTT_CONNACK_SERVER_UNAVAILABLE == err ? "CONNACK_SERVER_UNAVAILABLE" : - MG_EV_MQTT_CONNACK_BAD_AUTH == err ? "CONNACK_BAD_AUTH" : - MG_EV_MQTT_CONNACK_NOT_AUTHORIZED == err ? "CONNACK_NOT_AUTHORIZED" : - strerror(err) - ); - error_time = millis(); - }); - - mqttclient.onClose([]() - { - DBUGF("MQTT connection closed"); - // Don't send a disconnect event if we just had an error, as we already - // sent one. This is a bit of a hack, but it's the easiest way to avoid - // sending two events for the same disconnect. - if(error_time + 100 < millis()) { - mqtt_disconnected(-1, "CLOSED"); - } - }); - - String mqtt_host = mqtt_server + ":" + String(mqtt_port); - - DBUGF("MQTT Connecting to... %s://%s", MQTT_MQTT == config_mqtt_protocol() ? "mqtt" : "mqtts", mqtt_host.c_str()); - - // Build the last will message - DynamicJsonDocument willDoc(JSON_OBJECT_SIZE(3) + 60); - - willDoc["state"] = "disconnected"; - willDoc["id"] = ESPAL.getLongId(); - willDoc["name"] = esp_hostname; +// --- Public Interface Methods --- +bool Mqtt::isConnected() { + return _mqttclient.connected(); +} - lastWill = ""; - serializeJson(willDoc, lastWill); - DBUGVAR(lastWill); +void Mqtt::restartConnection() { + DBUGLN("MQTT restart requested"); + _mqttRestartTime = millis() + 50; // Schedule restart in the near future in loop +} - if(!config_mqtt_reject_unauthorized()) { - DEBUG.println("WARNING: Certificate verification disabled"); +void Mqtt::publishData(JsonDocument &data) { + if (!config_mqtt_enabled() || !_mqttclient.connected()) { + return; } - - mqttclient.setCredentials(mqtt_user, mqtt_pass); - mqttclient.setLastWillAndTestimment(mqtt_announce_topic, lastWill, true); - mqttclient.setRejectUnauthorized(config_mqtt_reject_unauthorized()); - - if(mqtt_certificate_id != "") - { - uint64_t cert_id = std::stoull(mqtt_certificate_id.c_str(), nullptr, 16); - const char *cert = certs.getCertificate(cert_id); - const char *key = certs.getKey(cert_id); - if(NULL != cert && NULL != key) { - mqttclient.setCertificate(cert, key); - } + JsonObject root = data.as(); + for (JsonPair kv : root) { + String topic = mqtt_topic + "/" + kv.key().c_str(); + String val = kv.value().as(); // Consider non-string values too if needed + _mqttclient.publish(topic, val, config_mqtt_retained()); } - - connecting = mqttclient.connect((MongooseMqttProtocol)config_mqtt_protocol(), mqtt_host, esp_hostname, []() - { - DBUGLN("MQTT connected"); - - DynamicJsonDocument doc(JSON_OBJECT_SIZE(5) + 200); - - doc["state"] = "connected"; - doc["id"] = ESPAL.getLongId(); - doc["name"] = esp_hostname; - doc["mqtt"] = mqtt_topic; - doc["http"] = "http://"+net.getIp()+"/"; - - // Once connected, publish an announcement.. - String announce = ""; - serializeJson(doc, announce); - DBUGVAR(announce); - mqttclient.publish(mqtt_announce_topic, announce, true); - - doc.clear(); - - doc["mqtt_connected"] = 1; - event_send(doc); - - // Publish MQTT override/claim - mqtt_publish_config(); - mqtt_publish_override(); - mqtt_publish_claim(); - mqtt_publish_schedule(); - mqtt_publish_limit(); - - // MQTT Topic to subscribe to receive RAPI commands via MQTT - String mqtt_sub_topic = mqtt_topic + "/rapi/in/#"; - - // e.g to set current to 13A: /rapi/in/$SC 13 - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - // subscribe to solar PV / grid_ie MQTT feeds - if(config_divert_enabled()) - { - if (divert_type == DIVERT_TYPE_SOLAR && mqtt_solar != "") - { - mqttclient.subscribe(mqtt_solar); - yield(); - } - if (divert_type == DIVERT_TYPE_GRID && mqtt_grid_ie != "") - { - mqttclient.subscribe(mqtt_grid_ie); - yield(); - } - } - // subscribe to current shaper MQTT feeds - if(config_current_shaper_enabled()) - { - if (mqtt_live_pwr != "") { - if ( mqtt_live_pwr != mqtt_grid_ie ) { - // only subscribe once - mqttclient.subscribe(mqtt_live_pwr); - yield(); - } - } - } - // subscribe to vehicle information from MQTT if we are configured for it - if (mqtt_vehicle_soc != "") { - mqttclient.subscribe(mqtt_vehicle_soc); - yield(); - } - if (mqtt_vehicle_range != "") { - mqttclient.subscribe(mqtt_vehicle_range); - yield(); - } - if (mqtt_vehicle_eta != "") { - mqttclient.subscribe(mqtt_vehicle_eta); - yield(); - } - - if (mqtt_vrms!="") { - mqttclient.subscribe(mqtt_vrms); - yield(); - } - // settable mqtt topics - mqtt_sub_topic = mqtt_topic + "/divertmode/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/shaper/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/override/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/claim/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/schedule/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/schedule/clear"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/limit/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - mqtt_sub_topic = mqtt_topic + "/config/set"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - // ask for a system restart - mqtt_sub_topic = mqtt_topic + "/restart"; - mqttclient.subscribe(mqtt_sub_topic); - yield(); - - connecting = false; - }); - - return true; } - - -// ------------------------------------------------------------------- -// Publish status to MQTT -// ------------------------------------------------------------------- -void -mqtt_publish(JsonDocument &data) { - Profile_Start(mqtt_publish); - - if(!config_mqtt_enabled() || !mqttclient.connected()) { +// Specific publish methods +void Mqtt::publishConfig() { + if (!isConnected() || _evse->getEvseState() == OPENEVSE_STATE_STARTING) { return; } + const size_t capacity = JSON_OBJECT_SIZE(128) + 1024; + DynamicJsonDocument doc(capacity); + config_serialize(doc, true, false, true); - JsonObject root = data.as(); - for (JsonPair kv : root) { - String topic = mqtt_topic + "/"; - topic += kv.key().c_str(); - String val = kv.value().as(); - mqttclient.publish(topic, val, config_mqtt_retained()); - topic = mqtt_topic + "/"; - } + String fulltopic = mqtt_topic + "/config"; + String payload; + serializeJson(doc, payload); + _mqttclient.publish(fulltopic, payload, true); // Config usually retained - Profile_End(mqtt_publish, 5); + if(config_version() == INITIAL_CONFIG_VERSION) { + String versionTopic = mqtt_topic + "/config_version"; + String versionPayload = String(config_version()); + _mqttclient.publish(versionTopic, versionPayload, true); + } } -void -mqtt_set_claim(bool override, EvseProperties &props) { - Profile_Start(mqtt_set_claim); - //0: claim , 1: manual override +void Mqtt::setClaim(bool override, EvseProperties &props) { if (override) { if (manual.claim(props)) { - mqtt_publish_override(); + publishOverride(); } - } - else { - if (evse.claim(EvseClient_OpenEVSE_MQTT, EvseManager_Priority_MQTT, props)) { - mqtt_publish_claim(); + } else { + if (_evse->claim(EvseClient_OpenEVSE_MQTT, EvseManager_Priority_MQTT, props)) { + publishClaim(); } } - - Profile_End(mqtt_set_claim, 5); } -void -mqtt_publish_claim() { - if(!config_mqtt_enabled() || !mqttclient.connected()) { - return; - } - bool hasclaim = evse.clientHasClaim(EvseClient_OpenEVSE_MQTT); +void Mqtt::publishClaim() { + if (!isConnected()) return; const size_t capacity = JSON_OBJECT_SIZE(7) + 1024; DynamicJsonDocument claimdata(capacity); - if(hasclaim) { - evse.serializeClaim(claimdata, EvseClient_OpenEVSE_MQTT); - - } - else { + if (_evse->clientHasClaim(EvseClient_OpenEVSE_MQTT)) { + _evse->serializeClaim(claimdata, EvseClient_OpenEVSE_MQTT); + } else { claimdata["state"] = "null"; } - mqtt_publish_json(claimdata, "/claim"); + String fulltopic = mqtt_topic + "/claim"; + String payload; + serializeJson(claimdata, payload); + _mqttclient.publish(fulltopic, payload, true); // Claims usually retained } -void -mqtt_publish_override() { - DBUGLN("MQTT publish_override()"); - if(!config_mqtt_enabled() || !mqttclient.connected()) { - return; - } +void Mqtt::publishOverride() { + if (!isConnected()) return; const size_t capacity = JSON_OBJECT_SIZE(7) + 1024; DynamicJsonDocument override_data(capacity); - EvseProperties props; - //check if there an override claim - if (evse.clientHasClaim(EvseClient_OpenEVSE_Manual) || manual.isActive()) { - props = evse.getClaimProperties(EvseClient_OpenEVSE_Manual); - //check if there's state property in override + if (_evse->clientHasClaim(EvseClient_OpenEVSE_Manual) || manual.isActive()) { + EvseProperties props = _evse->getClaimProperties(EvseClient_OpenEVSE_Manual); props.serialize(override_data); + } else { + override_data["state"] = "null"; } - else override_data["state"] = "null"; - mqtt_publish_json(override_data, "/override"); + String fulltopic = mqtt_topic + "/override"; + String payload; + serializeJson(override_data, payload); + _mqttclient.publish(fulltopic, payload, true); // Overrides usually retained } -void mqtt_set_schedule(String schedule) { - Profile_Start(mqtt_set_schedule); - scheduler.deserialize(schedule); - mqtt_publish_schedule(); - Profile_End(mqtt_set_schedule, 5); +void Mqtt::setSchedule(String schedulePayload) { + scheduler.deserialize(schedulePayload); + publishSchedule(); } -void -mqtt_clear_schedule(uint32_t event) { - Profile_Start(mqtt_clear_schedule); - scheduler.removeEvent(event); - Profile_End(mqtt_clear_schedule, 5); - mqtt_publish_schedule(); +void Mqtt::clearSchedule(uint32_t eventId) { + scheduler.removeEvent(eventId); + publishSchedule(); } -void -mqtt_publish_schedule() { - if(!config_mqtt_enabled() || !mqttclient.connected()) { - return; - } +void Mqtt::publishSchedule() { + if (!isConnected()) return; const size_t capacity = JSON_OBJECT_SIZE(40) + 2048; DynamicJsonDocument schedule_data(capacity); - EvseProperties props; - bool success = scheduler.serialize(schedule_data); - if (success) { - mqtt_publish_json(schedule_data, "/schedule"); + if (scheduler.serialize(schedule_data)) { + String fulltopic = mqtt_topic + "/schedule"; + String payload; + serializeJson(schedule_data, payload); + _mqttclient.publish(fulltopic, payload, true); // Schedule usually retained } } -bool -mqtt_publish_config() { - if(!config_mqtt_enabled() || !mqttclient.connected() || evse.getEvseState() == OPENEVSE_STATE_STARTING) { - return false; - } - const size_t capacity = JSON_OBJECT_SIZE(128) + 1024; - DynamicJsonDocument doc(capacity); - config_serialize(doc, true, false, true); - mqtt_publish_json(doc, "/config"); - - if(config_version() == INITIAL_CONFIG_VERSION) { - String fulltopic = mqtt_topic + "/config_version"; - String payload = String(config_version()); - mqttclient.publish(fulltopic, payload, true); - } - - return true; -} - -void -mqtt_set_limit(LimitProperties &limitProps) { - Profile_Start(mqtt_set_limit); +void Mqtt::setLimit(LimitProperties &limitProps) { limit.set(limitProps); - mqtt_publish_limit(); - Profile_End(mqtt_set_limit, 5); + publishLimit(); // This will also update the version } -void -mqtt_publish_limit() { - LimitProperties limitProps; +void Mqtt::publishLimit() { + if (!isConnected()) return; + LimitProperties currentLimitProps = limit.get(); const size_t capacity = JSON_OBJECT_SIZE(3) + 512; DynamicJsonDocument limit_data(capacity); - limitProps = limit.get(); - bool success = limitProps.serialize(limit_data); - mqtt_publish_json(limit_data, "/limit"); -} - -void -mqtt_publish_json(JsonDocument &data, const char* topic) { - Profile_Start(mqtt_publish_json); - if(!config_mqtt_enabled() || !mqttclient.connected()) { - return; - } - - String fulltopic = mqtt_topic + topic; - String doc; - serializeJson(data, doc); - mqttclient.publish(fulltopic,doc, true); // claims are always published as retained as they are not updated regularly - Profile_End(mqtt_publish_json, 5); - -} - -void -mqtt_restart_device(String payload_str) { - const size_t capacity = JSON_OBJECT_SIZE(1) + 16; - DynamicJsonDocument doc(capacity); - DeserializationError error = deserializeJson(doc, payload_str); - if(!error) - { - if(doc.containsKey("device")){ - if (strcmp(doc["device"], "gateway") == 0 ) { - restart_system(); - } - else if (strcmp(doc["device"], "evse") == 0) { - evse.restartEvse(); - } - } + if (currentLimitProps.serialize(limit_data)) { + String fulltopic = mqtt_topic + "/limit"; + String payload; + serializeJson(limit_data, payload); + _mqttclient.publish(fulltopic, payload, true); // Limits usually retained } } -// ------------------------------------------------------------------- -// MQTT state management -// -// Call every time around loop() if connected to the WiFi -// ------------------------------------------------------------------- -void mqtt_loop() -{ - Profile_Start(mqtt_loop); - - // Do we need to restart MQTT? - if(mqttRestartTime > 0 && millis() > mqttRestartTime) - { - mqttRestartTime = 0; - if (mqttclient.connected()) { - DBUGF("Disconnecting MQTT"); - mqttclient.disconnect(); - DynamicJsonDocument doc(JSON_OBJECT_SIZE(1) + 60); - doc["mqtt_connected"] = 0; - event_send(doc); - } - nextMqttReconnectAttempt = 0; - } - - if (config_mqtt_enabled() && !mqttclient.connected()) { - long now = millis(); - // try and reconnect every x seconds - if (now > nextMqttReconnectAttempt) { - nextMqttReconnectAttempt = now + MQTT_CONNECT_TIMEOUT; - mqtt_connect(); // Attempt to reconnect - } - } - - // Temporise loop - if (millis() - loop_timer > MQTT_LOOP) { - loop_timer = millis(); - if (claimsVersion != evse.getClaimsVersion()) { - mqtt_publish_claim(); - DBUGF("Claims has changed, publishing to MQTT"); - claimsVersion = evse.getClaimsVersion(); - } - if (overrideVersion != manual.getVersion()) { - mqtt_publish_override(); - DBUGF("Override has changed, publishing to MQTT"); - overrideVersion = manual.getVersion(); +// --- Notification methods --- +void Mqtt::notifyEvseClaimChanged() { + if (_claimsVersion != _evse->getClaimsVersion()) { + _claimsVersion = _evse->getClaimsVersion(); // Update internal version + if (isConnected()) publishClaim(); } +} - if (scheduleVersion != scheduler.getVersion()) { - mqtt_publish_schedule(); - DBUGF("Schedule has changed, publishing to MQTT"); - scheduleVersion = scheduler.getVersion(); - } - if (limitVersion != limit.getVersion()) { - mqtt_publish_limit(); - DBUGF("Limit has changed, publishing to MQTT"); - limitVersion = limit.getVersion(); +void Mqtt::notifyManualOverrideChanged() { + if (_overrideVersion != manual.getVersion()) { + _overrideVersion = manual.getVersion(); + if (isConnected()) publishOverride(); } +} - if(configVersion != config_version() && mqtt_publish_config()) { - DBUGF("Config has changed, publishing to MQTT"); - configVersion = config_version(); +void Mqtt::notifyScheduleChanged() { + if (_scheduleVersion != scheduler.getVersion()) { + _scheduleVersion = scheduler.getVersion(); + if (isConnected()) publishSchedule(); } - } - Profile_End(mqtt_loop, 5); } -void -mqtt_restart() { - // If connected disconnect MQTT to trigger re-connect with new details - mqttRestartTime = millis(); +void Mqtt::notifyLimitChanged() { + if (_limitVersion != limit.getVersion()) { + _limitVersion = limit.getVersion(); + if (isConnected()) publishLimit(); + } } -boolean -mqtt_connected() { - return mqttclient.connected(); -} +void Mqtt::notifyConfigChanged() { + if (_configVersion != config_version()) { + _configVersion = config_version(); + if (isConnected()) publishConfig(); + } +} \ No newline at end of file diff --git a/src/mqtt.h b/src/mqtt.h index 3ed76a6c..b7952732 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -1,59 +1,105 @@ #ifndef _EMONESP_MQTT_H #define _EMONESP_MQTT_H -// ------------------------------------------------------------------- -// MQTT support -// ------------------------------------------------------------------- - #include #include -#include "evse_man.h" -#include "limit.h" - -enum mqtt_protocol { - MQTT_PROTOCOL_MQTT, - MQTT_PROTOCOL_MQTT_SSL, - MQTT_PROTOCOL_WEBSOCKET, - MQTT_PROTOCOL_WEBSOCKET_SSL +#include +#include + +#include "emonesp.h" +#include "app_config.h" +#include "evse_man.h" // For EvseProperties, EvseClient_OpenEVSE_MQTT etc. +#include "limit.h" // For LimitProperties +#include "event.h" +#include "scheduler.h" // For scheduler interaction +#include "manual.h" // For manual override interaction +#include "divert.h" // For divert interaction +#include "input.h" // For solar, grid_ie +#include "espal.h" +#include "net_manager.h" +#include "certificates.h" +#include "current_shaper.h" // For shaper interaction, if any direct calls were made + +// Forward declarations + +#define MQTT_LOOP_INTERVAL 50 // ms, replaces MQTT_LOOP define +#define MQTT_CONNECT_TIMEOUT (5 * 1000) // ms + +class Mqtt : public MicroTasks::Task { + private: + MongooseMqttClient _mqttclient; + EvseManager *_evse; // Pointer to EvseManager instance + + // MQTT connection state and timing + long _nextMqttReconnectAttempt = 0; + unsigned long _mqttRestartTime = 0; + bool _connecting = false; + unsigned long _error_time = 0; // To handle disconnect events properly + + // Version tracking for publishing updates + uint8_t _claimsVersion = 0; + uint8_t _overrideVersion = 0; + uint8_t _scheduleVersion = 0; + uint8_t _limitVersion = 0; + uint32_t _configVersion = 0; + + String _lastWill = ""; + unsigned long _loop_timer = 0; // Timer for periodic publishing tasks + + // Properties for claims, overrides, limits + EvseProperties _claim_props; + EvseProperties _override_props; + LimitProperties _limit_props; + + // Internal helper methods + void attemptConnection(); + void onMqttConnect(); + void onMqttDisconnect(int err, const char *reason); + void subscribeTopics(); + void publishInitialState(); + void checkAndPublishUpdates(); + + // Callback for incoming MQTT messages + // Made static because MongooseMqttClient might need a C-style function pointer + // or we pass 'this' and use a wrapper. For simplicity, we can make it a member + // and ensure the lambda captures 'this' correctly. + // MongooseMqttClient::onMessage takes std::function, so member function is fine. + void handleMqttMessage(MongooseString topic, MongooseString payload); + + protected: + void setup() override; + unsigned long loop(MicroTasks::WakeReason reason) override; + + public: + Mqtt(EvseManager &evse); + ~Mqtt(); + + void begin(); // To start the task + + // Public interface (existing functions from mqtt.h, adapted) + bool isConnected(); + void restartConnection(); + + // Publishing methods - these can be called from other modules + void publishData(JsonDocument &data); // Generic data publish + void publishConfig(); + void publishClaim(); + void setClaim(bool override, EvseProperties &props); + void publishOverride(); + void publishSchedule(); + void setSchedule(String schedule); + void clearSchedule(uint32_t event); + void publishLimit(); + void setLimit(LimitProperties &limitProps); + + // Method to be called by other services when their state changes + void notifyEvseClaimChanged(); + void notifyManualOverrideChanged(); + void notifyScheduleChanged(); + void notifyLimitChanged(); + void notifyConfigChanged(); }; -#define MQTT_LOOP 500 - -extern void mqtt_msg_callback(); - -// ------------------------------------------------------------------- -// Perform the background MQTT operations. Must be called in the main -// loop function -// ------------------------------------------------------------------- -extern void mqtt_loop(); - -// ------------------------------------------------------------------- -// Publish values to MQTT -// -// data: a comma seperated list of name:value pairs to send -// ------------------------------------------------------------------- -extern void mqtt_publish(JsonDocument &data); -extern bool mqtt_publish_config(); -extern void mqtt_publish_claim(); -extern void mqtt_set_claim(bool override, EvseProperties &props); -extern void mqtt_publish_override(); -extern void mqtt_publish_json(JsonDocument &data, const char* topic); -extern void mqtt_publish_schedule(); -extern void mqtt_set_schedule(String schedule); -extern void mqtt_clear_schedule(uint32_t event); -extern void mqtt_publish_limit(); -extern void mqtt_set_limit(LimitProperties &limitProps); -extern void mqtt_restart_device(String payload_str); - -// ------------------------------------------------------------------- -// Restart the MQTT connection -// ------------------------------------------------------------------- -extern void mqtt_restart(); - - -// ------------------------------------------------------------------- -// Return true if we are connected to an MQTT broker, false if not -// ------------------------------------------------------------------- -extern boolean mqtt_connected(); - -#endif // _EMONESP_MQTT_H +extern Mqtt mqtt; // Global instance of the MQTT task + +#endif // _EMONESP_MQTT_H \ No newline at end of file diff --git a/src/scheduler.cpp b/src/scheduler.cpp index 9a56b70f..dd8ef810 100644 --- a/src/scheduler.cpp +++ b/src/scheduler.cpp @@ -373,7 +373,7 @@ void Scheduler::buildSchedule() event_send(doc); // publish updated schedule to mqtt - mqtt_publish_schedule(); + mqtt.publishSchedule(); // wake the main task to see if we actually need to do something MicroTask.wakeTask(this); diff --git a/src/web_server.cpp b/src/web_server.cpp index 356aef87..f241002b 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -218,7 +218,7 @@ void buildStatus(DynamicJsonDocument &doc) { doc["packets_sent"] = packets_sent; doc["packets_success"] = packets_success; - doc["mqtt_connected"] = (int)mqtt_connected(); + doc["mqtt_connected"] = (int)mqtt.isConnected(); doc["ocpp_connected"] = (int)OcppTask::isConnected(); @@ -666,7 +666,6 @@ void handleLimitPost(MongooseHttpServerRequest *request, MongooseHttpServerRespo if (limit.set(body)) { response->setCode(201); response->print("{\"msg\":\"done\"}"); - // todo: mqtt_publish_limit(); // update limit props to mqtt } else { // unused for now response->setCode(400); @@ -794,10 +793,10 @@ void handleOverridePost(MongooseHttpServerRequest *request, MongooseHttpServerRe if(manual.claim(props)) { response->setCode(201); response->print("{\"msg\":\"Created\"}"); - mqtt_publish_override(); // update override state to mqtt + mqtt.publishOverride(); // update override state to mqtt } else { response->setCode(500); - response->print("{\"msg\":\"Failed to claim manual overide\"}"); + response->print("{\"msg\":\"Failed to claim manual override\"}"); } } else { response->setCode(400); @@ -810,10 +809,10 @@ void handleOverrideDelete(MongooseHttpServerRequest *request, MongooseHttpServer if(manual.release()) { response->setCode(200); response->print("{\"msg\":\"Deleted\"}"); - mqtt_publish_override(); // update override state to mqtt + mqtt.publishOverride(); // update override state to mqtt } else { response->setCode(500); - response->print("{\"msg\":\"Failed to release manual overide\"}"); + response->print("{\"msg\":\"Failed to release manual override\"}"); } } @@ -823,10 +822,10 @@ void handleOverridePatch(MongooseHttpServerRequest *request, MongooseHttpServerR { response->setCode(200); response->print("{\"msg\":\"Updated\"}"); - mqtt_publish_override(); // update override state to mqtt + mqtt.publishOverride(); // update override state to mqtt } else { response->setCode(500); - response->print("{\"msg\":\"Failed to toggle manual overide\"}"); + response->print("{\"msg\":\"Failed to toggle manual override\"}"); } }