From f4344ab8aa7b5a802430aaf2e70a55225c3f9838 Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Tue, 16 May 2023 11:43:55 +0200 Subject: [PATCH] Expose config and config updates through MQTT --- docs/mqtt.md | 4 + src/app_config.cpp | 171 +++++++++++++++++++++++++++++++++++++- src/app_config.h | 2 + src/mqtt.cpp | 24 ++++++ src/mqtt.h | 1 + src/web_server.cpp | 4 +- src/web_server_config.cpp | 153 +--------------------------------- 7 files changed, 205 insertions(+), 154 deletions(-) diff --git a/docs/mqtt.md b/docs/mqtt.md index c4235a05..b9673df9 100644 --- a/docs/mqtt.md +++ b/docs/mqtt.md @@ -55,6 +55,10 @@ Main settings: `/shaper/set [0 | 1]` : temporary enable (1)/ disable (0) current shaper ( doesn't survive reboot ) `/restart {"device": "gateway|evse"}` : restart the gateway or openevse module +Config: + +`/config_version` : a volatile counter incremented for each config change +`/config` : expose the configuration as a json object MQTT setup is pre-populated with OpenEnergyMonitor [emonPi default MQTT server credentials](https://guide.openenergymonitor.org/technical/credentials/#mqtt). diff --git a/src/app_config.cpp b/src/app_config.cpp index fcff7fef..15620d21 100644 --- a/src/app_config.cpp +++ b/src/app_config.cpp @@ -31,6 +31,8 @@ #define FACTORY_OFFSET CONFIG_SIZE #define FACTORY_SIZE 1024 +uint32_t config_ver = 1; + // Wifi Network Strings String esid; String epass; @@ -251,6 +253,26 @@ ConfigOpt *opts[] = ConfigJson user_config(opts, sizeof(opts) / sizeof(opts[0]), EEPROM_SIZE, CONFIG_OFFSET); ConfigJson factory_config(opts, sizeof(opts) / sizeof(opts[0]), EEPROM_SIZE, FACTORY_OFFSET); +// ------------------------------------------------------------------- +// config version handling +// ------------------------------------------------------------------- +uint32_t +config_version() { + return config_ver; +} + +void +increment_config() { + config_ver++; + DBUGVAR(config_ver); + + #if ENABLE_CONFIG_CHANGE_NOTIFICATION + StaticJsonDocument<128> event; + event["config_version"] = config_ver; + event_send(event); + #endif +} + // ------------------------------------------------------------------- // Reset EEPROM, wipes all settings // ------------------------------------------------------------------- @@ -368,7 +390,117 @@ bool config_deserialize(const char *json) bool config_deserialize(DynamicJsonDocument &doc) { - return user_config.deserialize(doc); + bool config_modified = user_config.deserialize(doc); + + #if ENABLE_CONFIG_CHANGE_NOTIFICATION + // Update EVSE config + // Update the EVSE setting flags, a little low level, may move later + if(doc.containsKey("diode_check")) + { + bool enable = doc["diode_check"]; + if(enable != evse.isDiodeCheckEnabled()) { + evse.enableDiodeCheck(enable); + config_modified = true; + DBUGLN("diode_check changed"); + } + } + + if(doc.containsKey("gfci_check")) + { + bool enable = doc["gfci_check"]; + if(enable != evse.isGfiTestEnabled()) { + evse.enableGfiTestCheck(enable); + config_modified = true; + DBUGLN("gfci_check changed"); + } + } + + if(doc.containsKey("ground_check")) + { + bool enable = doc["ground_check"]; + if(enable != evse.isGroundCheckEnabled()) { + evse.enableGroundCheck(enable); + config_modified = true; + DBUGLN("ground_check changed"); + } + } + + if(doc.containsKey("relay_check")) + { + bool enable = doc["relay_check"]; + if(enable != evse.isStuckRelayCheckEnabled()) { + evse.enableStuckRelayCheck(enable); + config_modified = true; + DBUGLN("relay_check changed"); + } + } + + if(doc.containsKey("vent_check")) + { + bool enable = doc["vent_check"]; + if(enable != evse.isVentRequiredEnabled()) { + evse.enableVentRequired(enable); + config_modified = true; + DBUGLN("vent_check changed"); + } + } + + if(doc.containsKey("temp_check")) + { + bool enable = doc["temp_check"]; + if(enable != evse.isTemperatureCheckEnabled()) { + evse.enableTemperatureCheck(enable); + config_modified = true; + DBUGLN("temp_check changed"); + } + } + + if(doc.containsKey("service")) + { + EvseMonitor::ServiceLevel service = static_cast(doc["service"].as()); + if(service != evse.getServiceLevel()) { + evse.setServiceLevel(service); + config_modified = true; + DBUGLN("service changed"); + } + } + + if(doc.containsKey("max_current_soft")) + { + long current = doc["max_current_soft"]; + if(current != evse.getMaxConfiguredCurrent()) { + evse.setMaxConfiguredCurrent(current); + config_modified = true; + DBUGLN("max_current_soft changed"); + } + } + + if(doc.containsKey("scale") && doc.containsKey("offset")) + { + long scale = doc["scale"]; + long offset = doc["offset"]; + if(scale != evse.getCurrentSensorScale() || offset != evse.getCurrentSensorOffset()) { + evse.configureCurrentSensorScale(doc["scale"], doc["offset"]); + config_modified = true; + DBUGLN("scale changed"); + } + } + #endif + + if(config_modified) + { + #if ENABLE_CONFIG_CHANGE_NOTIFICATION + // HACK: force a flush of the RAPI command queue to make sure the config + // is updated before we send the response + DBUG("Flushing RAPI command queue ..."); + rapiSender.flush(); + DBUGLN(" Done"); + #endif + + increment_config(); + } + + return config_modified; } bool config_serialize(String& json, bool longNames, bool compactOutput, bool hideSecrets) @@ -378,6 +510,43 @@ bool config_serialize(String& json, bool longNames, bool compactOutput, bool hid bool config_serialize(DynamicJsonDocument &doc, bool longNames, bool compactOutput, bool hideSecrets) { + // Static supported protocols + JsonArray mqtt_supported_protocols = doc.createNestedArray("mqtt_supported_protocols"); + mqtt_supported_protocols.add("mqtt"); + mqtt_supported_protocols.add("mqtts"); + JsonArray http_supported_protocols = doc.createNestedArray("http_supported_protocols"); + http_supported_protocols.add("http"); + + #if ENABLE_CONFIG_CHANGE_NOTIFICATION + doc["buildenv"] = buildenv; + doc["version"] = currentfirmware; + doc["wifi_serial"] = serial; + doc["protocol"] = "-"; + doc["espinfo"] = ESPAL.getChipInfo(); + doc["espflash"] = ESPAL.getFlashChipSize(); + + // EVSE information are only evailable when config_version is incremented + if(config_ver > 0) { + // Read only information + doc["firmware"] = evse.getFirmwareVersion(); + doc["evse_serial"] = evse.getSerial(); + // OpenEVSE module config + doc["diode_check"] = evse.isDiodeCheckEnabled(); + doc["gfci_check"] = evse.isGfiTestEnabled(); + doc["ground_check"] = evse.isGroundCheckEnabled(); + doc["relay_check"] = evse.isStuckRelayCheckEnabled(); + doc["vent_check"] = evse.isVentRequiredEnabled(); + doc["temp_check"] = evse.isTemperatureCheckEnabled(); + doc["max_current_soft"] = evse.getMaxConfiguredCurrent(); + // OpenEVSE Read only information + doc["service"] = static_cast(evse.getServiceLevel()); + doc["scale"] = evse.getCurrentSensorScale(); + doc["offset"] = evse.getCurrentSensorOffset(); + doc["min_current_hard"] = evse.getMinCurrent(); + doc["max_current_hard"] = evse.getMaxHardwareCurrent(); + } + #endif + return user_config.serialize(doc, longNames, compactOutput, hideSecrets); } diff --git a/src/app_config.h b/src/app_config.h index f9b869b5..7b4e1fe1 100644 --- a/src/app_config.h +++ b/src/app_config.h @@ -221,6 +221,8 @@ inline EvseState config_default_state() // Ohm Connect Settings extern String ohm; +extern uint32_t config_version(); + // ------------------------------------------------------------------- // Load saved settings // ------------------------------------------------------------------- diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 5fbb328b..83e0ccd5 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -35,6 +35,7 @@ uint8_t claimsVersion = 0; uint8_t overrideVersion = 0; uint8_t scheduleVersion = 0; uint8_t limitVersion = 0; +uint32_t configVersion = 0; String lastWill = ""; @@ -300,6 +301,7 @@ mqtt_connect() event_send(doc); // Publish MQTT override/claim + mqtt_publish_config(); mqtt_publish_override(); mqtt_publish_claim(); mqtt_publish_schedule(); @@ -507,6 +509,23 @@ mqtt_publish_schedule() { } } +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"); + + 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); @@ -614,6 +633,11 @@ mqtt_loop() { DBUGF("Limit has changed, publishing to MQTT"); limitVersion = limit.getVersion(); } + + if(configVersion != config_version() && mqtt_publish_config()) { + DBUGF("Config has changed, publishing to MQTT"); + configVersion = config_version(); + } } Profile_End(mqtt_loop, 5); } diff --git a/src/mqtt.h b/src/mqtt.h index 13159cbb..3ed76a6c 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -33,6 +33,7 @@ extern void mqtt_loop(); // 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(); diff --git a/src/web_server.cpp b/src/web_server.cpp index 2a4e5104..8c4abe1e 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -78,8 +78,6 @@ void handleUpdateClose(MongooseHttpServerRequest *request); void handleTime(MongooseHttpServerRequest *request); void handleTimePost(MongooseHttpServerRequest *request, MongooseHttpServerResponseStream *response); -extern uint32_t config_version; - void dumpRequest(MongooseHttpServerRequest *request) { #ifdef ENABLE_DEBUG_WEB_REQUEST @@ -253,7 +251,7 @@ void buildStatus(DynamicJsonDocument &doc) { doc["ota_update"] = (int)Update.isRunning(); - doc["config_version"] = config_version; + doc["config_version"] = config_version(); doc["claims_version"] = evse.getClaimsVersion(); doc["override_version"] = manual.getVersion(); doc["schedule_version"] = scheduler.getVersion(); diff --git a/src/web_server_config.cpp b/src/web_server_config.cpp index 1be04d6c..ed869d97 100644 --- a/src/web_server_config.cpp +++ b/src/web_server_config.cpp @@ -13,8 +13,6 @@ typedef const __FlashStringHelper *fstr_t; #include "input.h" #include "event.h" -uint32_t config_version = 0; - extern bool isPositive(MongooseHttpServerRequest *request, const char *param); extern bool web_server_config_deserialise(DynamicJsonDocument &doc, bool factory); @@ -28,40 +26,6 @@ handleConfigGet(MongooseHttpServerRequest *request, MongooseHttpServerResponseSt const size_t capacity = JSON_OBJECT_SIZE(128) + 1024; DynamicJsonDocument doc(capacity); - // Read only information - doc["firmware"] = evse.getFirmwareVersion(); - doc["protocol"] = "-"; - doc["espflash"] = ESPAL.getFlashChipSize(); - doc["espinfo"] = ESPAL.getChipInfo(); - doc["buildenv"] = buildenv; - doc["version"] = currentfirmware; - doc["evse_serial"] = evse.getSerial(); - doc["wifi_serial"] = serial; - - // Static supported protocols - JsonArray mqtt_supported_protocols = doc.createNestedArray("mqtt_supported_protocols"); - mqtt_supported_protocols.add("mqtt"); - mqtt_supported_protocols.add("mqtts"); - - JsonArray http_supported_protocols = doc.createNestedArray("http_supported_protocols"); - http_supported_protocols.add("http"); - - // OpenEVSE module config - doc["diode_check"] = evse.isDiodeCheckEnabled(); - doc["gfci_check"] = evse.isGfiTestEnabled(); - doc["ground_check"] = evse.isGroundCheckEnabled(); - doc["relay_check"] = evse.isStuckRelayCheckEnabled(); - doc["vent_check"] = evse.isVentRequiredEnabled(); - doc["temp_check"] = evse.isTemperatureCheckEnabled(); - doc["service"] = static_cast(evse.getServiceLevel()); - doc["scale"] = evse.getCurrentSensorScale(); - doc["offset"] = evse.getCurrentSensorOffset(); - doc["max_current_soft"] = evse.getMaxConfiguredCurrent(); - - doc["min_current_hard"] = evse.getMinCurrent(); - doc["max_current_hard"] = evse.getMaxHardwareCurrent(); - - // WiFi module config config_serialize(doc, true, false, true); response->setCode(200); @@ -91,7 +55,7 @@ handleConfigPost(MongooseHttpServerRequest *request, MongooseHttpServerResponseS bool config_modified = web_server_config_deserialise(doc, storage.equals("factory")); StaticJsonDocument<128> reply; - reply["config_version"] = config_version; + reply["config_version"] = config_version(); reply["msg"] = config_modified ? "done" : "no change"; response->setCode(200); @@ -122,126 +86,15 @@ void handleConfig(MongooseHttpServerRequest *request) request->send(response); } -// TODO: move all this to app_config.cpp bool web_server_config_deserialise(DynamicJsonDocument &doc, bool factory) { - bool config_modified = false; + bool config_modified = config_deserialize(doc); - if(config_deserialize(doc)) + if(config_modified) { config_commit(factory); - config_modified = true; DBUGLN("Config updated"); } - // Update EVSE config - // Update the EVSE setting flags, a little low level, may move later - if(doc.containsKey("diode_check")) - { - bool enable = doc["diode_check"]; - if(enable != evse.isDiodeCheckEnabled()) { - evse.enableDiodeCheck(enable); - config_modified = true; - DBUGLN("diode_check changed"); - } - } - - if(doc.containsKey("gfci_check")) - { - bool enable = doc["gfci_check"]; - if(enable != evse.isGfiTestEnabled()) { - evse.enableGfiTestCheck(enable); - config_modified = true; - DBUGLN("gfci_check changed"); - } - } - - if(doc.containsKey("ground_check")) - { - bool enable = doc["ground_check"]; - if(enable != evse.isGroundCheckEnabled()) { - evse.enableGroundCheck(enable); - config_modified = true; - DBUGLN("ground_check changed"); - } - } - - if(doc.containsKey("relay_check")) - { - bool enable = doc["relay_check"]; - if(enable != evse.isStuckRelayCheckEnabled()) { - evse.enableStuckRelayCheck(enable); - config_modified = true; - DBUGLN("relay_check changed"); - } - } - - if(doc.containsKey("vent_check")) - { - bool enable = doc["vent_check"]; - if(enable != evse.isVentRequiredEnabled()) { - evse.enableVentRequired(enable); - config_modified = true; - DBUGLN("vent_check changed"); - } - } - - if(doc.containsKey("temp_check")) - { - bool enable = doc["temp_check"]; - if(enable != evse.isTemperatureCheckEnabled()) { - evse.enableTemperatureCheck(enable); - config_modified = true; - DBUGLN("temp_check changed"); - } - } - - if(doc.containsKey("service")) - { - EvseMonitor::ServiceLevel service = static_cast(doc["service"].as()); - if(service != evse.getServiceLevel()) { - evse.setServiceLevel(service); - config_modified = true; - DBUGLN("service changed"); - } - } - - if(doc.containsKey("max_current_soft")) - { - long current = doc["max_current_soft"]; - if(current != evse.getMaxConfiguredCurrent()) { - evse.setMaxConfiguredCurrent(current); - config_modified = true; - DBUGLN("max_current_soft changed"); - } - } - - if(doc.containsKey("scale") && doc.containsKey("offset")) - { - long scale = doc["scale"]; - long offset = doc["offset"]; - if(scale != evse.getCurrentSensorScale() || offset != evse.getCurrentSensorOffset()) { - evse.configureCurrentSensorScale(doc["scale"], doc["offset"]); - config_modified = true; - DBUGLN("scale changed"); - } - } - - if(config_modified) - { - // HACK: force a flush of the RAPI command queue to make sure the config - // is updated before we send the response - DBUG("Flushing RAPI command queue ..."); - rapiSender.flush(); - DBUGLN(" Done"); - - config_version++; - DBUGVAR(config_version); - - StaticJsonDocument<128> event; - event["config_version"] = config_version; - event_send(event); - } - return config_modified; }