From 22715a1a4a94698dacbdb1509c4e69b59fd38d6c Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 21 Nov 2022 23:02:47 +0100 Subject: [PATCH 01/12] first draft version, callback and subscription not included --- src/app.cpp | 5 +- src/defines.h | 2 +- src/platformio.ini | 3 +- src/publisher/pubMqtt.h | 407 ++++++++++++++++++++++------------------ src/wifi/ahoywifi.cpp | 14 ++ src/wifi/ahoywifi.h | 4 + 6 files changed, 248 insertions(+), 187 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index fcfee586..65dc8f4b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -27,6 +27,10 @@ void app::setup(uint32_t timeout) { mSettings.getPtr(mConfig); DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false"))); +#if !defined(AP_ONLY) + mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp, &mSunrise, &mSunset); +#endif + mWifi = new ahoywifi(mConfig); mWifi->setup(timeout, mSettings.getValid()); @@ -39,7 +43,6 @@ void app::setup(uint32_t timeout) { mPayload.enableSerialDebug(mConfig->serial.debug); #if !defined(AP_ONLY) if (mConfig->mqtt.broker[0] > 0) { - mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp, &mSunrise, &mSunset); mPayload.addListener(std::bind(&PubMqttType::payloadEventListener, &mMqtt, std::placeholders::_1)); addListener(EVERY_SEC, std::bind(&PubMqttType::tickerSecond, &mMqtt)); addListener(EVERY_MIN, std::bind(&PubMqttType::tickerMinute, &mMqtt)); diff --git a/src/defines.h b/src/defines.h index 36949daf..ea772a5f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 40 +#define VERSION_PATCH 42 //------------------------------------- typedef struct { diff --git a/src/platformio.ini b/src/platformio.ini index ac38b9e8..0610c51d 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -36,7 +36,8 @@ lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer nrf24/RF24 paulstoffregen/Time - knolleary/PubSubClient + https://github.com/bertmelis/espMqttClient#v1.3.3 + ;knolleary/PubSubClient bblanchon/ArduinoJson ;esp8266/DNSServer ;esp8266/EEPROM diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 90f27d45..cd27d884 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -15,115 +15,94 @@ #include "../utils/dbg.h" #include "../utils/ahoyTimer.h" #include "../config/config.h" -#include +#include #include #include "../defines.h" #include "../hm/hmSystem.h" + +#define QOS_0 0 + +//https://bert.emelis.net/espMqttClient/ + template class PubMqtt { public: PubMqtt() { - mClient = new PubSubClient(mEspClient); - mAddressSet = false; - - mLastReconnect = 0; mTxCnt = 0; } ~PubMqtt() { } void setup(cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs, uint32_t *sunrise, uint32_t *sunset) { - DPRINTLN(DBG_VERBOSE, F("PubMqtt.h:setup")); - mAddressSet = true; - - mCfg_mqtt = cfg_mqtt; + mCfgMqtt = cfg_mqtt; mDevName = devName; + mVersion = version; mSys = sys; mUtcTimestamp = utcTs; mSunrise = sunrise; mSunset = sunset; - mClient->setServer(mCfg_mqtt->broker, mCfg_mqtt->port); - mClient->setBufferSize(MQTT_MAX_PACKET_SIZE); + mHWifiCon = WiFi.onStationModeGotIP(std::bind(&PubMqtt::onWifiConnect, this, std::placeholders::_1)); + mHWifiDiscon = WiFi.onStationModeDisconnected(std::bind(&PubMqtt::onWifiDisconnect, this, std::placeholders::_1)); - setCallback(std::bind(&PubMqtt::cbMqtt, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - sendMsg("version", version); - sendMsg("device", devName); - sendMsg("uptime", "0"); + if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) + mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); + mClient.setClientId(mDevName); // TODO: add mac? + mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port); + mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1)); + mClient.onDisconnect(std::bind(&PubMqtt::onDisconnect, this, std::placeholders::_1)); + mClient.onSubscribe(std::bind(&PubMqtt::onSubscribe, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + mClient.onPublish(std::bind(&PubMqtt::onPublish, this, std::placeholders::_1)); + //mClient.setWill } void loop() { - if(mAddressSet) - mClient->loop(); + mClient.loop(); } void tickerSecond() { - if(mAddressSet) { - if(!mClient->connected()) - reconnect(); - } sendIvData(); } void tickerMinute() { - if(mAddressSet) { - char val[40]; - snprintf(val, 40, "%ld", millis() / 1000); - sendMsg("uptime", val); - - sendMsg("wifi_rssi", String(WiFi.RSSI()).c_str()); - } + char val[12]; + snprintf(val, 12, "%ld", millis() / 1000); + publish("uptime", val); + publish("wifi_rssi", String(WiFi.RSSI()).c_str()); } void tickerHour() { - if(mAddressSet) { - sendMsg("sunrise", String(*mSunrise).c_str()); - sendMsg("sunset", String(*mSunset).c_str()); - } + publish("sunrise", String(*mSunrise).c_str(), true); + publish("sunset", String(*mSunset).c_str(), true); } - void setCallback(MQTT_CALLBACK_SIGNATURE) { - mClient->setCallback(callback); + void publish(const char *subTopic, const char *payload, bool retained = false) { + char topic[MQTT_TOPIC_LEN + 2]; + snprintf(topic, (MQTT_TOPIC_LEN + 2), "%s/%s", mCfgMqtt->topic, subTopic); + mClient.publish(topic, QOS_0, retained, payload); + mTxCnt++; } - void sendMsg(const char *topic, const char *msg) { - //DPRINTLN(DBG_VERBOSE, F("mqtt.h:sendMsg")); - if(mAddressSet) { - char top[66]; - snprintf(top, 66, "%s/%s", mCfg_mqtt->topic, topic); - sendMsg2(top, msg, false); - } + void subscribe(const char *subTopic) { + char topic[MQTT_TOPIC_LEN + 20]; + snprintf(topic, (MQTT_TOPIC_LEN + 20), "%s/%s", mCfgMqtt->topic, subTopic); + mClient.subscribe(topic, QOS_0); } - void sendMsg2(const char *topic, const char *msg, boolean retained) { - if(mAddressSet) { - if(!mClient->connected()) - reconnect(); - if(mClient->connected()) - mClient->publish(topic, msg, retained); - mTxCnt++; - } + inline bool isConnected() { + return mClient.connected(); } - bool isConnected(bool doRecon = false) { - //DPRINTLN(DBG_VERBOSE, F("mqtt.h:isConnected")); - if(!mAddressSet) - return false; - if(doRecon && !mClient->connected()) - reconnect(); - return mClient->connected(); + inline uint32_t getTxCnt(void) { + return mTxCnt; } void payloadEventListener(uint8_t cmd) { mSendList.push(cmd); } - uint32_t getTxCnt(void) { - return mTxCnt; - } - void sendMqttDiscoveryConfig(const char *topic) { DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); @@ -165,8 +144,7 @@ class PubMqtt { doc["stat_cla"] = stateCls; serializeJson(doc, buffer); - sendMsg2(discoveryTopic, buffer, true); - // DPRINTLN(DBG_INFO, F("mqtt sent")); + publish(discoveryTopic, buffer, true); doc.clear(); } @@ -176,7 +154,69 @@ class PubMqtt { } private: - void reconnect(void) { + void onWifiConnect(const WiFiEventStationModeGotIP& event) { + DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); + mClient.connect(); + } + + void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { + DPRINTLN(DBG_WARN, F("TODO: MQTT reconnect!")); + } + + void onConnect(bool sessionPreset) { + DPRINTLN(DBG_INFO, F("MQTT connected")); + + publish("version", mVersion, true); + publish("device", mDevName, true); + publish("uptime", "0"); + + subscribe("devcontrol/#"); // TODO: register onMessage callback! + } + + void onDisconnect(espMqttClientTypes::DisconnectReason reason) { + DBGPRINT(F("MQTT disconnected, reason: ")); + switch (reason) { + case espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED: + DBGPRINTLN(F("TCP disconnect")); + break; + case espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: + DBGPRINTLN(F("wrong protocol version")); + break; + case espMqttClientTypes::DisconnectReason::MQTT_IDENTIFIER_REJECTED: + DBGPRINTLN(F("identifier rejected")); + break; + case espMqttClientTypes::DisconnectReason::MQTT_SERVER_UNAVAILABLE: + DBGPRINTLN(F("broker unavailable")); + break; + case espMqttClientTypes::DisconnectReason::MQTT_MALFORMED_CREDENTIALS: + DBGPRINTLN(F("malformed credentials")); + break; + case espMqttClientTypes::DisconnectReason::MQTT_NOT_AUTHORIZED: + DBGPRINTLN(F("not authorized")); + break; + default: + DBGPRINTLN(F("unknown")); + } + } + + void onSubscribe(uint16_t packetId, const espMqttClientTypes::SubscribeReturncode* codes, size_t len) { + DPRINTLN(DBG_INFO, F("MQTT Subscribe")); + Serial.print(" packetId: "); + Serial.println(packetId); + for (size_t i = 0; i < len; ++i) { + Serial.print(" qos: "); + Serial.println(static_cast(codes[i])); + } + } + + void onPublish(uint16_t packetId) { + Serial.println("Publish acknowledged."); + Serial.print(" packetId: "); + Serial.println(packetId); + } + + + /*void reconnect(void) { DPRINTLN(DBG_DEBUG, F("mqtt.h:reconnect")); DPRINTLN(DBG_DEBUG, F("MQTT mClient->_state ") + String(mClient->state()) ); @@ -190,27 +230,27 @@ class PubMqtt { if(strlen(mDevName) > 0) { // der Server und der Port müssen neu gesetzt werden, // da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat. - mClient->setServer(mCfg_mqtt->broker, mCfg_mqtt->port); + mClient->setServer(mCfgMqtt->broker, mCfgMqtt->port); mClient->setBufferSize(MQTT_MAX_PACKET_SIZE); char lwt[MQTT_TOPIC_LEN + 7 ]; // "/uptime" --> + 7 byte - snprintf(lwt, MQTT_TOPIC_LEN + 7, "%s/uptime", mCfg_mqtt->topic); + snprintf(lwt, MQTT_TOPIC_LEN + 7, "%s/uptime", mCfgMqtt->topic); - if((strlen(mCfg_mqtt->user) > 0) && (strlen(mCfg_mqtt->pwd) > 0)) - resub = mClient->connect(mDevName, mCfg_mqtt->user, mCfg_mqtt->pwd, lwt, 0, false, "offline"); + if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) + resub = mClient->connect(mDevName, mCfgMqtt->user, mCfgMqtt->pwd, lwt, 0, false, "offline"); else resub = mClient->connect(mDevName, lwt, 0, false, "offline"); // ein Subscribe ist nur nach einem connect notwendig if(resub) { char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte // ToDo: "/devcontrol/#" is hardcoded - snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfg_mqtt->topic); + snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfgMqtt->topic); DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic)); mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#" } } } - } + }*/ const char *getFieldDeviceClass(uint8_t fieldId) { uint8_t pos = 0; @@ -234,7 +274,6 @@ class PubMqtt { if(mSendList.empty()) return; - isConnected(true); // really needed? See comment from HorstG-57 #176 char topic[32 + MAX_NAME_LENGTH], val[40]; float total[4]; bool sendTotal = false; @@ -267,15 +306,15 @@ class PubMqtt { (status == MQTT_STATUS_AVAIL_NOT_PROD) ? "not " : "", (status == MQTT_STATUS_NOT_AVAIL_NOT_PROD) ? "" : "producing" ); - sendMsg(topic, val); + publish(topic, val); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); snprintf(val, 40, "%d", status); - sendMsg(topic, val); + publish(topic, val); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name); snprintf(val, 40, "%i", iv->getLastTs(rec) * 1000); - sendMsg(topic, val); + publish(topic, val); } // data @@ -283,7 +322,7 @@ class PubMqtt { for (uint8_t i = 0; i < rec->length; i++) { snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); snprintf(val, 40, "%.3f", iv->getValue(i, rec)); - sendMsg(topic, val); + publish(topic, val); // calculate total values for RealTimeRunData_Debug if (mSendList.front() == RealTimeRunData_Debug) { @@ -332,127 +371,127 @@ class PubMqtt { } snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); snprintf(val, 40, "%.3f", total[i]); - sendMsg(topic, val); + publish(topic, val); } } } } - void cbMqtt(char *topic, byte *payload, unsigned int length) { - // callback handling on subscribed devcontrol topic - DPRINTLN(DBG_INFO, F("cbMqtt")); - // subcribed topics are mTopic + "/devcontrol/#" where # is / - // eg. mypvsolar/devcontrol/1/11 with payload "400" --> inverter 1 active power limit 400 Watt - const char *token = strtok(topic, "/"); - while (token != NULL) { - if (strcmp(token, "devcontrol") == 0) { - token = strtok(NULL, "/"); - uint8_t iv_id = std::stoi(token); - - if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS) { - Inverter<> *iv = mSys->getInverterByPos(iv_id); - if (NULL != iv) { - if (!iv->devControlRequest) { // still pending - token = strtok(NULL, "/"); - - switch (std::stoi(token)) { - // Active Power Control - case ActivePowerContr: - token = strtok(NULL, "/"); // get ControlMode aka "PowerPF.Desc" in DTU-Pro Code from topic string - if (token == NULL) // default via mqtt ommit the LimitControlMode - iv->powerLimit[1] = AbsolutNonPersistent; - else - iv->powerLimit[1] = std::stoi(token); - if (length <= 5) { // if (std::stoi((char*)payload) > 0) more error handling powerlimit needed? - if (iv->powerLimit[1] >= AbsolutNonPersistent && iv->powerLimit[1] <= RelativPersistent) { - iv->devControlCmd = ActivePowerContr; - iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); // THX to @silversurfer - /*if (iv->powerLimit[1] & 0x0001) - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%")); - else - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W"));*/ - - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + String(iv->powerLimit[1] & 0x0001) ? F("%") : F("W")); - } - iv->devControlRequest = true; - } else { - DPRINTLN(DBG_INFO, F("Invalid mqtt payload recevied: ") + String((char *)payload)); - } - break; - - // Turn On - case TurnOn: - iv->devControlCmd = TurnOn; - DPRINTLN(DBG_INFO, F("Turn on inverter ") + String(iv->id)); - iv->devControlRequest = true; - break; - - // Turn Off - case TurnOff: - iv->devControlCmd = TurnOff; - DPRINTLN(DBG_INFO, F("Turn off inverter ") + String(iv->id)); - iv->devControlRequest = true; - break; - - // Restart - case Restart: - iv->devControlCmd = Restart; - DPRINTLN(DBG_INFO, F("Restart inverter ") + String(iv->id)); - iv->devControlRequest = true; - break; - - // Reactive Power Control - case ReactivePowerContr: - iv->devControlCmd = ReactivePowerContr; - if (true) { // if (std::stoi((char*)payload) > 0) error handling powerlimit needed? - iv->devControlCmd = ReactivePowerContr; - iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); - iv->powerLimit[1] = 0x0000; // if reactivepower limit is set via external interface --> set it temporay - DPRINTLN(DBG_DEBUG, F("Reactivepower limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W")); - iv->devControlRequest = true; - } - break; - - // Set Power Factor - case PFSet: - // iv->devControlCmd = PFSet; - // uint16_t power_factor = std::stoi(strtok(NULL, "/")); - DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id)); - break; - - // CleanState lock & alarm - case CleanState_LockAndAlarm: - iv->devControlCmd = CleanState_LockAndAlarm; - DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id)); - iv->devControlRequest = true; - break; - - default: - DPRINTLN(DBG_INFO, "Not implemented"); - break; - } - } - } - } - break; - } - token = strtok(NULL, "/"); - } - DPRINTLN(DBG_INFO, F("app::cbMqtt finished")); - } +// void cbMqtt(char *topic, byte *payload, unsigned int length) { +// // callback handling on subscribed devcontrol topic +// DPRINTLN(DBG_INFO, F("cbMqtt")); +// // subcribed topics are mTopic + "/devcontrol/#" where # is / +// // eg. mypvsolar/devcontrol/1/11 with payload "400" --> inverter 1 active power limit 400 Watt +// const char *token = strtok(topic, "/"); +// while (token != NULL) { +// if (strcmp(token, "devcontrol") == 0) { +// token = strtok(NULL, "/"); +// uint8_t iv_id = std::stoi(token); +// +// if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS) { +// Inverter<> *iv = mSys->getInverterByPos(iv_id); +// if (NULL != iv) { +// if (!iv->devControlRequest) { // still pending +// token = strtok(NULL, "/"); +// +// switch (std::stoi(token)) { +// // Active Power Control +// case ActivePowerContr: +// token = strtok(NULL, "/"); // get ControlMode aka "PowerPF.Desc" in DTU-Pro Code from topic string +// if (token == NULL) // default via mqtt ommit the LimitControlMode +// iv->powerLimit[1] = AbsolutNonPersistent; +// else +// iv->powerLimit[1] = std::stoi(token); +// if (length <= 5) { // if (std::stoi((char*)payload) > 0) more error handling powerlimit needed? +// if (iv->powerLimit[1] >= AbsolutNonPersistent && iv->powerLimit[1] <= RelativPersistent) { +// iv->devControlCmd = ActivePowerContr; +// iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); // THX to @silversurfer +// /*if (iv->powerLimit[1] & 0x0001) +// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%")); +// else +// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W"));*/ +// +// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + String(iv->powerLimit[1] & 0x0001) ? F("%") : F("W")); +// } +// iv->devControlRequest = true; +// } else { +// DPRINTLN(DBG_INFO, F("Invalid mqtt payload recevied: ") + String((char *)payload)); +// } +// break; +// +// // Turn On +// case TurnOn: +// iv->devControlCmd = TurnOn; +// DPRINTLN(DBG_INFO, F("Turn on inverter ") + String(iv->id)); +// iv->devControlRequest = true; +// break; +// +// // Turn Off +// case TurnOff: +// iv->devControlCmd = TurnOff; +// DPRINTLN(DBG_INFO, F("Turn off inverter ") + String(iv->id)); +// iv->devControlRequest = true; +// break; +// +// // Restart +// case Restart: +// iv->devControlCmd = Restart; +// DPRINTLN(DBG_INFO, F("Restart inverter ") + String(iv->id)); +// iv->devControlRequest = true; +// break; +// +// // Reactive Power Control +// case ReactivePowerContr: +// iv->devControlCmd = ReactivePowerContr; +// if (true) { // if (std::stoi((char*)payload) > 0) error handling powerlimit needed? +// iv->devControlCmd = ReactivePowerContr; +// iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); +// iv->powerLimit[1] = 0x0000; // if reactivepower limit is set via external interface --> set it temporay +// DPRINTLN(DBG_DEBUG, F("Reactivepower limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W")); +// iv->devControlRequest = true; +// } +// break; +// +// // Set Power Factor +// case PFSet: +// // iv->devControlCmd = PFSet; +// // uint16_t power_factor = std::stoi(strtok(NULL, "/")); +// DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id)); +// break; +// +// // CleanState lock & alarm +// case CleanState_LockAndAlarm: +// iv->devControlCmd = CleanState_LockAndAlarm; +// DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id)); +// iv->devControlRequest = true; +// break; +// +// default: +// DPRINTLN(DBG_INFO, "Not implemented"); +// break; +// } +// } +// } +// } +// break; +// } +// token = strtok(NULL, "/"); +// } +// DPRINTLN(DBG_INFO, F("app::cbMqtt finished")); +// } + + espMqttClient mClient; + cfgMqtt_t *mCfgMqtt; + WiFiEventHandler mHWifiCon, mHWifiDiscon; uint32_t *mSunrise, *mSunset; - WiFiClient mEspClient; - PubSubClient *mClient; HMSYSTEM *mSys; uint32_t *mUtcTimestamp; - - bool mAddressSet; - cfgMqtt_t *mCfg_mqtt; - const char *mDevName; - uint32_t mLastReconnect; uint32_t mTxCnt; std::queue mSendList; + + const char *mDevName, *mVersion; + //uint32_t mLastReconnect; }; #endif /*__PUB_MQTT_H__*/ diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 1d684bbe..9b95d25b 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -32,6 +32,8 @@ ahoywifi::ahoywifi(settings_t *config) { //----------------------------------------------------------------------------- void ahoywifi::setup(uint32_t timeout, bool settingValid) { + //wifiConnectHandler = WiFi.onStationModeGotIP(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); + //wifiDisconnectHandler = WiFi.onStationModeDisconnected(std::bind(&ahoywifi::onDisconnect, this, std::placeholders::_1)); #ifdef FB_WIFI_OVERRIDDEN mStationWifiIsDef = false; @@ -287,3 +289,15 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { mUdp->write(buf, NTP_PACKET_SIZE); mUdp->endPacket(); } + + +//----------------------------------------------------------------------------- +/*void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) { + Serial.println("Connected to Wi-Fi."); +} + + +//----------------------------------------------------------------------------- +void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) { + Serial.println("Disconnected from Wi-Fi."); +}*/ diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index c2add85b..32ca5cec 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -33,11 +33,15 @@ class ahoywifi { private: void sendNTPpacket(IPAddress& address); + //void onConnect(const WiFiEventStationModeGotIP& event); + //void onDisconnect(const WiFiEventStationModeDisconnected& event); settings_t *mConfig; DNSServer *mDns; WiFiUDP *mUdp; // for time server + //WiFiEventHandler wifiConnectHandler; + //WiFiEventHandler wifiDisconnectHandler; uint32_t mWifiStationTimeout; uint32_t mNextTryTs; From 43af07716e03f20778d961166e89aff75154a8a8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 22 Nov 2022 10:32:04 +0100 Subject: [PATCH 02/12] fix logout --- src/config/config.h | 2 +- src/defines.h | 2 +- src/web/web.cpp | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/config/config.h b/src/config/config.h index 70a36f58..2b5a0688 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -26,7 +26,7 @@ //#define AP_ONLY // timeout for automatic logoff (20 minutes) -#define LOGOUT_TIMEOUT (20 * 60 * 60) +#define LOGOUT_TIMEOUT (20 * 60) //------------------------------------- // CONFIGURATION - COMPILE TIME diff --git a/src/defines.h b/src/defines.h index 36949daf..ea772a5f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 40 +#define VERSION_PATCH 42 //------------------------------------- typedef struct { diff --git a/src/web/web.cpp b/src/web/web.cpp index 45be992e..3db382ef 100644 --- a/src/web/web.cpp +++ b/src/web/web.cpp @@ -107,8 +107,10 @@ void web::loop(void) { void web::tickSecond() { if(0 != mLogoutTimeout) { mLogoutTimeout -= 1; - if(0 == mLogoutTimeout) - mProtected = true; + if(0 == mLogoutTimeout) { + if(strlen(mConfig->sys.adminPwd) > 0) + mProtected = true; + } DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout)); } From 573bd31f0a4b5f9bfa032ac6eb3503ae03bc3c0b Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 23 Nov 2022 21:09:57 +0100 Subject: [PATCH 03/12] improved MQTT --- src/app.cpp | 12 ++- src/publisher/pubMqtt.h | 164 ++++++++++++++++++++++----------------- src/web/html/update.html | 6 -- 3 files changed, 102 insertions(+), 80 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 65dc8f4b..782ae853 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -27,6 +27,11 @@ void app::setup(uint32_t timeout) { mSettings.getPtr(mConfig); DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false"))); + mSys = new HmSystemType(); + mSys->enableDebug(); + mSys->setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); + mSys->addInverters(&mConfig->inst); + #if !defined(AP_ONLY) mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp, &mSunrise, &mSunset); #endif @@ -34,10 +39,6 @@ void app::setup(uint32_t timeout) { mWifi = new ahoywifi(mConfig); mWifi->setup(timeout, mSettings.getValid()); - mSys = new HmSystemType(); - mSys->enableDebug(); - mSys->setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); - mSys->addInverters(&mConfig->inst); mPayload.setup(mSys); mPayload.enableSerialDebug(mConfig->serial.debug); @@ -222,6 +223,9 @@ void app::resetSystem(void) { mUtcTimestamp = 0; #endif + mSunrise = 0; + mSunset = 0; + mHeapStatCnt = 0; mSendTicker = 0xffff; diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index cd27d884..fb3d8669 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -29,7 +29,9 @@ template class PubMqtt { public: PubMqtt() { + mRxCnt = 0; mTxCnt = 0; + mEnReconnect = false; } ~PubMqtt() { } @@ -43,6 +45,8 @@ class PubMqtt { mSunrise = sunrise; mSunset = sunset; + snprintf(mLwtTopic, MQTT_TOPIC_LEN + 7, "%s/status", mCfgMqtt->topic); + mHWifiCon = WiFi.onStationModeGotIP(std::bind(&PubMqtt::onWifiConnect, this, std::placeholders::_1)); mHWifiDiscon = WiFi.onStationModeDisconnected(std::bind(&PubMqtt::onWifiDisconnect, this, std::placeholders::_1)); @@ -51,11 +55,10 @@ class PubMqtt { mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); mClient.setClientId(mDevName); // TODO: add mac? mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port); + mClient.setWill(mLwtTopic, QOS_0, true, mLwtOffline); mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1)); mClient.onDisconnect(std::bind(&PubMqtt::onDisconnect, this, std::placeholders::_1)); - mClient.onSubscribe(std::bind(&PubMqtt::onSubscribe, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - mClient.onPublish(std::bind(&PubMqtt::onPublish, this, std::placeholders::_1)); - //mClient.setWill + mClient.onMessage(std::bind(&PubMqtt::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); } void loop() { @@ -71,6 +74,11 @@ class PubMqtt { snprintf(val, 12, "%ld", millis() / 1000); publish("uptime", val); publish("wifi_rssi", String(WiFi.RSSI()).c_str()); + + if(!mClient.connected()) { + if(mEnReconnect) + mClient.connect(); + } } void tickerHour() { @@ -78,10 +86,13 @@ class PubMqtt { publish("sunset", String(*mSunset).c_str(), true); } - void publish(const char *subTopic, const char *payload, bool retained = false) { + void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) { char topic[MQTT_TOPIC_LEN + 2]; snprintf(topic, (MQTT_TOPIC_LEN + 2), "%s/%s", mCfgMqtt->topic, subTopic); - mClient.publish(topic, QOS_0, retained, payload); + if(addTopic) + mClient.publish(topic, QOS_0, retained, payload); + else + mClient.publish(subTopic, QOS_0, retained, payload); mTxCnt++; } @@ -99,6 +110,10 @@ class PubMqtt { return mTxCnt; } + inline uint32_t getRxCnt(void) { + return mRxCnt; + } + void payloadEventListener(uint8_t cmd) { mSendList.push(cmd); } @@ -112,11 +127,11 @@ class PubMqtt { if (NULL != iv) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); DynamicJsonDocument deviceDoc(128); - deviceDoc["name"] = iv->config->name; - deviceDoc["ids"] = String(iv->config->serial.u64, HEX); - deviceDoc["cu"] = F("http://") + String(WiFi.localIP().toString()); - deviceDoc["mf"] = "Hoymiles"; - deviceDoc["mdl"] = iv->config->name; + deviceDoc[F("name")] = iv->config->name; + deviceDoc[F("ids")] = String(iv->config->serial.u64, HEX); + deviceDoc[F("cu")] = F("http://") + String(WiFi.localIP().toString()); + deviceDoc[F("mf")] = F("Hoymiles"); + deviceDoc[F("mdl")] = iv->config->name; JsonObject deviceObj = deviceDoc.as(); DynamicJsonDocument doc(384); @@ -132,19 +147,19 @@ class PubMqtt { const char *devCls = getFieldDeviceClass(rec->assign[i].fieldId); const char *stateCls = getFieldStateClass(rec->assign[i].fieldId); - doc["name"] = name; - doc["stat_t"] = stateTopic; - doc["unit_of_meas"] = iv->getUnit(i, rec); - doc["uniq_id"] = String(iv->config->serial.u64, HEX) + "_" + uniq_id; - doc["dev"] = deviceObj; - doc["exp_aft"] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!? + doc[F("name")] = name; + doc[F("stat_t")] = stateTopic; + doc[F("unit_of_meas")] = iv->getUnit(i, rec); + doc[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id; + doc[F("dev")] = deviceObj; + doc[F("exp_aft")] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!? if (devCls != NULL) - doc["dev_cla"] = devCls; + doc[F("dev_cla")] = devCls; if (stateCls != NULL) - doc["stat_cla"] = stateCls; + doc[F("stat_cla")] = stateCls; serializeJson(doc, buffer); - publish(discoveryTopic, buffer, true); + publish(discoveryTopic, buffer, true, false); doc.clear(); } @@ -160,17 +175,22 @@ class PubMqtt { } void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { - DPRINTLN(DBG_WARN, F("TODO: MQTT reconnect!")); + mEnReconnect = false; } void onConnect(bool sessionPreset) { DPRINTLN(DBG_INFO, F("MQTT connected")); + mEnReconnect = true; publish("version", mVersion, true); publish("device", mDevName, true); publish("uptime", "0"); - subscribe("devcontrol/#"); // TODO: register onMessage callback! + publish(mLwtTopic, mLwtOnline, true, false); + + subscribe("ctrl/#"); + subscribe("setup/#"); + subscribe("status/#"); } void onDisconnect(espMqttClientTypes::DisconnectReason reason) { @@ -199,58 +219,57 @@ class PubMqtt { } } - void onSubscribe(uint16_t packetId, const espMqttClientTypes::SubscribeReturncode* codes, size_t len) { - DPRINTLN(DBG_INFO, F("MQTT Subscribe")); - Serial.print(" packetId: "); - Serial.println(packetId); - for (size_t i = 0; i < len; ++i) { - Serial.print(" qos: "); - Serial.println(static_cast(codes[i])); + void onMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { + DPRINTLN(DBG_VERBOSE, F("MQTT got topic: ") + String(topic)); + char *tpc = new char[strlen(topic) + 1]; + uint8_t cnt = 0; + DynamicJsonDocument json(128); + JsonObject root = json.to(); + + strncpy(tpc, topic, strlen(topic) + 1); + if(len > 0) { + char *pyld = new char[len + 1]; + strncpy(pyld, (const char*)payload, len); + pyld[len] = '\0'; + root["val"] = atoi(pyld); + delete[] pyld; } - } - - void onPublish(uint16_t packetId) { - Serial.println("Publish acknowledged."); - Serial.print(" packetId: "); - Serial.println(packetId); - } - - /*void reconnect(void) { - DPRINTLN(DBG_DEBUG, F("mqtt.h:reconnect")); - DPRINTLN(DBG_DEBUG, F("MQTT mClient->_state ") + String(mClient->state()) ); - - #ifdef ESP8266 - DPRINTLN(DBG_DEBUG, F("WIFI mEspClient.status ") + String(mEspClient.status()) ); - #endif - - boolean resub = false; - if(!mClient->connected() && (millis() - mLastReconnect) > MQTT_RECONNECT_DELAY ) { - mLastReconnect = millis(); - if(strlen(mDevName) > 0) { - // der Server und der Port müssen neu gesetzt werden, - // da ein MQTT_CONNECTION_LOST -3 die Werte zerstört hat. - mClient->setServer(mCfgMqtt->broker, mCfgMqtt->port); - mClient->setBufferSize(MQTT_MAX_PACKET_SIZE); - - char lwt[MQTT_TOPIC_LEN + 7 ]; // "/uptime" --> + 7 byte - snprintf(lwt, MQTT_TOPIC_LEN + 7, "%s/uptime", mCfgMqtt->topic); - - if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) - resub = mClient->connect(mDevName, mCfgMqtt->user, mCfgMqtt->pwd, lwt, 0, false, "offline"); - else - resub = mClient->connect(mDevName, lwt, 0, false, "offline"); - // ein Subscribe ist nur nach einem connect notwendig - if(resub) { - char topic[MQTT_TOPIC_LEN + 13 ]; // "/devcontrol/#" --> + 6 byte - // ToDo: "/devcontrol/#" is hardcoded - snprintf(topic, MQTT_TOPIC_LEN + 13, "%s/devcontrol/#", mCfgMqtt->topic); - DPRINTLN(DBG_INFO, F("subscribe to ") + String(topic)); - mClient->subscribe(topic); // subscribe to mTopic + "/devcontrol/#" + char *p = strtok(tpc, "/"); + p = strtok(NULL, "/"); // remove mCfgMqtt->topic + while(NULL != p) { + if(0 == cnt) { + if(0 == strncmp(p, "ctrl", 4)) { + if(NULL != (p = strtok(NULL, "/"))) { + root[F("path")] = F("ctrl"); + root[F("cmd")] = p; + } + } else if(0 == strncmp(p, "setup", 5)) { + if(NULL != (p = strtok(NULL, "/"))) { + root[F("path")] = F("setup"); + root[F("setup")] = p; + } + } else if(0 == strncmp(p, "status", 6)) { + if(NULL != (p = strtok(NULL, "/"))) { + root[F("path")] = F("status"); + root[F("cmd")] = p; + } } } + else if(1 == cnt) { + root["id"] = atoi(p); + } + p = strtok(NULL, "/"); + cnt++; } - }*/ + delete[] tpc; + + char out[128]; + serializeJson(root, out, 128); + DPRINTLN(DBG_INFO, "json: " + String(out)); + + mRxCnt++; + } const char *getFieldDeviceClass(uint8_t fieldId) { uint8_t pos = 0; @@ -487,11 +506,16 @@ class PubMqtt { uint32_t *mSunrise, *mSunset; HMSYSTEM *mSys; uint32_t *mUtcTimestamp; - uint32_t mTxCnt; + uint32_t mRxCnt, mTxCnt; std::queue mSendList; + bool mEnReconnect; + + // last will topic and payload must be available trough lifetime of 'espMqttClient' + char mLwtTopic[MQTT_TOPIC_LEN+7]; + const char* mLwtOnline = "online"; + const char* mLwtOffline = "offline"; const char *mDevName, *mVersion; - //uint32_t mLastReconnect; }; #endif /*__PUB_MQTT_H__*/ diff --git a/src/web/html/update.html b/src/web/html/update.html index b4c0f924..ecc44275 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -18,12 +18,6 @@
-
- Make sure that you have noted all your settings before starting an update. New versions may have changed their memory layout which can break your existing settings.
-
- Download your settings (JSON file) -
-

From b1946df32f20805738fce0ce0c362ba67e6553f8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 23 Nov 2022 23:57:15 +0100 Subject: [PATCH 04/12] updated REST API and MQTT to use the same functionality added heap as MQTT publish updated User_Manual.md to latest changes development build will have now always the same name because of static link from https://ahoydtu.de --- .github/workflows/compile_development.yml | 2 +- User_Manual.md | 236 +++++++++++----------- src/app.cpp | 16 +- src/app.h | 5 +- src/defines.h | 2 +- src/publisher/pubMqtt.h | 166 +++++---------- src/web/html/index.html | 2 +- src/web/html/serial.html | 47 ++--- src/web/html/setup.html | 6 +- src/web/web.cpp | 5 +- src/web/web.h | 2 + src/web/webApi.cpp | 115 +++++------ src/web/webApi.h | 4 +- 13 files changed, 265 insertions(+), 343 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 274a6c73..ec435880 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -68,7 +68,7 @@ jobs: - name: Create Artifact uses: actions/upload-artifact@v3 with: - name: ${{ steps.rename-binary-files.outputs.name }}_dev_build + name: ahoydtu_dev path: | src/firmware/* src/User_Manual.md diff --git a/User_Manual.md b/User_Manual.md index 3b51a3e1..8be2d62c 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -1,4 +1,4 @@ -# User Manual Ahoy DTU (on ESP8266) +# User Manual AhoyDTU (on ESP8266) Version #{VERSION}# ## Introduction See the repository [README.md](Getting_Started.md) @@ -9,9 +9,9 @@ In the initial case or after click "erase settings" the fields for the inverter Set at least the serial number and a name for each inverter, check "reboot after save" and click the "Save" button. -## MQTT Output -The ahoy dtu will publish on the following topics -`//ch0/#` +## MQTT Output +The AhoyDTU will publish on the following topics +`//ch0/#` | Topic | Example Value | Remarks | |---|---|---| @@ -34,7 +34,7 @@ The ahoy dtu will publish on the following topics |PowerLimit | 80.000|actual set point for power limit control AC active power in percent| |LastAlarmCode | 1.000| Last Alarm Code eg. "inverter start"| -`//ch/#` +`//ch/#` `` is in the range 1 to 4 depending on the inverter type @@ -47,7 +47,8 @@ The ahoy dtu will publish on the following topics |YieldTotal | 110.819 | Energy converted to AC since reset Watt hours per module/channel (measured on DC) | |Irradiation |5.65 | ratio DC Power over set maximum power per module/channel in percent | -## Active Power Limit via Setup Page +## Active Power Limit via Serial / Control Page +URL: `/serial` If you leave the field "Active Power Limit" empty during the setup and reboot the ahoy-dtu will set a value of 65535 in the setup. That is the value you have to fill in case you want to operate the inverter without a active power limit. If the value is 65535 or -1 after another reboot the value will be set automatically to "100" and in the drop-down menu "relative in percent persistent" will be set. Of course you can do this also by your self. @@ -68,117 +69,137 @@ after a power cycle of the inverter (P_DC=0 and P_AC=0 for at least 10 seconds) The user has to ensure correct settings. Remember that for the inverters of 3rd generation the relative active power limit is in the range of 2% up to 100%. Also an absolute active power limit below approx. 30 Watt seems to be not meanful because of the control capabilities and reactive power load. -## Active Power Limit via MQTT -The ahoy-dtu subscribes on the topic `/devcontrol/#` if the mqtt broker is set-up correctly. The default topic is `inverter/devcontrol/#`. +## Control via MQTT -To set the active power limit (controled value is the AC Power of the inverter) you have four options. (Only single phase inverters are actually in focus). +### Generic Information -| topic | payload | active power limit in | Condition | -| --------------------------------------------------------------- | ----------- | -------------------------------------------- | -------------- | -| /devcontrol//11 OR /devcontrol//11/0 | [0..65535] | Watt | not persistent | -| /devcontrol//11/256 | [0..65535] | Watt | persistent | -| /devcontrol//11/1 | [2..100] | % | not persistent | -| /devcontrol//11/257 | [2..100] | % | persistent | +The AhoyDTU subscribes on three topics `/ctrl/#`, `/setup` and `/status`. +👆 `` can be set on setup page, default is `inverter`. 👆 `` is the number of the specific inverter in the setup page. -* First inverter --> `` = 0 -* Second inverter --> `` = 1 -* ... - -### Developer Information MQTT Interface -`/devcontrol///` - -The implementation allows to set any of the available `` Commands: -```C -typedef enum { - TurnOn = 0, // 0x00 - TurnOff = 1, // 0x01 - Restart = 2, // 0x02 - Lock = 3, // 0x03 - Unlock = 4, // 0x04 - ActivePowerContr = 11, // 0x0b - ReactivePowerContr = 12, // 0x0c - PFSet = 13, // 0x0d - CleanState_LockAndAlarm = 20, // 0x14 - SelfInspection = 40, // 0x28, self-inspection of grid-connected protection files - Init = 0xff -} DevControlCmdType; + +### Inverter Power (On / Off) +`/ctrl/power/` with payload `1` for `ON` and `0` for `OFF` +Example: +`inverter/ctrl/power/0` `1` + + +### Inverter restart + +`/ctrl/restart/` +Example: +`inverter/ctrl/restart/0` + + +### Power Limit relative persistent [%] + +`/ctrl/limit_persistent_relative/` with a payload `[2 .. 100]` +Example: +`inverter/ctrl/limit_persistent_relative/0` `70` + + +### Power Limit absolute persistent [Watts] + +`/ctrl/limit_persistent_relative/` with a payload `[0 .. 65535]` +Example: +`inverter/ctrl/limit_persistent_relative/0` `600` + + +### Power Limit relative non persistent [%] + +`/ctrl/limit_nonpersistent_relative/` with a payload `[2 .. 100]` +Example: +`inverter/ctrl/limit_nonpersistent_relative/0` `70` + + +### Power Limit absolute non persistent [Watts] + +`/ctrl/limit_nonpersistent_relative/` with a payload `[0 .. 65535]` +Example: +`inverter/ctrl/limit_nonpersistent_relative/0` `600` + + +## Control via REST API + +### Generic Information + +The rest API works with *JSON* POST requests. All the following instructions must be sent to the `/api` endpoint of the AhoyDTU. + +👆 `` is the number of the specific inverter in the setup page. + +### Inverter Power (On / Off) + +```json +{ + "id": , + "cmd": "power", + "val": +} ``` -The MQTT payload will be set on first to bytes and ``, which is taken from the topic path will be set on the second two bytes if the corresponding DevControlCmdType supports 4 byte data. -See here the actual implementation to set the send buffer bytes. -```C -void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data) { - sendCmdPacket(invId, TX_REQ_DEVCONTROL, ALL_FRAMES, false); - int cnt = 0; - // cmd --> 0x0b => Type_ActivePowerContr, 0 on, 1 off, 2 restart, 12 reactive power, 13 power factor - mTxBuf[10] = cmd; - mTxBuf[10 + (++cnt)] = 0x00; - if (cmd >= ActivePowerContr && cmd <= PFSet){ - mTxBuf[10 + (++cnt)] = ((data[0] * 10) >> 8) & 0xff; // power limit || high byte from MQTT payload - mTxBuf[10 + (++cnt)] = ((data[0] * 10) ) & 0xff; // power limit || low byte from MQTT payload - mTxBuf[10 + (++cnt)] = ((data[1] ) >> 8) & 0xff; // high byte from MQTT topic value - mTxBuf[10 + (++cnt)] = ((data[1] ) ) & 0xff; // low byte from MQTT topic value - } - // crc control data - uint16_t crc = Hoymiles::crc16(&mTxBuf[10], cnt+1); - mTxBuf[10 + (++cnt)] = (crc >> 8) & 0xff; - mTxBuf[10 + (++cnt)] = (crc ) & 0xff; - // crc over all - cnt +=1; - mTxBuf[10 + cnt] = Hoymiles::crc8(mTxBuf, 10 + cnt); - - sendPacket(invId, mTxBuf, 10 + (++cnt), true); +The `` should be set to `1` for `ON` and `0` for `OFF` + + +### Inverter restart + +```json +{ + "id": , + "cmd": "restart" } ``` -So as example sending any payload on `inverter/devcontrol/0/1` will switch off the inverter. -## Active Power Limit via REST API -It is also implemented to set the power limit via REST API call. Therefore send a POST request to the endpoint /api. -The response will always be a json with {success:true} -The payload shall be a json formated string in the following manner +### Power Limit relative persistent [%] + ```json { - "inverter":, - "tx_request": , - "cmd": , - "payload": , - "payload2": + "id": , + "cmd": "limit_persistent_relative", + "val": } ``` -With the following value ranges +The `VALUE` represents a percent number in a range of `[2 .. 100]` -| Value | range | note | -| --------------------------- | ----------- | ------------------------------- | -| | 81 or 21 | integer uint8, (0x15 or 0x51) | -| | [0...255] | integer uint8, subcmds eg. 0x0b | -| | [0...65535] | uint16 | -| | [0...3] | integer uint8 | +### Power Limit absolute persistent [Watts] -Example to set the active power limit non persistent to 10% ```json { - "inverter":0, - "tx_request": 81, - "cmd": 11, - "payload": 10, - "payload2": 1 + "id": , + "cmd": "limit_persistent_absolute", + "val": } ``` -Example to set the active power limit persistent to 600Watt +The `VALUE` represents watts in a range of `[0 .. 65535]` + + +### Power Limit relative non persistent [%] + ```json { - "inverter":0, - "tx_request": 81, - "cmd": 11, - "payload": 600, - "payload2": 256 + "id": , + "cmd": "limit_nonpersistent_relative", + "val": } ``` +The `VALUE` represents a percent number in a range of `[2 .. 100]` -### Developer Information REST API + +### Power Limit absolute non persistent [Watts] + +```json +{ + "id": , + "cmd": "limit_nonpersistent_absolute", + "val": +} +``` +The `VALUE` represents watts in a range of `[0 .. 65535]` + + + +### Developer Information REST API (obsolete) In the same approach as for MQTT any other SubCmd and also MainCmd can be applied and the response payload can be observed in the serial logs. Eg. request the Alarm-Data from the Alarm-Index 5 from inverter 0 will look like this: ```json { @@ -190,38 +211,15 @@ In the same approach as for MQTT any other SubCmd and also MainCmd can be applie } ``` -## Zero Export Control -* You can use the mqtt topic `/devcontrol//11` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet) -* You can check the inverter set point for the power limit control on the topic `//ch0/PowerLimit` 👆 This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall) -* You can monitor the actual AC power by subscribing to the topic `//ch0/P_AC` 👆 This value is ALWAYS in Watt - -## Issues and Debuging for active power limit settings +## Zero Export Control (needs rework) +* You can use the mqtt topic `/devcontrol//11` with a number as payload (eg. 300 -> 300 Watt) to set the power limit to the published number in Watt. (In regular cases the inverter will use the new set point within one intervall period; to verify this see next bullet) +* You can check the inverter set point for the power limit control on the topic `//ch0/PowerLimit` 👆 This value is ALWAYS in percent of the maximum power limit of the inverter. In regular cases this value will be updated within approx. 15 seconds. (depends on request intervall) +* You can monitor the actual AC power by subscribing to the topic `//ch0/P_AC` 👆 This value is ALWAYS in Watt -Turn on the serial debugging in the setup. Try to have find out if the behavior is deterministic. That means can you reproduce the behavior. Be patient and wait on inverter reactions at least some minutes and beware that the DC-Power is sufficient. - -In case of issues please report: - -1. Version of firmware -2. The output of the serial debug esp. the TX messages starting with "0x51" and the RX messages starting with "0xD1" or "0xF1" -3. Which case you have tried: Setup-Page, MQTT, REST API and at what was shown on the "Visualization Page" at the Location "Limit" -4. The setting means payload, relative, absolute, persistent, not persistent (see tables above) - -**Developer Information General for Active Power Limit** - -⚡The following was verified by field tests and feedback from users - -Internally this values will be set for the second two bytes for MainCmd: 0x51 SubCmd: 0x0b --> DevControl set ActivePowerLimit -```C -typedef enum { - AbsolutNonPersistent = 0x0000, // 0 - RelativNonPersistent = 0x0001, // 1 - AbsolutPersistent = 0x0100, // 256 - RelativPersistent = 0x0101 // 257 -} PowerLimitControlType; -``` ## Firmware Version collection Gather user inverter information here to understand what differs between some inverters. +To get the information open the URL `/api/record/info` on your AhoyDTU. The information will only be present once the AhoyDTU was able to communicate with an inverter. | Name | Inverter Typ | Bootloader V. | FWVersion | FWBuild [YYYY] | FWBuild [MM-DD] | HWPartId | | | | ---------- | ------------ | ------------- | --------- | -------------- | --------------- | --------- | -------- | --------- | diff --git a/src/app.cpp b/src/app.cpp index 782ae853..e7b86283 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -12,6 +12,12 @@ #include #include "utils/sun.h" +//----------------------------------------------------------------------------- +app::app() : ah::Scheduler() { + mWeb = NULL; +} + + //----------------------------------------------------------------------------- void app::setup(uint32_t timeout) { Serial.begin(115200); @@ -39,7 +45,6 @@ void app::setup(uint32_t timeout) { mWifi = new ahoywifi(mConfig); mWifi->setup(timeout, mSettings.getValid()); - mPayload.setup(mSys); mPayload.enableSerialDebug(mConfig->serial.debug); #if !defined(AP_ONLY) @@ -48,6 +53,7 @@ void app::setup(uint32_t timeout) { addListener(EVERY_SEC, std::bind(&PubMqttType::tickerSecond, &mMqtt)); addListener(EVERY_MIN, std::bind(&PubMqttType::tickerMinute, &mMqtt)); addListener(EVERY_HR, std::bind(&PubMqttType::tickerHour, &mMqtt)); + mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); } #endif setupLed(); @@ -226,8 +232,6 @@ void app::resetSystem(void) { mSunrise = 0; mSunset = 0; - mHeapStatCnt = 0; - mSendTicker = 0xffff; mTicker = 0; @@ -240,6 +244,12 @@ void app::resetSystem(void) { memset(&mStat, 0, sizeof(statistics_t)); } +//----------------------------------------------------------------------------- +void app::mqttSubRxCb(JsonObject obj) { + if(NULL != mWeb) + mWeb->apiCtrlRequest(obj); +} + //----------------------------------------------------------------------------- void app::setupLed(void) { /** LED connection diagram diff --git a/src/app.h b/src/app.h index 0d5b49cb..038be0c4 100644 --- a/src/app.h +++ b/src/app.h @@ -43,7 +43,7 @@ class web; class app : public ah::Scheduler { public: - app() : ah::Scheduler() {} + app(); ~app() {} void setup(uint32_t timeout); @@ -140,7 +140,7 @@ class app : public ah::Scheduler { private: void resetSystem(void); - void setupMqtt(void); + void mqttSubRxCb(JsonObject obj); void setupLed(void); void updateLed(void); @@ -197,7 +197,6 @@ class app : public ah::Scheduler { } uint32_t mUptimeSecs; - uint8_t mHeapStatCnt; uint32_t mUtcTimestamp; bool mUpdateNtp; diff --git a/src/defines.h b/src/defines.h index ea772a5f..930e4243 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 42 +#define VERSION_PATCH 43 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index fb3d8669..2a42501a 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -3,6 +3,8 @@ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- +// https://bert.emelis.net/espMqttClient/ + #ifndef __PUB_MQTT_H__ #define __PUB_MQTT_H__ @@ -20,10 +22,9 @@ #include "../defines.h" #include "../hm/hmSystem.h" - #define QOS_0 0 -//https://bert.emelis.net/espMqttClient/ +typedef std::function subscriptionCb; template class PubMqtt { @@ -32,6 +33,7 @@ class PubMqtt { mRxCnt = 0; mTxCnt = 0; mEnReconnect = false; + mSubscriptionCb = NULL; } ~PubMqtt() { } @@ -47,9 +49,12 @@ class PubMqtt { snprintf(mLwtTopic, MQTT_TOPIC_LEN + 7, "%s/status", mCfgMqtt->topic); + #if defined(ESP8266) mHWifiCon = WiFi.onStationModeGotIP(std::bind(&PubMqtt::onWifiConnect, this, std::placeholders::_1)); mHWifiDiscon = WiFi.onStationModeDisconnected(std::bind(&PubMqtt::onWifiDisconnect, this, std::placeholders::_1)); - + #else + WiFi.onEvent(std::bind(&PubMqtt::onWiFiEvent, this, std::placeholders::_1)); + #endif if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); @@ -62,7 +67,9 @@ class PubMqtt { } void loop() { + #if defined(ESP8266) mClient.loop(); + #endif } void tickerSecond() { @@ -74,6 +81,7 @@ class PubMqtt { snprintf(val, 12, "%ld", millis() / 1000); publish("uptime", val); publish("wifi_rssi", String(WiFi.RSSI()).c_str()); + publish("free_heap", String(ESP.getFreeHeap()).c_str()); if(!mClient.connected()) { if(mEnReconnect) @@ -102,6 +110,14 @@ class PubMqtt { mClient.subscribe(topic, QOS_0); } + void payloadEventListener(uint8_t cmd) { + mSendList.push(cmd); + } + + void setSubscriptionCb(subscriptionCb cb) { + mSubscriptionCb = cb; + } + inline bool isConnected() { return mClient.connected(); } @@ -114,10 +130,6 @@ class PubMqtt { return mRxCnt; } - void payloadEventListener(uint8_t cmd) { - mSendList.push(cmd); - } - void sendMqttDiscoveryConfig(const char *topic) { DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); @@ -169,6 +181,7 @@ class PubMqtt { } private: + #if defined(ESP8266) void onWifiConnect(const WiFiEventStationModeGotIP& event) { DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); mClient.connect(); @@ -178,6 +191,24 @@ class PubMqtt { mEnReconnect = false; } + #else + void onWiFiEvent(WiFiEvent_t event) { + switch(event) { + case SYSTEM_EVENT_STA_GOT_IP: + DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); + mClient.connect(); + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + mEnReconnect = false; + break; + + default: + break; + } + } + #endif + void onConnect(bool sessionPreset) { DPRINTLN(DBG_INFO, F("MQTT connected")); mEnReconnect = true; @@ -185,7 +216,6 @@ class PubMqtt { publish("version", mVersion, true); publish("device", mDevName, true); publish("uptime", "0"); - publish(mLwtTopic, mLwtOnline, true, false); subscribe("ctrl/#"); @@ -221,6 +251,9 @@ class PubMqtt { void onMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, F("MQTT got topic: ") + String(topic)); + if(NULL == mSubscriptionCb) + return; + char *tpc = new char[strlen(topic) + 1]; uint8_t cnt = 0; DynamicJsonDocument json(128); @@ -247,7 +280,7 @@ class PubMqtt { } else if(0 == strncmp(p, "setup", 5)) { if(NULL != (p = strtok(NULL, "/"))) { root[F("path")] = F("setup"); - root[F("setup")] = p; + root[F("cmd")] = p; } } else if(0 == strncmp(p, "status", 6)) { if(NULL != (p = strtok(NULL, "/"))) { @@ -257,16 +290,18 @@ class PubMqtt { } } else if(1 == cnt) { - root["id"] = atoi(p); + root[F("id")] = atoi(p); } p = strtok(NULL, "/"); cnt++; } delete[] tpc; - char out[128]; + /*char out[128]; serializeJson(root, out, 128); - DPRINTLN(DBG_INFO, "json: " + String(out)); + DPRINTLN(DBG_INFO, "json: " + String(out));*/ + if(NULL != mSubscriptionCb) + (mSubscriptionCb)(root); mRxCnt++; } @@ -396,112 +431,11 @@ class PubMqtt { } } -// void cbMqtt(char *topic, byte *payload, unsigned int length) { -// // callback handling on subscribed devcontrol topic -// DPRINTLN(DBG_INFO, F("cbMqtt")); -// // subcribed topics are mTopic + "/devcontrol/#" where # is / -// // eg. mypvsolar/devcontrol/1/11 with payload "400" --> inverter 1 active power limit 400 Watt -// const char *token = strtok(topic, "/"); -// while (token != NULL) { -// if (strcmp(token, "devcontrol") == 0) { -// token = strtok(NULL, "/"); -// uint8_t iv_id = std::stoi(token); -// -// if (iv_id >= 0 && iv_id <= MAX_NUM_INVERTERS) { -// Inverter<> *iv = mSys->getInverterByPos(iv_id); -// if (NULL != iv) { -// if (!iv->devControlRequest) { // still pending -// token = strtok(NULL, "/"); -// -// switch (std::stoi(token)) { -// // Active Power Control -// case ActivePowerContr: -// token = strtok(NULL, "/"); // get ControlMode aka "PowerPF.Desc" in DTU-Pro Code from topic string -// if (token == NULL) // default via mqtt ommit the LimitControlMode -// iv->powerLimit[1] = AbsolutNonPersistent; -// else -// iv->powerLimit[1] = std::stoi(token); -// if (length <= 5) { // if (std::stoi((char*)payload) > 0) more error handling powerlimit needed? -// if (iv->powerLimit[1] >= AbsolutNonPersistent && iv->powerLimit[1] <= RelativPersistent) { -// iv->devControlCmd = ActivePowerContr; -// iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); // THX to @silversurfer -// /*if (iv->powerLimit[1] & 0x0001) -// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("%")); -// else -// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W"));*/ -// -// DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + String(iv->powerLimit[1] & 0x0001) ? F("%") : F("W")); -// } -// iv->devControlRequest = true; -// } else { -// DPRINTLN(DBG_INFO, F("Invalid mqtt payload recevied: ") + String((char *)payload)); -// } -// break; -// -// // Turn On -// case TurnOn: -// iv->devControlCmd = TurnOn; -// DPRINTLN(DBG_INFO, F("Turn on inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// // Turn Off -// case TurnOff: -// iv->devControlCmd = TurnOff; -// DPRINTLN(DBG_INFO, F("Turn off inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// // Restart -// case Restart: -// iv->devControlCmd = Restart; -// DPRINTLN(DBG_INFO, F("Restart inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// // Reactive Power Control -// case ReactivePowerContr: -// iv->devControlCmd = ReactivePowerContr; -// if (true) { // if (std::stoi((char*)payload) > 0) error handling powerlimit needed? -// iv->devControlCmd = ReactivePowerContr; -// iv->powerLimit[0] = std::stoi(std::string((char *)payload, (unsigned int)length)); -// iv->powerLimit[1] = 0x0000; // if reactivepower limit is set via external interface --> set it temporay -// DPRINTLN(DBG_DEBUG, F("Reactivepower limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W")); -// iv->devControlRequest = true; -// } -// break; -// -// // Set Power Factor -// case PFSet: -// // iv->devControlCmd = PFSet; -// // uint16_t power_factor = std::stoi(strtok(NULL, "/")); -// DPRINTLN(DBG_INFO, F("Set Power Factor not implemented for inverter ") + String(iv->id)); -// break; -// -// // CleanState lock & alarm -// case CleanState_LockAndAlarm: -// iv->devControlCmd = CleanState_LockAndAlarm; -// DPRINTLN(DBG_INFO, F("CleanState lock & alarm for inverter ") + String(iv->id)); -// iv->devControlRequest = true; -// break; -// -// default: -// DPRINTLN(DBG_INFO, "Not implemented"); -// break; -// } -// } -// } -// } -// break; -// } -// token = strtok(NULL, "/"); -// } -// DPRINTLN(DBG_INFO, F("app::cbMqtt finished")); -// } - espMqttClient mClient; cfgMqtt_t *mCfgMqtt; + #if defined(ESP8266) WiFiEventHandler mHWifiCon, mHWifiDiscon; + #endif uint32_t *mSunrise, *mSunset; HMSYSTEM *mSys; @@ -509,12 +443,12 @@ class PubMqtt { uint32_t mRxCnt, mTxCnt; std::queue mSendList; bool mEnReconnect; + subscriptionCb mSubscriptionCb; // last will topic and payload must be available trough lifetime of 'espMqttClient' char mLwtTopic[MQTT_TOPIC_LEN+7]; const char* mLwtOnline = "online"; const char* mLwtOffline = "offline"; - const char *mDevName, *mVersion; }; diff --git a/src/web/html/index.html b/src/web/html/index.html index c2d25038..96c92feb 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -97,7 +97,7 @@ var date = new Date(); var obj = new Object(); obj.cmd = "set_time"; - obj.ts = parseInt(date.getTime() / 1000); + obj.val = parseInt(date.getTime() / 1000); getAjax("/api/setup", apiCb, "POST", JSON.stringify(obj)); } diff --git a/src/web/html/serial.html b/src/web/html/serial.html index b36d2350..c7e3dd8b 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -45,15 +45,15 @@


- + - - - - - - + + + +
@@ -152,7 +152,6 @@ } - // only for test function ctrlCb(obj) { var e = document.getElementById("result"); if(obj["success"]) @@ -169,44 +168,36 @@ const wrapper = document.getElementById('power'); wrapper.addEventListener('click', (event) => { - var power = event.target.value; var obj = new Object(); + obj.id = get_selected_iv(); + obj.cmd = "power"; - switch (power) - { + switch (event.target.value) { + default: case "Turn On": - obj.cmd = 0; + obj.val = 1; break; case "Turn Off": - obj.cmd = 1; + obj.val = 0; break; - default: - obj.cmd = 2; } - obj.inverter = get_selected_iv(); - obj.tx_request = 81; getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); }); document.getElementById("sendpwrlim").addEventListener("click", function() { var val = parseInt(document.getElementsByName('pwrlimval')[0].value); - var ctrl = parseInt(document.getElementsByName('pwrlimcntrl')[0].value); - - if((ctrl == 1 || ctrl == 257) && val < 2) val = 2; + var cmd = document.getElementsByName('pwrlimctrl')[0].value; - if(isNaN(val) || isNaN(ctrl)) - { - var tmp = (isNaN(val)) ? "Value" : "Unit"; - document.getElementById("result").textContent = tmp + " is missing"; + if(isNaN(val)) { + document.getElementById("result").textContent = "value is missing"; return; } var obj = new Object(); - obj.inverter = get_selected_iv(); - obj.cmd = 11; - obj.tx_request = 81; - obj.payload = [val, ctrl]; + obj.id = get_selected_iv(); + obj.cmd = cmd; + obj.val = val; getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); }); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index c86b7b4b..0a586849 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -50,7 +50,7 @@
@@ -226,7 +226,7 @@ var date = new Date(); var obj = new Object(); obj.cmd = "set_time"; - obj.ts = parseInt(date.getTime() / 1000); + obj.val = parseInt(date.getTime() / 1000); getAjax("/api/setup", apiCbNtp, "POST", JSON.stringify(obj)); } @@ -234,7 +234,7 @@ var obj = new Object(); obj.cmd = "scan_wifi"; getAjax("/api/setup", apiCbWifi, "POST", JSON.stringify(obj)); - setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 7000); + setTimeout(function() {getAjax('/api/setup/networks', listNetworks)}, 5000); } function syncTime() { diff --git a/src/web/web.cpp b/src/web/web.cpp index 3db382ef..d302eee3 100644 --- a/src/web/web.cpp +++ b/src/web/web.cpp @@ -648,9 +648,12 @@ void web::serialCb(String msg) { mSerialBufFill = 0; mEvts->send("webSerial, buffer overflow!", "serial", millis()); } - } +//----------------------------------------------------------------------------- +void web::apiCtrlRequest(JsonObject obj) { + mApi->ctrlRequest(obj); +} //----------------------------------------------------------------------------- #ifdef ENABLE_JSON_EP diff --git a/src/web/web.h b/src/web/web.h index 2c74faf5..849f1caa 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -40,6 +40,8 @@ class web { void serialCb(String msg); + void apiCtrlRequest(JsonObject obj); + private: void onConnect(AsyncEventSourceClient *client); diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp index e71aa511..82a9efda 100644 --- a/src/web/webApi.cpp +++ b/src/web/webApi.cpp @@ -27,13 +27,26 @@ void webApi::setup(void) { mSrv->on("/api", HTTP_POST, std::bind(&webApi::onApiPost, this, std::placeholders::_1)).onBody( std::bind(&webApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - mSrv->on("/get_setup", HTTP_GET, std::bind(&webApi::onDwnldSetup, this, std::placeholders::_1)); + mSrv->on("/get_setup", HTTP_GET, std::bind(&webApi::onDwnldSetup, this, std::placeholders::_1)); } //----------------------------------------------------------------------------- void webApi::loop(void) { } +//----------------------------------------------------------------------------- +void webApi::ctrlRequest(JsonObject obj) { + /*char out[128]; + serializeJson(obj, out, 128); + DPRINTLN(DBG_INFO, "webApi: " + String(out));*/ + DynamicJsonDocument json(128); + JsonObject dummy = json.to(); + if(obj[F("path")] == "ctrl") + setCtrl(obj, dummy); + else if(obj[F("path")] == "setup") + setSetup(obj, dummy); +} + //----------------------------------------------------------------------------- void webApi::onApi(AsyncWebServerRequest *request) { @@ -333,7 +346,7 @@ void webApi::getStaticIp(JsonObject obj) { void webApi::getMenu(JsonObject obj) { obj["name"][0] = "Live"; obj["link"][0] = "/live"; - obj["name"][1] = "Serial Console"; + obj["name"][1] = "Serial / Control"; obj["link"][1] = "/serial"; obj["name"][2] = "Settings"; obj["link"][2] = "/setup"; @@ -346,10 +359,14 @@ void webApi::getMenu(JsonObject obj) { obj["link"][6] = "/update"; obj["name"][7] = "System"; obj["link"][7] = "/system"; + obj["name"][8] = "-"; + obj["name"][9] = "Documentation"; + obj["link"][9] = "https://ahoydtu.de"; + obj["trgt"][9] = "_blank"; if(strlen(mConfig->sys.adminPwd) > 0) { - obj["name"][8] = "-"; - obj["name"][9] = "Logout"; - obj["link"][9] = "/logout"; + obj["name"][10] = "-"; + obj["name"][11] = "Logout"; + obj["link"][11] = "/logout"; } } @@ -494,72 +511,50 @@ void webApi::getRecord(JsonObject obj, record_t<> *rec) { //----------------------------------------------------------------------------- bool webApi::setCtrl(JsonObject jsonIn, JsonObject jsonOut) { - uint8_t cmd = jsonIn[F("cmd")]; - - // Todo: num is the inverter number 0-3. For better display in DPRINTLN - uint8_t num = jsonIn[F("inverter")]; - uint8_t tx_request = jsonIn[F("tx_request")]; - - if(TX_REQ_DEVCONTROL == tx_request) - { - DPRINTLN(DBG_INFO, F("devcontrol [") + String(num) + F("], cmd: 0x") + String(cmd, HEX)); - - Inverter<> *iv = getInverter(jsonIn, jsonOut); - JsonArray payload = jsonIn[F("payload")].as(); - - if(NULL != iv) - { - switch (cmd) - { - case TurnOn: - iv->devControlCmd = TurnOn; - iv->devControlRequest = true; - break; - case TurnOff: - iv->devControlCmd = TurnOff; - iv->devControlRequest = true; - break; - case CleanState_LockAndAlarm: - iv->devControlCmd = CleanState_LockAndAlarm; - iv->devControlRequest = true; - break; - case Restart: - iv->devControlCmd = Restart; - iv->devControlRequest = true; - break; - case ActivePowerContr: - iv->devControlCmd = ActivePowerContr; - iv->devControlRequest = true; - iv->powerLimit[0] = payload[0]; - iv->powerLimit[1] = payload[1]; - break; - default: - jsonOut["error"] = "unknown 'cmd' = " + String(cmd); - return false; - } - } else { - return false; - } + Inverter<> *iv = mApp->mSys->getInverterByPos(jsonIn[F("id")]); + if(NULL == iv) { + jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); + return false; + } + + if(F("power") == jsonIn[F("cmd")]) { + iv->devControlCmd = (jsonIn[F("val")] == 1) ? TurnOn : TurnOff; + iv->devControlRequest = true; + } else if(F("restart") == jsonIn[F("restart")]) { + iv->devControlCmd = Restart; + iv->devControlRequest = true; + } + else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { + iv->powerLimit[0] = jsonIn["val"]; + if(F("limit_persistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativPersistent; + else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutPersistent; + else if(F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativNonPersistent; + else if(F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutNonPersistent; + iv->devControlCmd = ActivePowerContr; + iv->devControlRequest = true; } else { - jsonOut[F("error")] = F("unknown 'tx_request'"); + jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; return false; } return true; } - //----------------------------------------------------------------------------- bool webApi::setSetup(JsonObject jsonIn, JsonObject jsonOut) { if(F("scan_wifi") == jsonIn[F("cmd")]) mApp->scanAvailNetworks(); else if(F("set_time") == jsonIn[F("cmd")]) - mApp->setTimestamp(jsonIn[F("ts")]); + mApp->setTimestamp(jsonIn[F("val")]); else if(F("sync_ntp") == jsonIn[F("cmd")]) mApp->setTimestamp(0); // 0: update ntp flag else if(F("serial_utc_offset") == jsonIn[F("cmd")]) - mTimezoneOffset = jsonIn[F("ts")]; + mTimezoneOffset = jsonIn[F("val")]; else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->mFlagSendDiscoveryConfig = true; // for homeassistant else { @@ -569,13 +564,3 @@ bool webApi::setSetup(JsonObject jsonIn, JsonObject jsonOut) { return true; } - - -//----------------------------------------------------------------------------- -Inverter<> *webApi::getInverter(JsonObject jsonIn, JsonObject jsonOut) { - uint8_t id = jsonIn[F("inverter")]; - Inverter<> *iv = mApp->mSys->getInverterByPos(id); - if(NULL == iv) - jsonOut[F("error")] = F("inverter index to high: ") + String(id); - return iv; -} diff --git a/src/web/webApi.h b/src/web/webApi.h index 873080d7..de37d9ba 100644 --- a/src/web/webApi.h +++ b/src/web/webApi.h @@ -25,6 +25,8 @@ class webApi { return mTimezoneOffset; } + void ctrlRequest(JsonObject obj); + private: void onApi(AsyncWebServerRequest *request); void onApiPost(AsyncWebServerRequest *request); @@ -57,8 +59,6 @@ class webApi { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut); bool setSetup(JsonObject jsonIn, JsonObject jsonOut); - Inverter<> *getInverter(JsonObject jsonIn, JsonObject jsonOut); - double round3(double value) { return (int)(value * 1000 + 0.5) / 1000.0; } From 1c7746b73ff66dce5fe2e2483175fc2e1254699e Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 24 Nov 2022 00:00:38 +0100 Subject: [PATCH 05/12] User_Manual.md format --- User_Manual.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/User_Manual.md b/User_Manual.md index 8be2d62c..4f26a150 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -76,13 +76,20 @@ Also an absolute active power limit below approx. 30 Watt seems to be not meanfu The AhoyDTU subscribes on three topics `/ctrl/#`, `/setup` and `/status`. 👆 `` can be set on setup page, default is `inverter`. + 👆 `` is the number of the specific inverter in the setup page. ### Inverter Power (On / Off) -`/ctrl/power/` with payload `1` for `ON` and `0` for `OFF` +```mqtt +/ctrl/power/ +``` +with payload `1` for `ON` and `0` for `OFF` + Example: +```mqtt `inverter/ctrl/power/0` `1` +``` ### Inverter restart From f4c3aea9648f4c4014c34578152071746bfd1822 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 24 Nov 2022 00:04:07 +0100 Subject: [PATCH 06/12] update User_Manual.md --- User_Manual.md | 58 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/User_Manual.md b/User_Manual.md index 4f26a150..3556c1d1 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -84,48 +84,66 @@ The AhoyDTU subscribes on three topics `/ctrl/#`, `/setup` and `/ctrl/power/ ``` -with payload `1` for `ON` and `0` for `OFF` +with payload `1` = `ON` and `0` = `OFF` Example: ```mqtt -`inverter/ctrl/power/0` `1` +inverter/ctrl/power/0 1 ``` - ### Inverter restart - -`/ctrl/restart/` +```mqtt +/ctrl/restart/ +``` Example: -`inverter/ctrl/restart/0` - +```mqtt +inverter/ctrl/restart/0 +``` ### Power Limit relative persistent [%] -`/ctrl/limit_persistent_relative/` with a payload `[2 .. 100]` -Example: -`inverter/ctrl/limit_persistent_relative/0` `70` +```mqtt +/ctrl/limit_persistent_relative/ +``` +with a payload `[2 .. 100]` +Example: +```mqtt +inverter/ctrl/limit_persistent_relative/0 70 +``` ### Power Limit absolute persistent [Watts] +```mqtt +/ctrl/limit_persistent_relative/ +``` +with a payload `[0 .. 65535]` -`/ctrl/limit_persistent_relative/` with a payload `[0 .. 65535]` Example: -`inverter/ctrl/limit_persistent_relative/0` `600` - +```mqtt +inverter/ctrl/limit_persistent_relative/0 600 +``` ### Power Limit relative non persistent [%] +```mqtt +/ctrl/limit_nonpersistent_relative/ +``` +with a payload `[2 .. 100]` -`/ctrl/limit_nonpersistent_relative/` with a payload `[2 .. 100]` Example: -`inverter/ctrl/limit_nonpersistent_relative/0` `70` - +```mqtt +inverter/ctrl/limit_nonpersistent_relative/0 70 +``` ### Power Limit absolute non persistent [Watts] +```mqtt +/ctrl/limit_nonpersistent_relative/ +``` +with a payload `[0 .. 65535]` -`/ctrl/limit_nonpersistent_relative/` with a payload `[0 .. 65535]` Example: -`inverter/ctrl/limit_nonpersistent_relative/0` `600` - +```mqtt +inverter/ctrl/limit_nonpersistent_relative/0 600 +``` ## Control via REST API @@ -144,7 +162,7 @@ The rest API works with *JSON* POST requests. All the following instructions mus "val": } ``` -The `` should be set to `1` for `ON` and `0` for `OFF` +The `` should be set to `1` = `ON` and `0` = `OFF` ### Inverter restart From 0a5833e6ec299db994218d17d2f0bf2791b2070f Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 28 Nov 2022 17:58:02 +0100 Subject: [PATCH 07/12] improved mqtt removed wrong "inverter type can't be detected!" messages repaired NTP and static IP #459 MQTT status about availability and produce are retain messages now --- src/config/settings.h | 41 +++++++++++-------------------------- src/defines.h | 2 +- src/hm/hmSystem.h | 2 +- src/publisher/pubMqtt.h | 11 ++++++---- src/utils/helper.cpp | 29 ++++++++++++++++++++++++++ src/utils/helper.h | 19 +++++++++++++++++ src/web/html/index.html | 4 ++-- src/web/web.cpp | 45 +++++++++++++++++------------------------ src/web/web.h | 9 --------- src/web/webApi.cpp | 15 +++++++------- src/web/webApi.h | 6 ------ 11 files changed, 96 insertions(+), 87 deletions(-) create mode 100644 src/utils/helper.cpp create mode 100644 src/utils/helper.h diff --git a/src/config/settings.h b/src/config/settings.h index 376dea09..c37c19fa 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -10,6 +10,7 @@ #include #include #include "../utils/dbg.h" +#include "../utils/helper.h" #include "../defines.h" /** @@ -223,25 +224,6 @@ class settings { return saveSettings(); } - String ip2Str(uint8_t ip[]) { - return String(ip[0]) + F(".") - + String(ip[1]) + F(".") - + String(ip[2]) + F(".") - + String(ip[3]); - } - - void ip2Arr(uint8_t ip[], const char *ipStr) { - char *tmp = new char[strlen(ipStr)]; - strncpy(tmp, ipStr, strlen(ipStr)); - char *p = strtok(tmp, "."); - uint8_t i = 0; - while(NULL != p) { - ip[i++] = atoi(p); - p = strtok(NULL, "."); - } - delete[] tmp; - } - private: void loadDefaults(bool wifi = true) { DPRINTLN(DBG_INFO, F("loadDefaults")); @@ -288,25 +270,26 @@ class settings { void jsonWifi(JsonObject obj, bool set = false) { if(set) { + char buf[16]; obj[F("ssid")] = mCfg.sys.stationSsid; obj[F("pwd")] = mCfg.sys.stationPwd; obj[F("dev")] = mCfg.sys.deviceName; obj[F("adm")] = mCfg.sys.adminPwd; - obj[F("ip")] = ip2Str(mCfg.sys.ip.ip); - obj[F("mask")] = ip2Str(mCfg.sys.ip.mask); - obj[F("dns1")] = ip2Str(mCfg.sys.ip.dns1); - obj[F("dns2")] = ip2Str(mCfg.sys.ip.dns2); - obj[F("gtwy")] = ip2Str(mCfg.sys.ip.gateway); + ah::ip2Char(mCfg.sys.ip.ip, buf); obj[F("ip")] = String(buf); + ah::ip2Char(mCfg.sys.ip.mask, buf); obj[F("mask")] = String(buf); + ah::ip2Char(mCfg.sys.ip.dns1, buf); obj[F("dns1")] = String(buf); + ah::ip2Char(mCfg.sys.ip.dns2, buf); obj[F("dns2")] = String(buf); + ah::ip2Char(mCfg.sys.ip.gateway, buf); obj[F("gtwy")] = String(buf); } else { snprintf(mCfg.sys.stationSsid, SSID_LEN, "%s", obj[F("ssid")].as()); snprintf(mCfg.sys.stationPwd, PWD_LEN, "%s", obj[F("pwd")].as()); snprintf(mCfg.sys.deviceName, DEVNAME_LEN, "%s", obj[F("dev")].as()); snprintf(mCfg.sys.adminPwd, PWD_LEN, "%s", obj[F("adm")].as()); - ip2Arr(mCfg.sys.ip.ip, obj[F("ip")]); - ip2Arr(mCfg.sys.ip.mask, obj[F("mask")]); - ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")]); - ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")]); - ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")]); + ah::ip2Arr(mCfg.sys.ip.ip, obj[F("ip")].as()); + ah::ip2Arr(mCfg.sys.ip.mask, obj[F("mask")].as()); + ah::ip2Arr(mCfg.sys.ip.dns1, obj[F("dns1")].as()); + ah::ip2Arr(mCfg.sys.ip.dns2, obj[F("dns2")].as()); + ah::ip2Arr(mCfg.sys.ip.gateway, obj[F("gtwy")].as()); } } diff --git a/src/defines.h b/src/defines.h index 930e4243..62f2cc42 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 43 +#define VERSION_PATCH 44 //------------------------------------- typedef struct { diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index 53b6e85d..5338d1a0 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -70,7 +70,7 @@ class HmSystem { break; } } - else + else if(p->config->serial.u64 != 0ULL) DPRINTLN(DBG_ERROR, F("inverter type can't be detected!")); p->init(); diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 2a42501a..89d8633b 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -111,7 +111,8 @@ class PubMqtt { } void payloadEventListener(uint8_t cmd) { - mSendList.push(cmd); + if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set + mSendList.push(cmd); } void setSubscriptionCb(subscriptionCb cb) { @@ -185,6 +186,7 @@ class PubMqtt { void onWifiConnect(const WiFiEventStationModeGotIP& event) { DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); mClient.connect(); + mEnReconnect = true; } void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) { @@ -197,6 +199,7 @@ class PubMqtt { case SYSTEM_EVENT_STA_GOT_IP: DPRINTLN(DBG_VERBOSE, F("MQTT connecting")); mClient.connect(); + mEnReconnect = true; break; case SYSTEM_EVENT_STA_DISCONNECTED: @@ -360,15 +363,15 @@ class PubMqtt { (status == MQTT_STATUS_AVAIL_NOT_PROD) ? "not " : "", (status == MQTT_STATUS_NOT_AVAIL_NOT_PROD) ? "" : "producing" ); - publish(topic, val); + publish(topic, val, true); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); snprintf(val, 40, "%d", status); - publish(topic, val); + publish(topic, val, true); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name); snprintf(val, 40, "%i", iv->getLastTs(rec) * 1000); - publish(topic, val); + publish(topic, val, true); } // data diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp new file mode 100644 index 00000000..8108ca3e --- /dev/null +++ b/src/utils/helper.cpp @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://github.com/lumpapu/ahoy +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +#include "helper.h" + +namespace ah { + void ip2Arr(uint8_t ip[], const char *ipStr) { + memset(ip, 0, 4); + char *tmp = new char[strlen(ipStr)+1]; + strncpy(tmp, ipStr, strlen(ipStr)+1); + char *p = strtok(tmp, "."); + uint8_t i = 0; + while(NULL != p) { + ip[i++] = atoi(p); + p = strtok(NULL, "."); + } + delete[] tmp; + } + + // note: char *str needs to be at least 16 bytes long + void ip2Char(uint8_t ip[], char *str) { + if(0 == ip[0]) + str[0] = '\0'; + else + snprintf(str, 16, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + } +} diff --git a/src/utils/helper.h b/src/utils/helper.h new file mode 100644 index 00000000..49351cc9 --- /dev/null +++ b/src/utils/helper.h @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://ahoydtu.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +#ifndef __HELPER_H__ +#define __HELPER_H__ + +#include +#include +#include +#include + +namespace ah { + void ip2Arr(uint8_t ip[], const char *ipStr); + void ip2Char(uint8_t ip[], char *str); +} + +#endif /*__HELPER_H__*/ diff --git a/src/web/html/index.html b/src/web/html/index.html index 96c92feb..e0474c08 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -196,9 +196,9 @@ parseStat(obj["statistics"]); parseIv(obj["inverter"]); parseWarnInfo(obj["warnings"], obj["infos"]); - document.getElementById("refresh").innerHTML = obj["refresh_interval"]; + document.getElementById("refresh").innerHTML = 10; if(exeOnce) { - window.setInterval("getAjax('/api/index', parse)", obj["refresh_interval"] * 1000); + window.setInterval("getAjax('/api/index', parse)", 10000); exeOnce = false; } } diff --git a/src/web/web.cpp b/src/web/web.cpp index d302eee3..e7425e76 100644 --- a/src/web/web.cpp +++ b/src/web/web.cpp @@ -11,6 +11,7 @@ #include "web.h" #include "../utils/ahoyTimer.h" +#include "../utils/helper.h" #include "html/h/index_html.h" #include "html/h/login_html.h" @@ -343,28 +344,16 @@ void web::showSave(AsyncWebServerRequest *request) { // static ip - if(request->arg("ipAddr") != "") { - request->arg("ipAddr").toCharArray(buf, SSID_LEN); - ip2Arr(mConfig->sys.ip.ip, buf); - if(request->arg("ipMask") != "") { - request->arg("ipMask").toCharArray(buf, SSID_LEN); - ip2Arr(mConfig->sys.ip.mask, buf); - } - if(request->arg("ipDns1") != "") { - request->arg("ipDns1").toCharArray(buf, SSID_LEN); - ip2Arr(mConfig->sys.ip.dns1, buf); - } - if(request->arg("ipDns2") != "") { - request->arg("ipDns2").toCharArray(buf, SSID_LEN); - ip2Arr(mConfig->sys.ip.dns2, buf); - } - if(request->arg("ipGateway") != "") { - request->arg("ipGateway").toCharArray(buf, SSID_LEN); - ip2Arr(mConfig->sys.ip.gateway, buf); - } - } - else - memset(&mConfig->sys.ip.ip, 0, 4); + request->arg("ipAddr").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.ip, buf); + request->arg("ipMask").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.mask, buf); + request->arg("ipDns1").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.dns1, buf); + request->arg("ipDns2").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.dns2, buf); + request->arg("ipGateway").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.gateway, buf); // inverter @@ -437,12 +426,14 @@ void web::showSave(AsyncWebServerRequest *request) { String addr = request->arg("mqttAddr"); addr.trim(); addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); - request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); - if(request->arg("mqttPwd") != "{PWD}") - request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); - request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); - mConfig->mqtt.port = request->arg("mqttPort").toInt(); } + else + mConfig->mqtt.broker[0] = '\0'; + request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); + if(request->arg("mqttPwd") != "{PWD}") + request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); + request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); + mConfig->mqtt.port = request->arg("mqttPort").toInt(); // serial console if(request->arg("serIntvl") != "") { diff --git a/src/web/web.h b/src/web/web.h index 849f1caa..7560e039 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -64,15 +64,6 @@ class web { void onSerial(AsyncWebServerRequest *request); void onSystem(AsyncWebServerRequest *request); - void ip2Arr(uint8_t ip[], char *ipStr) { - char *p = strtok(ipStr, "."); - uint8_t i = 0; - while(NULL != p) { - ip[i++] = atoi(p); - p = strtok(NULL, "."); - } - } - #ifdef ENABLE_JSON_EP void showJson(void); #endif diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp index 82a9efda..4987e147 100644 --- a/src/web/webApi.cpp +++ b/src/web/webApi.cpp @@ -332,13 +332,12 @@ void webApi::getSerial(JsonObject obj) { //----------------------------------------------------------------------------- void webApi::getStaticIp(JsonObject obj) { - if(mConfig->sys.ip.ip[0] != 0) { - obj[F("ip")] = ip2String(mConfig->sys.ip.ip); - obj[F("mask")] = ip2String(mConfig->sys.ip.mask); - obj[F("dns1")] = ip2String(mConfig->sys.ip.dns1); - obj[F("dns2")] = ip2String(mConfig->sys.ip.dns2); - obj[F("gateway")] = ip2String(mConfig->sys.ip.gateway); - } + char buf[16]; + ah::ip2Char(mConfig->sys.ip.ip, buf); obj[F("ip")] = String(buf); + ah::ip2Char(mConfig->sys.ip.mask, buf); obj[F("mask")] = String(buf); + ah::ip2Char(mConfig->sys.ip.dns1, buf); obj[F("dns1")] = String(buf); + ah::ip2Char(mConfig->sys.ip.dns2, buf); obj[F("dns2")] = String(buf); + ah::ip2Char(mConfig->sys.ip.gateway, buf); obj[F("gateway")] = String(buf); } @@ -397,7 +396,7 @@ void webApi::getIndex(JsonObject obj) { JsonArray warn = obj.createNestedArray(F("warnings")); if(!mApp->mSys->Radio.isChipConnected()) warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); - if(!mApp->mqttIsConnected()) + if((!mApp->mqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) warn.add(F("MQTT is not connected")); JsonArray info = obj.createNestedArray(F("infos")); diff --git a/src/web/webApi.h b/src/web/webApi.h index de37d9ba..8b539e05 100644 --- a/src/web/webApi.h +++ b/src/web/webApi.h @@ -63,12 +63,6 @@ class webApi { return (int)(value * 1000 + 0.5) / 1000.0; } - String ip2String(uint8_t ip[]) { - char str[16]; - snprintf(str, 16, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return String(str); - } - AsyncWebServer *mSrv; app *mApp; From f6f05525e30e11c7c1f323aeb7bed64201dbc9fc Mon Sep 17 00:00:00 2001 From: Sven Naumann <3747263+sVnsation@users.noreply.github.com> Date: Wed, 30 Nov 2022 21:03:54 +0100 Subject: [PATCH 08/12] compress firmware bin with gzip for ota allows smaller update files for ota update --- scripts/getVersion.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index 2346966d..21406418 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -1,4 +1,6 @@ import os +import shutil +import gzip from datetime import date def genOtaBin(path): @@ -24,6 +26,11 @@ def genOtaBin(path): with open(path + "ota.bin", "wb") as f: f.write(bytearray(arr)) +# write gzip firmware file +def gzip_bin(bin_file, gzip_file): + with open(bin_file,"rb") as fp: + with gzip.open(gzip_file, "wb", compresslevel = 9) as f: + shutil.copyfileobj(fp, f) def readVersion(path, infile): f = open(path + infile, "r") @@ -48,16 +55,19 @@ def readVersion(path, infile): src = path + ".pio/build/esp8266-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) + gzip_bin(dst, dst + ".gz") versionout = version[:-1] + "_esp8266_1m_" + sha + ".bin" src = path + ".pio/build/esp8285-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) + gzip_bin(dst, dst + ".gz") versionout = version[:-1] + "_esp32_" + sha + ".bin" src = path + ".pio/build/esp32-wroom32-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) + gzip_bin(dst, dst + ".gz") # other ESP32 bin files src = path + ".pio/build/esp32-wroom32-release/" From 07d816b29d18f94e4c76d03c965be5eeb1bcb6bc Mon Sep 17 00:00:00 2001 From: Sven Naumann <3747263+sVnsation@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:15:28 +0000 Subject: [PATCH 09/12] move round3 function to helper --- src/utils/helper.cpp | 4 ++++ src/utils/helper.h | 1 + src/web/webApi.cpp | 6 +++--- src/web/webApi.h | 4 ---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index 8108ca3e..9bae7bed 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -26,4 +26,8 @@ namespace ah { else snprintf(str, 16, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); } + + double round3(double value) { + return (int)(value * 1000 + 0.5) / 1000.0; + } } diff --git a/src/utils/helper.h b/src/utils/helper.h index 49351cc9..1898ba7e 100644 --- a/src/utils/helper.h +++ b/src/utils/helper.h @@ -14,6 +14,7 @@ namespace ah { void ip2Arr(uint8_t ip[], const char *ipStr); void ip2Char(uint8_t ip[], char *str); + double round3(double value); } #endif /*__HELPER_H__*/ diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp index 4987e147..b11637fa 100644 --- a/src/web/webApi.cpp +++ b/src/web/webApi.cpp @@ -448,7 +448,7 @@ void webApi::getLive(JsonObject obj) { JsonObject obj2 = invArr.createNestedObject(); obj2[F("name")] = String(iv->config->name); obj2[F("channels")] = iv->channels; - obj2[F("power_limit_read")] = round3(iv->actPowerLimit); + obj2[F("power_limit_read")] = ah::round3(iv->actPowerLimit); obj2[F("last_alarm")] = String(iv->lastAlarmMsg); obj2[F("ts_last_success")] = rec->ts; @@ -457,7 +457,7 @@ void webApi::getLive(JsonObject obj) { obj2[F("ch_names")][0] = "AC"; for (uint8_t fld = 0; fld < sizeof(list); fld++) { pos = (iv->getPosByChFld(CH0, list[fld], rec)); - ch0[fld] = (0xff != pos) ? round3(iv->getValue(pos, rec)) : 0.0; + ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; } @@ -474,7 +474,7 @@ void webApi::getLive(JsonObject obj) { case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; } - cur[k] = (0xff != pos) ? round3(iv->getValue(pos, rec)) : 0.0; + cur[k] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; if(1 == j) { obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; diff --git a/src/web/webApi.h b/src/web/webApi.h index 8b539e05..04e16635 100644 --- a/src/web/webApi.h +++ b/src/web/webApi.h @@ -59,10 +59,6 @@ class webApi { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut); bool setSetup(JsonObject jsonIn, JsonObject jsonOut); - double round3(double value) { - return (int)(value * 1000 + 0.5) / 1000.0; - } - AsyncWebServer *mSrv; app *mApp; From 08e417ee85b7c6a77ffc728545e1d0fc6fc00170 Mon Sep 17 00:00:00 2001 From: Sven Naumann <3747263+sVnsation@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:34:38 +0000 Subject: [PATCH 10/12] mqtt avoid trailing zeroes --- src/publisher/pubMqtt.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 89d8633b..91e6545e 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -378,7 +378,7 @@ class PubMqtt { if(iv->isAvailable(*mUtcTimestamp, rec)) { for (uint8_t i = 0; i < rec->length; i++) { snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); - snprintf(val, 40, "%.3f", iv->getValue(i, rec)); + snprintf(val, sizeof(val), "%g", ah::round3(iv->getValue(i, rec))); publish(topic, val); // calculate total values for RealTimeRunData_Debug @@ -427,7 +427,7 @@ class PubMqtt { break; } snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); - snprintf(val, 40, "%.3f", total[i]); + snprintf(val, sizeof(val), "%g", ah::round3(total[i])); publish(topic, val); } } From 28594aff4ff98b5953e9640ec7fda2a05314d55b Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 1 Dec 2022 14:56:18 +0100 Subject: [PATCH 11/12] partial fix #468 MQTT fix #466 calculation of sunrise and sunset fix #463 Webserial Timestamp fix --- src/defines.h | 2 +- src/publisher/pubMqtt.h | 26 ++++++++++++++++++-------- src/utils/sun.h | 4 ++-- src/web/html/serial.html | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/defines.h b/src/defines.h index 62f2cc42..68b9c80a 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 44 +#define VERSION_PATCH 45 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 89d8633b..4862d77e 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -47,7 +47,7 @@ class PubMqtt { mSunrise = sunrise; mSunset = sunset; - snprintf(mLwtTopic, MQTT_TOPIC_LEN + 7, "%s/status", mCfgMqtt->topic); + snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); #if defined(ESP8266) mHWifiCon = WiFi.onStationModeGotIP(std::bind(&PubMqtt::onWifiConnect, this, std::placeholders::_1)); @@ -358,10 +358,10 @@ class PubMqtt { } snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available_text", iv->config->name); snprintf(val, 40, "%s%s%s%s", - (status == MQTT_STATUS_NOT_AVAIL_NOT_PROD) ? "not yet " : "", + (status == MQTT_STATUS_NOT_AVAIL_NOT_PROD) ? "not " : "", "available and ", (status == MQTT_STATUS_AVAIL_NOT_PROD) ? "not " : "", - (status == MQTT_STATUS_NOT_AVAIL_NOT_PROD) ? "" : "producing" + "producing" ); publish(topic, val, true); @@ -377,9 +377,19 @@ class PubMqtt { // data if(iv->isAvailable(*mUtcTimestamp, rec)) { for (uint8_t i = 0; i < rec->length; i++) { + bool retained = false; + if (mSendList.front() == RealTimeRunData_Debug) { + switch (rec->assign[i].fieldId) { + case FLD_YT: + case FLD_YD: + retained = true; + break; + } + } + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, fields[rec->assign[i].fieldId]); snprintf(val, 40, "%.3f", iv->getValue(i, rec)); - publish(topic, val); + publish(topic, val, retained); // calculate total values for RealTimeRunData_Debug if (mSendList.front() == RealTimeRunData_Debug) { @@ -428,7 +438,7 @@ class PubMqtt { } snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]); snprintf(val, 40, "%.3f", total[i]); - publish(topic, val); + publish(topic, val, true); } } } @@ -449,9 +459,9 @@ class PubMqtt { subscriptionCb mSubscriptionCb; // last will topic and payload must be available trough lifetime of 'espMqttClient' - char mLwtTopic[MQTT_TOPIC_LEN+7]; - const char* mLwtOnline = "online"; - const char* mLwtOffline = "offline"; + char mLwtTopic[MQTT_TOPIC_LEN+5]; + const char* mLwtOnline = "connected"; + const char* mLwtOffline = "not connected"; const char *mDevName, *mVersion; }; diff --git a/src/utils/sun.h b/src/utils/sun.h index 476ad0ff..1036f9ec 100644 --- a/src/utils/sun.h +++ b/src/utils/sun.h @@ -11,7 +11,7 @@ namespace ah { // Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth // Julian day since 1.1.2000 12:00 + correction 69.12s - double n_JulianDay = (utcTs + offset) / 86400 - 10957.0 + 0.0008; + double n_JulianDay = (utcTs + offset) / 86400 - 10957.0; // Mean solar time double J = n_JulianDay - lon / 360; // Solar mean anomaly @@ -25,7 +25,7 @@ namespace ah { // Declination of the sun double delta = ASIN(SIN(lambda) * SIN(23.44)); // Hour angle - double omega = ACOS(SIN(-0.83) - SIN(lat) * SIN(delta) / COS(lat) * COS(delta)); + double omega = ACOS((SIN(-0.83) - SIN(lat) * SIN(delta) / COS(lat) * COS(delta))); // Calculate sunrise and sunset double Jrise = Jtransit - omega / 360; double Jset = Jtransit + omega / 360; diff --git a/src/web/html/serial.html b/src/web/html/serial.html index c7e3dd8b..ccbd1156 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -120,7 +120,7 @@ // set time offset for serial console var obj = new Object(); obj.cmd = "serial_utc_offset"; - obj.ts = new Date().getTimezoneOffset() * -60; + obj.val = new Date().getTimezoneOffset() * -60; getAjax("/api/setup", null, "POST", JSON.stringify(obj)); } From 614af26cd9e768acbffa2cda46d4fba68003225d Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 2 Dec 2022 07:56:48 +0100 Subject: [PATCH 12/12] fix #466 --- src/defines.h | 2 +- src/utils/sun.h | 2 +- src/web/html/setup.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/defines.h b/src/defines.h index 68b9c80a..09665137 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 45 +#define VERSION_PATCH 46 //------------------------------------- typedef struct { diff --git a/src/utils/sun.h b/src/utils/sun.h index 1036f9ec..c6d078d4 100644 --- a/src/utils/sun.h +++ b/src/utils/sun.h @@ -25,7 +25,7 @@ namespace ah { // Declination of the sun double delta = ASIN(SIN(lambda) * SIN(23.44)); // Hour angle - double omega = ACOS((SIN(-0.83) - SIN(lat) * SIN(delta) / COS(lat) * COS(delta))); + double omega = ACOS((SIN(-0.83) - SIN(lat) * SIN(delta)) / (COS(lat) * COS(delta))); // Calculate sunrise and sunset double Jrise = Jtransit - omega / 360; double Jset = Jtransit + omega / 360; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 0a586849..e5bf96f3 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -163,7 +163,7 @@
- +

Download your settings (JSON file) (only saved values)