From 22715a1a4a94698dacbdb1509c4e69b59fd38d6c Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 21 Nov 2022 23:02:47 +0100 Subject: [PATCH 01/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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/20] 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) From 7c6ab6792bf7cc10972d66bc3ca6b8281985da2e Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 3 Dec 2022 02:06:20 +0100 Subject: [PATCH 13/20] fix #472 refactored ahoyWifi class completely -> now Ahoy opens an AP during boot. This will be closed once a station WiFi connection is established improved NTP after boot, will be synced immediately after successful WiFi connection --- src/app.cpp | 16 ++- src/app.h | 22 +--- src/config/settings.h | 2 +- src/hm/hmRadio.h | 9 +- src/utils/helper.cpp | 9 ++ src/utils/helper.h | 3 + src/web/web.cpp | 4 +- src/wifi/ahoywifi.cpp | 233 +++++++++++++++--------------------------- src/wifi/ahoywifi.h | 41 ++++---- 9 files changed, 132 insertions(+), 207 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index e7b86283..21bee17e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -42,8 +42,7 @@ void app::setup(uint32_t timeout) { mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp, &mSunrise, &mSunset); #endif - mWifi = new ahoywifi(mConfig); - mWifi->setup(timeout, mSettings.getValid()); + mWifi.setup(mConfig, &mUtcTimestamp); mPayload.setup(mSys); mPayload.enableSerialDebug(mConfig->serial.debug); @@ -72,6 +71,10 @@ void app::loop(void) { ah::Scheduler::loop(); + #if !defined(AP_ONLY) + mWifi.loop(); + #endif + mWeb->loop(); if (mFlagSendDiscoveryConfig) { @@ -198,19 +201,14 @@ void app::handleIntr(void) { mSys->Radio.handleIntr(); } -//----------------------------------------------------------------------------- -bool app::getWifiApActive(void) { - return mWifi->getApActive(); -} - //----------------------------------------------------------------------------- void app::scanAvailNetworks(void) { - mWifi->scanAvailNetworks(); + mWifi.scanAvailNetworks(); } //----------------------------------------------------------------------------- void app::getAvailNetworks(JsonObject obj) { - mWifi->getAvailNetworks(obj); + mWifi.getAvailNetworks(obj); } diff --git a/src/app.h b/src/app.h index 038be0c4..e2498815 100644 --- a/src/app.h +++ b/src/app.h @@ -84,15 +84,6 @@ class app : public ah::Scheduler { return ret; } - String getDateTimeStr(time_t t) { - char str[20]; - if(0 == t) - sprintf(str, "n/a"); - else - sprintf(str, "%04d-%02d-%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); - return String(str); - } - String getTimeStr(uint32_t offset = 0) { char str[10]; if(0 == mUtcTimestamp) @@ -155,25 +146,20 @@ class app : public ah::Scheduler { ESP.restart(); } - - if (mUpdateNtp) { mUpdateNtp = false; - mUtcTimestamp = mWifi->getNtpTime(); - DPRINTLN(DBG_INFO, F("[NTP]: ") + getDateTimeStr(mUtcTimestamp) + F(" UTC")); + mWifi.getNtpTime(); } } void minuteTick(void) { if(0 == mUtcTimestamp) { - if(!mWifi->getApActive()) - mUpdateNtp = true; + mUpdateNtp = true; } } void ntpUpdateTick(void) { - if (!mWifi->getApActive()) - mUpdateNtp = true; + mUpdateNtp = true; } void stats(void) { @@ -203,7 +189,7 @@ class app : public ah::Scheduler { bool mShowRebootRequest; - ahoywifi *mWifi; + ahoywifi mWifi; web *mWeb; PayloadType mPayload; PubSerialType mPubSerial; diff --git a/src/config/settings.h b/src/config/settings.h index c37c19fa..1895cd12 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -226,7 +226,7 @@ class settings { private: void loadDefaults(bool wifi = true) { - DPRINTLN(DBG_INFO, F("loadDefaults")); + DPRINTLN(DBG_VERBOSE, F("loadDefaults")); if(wifi) { snprintf(mCfg.sys.stationSsid, SSID_LEN, FB_WIFI_SSID); snprintf(mCfg.sys.stationPwd, PWD_LEN, FB_WIFI_PWD); diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 4e1b749c..efedfab1 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -139,14 +139,15 @@ class HmRadio { mNrf24.setPALevel(ampPwr & 0x03); mNrf24.startListening(); - DPRINTLN(DBG_INFO, F("Radio Config:")); - mNrf24.printPrettyDetails(); mTxCh = setDefaultChannels(); - if(!mNrf24.isChipConnected()) { - DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); + if(mNrf24.isChipConnected()) { + DPRINTLN(DBG_INFO, F("Radio Config:")); + mNrf24.printPrettyDetails(); } + else + DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); } void loop(void) { diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index 9bae7bed..5a9eaf22 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -30,4 +30,13 @@ namespace ah { double round3(double value) { return (int)(value * 1000 + 0.5) / 1000.0; } + + String getDateTimeStr(time_t t) { + char str[20]; + if(0 == t) + sprintf(str, "n/a"); + else + sprintf(str, "%04d-%02d-%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); + return String(str); + } } diff --git a/src/utils/helper.h b/src/utils/helper.h index 1898ba7e..1372fec8 100644 --- a/src/utils/helper.h +++ b/src/utils/helper.h @@ -6,15 +6,18 @@ #ifndef __HELPER_H__ #define __HELPER_H__ +#include #include #include #include #include +#include namespace ah { void ip2Arr(uint8_t ip[], const char *ipStr); void ip2Char(uint8_t ip[], char *str); double round3(double value); + String getDateTimeStr(time_t t); } #endif /*__HELPER_H__*/ diff --git a/src/web/web.cpp b/src/web/web.cpp index e7425e76..0ea3c307 100644 --- a/src/web/web.cpp +++ b/src/web/web.cpp @@ -664,10 +664,10 @@ void web::showJson(void) { snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i), iv->getUnit(i)); modJson += String(topic) + ": " + String(val) + F(",\n"); } - modJson += F("\t\"last_msg\": \"") + mMain->getDateTimeStr(iv->ts) + F("\"\n\t},\n"); + modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(iv->ts) + F("\"\n\t},\n"); } } - modJson += F("\"json_ts\": \"") + String(mMain->getDateTimeStr(mMain->mTimestamp)) + F("\"\n}\n"); + modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mMain->mTimestamp)) + F("\"\n}\n"); mWeb->send(200, F("application/json"), modJson); } diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 9b95d25b..b6fbce73 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -16,144 +16,84 @@ //----------------------------------------------------------------------------- -ahoywifi::ahoywifi(settings_t *config) { - mConfig = config; - - mDns = new DNSServer(); - mUdp = new WiFiUDP(); - - mWifiStationTimeout = 10; - wifiWasEstablished = false; - mNextTryTs = 0; - mApLastTick = 0; - mApActive = false; +ahoywifi::ahoywifi() { + mCnt = 0; + mConnected = false; + mInitNtp = true; } //----------------------------------------------------------------------------- -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; - #else - mStationWifiIsDef = (strncmp(mConfig->sys.stationSsid, FB_WIFI_SSID, 14) == 0); +void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp) { + char ipSta[16]; + mConfig = config; + mUtcTimestamp = utcTimestamp; + + #if !defined(FB_WIFI_OVERRIDDEN) + if(strncmp(mConfig->sys.stationSsid, FB_WIFI_SSID, 14) != 0) + setupAp(); #endif - mWifiStationTimeout = timeout; - #ifndef AP_ONLY - if(false == mApActive) - mApActive = (mStationWifiIsDef) ? true : setupStation(mWifiStationTimeout); + #if !defined(AP_ONLY) + setupStation(); + ah::ip2Char(mConfig->sys.ip.ip, ipSta); #endif - if(!settingValid) { - DPRINTLN(DBG_WARN, F("your settings are not valid! check [IP]/setup")); - mApActive = true; - mApLastTick = millis(); - mNextTryTs = (millis() + (WIFI_AP_ACTIVE_TIME * 1000)); - setupAp(WIFI_AP_SSID, WIFI_AP_PWD); - } - else { - DPRINTLN(DBG_INFO, F("\n\n----------------------------------------")); - DPRINTLN(DBG_INFO, F("Welcome to AHOY!")); - DPRINT(DBG_INFO, F("\npoint your browser to http://")); - if(mApActive) - DBGPRINTLN(F("192.168.4.1")); - else - DBGPRINTLN(WiFi.localIP().toString()); - DPRINTLN(DBG_INFO, F("to configure your device")); - DPRINTLN(DBG_INFO, F("----------------------------------------\n")); - } + + wifiConnectHandler = WiFi.onStationModeGotIP(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); + wifiDisconnectHandler = WiFi.onStationModeDisconnected(std::bind(&ahoywifi::onDisconnect, this, std::placeholders::_1)); } //----------------------------------------------------------------------------- -bool ahoywifi::loop(void) { - if(mApActive) { - mDns->processNextRequest(); -#ifndef AP_ONLY - if(ah::checkTicker(&mNextTryTs, (WIFI_AP_ACTIVE_TIME * 1000))) { - mApActive = (mStationWifiIsDef) ? true : setupStation(mWifiStationTimeout); - if(mApActive) { - if(strlen(WIFI_AP_PWD) < 8) - DPRINTLN(DBG_ERROR, F("password must be at least 8 characters long")); - mApLastTick = millis(); - mNextTryTs = (millis() + (WIFI_AP_ACTIVE_TIME * 1000)); - setupAp(WIFI_AP_SSID, WIFI_AP_PWD); - } - } - else { - if(millis() - mApLastTick > 10000) { - mApLastTick = millis(); - uint8_t cnt = WiFi.softAPgetStationNum(); - if(cnt > 0) { - DPRINTLN(DBG_INFO, String(cnt) + F(" client connected (no timeout)")); - mNextTryTs = (millis() + (WIFI_AP_ACTIVE_TIME * 1000)); - } - else { - DBGPRINT(F("AP will be closed in ")); - DBGPRINT(String((mNextTryTs - mApLastTick) / 1000)); - DBGPRINTLN(F(" seconds")); - } - } - } -#endif - } - - if((WiFi.status() != WL_CONNECTED) && wifiWasEstablished) { - if(!mApActive) { - DPRINTLN(DBG_INFO, "[WiFi]: Connection Lost"); - mApActive = (mStationWifiIsDef) ? true : setupStation(mWifiStationTimeout); +void ahoywifi::loop() { + #if !defined(AP_ONLY) + if(!mConnected) { + delay(100); + mCnt++; + if((mCnt % 50) == 0) + WiFi.disconnect(); + else if((mCnt % 60) == 0) { + WiFi.reconnect(); + mCnt = 0; } + } else if(mInitNtp) { + getNtpTime(); + mInitNtp = false; } + mCnt = 0; + #endif - return mApActive; } //----------------------------------------------------------------------------- -void ahoywifi::setupAp(const char *ssid, const char *pwd) { - DPRINTLN(DBG_VERBOSE, F("app::setupAp")); +void ahoywifi::setupAp(void) { + DPRINTLN(DBG_VERBOSE, F("wifi::setupAp")); IPAddress apIp(192, 168, 4, 1); - DBGPRINTLN(F("\n---------\nAhoy Info:")); + DBGPRINTLN(F("\n---------\nAhoyDTU Info:")); DBGPRINT(F("Version: ")); DBGPRINTLN(String(VERSION_MAJOR) + F(".") + String(VERSION_MINOR) + F(".") + String(VERSION_PATCH)); DBGPRINT(F("Github Hash: ")); DBGPRINTLN(String(AUTO_GIT_HASH)); DBGPRINT(F("\n---------\nAP MODE\nSSID: ")); - DBGPRINTLN(ssid); + DBGPRINTLN(WIFI_AP_SSID); DBGPRINT(F("PWD: ")); - DBGPRINTLN(pwd); - DBGPRINT(F("\nActive for: ")); - DBGPRINT(String(WIFI_AP_ACTIVE_TIME)); - DBGPRINTLN(F(" seconds")); + DBGPRINTLN(WIFI_AP_PWD); + DBGPRINTLN("IP Address: http://" + apIp.toString()); + DBGPRINTLN(F("---------\n")); - DBGPRINTLN("\nIp Address: " + apIp[0] + apIp[1] + apIp[2] + apIp[3]); - DBGPRINTLN(F("\n---------\n")); - - WiFi.mode(WIFI_AP); + WiFi.mode(WIFI_AP_STA); WiFi.softAPConfig(apIp, apIp, IPAddress(255, 255, 255, 0)); - WiFi.softAP(ssid, pwd); + WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PWD); - mDns->start(53, "*", apIp); + mDns.start(53, "*", apIp); } //----------------------------------------------------------------------------- -bool ahoywifi::setupStation(uint32_t timeout) { - DPRINTLN(DBG_VERBOSE, F("app::setupStation")); - int32_t cnt; - bool startAp = false; - - if(timeout >= 3) - cnt = (timeout - 3) / 2 * 10; - else { - timeout = 1; - cnt = 1; - } - - WiFi.mode(WIFI_STA); +void ahoywifi::setupStation(void) { + DPRINTLN(DBG_VERBOSE, F("wifi::setupStation")); if(mConfig->sys.ip.ip[0] != 0) { IPAddress ip(mConfig->sys.ip.ip); IPAddress mask(mConfig->sys.ip.mask); @@ -167,45 +107,14 @@ bool ahoywifi::setupStation(uint32_t timeout) { if(String(mConfig->sys.deviceName) != "") WiFi.hostname(mConfig->sys.deviceName); - delay(2000); DBGPRINT(F("connect to network '")); DBGPRINT(mConfig->sys.stationSsid); DBGPRINTLN(F("' ...")); - while (WiFi.status() != WL_CONNECTED) { - delay(100); - if(cnt % 40 == 0) - DBGPRINTLN("."); - else - DBGPRINT("."); - - if(timeout > 0) { // limit == 0 -> no limit - if(--cnt <= 0) { - if(WiFi.status() != WL_CONNECTED) { - startAp = true; - WiFi.disconnect(); - } - delay(100); - break; - } - } - } - Serial.println("."); - - if(false == startAp) - wifiWasEstablished = true; - - delay(1000); - return startAp; } //----------------------------------------------------------------------------- -bool ahoywifi::getApActive(void) { - return mApActive; -} - -//----------------------------------------------------------------------------- -time_t ahoywifi::getNtpTime(void) { +void ahoywifi::getNtpTime(void) { //DPRINTLN(DBG_VERBOSE, F("wifi::getNtpTime")); time_t date = 0; IPAddress timeServer; @@ -213,16 +122,16 @@ time_t ahoywifi::getNtpTime(void) { uint8_t retry = 0; WiFi.hostByName(mConfig->ntp.addr, timeServer); - mUdp->begin(mConfig->ntp.port); + mUdp.begin(mConfig->ntp.port); sendNTPpacket(timeServer); while(retry++ < 5) { int wait = 150; while(--wait) { - if(NTP_PACKET_SIZE <= mUdp->parsePacket()) { + if(NTP_PACKET_SIZE <= mUdp.parsePacket()) { uint64_t secsSince1900; - mUdp->read(buf, NTP_PACKET_SIZE); + mUdp.read(buf, NTP_PACKET_SIZE); secsSince1900 = (buf[40] << 24); secsSince1900 |= (buf[41] << 16); secsSince1900 |= (buf[42] << 8); @@ -230,13 +139,14 @@ time_t ahoywifi::getNtpTime(void) { date = secsSince1900 - 2208988800UL; // UTC time break; - } - else + } else delay(10); } } - return date; + *mUtcTimestamp = date; + + DPRINTLN(DBG_INFO, F("[NTP]: ") + ah::getDateTimeStr(*mUtcTimestamp) + F(" UTC")); } @@ -285,19 +195,42 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { buf[14] = 49; buf[15] = 52; - mUdp->beginPacket(address, 123); // NTP request, port 123 - mUdp->write(buf, NTP_PACKET_SIZE); - mUdp->endPacket(); + mUdp.beginPacket(address, 123); // NTP request, port 123 + mUdp.write(buf, NTP_PACKET_SIZE); + mUdp.endPacket(); } //----------------------------------------------------------------------------- -/*void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) { - Serial.println("Connected to Wi-Fi."); +void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) { + if(!mConnected) { + mConnected = true; + DBGPRINTLN(F("\n[WiFi] Connected")); + WiFi.mode(WIFI_STA); + WiFi.begin(); + DBGPRINTLN(F("[WiFi] AP disabled")); + mDns.stop(); + + welcome(WiFi.localIP().toString() + F(" (Station)")); + } } //----------------------------------------------------------------------------- void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) { - Serial.println("Disconnected from Wi-Fi."); -}*/ + if(mConnected) { + mConnected = false; + DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); + } +} + + +//----------------------------------------------------------------------------- +void ahoywifi::welcome(String msg) { + DBGPRINTLN(F("\n\n--------------------------------")); + DBGPRINTLN(F("Welcome to AHOY!")); + DBGPRINT(F("\npoint your browser to http://")); + DBGPRINTLN(msg); + DBGPRINTLN(F("to configure your device")); + DBGPRINTLN(F("--------------------------------\n")); +} diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 32ca5cec..1cda6ab4 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -9,7 +9,6 @@ #include "../utils/dbg.h" #include #include -#include #include #include "ESPAsyncWebServer.h" @@ -19,36 +18,32 @@ class app; class ahoywifi { public: - ahoywifi(settings_t *config); - ~ahoywifi() {} - - void setup(uint32_t timeout, bool settingValid); - bool loop(void); - void setupAp(const char *ssid, const char *pwd); - bool setupStation(uint32_t timeout); - bool getApActive(void); - time_t getNtpTime(void); + ahoywifi(); + + void setup(settings_t *config, uint32_t *utcTimestamp); + void loop(void); + void getNtpTime(void); void scanAvailNetworks(void); void getAvailNetworks(JsonObject obj); private: + void setupAp(void); + void setupStation(void); void sendNTPpacket(IPAddress& address); - //void onConnect(const WiFiEventStationModeGotIP& event); - //void onDisconnect(const WiFiEventStationModeDisconnected& event); + void onConnect(const WiFiEventStationModeGotIP& event); + void onDisconnect(const WiFiEventStationModeDisconnected& event); + void welcome(String msg); settings_t *mConfig; - DNSServer *mDns; - WiFiUDP *mUdp; // for time server - //WiFiEventHandler wifiConnectHandler; - //WiFiEventHandler wifiDisconnectHandler; - - uint32_t mWifiStationTimeout; - uint32_t mNextTryTs; - uint32_t mApLastTick; - bool mApActive; - bool wifiWasEstablished; - bool mStationWifiIsDef; + DNSServer mDns; + WiFiUDP mUdp; // for time server + WiFiEventHandler wifiConnectHandler; + WiFiEventHandler wifiDisconnectHandler; + + bool mConnected, mInitNtp; + uint8_t mCnt; + uint32_t *mUtcTimestamp; }; #endif /*__AHOYWIFI_H__*/ From d11d3134f69de240d3f1e6f63c657dc69d973480 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 3 Dec 2022 02:44:25 +0100 Subject: [PATCH 14/20] fix ESP32 (also tested in hardware) --- src/defines.h | 2 +- src/publisher/pubMqtt.h | 2 +- src/wifi/ahoywifi.cpp | 71 ++++++++++++++++++++++++++++++----------- src/wifi/ahoywifi.h | 6 ++++ 4 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/defines.h b/src/defines.h index 09665137..229d583b 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 46 +#define VERSION_PATCH 47 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 43ef277a..0e3e8417 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -227,7 +227,7 @@ class PubMqtt { } void onDisconnect(espMqttClientTypes::DisconnectReason reason) { - DBGPRINT(F("MQTT disconnected, reason: ")); + DPRINT(DBG_INFO, F("MQTT disconnected, reason: ")); switch (reason) { case espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED: DBGPRINTLN(F("TCP disconnect")); diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index b6fbce73..82370031 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -38,8 +38,12 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp) { ah::ip2Char(mConfig->sys.ip.ip, ipSta); #endif + #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeGotIP(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); wifiDisconnectHandler = WiFi.onStationModeDisconnected(std::bind(&ahoywifi::onDisconnect, this, std::placeholders::_1)); + #else + WiFi.onEvent(std::bind(&ahoywifi::onWiFiEvent, this, std::placeholders::_1)); + #endif } @@ -146,7 +150,7 @@ void ahoywifi::getNtpTime(void) { *mUtcTimestamp = date; - DPRINTLN(DBG_INFO, F("[NTP]: ") + ah::getDateTimeStr(*mUtcTimestamp) + F(" UTC")); + DPRINTLN(DBG_INFO, "[NTP]: " + ah::getDateTimeStr(*mUtcTimestamp) + " UTC"); } @@ -202,27 +206,58 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { //----------------------------------------------------------------------------- -void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) { - if(!mConnected) { - mConnected = true; - DBGPRINTLN(F("\n[WiFi] Connected")); - WiFi.mode(WIFI_STA); - WiFi.begin(); - DBGPRINTLN(F("[WiFi] AP disabled")); - mDns.stop(); - - welcome(WiFi.localIP().toString() + F(" (Station)")); +#if defined(ESP8266) + void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) { + if(!mConnected) { + mConnected = true; + DBGPRINTLN(F("\n[WiFi] Connected")); + WiFi.mode(WIFI_STA); + DBGPRINTLN(F("[WiFi] AP disabled")); + mDns.stop(); + + welcome(WiFi.localIP().toString() + F(" (Station)")); + } } -} -//----------------------------------------------------------------------------- -void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) { - if(mConnected) { - mConnected = false; - DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); + //------------------------------------------------------------------------- + void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) { + if(mConnected) { + mConnected = false; + DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); + } } -} + +#else + //------------------------------------------------------------------------- + void ahoywifi::onWiFiEvent(WiFiEvent_t event) { + switch(event) { + case SYSTEM_EVENT_STA_GOT_IP: + if(!mConnected) { + delay(1000); + mConnected = true; + DBGPRINTLN(F("\n[WiFi] Connected")); + welcome(WiFi.localIP().toString() + F(" (Station)")); + WiFi.mode(WIFI_STA); + WiFi.begin(); + DBGPRINTLN(F("[WiFi] AP disabled")); + mDns.stop(); + + } + break; + + case SYSTEM_EVENT_STA_DISCONNECTED: + if(mConnected) { + mConnected = false; + DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); + } + break; + + default: + break; + } + } +#endif //----------------------------------------------------------------------------- diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 1cda6ab4..767265c5 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -30,16 +30,22 @@ class ahoywifi { void setupAp(void); void setupStation(void); void sendNTPpacket(IPAddress& address); + #if defined(ESP8266) void onConnect(const WiFiEventStationModeGotIP& event); void onDisconnect(const WiFiEventStationModeDisconnected& event); + #else + void onWiFiEvent(WiFiEvent_t event); + #endif void welcome(String msg); settings_t *mConfig; DNSServer mDns; WiFiUDP mUdp; // for time server + #if defined(ESP8266) WiFiEventHandler wifiConnectHandler; WiFiEventHandler wifiDisconnectHandler; + #endif bool mConnected, mInitNtp; uint8_t mCnt; From 3c6e3221e8d0989f7f28e0307049a7938e4f1da8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 4 Dec 2022 20:19:02 +0100 Subject: [PATCH 15/20] added display-class: Nokia5110 and SDD1306 --- src/app.cpp | 15 +- src/app.h | 13 +- src/platformio.ini | 35 ++- .../MonochromeDisplay/MonochromeDisplay.h | 237 ++++++++++++++++++ 4 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 src/plugins/MonochromeDisplay/MonochromeDisplay.h diff --git a/src/app.cpp b/src/app.cpp index 21bee17e..303ab562 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -38,15 +38,15 @@ void app::setup(uint32_t timeout) { mSys->setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); mSys->addInverters(&mConfig->inst); -#if !defined(AP_ONLY) + #if !defined(AP_ONLY) mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mUtcTimestamp, &mSunrise, &mSunset); -#endif + #endif mWifi.setup(mConfig, &mUtcTimestamp); mPayload.setup(mSys); mPayload.enableSerialDebug(mConfig->serial.debug); -#if !defined(AP_ONLY) + #if !defined(AP_ONLY) if (mConfig->mqtt.broker[0] > 0) { mPayload.addListener(std::bind(&PubMqttType::payloadEventListener, &mMqtt, std::placeholders::_1)); addListener(EVERY_SEC, std::bind(&PubMqttType::tickerSecond, &mMqtt)); @@ -54,7 +54,7 @@ void app::setup(uint32_t timeout) { addListener(EVERY_HR, std::bind(&PubMqttType::tickerHour, &mMqtt)); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); } -#endif + #endif setupLed(); mWeb = new web(this, mConfig, &mStat, mVersion); @@ -62,6 +62,13 @@ void app::setup(uint32_t timeout) { mWeb->setProtection(strlen(mConfig->sys.adminPwd) != 0); addListener(EVERY_SEC, std::bind(&web::tickSecond, mWeb)); + // Plugins + #if defined(ENA_NOKIA) || defined(ENA_SSD1306) + mMonoDisplay.setup(mSys, &mUtcTimestamp); + mPayload.addListener(std::bind(&MonoDisplayType::payloadEventListener, &mMonoDisplay, std::placeholders::_1)); + addListener(EVERY_SEC, std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay)); + #endif + //addListener(EVERY_MIN, std::bind(&PubSerialType::tickerMinute, &mPubSerial)); } diff --git a/src/app.h b/src/app.h index e2498815..82b0e646 100644 --- a/src/app.h +++ b/src/app.h @@ -27,6 +27,7 @@ #include "publisher/pubMqtt.h" #include "publisher/pubSerial.h" + // convert degrees and radians for sun calculation #define SIN(x) (sin(radians(x))) #define COS(x) (cos(radians(x))) @@ -38,7 +39,12 @@ typedef Payload PayloadType; typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; -class ahoywifi; +// PLUGINS +#if defined(ENA_NOKIA) || defined(ENA_SSD1306) + #include "plugins/MonochromeDisplay/MonochromeDisplay.h" + typedef MonochromeDisplay MonoDisplayType; +#endif + class web; class app : public ah::Scheduler { @@ -215,6 +221,11 @@ class app : public ah::Scheduler { int32_t mCalculatedTimezoneOffset; uint32_t mSunrise, mSunset; uint32_t mLatestSunTimestamp; + + // plugins + #if defined(ENA_NOKIA) || defined(ENA_SSD1306) + MonoDisplayType mMonoDisplay; + #endif }; #endif /*__APP_H__*/ diff --git a/src/platformio.ini b/src/platformio.ini index 0610c51d..3bf76cdc 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -37,7 +37,6 @@ lib_deps = nrf24/RF24 paulstoffregen/Time https://github.com/bertmelis/espMqttClient#v1.3.3 - ;knolleary/PubSubClient bblanchon/ArduinoJson ;esp8266/DNSServer ;esp8266/EEPROM @@ -90,6 +89,40 @@ monitor_filters = time ; Add timestamp with milliseconds for each new line log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory +[env:esp8266-nokia5110] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L +build_flags = -D RELEASE -DU8X8_NO_HW_I2C -DENA_NOKIA +monitor_filters = + ;default ; Remove typical terminal control codes from input + time ; Add timestamp with milliseconds for each new line + ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory +lib_deps = + https://github.com/yubox-node-org/ESPAsyncWebServer + nrf24/RF24 + paulstoffregen/Time + https://github.com/bertmelis/espMqttClient#v1.3.3 + bblanchon/ArduinoJson + olikraus/U8g2 + +[env:esp8266-ssd1306] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L +build_flags = -D RELEASE -DENA_SSD1306 +monitor_filters = + ;default ; Remove typical terminal control codes from input + time ; Add timestamp with milliseconds for each new line + ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory +lib_deps = + https://github.com/yubox-node-org/ESPAsyncWebServer + nrf24/RF24 + paulstoffregen/Time + https://github.com/bertmelis/espMqttClient#v1.3.3 + bblanchon/ArduinoJson + https://github.com/ThingPulse/esp8266-oled-ssd1306.git + [env:esp32-wroom32-release] platform = espressif32 board = lolin_d32 diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h new file mode 100644 index 00000000..09ec45d4 --- /dev/null +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -0,0 +1,237 @@ +#ifndef __MONOCHROME_DISPLAY__ +#define __MONOCHROME_DISPLAY__ + +#ifdef ENA_NOKIA + #include + #define DISP_PROGMEM U8X8_PROGMEM +#else // ENA_SSD1306 + /* esp8266 : SCL = 5, SDA = 4 */ + /* ewsp32 : SCL = 22, SDA = 21 */ + #include + #include + #define DISP_PROGMEM PROGMEM +#endif + + +#include "../../utils/helper.h" +#include "../../hm/hmSystem.h" + +static uint8_t bmp_arrow[] DISP_PROGMEM = { + B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111, + B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111, + B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000} ; + +#if defined(ENA_NOKIA) || defined(ENA_SSD1306) +template +class MonochromeDisplay { + public: + #if defined(ENA_NOKIA) + MonochromeDisplay() : mDisplay(U8G2_R0,5,4,16) { + mNewPayload = false; + mExtra = 0; + } + #else // ENA_SSD1306 + MonochromeDisplay() : mDisplay(0x3c, SDA, SCL) { + mNewPayload = false; + mExtra = 0; + mRx = 50; + mUp = 1; + } + #endif + + void setup(HMSYSTEM *sys, uint32_t *utcTs) { + mSys = sys; + mUtcTs = utcTs; + #if defined(ENA_NOKIA) + mDisplay.begin(); + ShowInfoText("booting..."); + #else + mDisplay.init(); + mDisplay.flipScreenVertically(); + mDisplay.setContrast(63); + mDisplay.setBrightness(63); + + mDisplay.clear(); + mDisplay.setFont(ArialMT_Plain_24); + mDisplay.setTextAlignment(TEXT_ALIGN_CENTER_BOTH); + + mDisplay.drawString(64,22,"Starting..."); + mDisplay.display(); + mDisplay.setTextAlignment(TEXT_ALIGN_LEFT); + #endif + } + + void loop(void) { + + } + + void payloadEventListener(uint8_t cmd) { + mNewPayload = true; + } + + void tickerSecond() { + if(mNewPayload) { + mNewPayload = false; + DataScreen(); + } + } + + private: + #if defined(ENA_NOKIA) + void ShowInfoText(const char *txt) { + /* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */ + mDisplay.clear(); + mDisplay.firstPage(); + do { + const char *e; + const char *p = txt; + int y=10; + mDisplay.setFont(u8g2_font_5x8_tr); + while(1) { + for(e=p+1; (*e && (*e != '\n')); e++); + size_t len=e-p; + mDisplay.setCursor(2,y); + String res=((String)p).substring(0,len); + mDisplay.print(res); + if ( !*e ) + break; + p=e+1; + y+=12; + } + mDisplay.sendBuffer(); + } while( mDisplay.nextPage() ); + } + #endif + + void DataScreen(void) { + String timeStr = ah::getDateTimeStr(*mUtcTs).substring(2, 22); + IPAddress ip = WiFi.localIP(); + float totalYield = 0.000, totalYieldToday = 0.000, totalActual = 0.0; + char fmtText[32]; + int ucnt=0; + + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos; + uint8_t list[] = {FLD_PAC, FLD_YT, FLD_YD}; + + if ( !iv->isProducing(*mUtcTs,rec) ) + continue; + + ucnt++; + + for (uint8_t fld = 0; fld < 3; fld++) { + pos = iv->getPosByChFld(CH0, list[fld],rec); + + if(fld == 1) + totalYield += iv->getValue(pos,rec); + if(fld == 2) + totalYieldToday += iv->getValue(pos,rec); + if(fld == 0) + totalActual += iv->getValue(pos,rec); + } + } + } + + /* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */ + mDisplay.clear(); + #if defined(ENA_NOKIA) + mDisplay.firstPage(); + do { + if(ucnt) { + mDisplay.drawXBMP(10,0,8,17,bmp_arrow); + mDisplay.setFont(u8g2_font_logisoso16_tr); + mDisplay.setCursor(25,16); + sprintf(fmtText,"%3.0f",totalActual); + mDisplay.print(String(fmtText)+F(" W")); + mDisplay.drawHLine(2,20,78); + mDisplay.setFont(u8g2_font_5x8_tr); + mDisplay.setCursor(5,29); + sprintf(fmtText,"%4.0f",totalYieldToday); + mDisplay.print(F("today ")+String(fmtText)+F(" Wh")); + mDisplay.setCursor(5,37); + sprintf(fmtText,"%.1f",totalYield); + mDisplay.print(F("total ")+String(fmtText)+F(" kWh")); + } + else { + mDisplay.setFont(u8g2_font_logisoso16_tr); + mDisplay.setCursor(30,30); + mDisplay.print(F("off")); + mDisplay.setFont(u8g2_font_5x8_tr); + } + if ( !(mExtra%20) ) { + mDisplay.setCursor(5,57); + mDisplay.print(ip); + } + else { + mDisplay.setCursor(0,47); + mDisplay.print(timeStr); + } + + mDisplay.sendBuffer(); + } while( mDisplay.nextPage() ); + mExtra++; + #else // ENA_SSD1306 + if(ucnt) { + mDisplay.setBrightness(63); + mDisplay.drawXbm(10,5,8,17,bmp_arrow); + mDisplay.setFont(ArialMT_Plain_24); + sprintf(fmtText,"%3.0f",totalActual); + mDisplay.drawString(25,0,String(fmtText)+F(" W")); + mDisplay.setFont(ArialMT_Plain_16); + sprintf(fmtText,"%4.0f",totalYieldToday); + mDisplay.drawString(5,22,F("today ")+String(fmtText)+F(" Wh")); + sprintf(fmtText,"%.1f",totalYield); + mDisplay.drawString(5,35,F("total ")+String(fmtText)+F(" kWh")); + mDisplay.drawLine(2,23,123,23); + } + else { + if(mUp) { + mRx += 2; + if(mRx >= 70) + mUp = 0; + } else { + mRx -= 2; + if(mRx <= 50) + mUp = 1; + } + mDisplay.setBrightness(1); + mDisplay.setFont(ArialMT_Plain_24); + mDisplay.drawString(mRx, 10, F("off")); + mDisplay.setFont(ArialMT_Plain_16); + } + if (!(mExtra % 20)) { + mDisplay.drawString(5, 49, ip.toString()); + } else { + int w = mDisplay.getStringWidth(timeStr.c_str(), timeStr.length(), 0); + if (w > 127) { + String tt=timeStr.substring(9, 17); + w=mDisplay.getStringWidth(tt.c_str(),tt.length(), 0); + mDisplay.drawString(127 - w, 49, tt); + } else + mDisplay.drawString(0, 49, timeStr); + } + + mDisplay.display(); + mExtra++; + #endif + } + + // private member variables + #if defined(ENA_NOKIA) + U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay; + #else // ENA_SSD1306 + SSD1306Wire mDisplay; + int mRx; + char mUp; + #endif + int mExtra; + bool mNewPayload; + uint32_t *mUtcTs; + HMSYSTEM *mSys; +}; +#endif + +#endif /*__MONOCHROME_DISPLAY__*/ From 9da0fc4058b1bb21d9dcd9829753d9f22c1f22c7 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 5 Dec 2022 23:16:54 +0100 Subject: [PATCH 16/20] fix #468 MQTT status at sunset --- src/app.cpp | 175 +++++++++--------- src/app.h | 38 +--- src/defines.h | 2 +- .../MonochromeDisplay/MonochromeDisplay.h | 2 +- src/publisher/pubMqtt.h | 29 ++- src/utils/scheduler.h | 89 ++++++++- src/web/html/index.html | 2 +- src/web/webApi.cpp | 1 - 8 files changed, 211 insertions(+), 127 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 303ab562..81a75980 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -24,25 +24,30 @@ void app::setup(uint32_t timeout) { while (!Serial) yield(); - addListener(EVERY_SEC, std::bind(&app::uptimeTick, this)); - addListener(EVERY_MIN, std::bind(&app::minuteTick, this)); - addListener(EVERY_12H, std::bind(&app::ntpUpdateTick, this)); - resetSystem(); mSettings.setup(); mSettings.getPtr(mConfig); DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false"))); + addListener(EVERY_SEC, std::bind(&app::tickSecond, this)); + addListener(EVERY_MIN, std::bind(&app::tickMinute, this)); + addListener(EVERY_12H, std::bind(&app::tickNtpUpdate, this)); + once(mConfig->nrf.sendInterval, std::bind(&app::tickSend, this), "tickSend"); + if((mConfig->sun.lat) && (mConfig->sun.lon)) { + once(5, std::bind(&app::tickCalcSunrise, this)); + mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; + } + 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); + mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mTimestamp, &mSunrise, &mSunset); #endif - mWifi.setup(mConfig, &mUtcTimestamp); + mWifi.setup(mConfig, &mTimestamp); mPayload.setup(mSys); mPayload.enableSerialDebug(mConfig->serial.debug); @@ -51,8 +56,8 @@ void app::setup(uint32_t timeout) { 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)); - addListener(EVERY_HR, std::bind(&PubMqttType::tickerHour, &mMqtt)); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); + } #endif setupLed(); @@ -64,7 +69,7 @@ void app::setup(uint32_t timeout) { // Plugins #if defined(ENA_NOKIA) || defined(ENA_SSD1306) - mMonoDisplay.setup(mSys, &mUtcTimestamp); + mMonoDisplay.setup(mSys, &mTimestamp); mPayload.addListener(std::bind(&MonoDisplayType::payloadEventListener, &mMonoDisplay, std::placeholders::_1)); addListener(EVERY_SEC, std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay)); #endif @@ -119,87 +124,95 @@ void app::loop(void) { } mMqtt.loop(); +} - if (ah::checkTicker(&mTicker, 1000)) { - if (mUtcTimestamp > 946684800 && mConfig->sun.lat && mConfig->sun.lon && (mUtcTimestamp + mCalculatedTimezoneOffset) / 86400 != (mLatestSunTimestamp + mCalculatedTimezoneOffset) / 86400) { // update on reboot or midnight - if (!mLatestSunTimestamp) { // first call: calculate time zone from longitude to refresh at local midnight - mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; - } - ah::calculateSunriseSunset(mUtcTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); - mLatestSunTimestamp = mUtcTimestamp; +//----------------------------------------------------------------------------- +void app::tickCalcSunrise(void) { + if (0 == mTimestamp) { + once(5, std::bind(&app::tickCalcSunrise, this)); // check again in 5 secs + return; + } + ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); + + uint32_t nxtTrig = mTimestamp - (mTimestamp % 86400) + 86400; // next midnight + onceAt(nxtTrig, std::bind(&app::tickCalcSunrise, this), "calc sunrise"); + onceAt(mSunrise, std::bind(&app::tickSend, this), "tickSend"); // register next event + if (mConfig->mqtt.broker[0] > 0) { + once(1, std::bind(&PubMqttType::tickerSun, &mMqtt), "MQTT-tickerSun"); + onceAt(mSunset, std::bind(&PubMqttType::tickSunset, &mMqtt)); + } +} + +//----------------------------------------------------------------------------- +void app::tickSend(void) { + if ((mTimestamp > 0) && (!mConfig->sun.disNightCom || (mTimestamp >= mSunrise && mTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set) + once(mConfig->nrf.sendInterval, std::bind(&app::tickSend, this), "tickSend"); // register next event + if (mConfig->serial.debug) + DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX)); + + if (!mSys->BufCtrl.empty()) { + if (mConfig->serial.debug) + DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill())); } + int8_t maxLoop = MAX_NUM_INVERTERS; + Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId); + do { + mSendLastIvId = ((MAX_NUM_INVERTERS - 1) == mSendLastIvId) ? 0 : mSendLastIvId + 1; + iv = mSys->getInverterByPos(mSendLastIvId); + } while ((NULL == iv) && ((maxLoop--) > 0)); + if (NULL != iv) { + if (!mPayload.isComplete(iv)) + mPayload.process(false, mConfig->nrf.maxRetransPerPyld, &mStat); - if (++mSendTicker >= mConfig->nrf.sendInterval) { - mSendTicker = 0; + if (!mPayload.isComplete(iv)) { + if (0 == mPayload.getMaxPacketId(iv)) + mStat.rxFailNoAnser++; + else + mStat.rxFail++; - if (mUtcTimestamp > 946684800 && (!mConfig->sun.disNightCom || !mLatestSunTimestamp || (mUtcTimestamp >= mSunrise && mUtcTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set) + iv->setQueuedCmdFinished(); // command failed if (mConfig->serial.debug) - DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX)); - - if (!mSys->BufCtrl.empty()) { - if (mConfig->serial.debug) - DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill())); + DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); + if (mConfig->serial.debug) { + DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); + DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload.getRetransmits(iv)) + ")"); } + } + + mPayload.reset(iv, mTimestamp); + mPayload.request(iv); - int8_t maxLoop = MAX_NUM_INVERTERS; - Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId); - do { - mSendLastIvId = ((MAX_NUM_INVERTERS - 1) == mSendLastIvId) ? 0 : mSendLastIvId + 1; - iv = mSys->getInverterByPos(mSendLastIvId); - } while ((NULL == iv) && ((maxLoop--) > 0)); - - if (NULL != iv) { - if (!mPayload.isComplete(iv)) - mPayload.process(false, mConfig->nrf.maxRetransPerPyld, &mStat); - - if (!mPayload.isComplete(iv)) { - if (0 == mPayload.getMaxPacketId(iv)) - mStat.rxFailNoAnser++; - else - mStat.rxFail++; - - iv->setQueuedCmdFinished(); // command failed - if (mConfig->serial.debug) - DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); - if (mConfig->serial.debug) { - DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); - DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload.getRetransmits(iv)) + ")"); - } - } - - mPayload.reset(iv, mUtcTimestamp); - mPayload.request(iv); - - yield(); - if (mConfig->serial.debug) { - DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status())); - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX)); - } - - if (iv->devControlRequest) { - if (mConfig->serial.debug) - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0])); - mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit); - mPayload.setTxCmd(iv, iv->devControlCmd); - iv->clearCmdQueue(); - iv->enqueCommand(SystemConfigPara); - } else { - uint8_t cmd = iv->getQueuedCmd(); - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); - mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload.getTs(iv), iv->alarmMesIndex); - mPayload.setTxCmd(iv, cmd); - mRxTicker = 0; - } - } - } else if (mConfig->serial.debug) - DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!")); yield(); + if (mConfig->serial.debug) { + DPRINTLN(DBG_DEBUG, F("app:loop WiFi WiFi.status ") + String(WiFi.status())); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX)); + } - updateLed(); + if (iv->devControlRequest) { + if (mConfig->serial.debug) + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Devcontrol request ") + String(iv->devControlCmd) + F(" power limit ") + String(iv->powerLimit[0])); + mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit); + mPayload.setTxCmd(iv, iv->devControlCmd); + iv->clearCmdQueue(); + iv->enqueCommand(SystemConfigPara); + } else { + uint8_t cmd = iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); + mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload.getTs(iv), iv->alarmMesIndex); + mPayload.setTxCmd(iv, cmd); + mRxTicker = 0; + } } + } else { + once(3600, std::bind(&app::tickSend, this), "tickSend"); // register next event (one hour) + if (mConfig->serial.debug) + DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!")); } + yield(); + + updateLed(); } //----------------------------------------------------------------------------- @@ -224,22 +237,18 @@ void app::resetSystem(void) { snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); mShouldReboot = false; - mUptimeSecs = 0; mUpdateNtp = false; mFlagSendDiscoveryConfig = false; #ifdef AP_ONLY - mUtcTimestamp = 1; + mTimestamp = 1; #else - mUtcTimestamp = 0; + mTimestamp = 0; #endif mSunrise = 0; mSunset = 0; - mSendTicker = 0xffff; - - mTicker = 0; mRxTicker = 0; mSendLastIvId = 0; @@ -278,7 +287,7 @@ void app::updateLed(void) { Inverter<> *iv = mSys->getInverterByPos(0); if (NULL != iv) { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - if(iv->isProducing(mUtcTimestamp, rec)) + if(iv->isProducing(mTimestamp, rec)) digitalWrite(mConfig->led.led0, LOW); // LED on else digitalWrite(mConfig->led.led0, HIGH); // LED off diff --git a/src/app.h b/src/app.h index 82b0e646..08c2e3fe 100644 --- a/src/app.h +++ b/src/app.h @@ -92,27 +92,19 @@ class app : public ah::Scheduler { String getTimeStr(uint32_t offset = 0) { char str[10]; - if(0 == mUtcTimestamp) + if(0 == mTimestamp) sprintf(str, "n/a"); else - sprintf(str, "%02d:%02d:%02d ", hour(mUtcTimestamp + offset), minute(mUtcTimestamp + offset), second(mUtcTimestamp + offset)); + sprintf(str, "%02d:%02d:%02d ", hour(mTimestamp + offset), minute(mTimestamp + offset), second(mTimestamp + offset)); return String(str); } - inline uint32_t getUptime(void) { - return mUptimeSecs; - } - - inline uint32_t getTimestamp(void) { - return mUtcTimestamp; - } - void setTimestamp(uint32_t newTime) { DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime)); if(0 == newTime) mUpdateNtp = true; else - mUtcTimestamp = newTime; + Scheduler::setTimestamp(newTime); } inline uint32_t getSunrise(void) { @@ -121,9 +113,6 @@ class app : public ah::Scheduler { inline uint32_t getSunset(void) { return mSunset; } - inline uint32_t getLatestSunTimestamp(void) { - return mLatestSunTimestamp; - } inline bool mqttIsConnected(void) { return mMqtt.isConnected(); } inline bool getSettingsValid(void) { return mSettings.getValid(); } @@ -142,11 +131,7 @@ class app : public ah::Scheduler { void setupLed(void); void updateLed(void); - void uptimeTick(void) { - mUptimeSecs++; - if (0 != mUtcTimestamp) - mUtcTimestamp++; - + void tickSecond(void) { if (mShouldReboot) { DPRINTLN(DBG_INFO, F("Rebooting...")); ESP.restart(); @@ -158,16 +143,19 @@ class app : public ah::Scheduler { } } - void minuteTick(void) { - if(0 == mUtcTimestamp) { + void tickMinute(void) { + if(0 == mTimestamp) { mUpdateNtp = true; } } - void ntpUpdateTick(void) { + void tickNtpUpdate(void) { mUpdateNtp = true; } + void tickCalcSunrise(void); + void tickSend(void); + void stats(void) { DPRINTLN(DBG_VERBOSE, F("main.h:stats")); #ifdef ESP8266 @@ -188,9 +176,6 @@ class app : public ah::Scheduler { DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag)); } - uint32_t mUptimeSecs; - - uint32_t mUtcTimestamp; bool mUpdateNtp; bool mShowRebootRequest; @@ -204,13 +189,11 @@ class app : public ah::Scheduler { settings mSettings; settings_t *mConfig; - uint16_t mSendTicker; uint8_t mSendLastIvId; statistics_t mStat; // timer - uint32_t mTicker; uint32_t mRxTicker; // mqtt @@ -220,7 +203,6 @@ class app : public ah::Scheduler { // sun int32_t mCalculatedTimezoneOffset; uint32_t mSunrise, mSunset; - uint32_t mLatestSunTimestamp; // plugins #if defined(ENA_NOKIA) || defined(ENA_SSD1306) diff --git a/src/defines.h b/src/defines.h index 229d583b..d2f9b98f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 47 +#define VERSION_PATCH 48 //------------------------------------- typedef struct { diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 09ec45d4..29f44be2 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -1,6 +1,7 @@ #ifndef __MONOCHROME_DISPLAY__ #define __MONOCHROME_DISPLAY__ +#if defined(ENA_NOKIA) || defined(ENA_SSD1306) #ifdef ENA_NOKIA #include #define DISP_PROGMEM U8X8_PROGMEM @@ -21,7 +22,6 @@ static uint8_t bmp_arrow[] DISP_PROGMEM = { B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111, B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000} ; -#if defined(ENA_NOKIA) || defined(ENA_SSD1306) template class MonochromeDisplay { public: diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 0e3e8417..e1dc1acd 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -89,11 +89,33 @@ class PubMqtt { } } - void tickerHour() { + void tickerSun() { publish("sunrise", String(*mSunrise).c_str(), true); publish("sunset", String(*mSunset).c_str(), true); } + void tickSunset() { + char topic[MAX_NAME_LENGTH + 15], val[32]; + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + + snprintf(topic, MAX_NAME_LENGTH + 15, "%s/available_text", iv->config->name); + snprintf(val, 32, "not available and not producing"); + publish(topic, val, true); + + snprintf(topic, MAX_NAME_LENGTH + 15, "%s/available", iv->config->name); + snprintf(val, 32, "%d", MQTT_STATUS_NOT_AVAIL_NOT_PROD); + publish(topic, val, true); + } + } + + void payloadEventListener(uint8_t cmd) { + if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set + mSendList.push(cmd); + } + 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); @@ -110,11 +132,6 @@ class PubMqtt { mClient.subscribe(topic, QOS_0); } - void payloadEventListener(uint8_t cmd) { - if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set - mSendList.push(cmd); - } - void setSubscriptionCb(subscriptionCb cb) { mSubscriptionCb = cb; } diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index ff732b58..25702ce3 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -14,6 +14,13 @@ enum {EVERY_SEC = 1, EVERY_MIN, EVERY_HR, EVERY_12H, EVERY_DAY}; typedef std::function SchedulerCb; +struct once_t { + uint32_t n; + SchedulerCb f; + once_t(uint32_t a, SchedulerCb b) : n(a), f(b) {} + once_t() : n(0), f(NULL) {} +}; + namespace ah { class Scheduler { public: @@ -21,18 +28,25 @@ class Scheduler { void setup() { mPrevMillis = 0; - mSeconds = 0; - mMinutes = 0; - mHours = 0; + mSeconds = 0; + mMinutes = 0; + mHours = 0; + mUptime = 0; + mTimestamp = 0; } void loop() { if (millis() - mPrevMillis >= 1000) { mPrevMillis += 1000; + mUptime++; + if(0 != mTimestamp) + mTimestamp++; notify(&mListSecond); + onceFuncTick(); if(++mSeconds >= 60) { mSeconds = 0; notify(&mListMinute); + onceAtFuncTick(); if(++mMinutes >= 60) { mMinutes = 0; notify(&mListHour); @@ -48,6 +62,30 @@ class Scheduler { } } + // checked every second + void once(uint32_t sec, SchedulerCb cb, const char *info = NULL) { + if(NULL != info) { + DPRINT(DBG_INFO, F("once in [s]: ") + String(sec)); + DBGPRINTLN(F(", ") + String(info)); + } + mOnce.push_back(once_t(sec, cb)); + } + + // checked every minute + void onceAt(uint32_t timestamp, SchedulerCb cb, const char *info = NULL) { + if(timestamp > mTimestamp) { + if(NULL != info) { + DPRINT(DBG_INFO, F("onceAt (UTC): ") + getDateTimeStr(timestamp)); + DBGPRINTLN(F(", ") + String(info)); + } + mOnceAt.push_back(once_t(timestamp, cb)); + } + } + + virtual void setTimestamp(uint32_t ts) { + mTimestamp = ts; + } + void addListener(uint8_t every, SchedulerCb cb) { switch(every) { case EVERY_SEC: mListSecond.push_back(cb); break; @@ -59,21 +97,60 @@ class Scheduler { } } + uint32_t getUptime(void) { + return mUptime; + } + + uint32_t getTimestamp(void) { + return mTimestamp; + } + + protected: virtual void notify(std::list *lType) { for(std::list::iterator it = lType->begin(); it != lType->end(); ++it) { (*it)(); } } - protected: + uint32_t mTimestamp; + + private: + void onceFuncTick(void) { + if(mOnce.empty()) + return; + for(std::list::iterator it = mOnce.begin(); it != mOnce.end();) { + if(((*it).n)-- == 0) { + ((*it).f)(); + it = mOnce.erase(it); + } + else + ++it; + } + } + + void onceAtFuncTick(void) { + if(mOnceAt.empty()) + return; + for(std::list::iterator it = mOnceAt.begin(); it != mOnceAt.end();) { + if(((*it).n) < mTimestamp) { + ((*it).f)(); + it = mOnceAt.erase(it); + } + else + ++it; + } + } + std::list mListSecond; std::list mListMinute; std::list mListHour; std::list mList12h; std::list mListDay; - private: - uint32_t mPrevMillis; + std::list mOnce; + std::list mOnceAt; + + uint32_t mPrevMillis, mUptime; uint8_t mSeconds, mMinutes, mHours; }; } diff --git a/src/web/html/index.html b/src/web/html/index.html index e0474c08..070aee31 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -134,7 +134,7 @@ e.addEventListener("click", setTime); } - if(!obj["ts_sun_upd"]) { + if(0 == obj["ts_sunrise"]) { var e = document.getElementById("sun"); if(null != e) e.parentNode.removeChild(e); diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp index b11637fa..bed67fb7 100644 --- a/src/web/webApi.cpp +++ b/src/web/webApi.cpp @@ -163,7 +163,6 @@ void webApi::getSysInfo(JsonObject obj) { obj[F("ts_now")] = mApp->getTimestamp(); obj[F("ts_sunrise")] = mApp->getSunrise(); obj[F("ts_sunset")] = mApp->getSunset(); - obj[F("ts_sun_upd")] = mApp->getLatestSunTimestamp(); obj[F("wifi_rssi")] = WiFi.RSSI(); obj[F("mac")] = WiFi.macAddress(); obj[F("hostname")] = WiFi.getHostname(); From d9290d9fdf1c666b92214aa97afbb3ee23286cc1 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 8 Dec 2022 17:44:34 +0100 Subject: [PATCH 17/20] fix #480 AP Mode on fresh ESP included #483 improvements fix #468 last_success MQTT fix #468 update available status at sunset fix #447 reorderd enqueue commands to not have same payload length in a row added ssd1306 and nokia display to build script --- .github/workflows/compile_development.yml | 2 +- .github/workflows/compile_esp8266.yml | 2 +- src/app.cpp | 53 ++--- src/app.h | 2 +- src/config/settings.h | 8 +- src/defines.h | 2 +- src/hm/hmInverter.h | 9 +- src/main.cpp | 2 +- src/publisher/pubMqtt.h | 3 +- src/publisher/pubSerial.h | 37 ++-- src/utils/llist.h | 108 ++++++++++ src/utils/scheduler.h | 238 ++++++++++------------ src/utils/sun.h | 2 +- src/web/html/index.html | 14 +- src/wifi/ahoywifi.cpp | 9 +- 15 files changed, 285 insertions(+), 206 deletions(-) create mode 100644 src/utils/llist.h diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index ec435880..8188e6d1 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -47,7 +47,7 @@ jobs: run: python convert.py - name: Run PlatformIO - run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp32-wroom32-release + run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release - name: Rename Binary files id: rename-binary-files diff --git a/.github/workflows/compile_esp8266.yml b/.github/workflows/compile_esp8266.yml index cf61b1f9..ab902f74 100644 --- a/.github/workflows/compile_esp8266.yml +++ b/.github/workflows/compile_esp8266.yml @@ -49,7 +49,7 @@ jobs: working-directory: src/web/html run: python convert.py - name: Run PlatformIO - run: pio run -d tools/esp8266 --environment esp8266-release --environment esp8285-release --environment esp32-wroom32-release + run: pio run -d tools/esp8266 --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release - name: Rename Binary files id: rename-binary-files diff --git a/src/app.cpp b/src/app.cpp index 81a75980..7b7c6e0e 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -19,24 +19,29 @@ app::app() : ah::Scheduler() { //----------------------------------------------------------------------------- -void app::setup(uint32_t timeout) { +void app::setup() { Serial.begin(115200); while (!Serial) yield(); + ah::Scheduler::setup(); + resetSystem(); mSettings.setup(); mSettings.getPtr(mConfig); DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false"))); - addListener(EVERY_SEC, std::bind(&app::tickSecond, this)); - addListener(EVERY_MIN, std::bind(&app::tickMinute, this)); - addListener(EVERY_12H, std::bind(&app::tickNtpUpdate, this)); - once(mConfig->nrf.sendInterval, std::bind(&app::tickSend, this), "tickSend"); - if((mConfig->sun.lat) && (mConfig->sun.lon)) { - once(5, std::bind(&app::tickCalcSunrise, this)); - mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; - } + + everySec(std::bind(&app::tickSecond, this)); + everyMin(std::bind(&app::tickMinute, this)); + every12h(std::bind(&app::tickNtpUpdate, this)); + every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval); + #if !defined(AP_ONLY) + if((mConfig->sun.lat) && (mConfig->sun.lon)) { + mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; + once(std::bind(&app::tickCalcSunrise, this), 5); + } + #endif mSys = new HmSystemType(); mSys->enableDebug(); @@ -54,10 +59,9 @@ void app::setup(uint32_t timeout) { #if !defined(AP_ONLY) if (mConfig->mqtt.broker[0] > 0) { 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)); + everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt)); + everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt)); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); - } #endif setupLed(); @@ -65,16 +69,17 @@ void app::setup(uint32_t timeout) { mWeb = new web(this, mConfig, &mStat, mVersion); mWeb->setup(); mWeb->setProtection(strlen(mConfig->sys.adminPwd) != 0); - addListener(EVERY_SEC, std::bind(&web::tickSecond, mWeb)); + everySec(std::bind(&web::tickSecond, mWeb)); // Plugins #if defined(ENA_NOKIA) || defined(ENA_SSD1306) mMonoDisplay.setup(mSys, &mTimestamp); mPayload.addListener(std::bind(&MonoDisplayType::payloadEventListener, &mMonoDisplay, std::placeholders::_1)); - addListener(EVERY_SEC, std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay)); + everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay)); #endif - //addListener(EVERY_MIN, std::bind(&PubSerialType::tickerMinute, &mPubSerial)); + mPubSerial.setup(mConfig, mSys, &mTimestamp); + every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval); } //----------------------------------------------------------------------------- @@ -129,27 +134,26 @@ void app::loop(void) { //----------------------------------------------------------------------------- void app::tickCalcSunrise(void) { if (0 == mTimestamp) { - once(5, std::bind(&app::tickCalcSunrise, this)); // check again in 5 secs + once(std::bind(&app::tickCalcSunrise, this), 5); // check again in 5 secs return; } ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); uint32_t nxtTrig = mTimestamp - (mTimestamp % 86400) + 86400; // next midnight - onceAt(nxtTrig, std::bind(&app::tickCalcSunrise, this), "calc sunrise"); - onceAt(mSunrise, std::bind(&app::tickSend, this), "tickSend"); // register next event + onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig); if (mConfig->mqtt.broker[0] > 0) { - once(1, std::bind(&PubMqttType::tickerSun, &mMqtt), "MQTT-tickerSun"); - onceAt(mSunset, std::bind(&PubMqttType::tickSunset, &mMqtt)); + once(std::bind(&PubMqttType::tickerSun, &mMqtt), 1); + onceAt(std::bind(&PubMqttType::tickSunset, &mMqtt), mSunset); } } //----------------------------------------------------------------------------- void app::tickSend(void) { + if(!mSys->Radio.isChipConnected()) { + DPRINTLN(DBG_WARN, "NRF24 not connected!"); + return; + } if ((mTimestamp > 0) && (!mConfig->sun.disNightCom || (mTimestamp >= mSunrise && mTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set) - once(mConfig->nrf.sendInterval, std::bind(&app::tickSend, this), "tickSend"); // register next event - if (mConfig->serial.debug) - DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX)); - if (!mSys->BufCtrl.empty()) { if (mConfig->serial.debug) DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill())); @@ -206,7 +210,6 @@ void app::tickSend(void) { } } } else { - once(3600, std::bind(&app::tickSend, this), "tickSend"); // register next event (one hour) if (mConfig->serial.debug) DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!")); } diff --git a/src/app.h b/src/app.h index 08c2e3fe..e494cf91 100644 --- a/src/app.h +++ b/src/app.h @@ -52,7 +52,7 @@ class app : public ah::Scheduler { app(); ~app() {} - void setup(uint32_t timeout); + void setup(void); void loop(void); void handleIntr(void); void cbMqtt(char* topic, byte* payload, unsigned int length); diff --git a/src/config/settings.h b/src/config/settings.h index 1895cd12..01d2b8c1 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -98,6 +98,7 @@ typedef struct { cfgMqtt_t mqtt; cfgLed_t led; cfgInst_t inst; + bool valid; } settings_t; class settings { @@ -107,7 +108,7 @@ class settings { void setup() { DPRINTLN(DBG_INFO, F("Initializing FS ..")); - mValid = false; + mCfg.valid = false; #if !defined(ESP32) LittleFSConfig cfg; cfg.setAutoFormat(false); @@ -145,7 +146,7 @@ class settings { } bool getValid(void) { - return mValid; + return mCfg.valid; } void getInfo(uint32_t *used, uint32_t *size) { @@ -173,7 +174,7 @@ class settings { DynamicJsonDocument root(4096); DeserializationError err = deserializeJson(root, fp); if(!err) { - mValid = true; + mCfg.valid = true; jsonWifi(root["wifi"]); jsonNrf(root["nrf"]); jsonNtp(root["ntp"]); @@ -409,7 +410,6 @@ class settings { } settings_t mCfg; - bool mValid; }; #endif /*__SETTINGS_H__*/ diff --git a/src/defines.h b/src/defines.h index d2f9b98f..d0c7a85f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 48 +#define VERSION_PATCH 49 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index f38a36cb..2494a979 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -160,17 +160,12 @@ class Inverter { } uint8_t getQueuedCmd() { - if (_commandQueue.empty()){ - // Fill with default commands - enqueCommand(RealTimeRunData_Debug); + if (_commandQueue.empty()) { if (fwVersion == 0) - { // info needed maybe after "one night" (=> DC>0 to DC=0 and to DC>0) or reboot enqueCommand(InverterDevInform_All); - } + enqueCommand(RealTimeRunData_Debug); if (actPowerLimit == 0xffff) - { // info needed maybe after "one nigth" (=> DC>0 to DC=0 and to DC>0) or reboot enqueCommand(SystemConfigPara); - } } return _commandQueue.front().get()->getCmd(); } diff --git a/src/main.cpp b/src/main.cpp index ea708282..c585d0f2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,7 +18,7 @@ IRAM_ATTR void handleIntr(void) { //----------------------------------------------------------------------------- void setup() { - myApp.setup(WIFI_TRY_CONNECT_TIME); + myApp.setup(); // TODO: move to HmRadio attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING); diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index e1dc1acd..dbea4957 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -95,6 +95,7 @@ class PubMqtt { } void tickSunset() { + printf("tickSunset\n"); char topic[MAX_NAME_LENGTH + 15], val[32]; for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); @@ -387,7 +388,7 @@ class PubMqtt { publish(topic, val, true); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name); - snprintf(val, 40, "%i", iv->getLastTs(rec) * 1000); + snprintf(val, 40, "%d", iv->getLastTs(rec)); publish(topic, val, true); } diff --git a/src/publisher/pubSerial.h b/src/publisher/pubSerial.h index ef4fb88f..d1cc55aa 100644 --- a/src/publisher/pubSerial.h +++ b/src/publisher/pubSerial.h @@ -21,28 +21,24 @@ class PubSerial { mUtcTimestamp = utcTs; } - void tickerMinute() { - DPRINTLN(DBG_INFO, "tickerMinute"); - if(++mTick >= mCfg->serial.interval) { - mTick = 0; - if (mCfg->serial.showIv) { - char topic[30], val[10]; - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL != iv) { - record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv->isAvailable(*mUtcTimestamp, rec)) { - DPRINTLN(DBG_INFO, F("Inverter: ") + String(id)); - for (uint8_t i = 0; i < rec->length; i++) { - if (0.0f != iv->getValue(i, rec)) { - snprintf(topic, 30, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); - snprintf(val, 10, "%.3f %s", iv->getValue(i, rec), iv->getUnit(i, rec)); - DPRINTLN(DBG_INFO, String(topic) + ": " + String(val)); - } - yield(); + void tick(void) { + if (mCfg->serial.showIv) { + char topic[32 + MAX_NAME_LENGTH], val[40]; + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv->isAvailable(*mUtcTimestamp, rec)) { + DPRINTLN(DBG_INFO, F("Inverter: ") + String(id)); + for (uint8_t i = 0; i < rec->length; i++) { + if (0.0f != iv->getValue(i, rec)) { + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(val, 40, "%.3f %s", iv->getValue(i, rec), iv->getUnit(i, rec)); + DPRINTLN(DBG_INFO, String(topic) + ": " + String(val)); } - DPRINTLN(DBG_INFO, ""); + yield(); } + DPRINTLN(DBG_INFO, ""); } } } @@ -52,7 +48,6 @@ class PubSerial { private: settings_t *mCfg; HMSYSTEM *mSys; - uint8_t mTick; uint32_t *mUtcTimestamp; }; diff --git a/src/utils/llist.h b/src/utils/llist.h new file mode 100644 index 00000000..dd139c86 --- /dev/null +++ b/src/utils/llist.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://ahoydtu.de +// Lukas Pusch, lukas@lpusch.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- +#ifndef __LIST_H__ +#define __LIST_H__ + +template +struct node_s { + typedef T dT; + node_s *pre; + node_s *nxt; + uint32_t id; + dT d; + node_s() : pre(NULL), nxt(NULL), d() {} + node_s(Args... args) : id(0), pre(NULL), nxt(NULL), d(args...) {} +}; + +template +class llist { + typedef node_s elmType; + typedef T dataType; + public: + llist() : root(mPool) { + root = NULL; + elmType *p = mPool; + for(uint32_t i = 0; i < MAX_NUM; i++) { + p->id = i; + p++; + } + mFill = mMax = 0; + } + + elmType *add(Args... args) { + elmType *p = root, *t; + if(NULL == (t = getFreeNode())) + return NULL; + if(++mFill > mMax) + mMax = mFill; + + if(NULL == root) { + p = root = t; + p->pre = p; + p->nxt = p; + } + else { + p = root->pre; + t->pre = p; + p->nxt->pre = t; + t->nxt = p->nxt; + p->nxt = t; + } + t->d = dataType(args...); + return p; + } + + elmType *getFront() { + return root; + } + + elmType *get(elmType *p) { + p = p->nxt; + return (p == root) ? NULL : p; + } + + elmType *rem(elmType *p) { + if(NULL == p) + return NULL; + elmType *t = p->nxt; + p->nxt->pre = p->pre; + p->pre->nxt = p->nxt; + if(root == p) + root = NULL; + p->nxt = NULL; + p->pre = NULL; + p = NULL; + mFill--; + return (NULL == root) ? NULL : ((t == root) ? NULL : t); + } + + uint16_t getFill(void) { + return mFill; + } + + uint16_t getMaxFill(void) { + return mMax; + } + + protected: + elmType *root; + + private: + elmType *getFreeNode(void) { + elmType *n = mPool; + for(uint32_t i = 0; i < MAX_NUM; i++) { + if(NULL == n->nxt) + return n; + n++; + } + return NULL; + } + + elmType mPool[MAX_NUM]; + uint16_t mFill, mMax; +}; + +#endif /*__LIST_H__*/ diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 25702ce3..5e7848b1 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -7,152 +7,132 @@ #ifndef __SCHEDULER_H__ #define __SCHEDULER_H__ -#include #include -#include - -enum {EVERY_SEC = 1, EVERY_MIN, EVERY_HR, EVERY_12H, EVERY_DAY}; -typedef std::function SchedulerCb; - -struct once_t { - uint32_t n; - SchedulerCb f; - once_t(uint32_t a, SchedulerCb b) : n(a), f(b) {} - once_t() : n(0), f(NULL) {} -}; +#include "llist.h" +#include "dbg.h" namespace ah { -class Scheduler { - public: - Scheduler() {} - - void setup() { - mPrevMillis = 0; - mSeconds = 0; - mMinutes = 0; - mHours = 0; - mUptime = 0; - mTimestamp = 0; - } - - void loop() { - if (millis() - mPrevMillis >= 1000) { - mPrevMillis += 1000; - mUptime++; - if(0 != mTimestamp) - mTimestamp++; - notify(&mListSecond); - onceFuncTick(); - if(++mSeconds >= 60) { - mSeconds = 0; - notify(&mListMinute); - onceAtFuncTick(); - if(++mMinutes >= 60) { - mMinutes = 0; - notify(&mListHour); - if(++mHours >= 24) { - mHours = 0; - notify(&mListDay); - notify(&mList12h); - } - else if(mHours == 12) - notify(&mList12h); - } - } + typedef std::function scdCb; + + enum {SCD_SEC = 1, SCD_MIN = 60, SCD_HOUR = 3600, SCD_12H = 43200, SCD_DAY = 86400}; + + struct scdEvry_s { + scdCb c; + uint32_t timeout; + uint32_t reload; + scdEvry_s() : c(NULL), timeout(0), reload(0) {} + scdEvry_s(scdCb a, uint32_t tmt, uint32_t rl) : c(a), timeout(tmt), reload(rl) {} + }; + + struct scdAt_s { + scdCb c; + uint32_t timestamp; + scdAt_s() : c(NULL), timestamp(0) {} + scdAt_s(scdCb a, uint32_t ts) : c(a), timestamp(ts) {} + }; + + + typedef node_s sP; + typedef node_s sPAt; + class Scheduler { + public: + Scheduler() {} + + void setup() { + mUptime = 0; + mTimestamp = 0; + mPrevMillis = millis(); } - } - // checked every second - void once(uint32_t sec, SchedulerCb cb, const char *info = NULL) { - if(NULL != info) { - DPRINT(DBG_INFO, F("once in [s]: ") + String(sec)); - DBGPRINTLN(F(", ") + String(info)); - } - mOnce.push_back(once_t(sec, cb)); - } - - // checked every minute - void onceAt(uint32_t timestamp, SchedulerCb cb, const char *info = NULL) { - if(timestamp > mTimestamp) { - if(NULL != info) { - DPRINT(DBG_INFO, F("onceAt (UTC): ") + getDateTimeStr(timestamp)); - DBGPRINTLN(F(", ") + String(info)); + void loop(void) { + mMillis = millis(); + mDiff = mMillis - mPrevMillis; + if(mDiff >= 1000) { + if(mMillis < mPrevMillis) { // overflow + mPrevMillis = mMillis; + return; + } + mDiffSeconds = mDiff / 1000; + mPrevMillis += (mPrevMillis * 1000); + checkEvery(); + checkAt(); + mUptime += mDiffSeconds; + if(0 != mTimestamp) + mTimestamp += mDiffSeconds; } - mOnceAt.push_back(once_t(timestamp, cb)); } - } - - virtual void setTimestamp(uint32_t ts) { - mTimestamp = ts; - } - - void addListener(uint8_t every, SchedulerCb cb) { - switch(every) { - case EVERY_SEC: mListSecond.push_back(cb); break; - case EVERY_MIN: mListMinute.push_back(cb); break; - case EVERY_HR: mListHour.push_back(cb); break; - case EVERY_12H: mList12h.push_back(cb); break; - case EVERY_DAY: mListDay.push_back(cb); break; - default: break; + + void once(scdCb c, uint32_t timeout) { mStack.add(c, timeout, 0); } + void every(scdCb c, uint32_t interval) { mStack.add(c, interval, interval); } + void onceAt(scdCb c, uint32_t timestamp) { mStackAt.add(c, timestamp); } + + void everySec(scdCb c) { mStack.add(c, SCD_SEC, SCD_SEC); } + void everyMin(scdCb c) { mStack.add(c, SCD_MIN, SCD_MIN); } + void everyHour(scdCb c) { mStack.add(c, SCD_HOUR, SCD_HOUR); } + void every12h(scdCb c) { mStack.add(c, SCD_12H, SCD_12H); } + void everyDay(scdCb c) { mStack.add(c, SCD_DAY, SCD_DAY); } + + virtual void setTimestamp(uint32_t ts) { + mTimestamp = ts; } - } - uint32_t getUptime(void) { - return mUptime; - } + uint32_t getUptime(void) { + return mUptime; + } - uint32_t getTimestamp(void) { - return mTimestamp; - } + uint32_t getTimestamp(void) { + return mTimestamp; + } - protected: - virtual void notify(std::list *lType) { - for(std::list::iterator it = lType->begin(); it != lType->end(); ++it) { - (*it)(); + void stat() { + DPRINTLN(DBG_INFO, "max fill every: " + String(mStack.getMaxFill())); + DPRINTLN(DBG_INFO, "max fill at: " + String(mStackAt.getMaxFill())); } - } - - uint32_t mTimestamp; - - private: - void onceFuncTick(void) { - if(mOnce.empty()) - return; - for(std::list::iterator it = mOnce.begin(); it != mOnce.end();) { - if(((*it).n)-- == 0) { - ((*it).f)(); - it = mOnce.erase(it); + + protected: + uint32_t mTimestamp; + + private: + inline void checkEvery(void) { + bool expired; + sP *p = mStack.getFront(); + while(NULL != p) { + if(mDiffSeconds >= p->d.timeout) expired = true; + else if((p->d.timeout--) == 0) expired = true; + else expired = false; + + if(expired) { + (p->d.c)(); + if(0 == p->d.reload) + p = mStack.rem(p); + else { + p->d.timeout = p->d.reload - 1; + p = mStack.get(p); + } + } + else + p = mStack.get(p); } - else - ++it; } - } - - void onceAtFuncTick(void) { - if(mOnceAt.empty()) - return; - for(std::list::iterator it = mOnceAt.begin(); it != mOnceAt.end();) { - if(((*it).n) < mTimestamp) { - ((*it).f)(); - it = mOnceAt.erase(it); + + inline void checkAt(void) { + sPAt *p = mStackAt.getFront(); + while(NULL != p) { + if((p->d.timestamp) <= mTimestamp) { + (p->d.c)(); + p = mStackAt.rem(p); + } + else + p = mStackAt.get(p); } - else - ++it; } - } - - std::list mListSecond; - std::list mListMinute; - std::list mListHour; - std::list mList12h; - std::list mListDay; - - std::list mOnce; - std::list mOnceAt; - uint32_t mPrevMillis, mUptime; - uint8_t mSeconds, mMinutes, mHours; -}; + llist<25, scdEvry_s, scdCb, uint32_t, uint32_t> mStack; + llist<10, scdAt_s, scdCb, uint32_t> mStackAt; + uint32_t mMillis, mPrevMillis, mDiff; + uint32_t mUptime; + uint8_t mDiffSeconds; + }; } #endif /*__SCHEDULER_H__*/ diff --git a/src/utils/sun.h b/src/utils/sun.h index c6d078d4..c66149c2 100644 --- a/src/utils/sun.h +++ b/src/utils/sun.h @@ -10,7 +10,7 @@ namespace ah { void calculateSunriseSunset(uint32_t utcTs, uint32_t offset, float lat, float lon, uint32_t *sunrise, uint32_t *sunset) { // Source: https://en.wikipedia.org/wiki/Sunrise_equation#Complete_calculation_on_Earth - // Julian day since 1.1.2000 12:00 + correction 69.12s + // Julian day since 1.1.2000 12:00 double n_JulianDay = (utcTs + offset) / 86400 - 10957.0; // Mean solar time double J = n_JulianDay - lon / 360; diff --git a/src/web/html/index.html b/src/web/html/index.html index 070aee31..b5b55b9b 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -48,9 +48,6 @@
Discuss with us on Discord
-

Documentation

- ahoydtu.de -

Support this project:

  • Report issues
  • @@ -118,10 +115,13 @@ var sec = up % 60; var sunrise = new Date(obj["ts_sunrise"] * 1000); var sunset = new Date(obj["ts_sunset"] * 1000); - document.getElementById("uptime").innerHTML = days + " Days, " - + ("0"+hrs).substr(-2) + ":" - + ("0"+min).substr(-2) + ":" - + ("0"+sec).substr(-2); + var e = document.getElementById("uptime"); + e.innerHTML = days + " Day"; + if(1 != days) + e.innerHTML += "s"; + e.innerHTML += ", " + ("0"+hrs).substr(-2) + ":" + + ("0"+min).substr(-2) + ":" + + ("0"+sec).substr(-2); var dSpan = document.getElementById("date"); if(0 != obj["ts_now"]) dSpan.innerHTML = date.toLocaleString('de-DE'); diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 82370031..c8a10412 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -8,8 +8,6 @@ #define F(sl) (sl) #endif #include "ahoywifi.h" -#include "../utils/ahoyTimer.h" - // NTP CONFIG #define NTP_PACKET_SIZE 48 @@ -25,17 +23,16 @@ ahoywifi::ahoywifi() { //----------------------------------------------------------------------------- void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp) { - char ipSta[16]; mConfig = config; mUtcTimestamp = utcTimestamp; #if !defined(FB_WIFI_OVERRIDDEN) - if(strncmp(mConfig->sys.stationSsid, FB_WIFI_SSID, 14) != 0) + if(strncmp(mConfig->sys.stationSsid, FB_WIFI_SSID, 14) == 0) setupAp(); #endif #if !defined(AP_ONLY) - setupStation(); - ah::ip2Char(mConfig->sys.ip.ip, ipSta); + if(mConfig->valid) + setupStation(); #endif #if defined(ESP8266) From 1fb45191a1ee62f964c7558ac794f62f771c3b74 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 8 Dec 2022 17:52:40 +0100 Subject: [PATCH 18/20] added builds for ESP32 and Nokia5110 and SSD1306 displays --- .github/workflows/compile_development.yml | 2 +- .github/workflows/compile_esp8266.yml | 2 +- src/platformio.ini | 34 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 8188e6d1..8d8ab45e 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -47,7 +47,7 @@ jobs: run: python convert.py - name: Run PlatformIO - run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release + run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 - name: Rename Binary files id: rename-binary-files diff --git a/.github/workflows/compile_esp8266.yml b/.github/workflows/compile_esp8266.yml index ab902f74..a3c450d2 100644 --- a/.github/workflows/compile_esp8266.yml +++ b/.github/workflows/compile_esp8266.yml @@ -49,7 +49,7 @@ jobs: working-directory: src/web/html run: python convert.py - name: Run PlatformIO - run: pio run -d tools/esp8266 --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release + run: pio run -d tools/esp8266 --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 - name: Rename Binary files id: rename-binary-files diff --git a/src/platformio.ini b/src/platformio.ini index 3bf76cdc..406bcdd1 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -143,3 +143,37 @@ monitor_filters = ;default ; Remove typical terminal control codes from input time ; Add timestamp with milliseconds for each new line log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory + +[env:esp32-wroom32-nokia5110] +platform = espressif32 +board = lolin_d32 +build_flags = -D RELEASE -std=gnu++14 -DU8X8_NO_HW_I2C -DENA_NOKIA +build_unflags = -std=gnu++11 +monitor_filters = + ;default ; Remove typical terminal control codes from input + time ; Add timestamp with milliseconds for each new line + ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory +lib_deps = + https://github.com/yubox-node-org/ESPAsyncWebServer + nrf24/RF24 + paulstoffregen/Time + https://github.com/bertmelis/espMqttClient#v1.3.3 + bblanchon/ArduinoJson + olikraus/U8g2 + +[env:esp32-wroom32-ssd1306] +platform = espressif32 +board = lolin_d32 +build_flags = -D RELEASE -std=gnu++14 -DENA_SSD1306 +build_unflags = -std=gnu++11 +monitor_filters = + ;default ; Remove typical terminal control codes from input + time ; Add timestamp with milliseconds for each new line + ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory +lib_deps = + https://github.com/yubox-node-org/ESPAsyncWebServer + nrf24/RF24 + paulstoffregen/Time + https://github.com/bertmelis/espMqttClient#v1.3.3 + bblanchon/ArduinoJson + https://github.com/ThingPulse/esp8266-oled-ssd1306.git From a9e4429f9f3b88d9ab25b498f52a5e1b5b0e53b1 Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 8 Dec 2022 21:08:58 +0100 Subject: [PATCH 19/20] system clock was too fast included display builds into zip removed 10s info from index.html --- scripts/getVersion.py | 27 ++++++++++++++++++++++++++- src/defines.h | 2 +- src/publisher/pubMqtt.h | 2 +- src/utils/scheduler.h | 5 ++++- src/web/html/index.html | 21 +++++++++++++++++---- 5 files changed, 49 insertions(+), 8 deletions(-) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index 21406418..ffcc2714 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -51,13 +51,26 @@ def readVersion(path, infile): os.mkdir(path + "firmware/") sha = os.getenv("SHA",default="sha") + versionout = version[:-1] + "_esp8266_" + sha + ".bin" 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_nokia5110_" + sha + ".bin" + src = path + ".pio/build/esp8266-nokia5110/firmware.bin" + dst = path + "firmware/" + versionout + os.rename(src, dst) + gzip_bin(dst, dst + ".gz") + + versionout = version[:-1] + "_esp8266_ssd1306_" + sha + ".bin" + src = path + ".pio/build/esp8266-ssd1306/firmware.bin" + dst = path + "firmware/" + versionout + os.rename(src, dst) + gzip_bin(dst, dst + ".gz") - versionout = version[:-1] + "_esp8266_1m_" + sha + ".bin" + versionout = version[:-1] + "_esp8285_" + sha + ".bin" src = path + ".pio/build/esp8285-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) @@ -69,6 +82,18 @@ def readVersion(path, infile): os.rename(src, dst) gzip_bin(dst, dst + ".gz") + versionout = version[:-1] + "_esp32_nokia5110_" + sha + ".bin" + src = path + ".pio/build/esp32-wroom32-nokia5110/firmware.bin" + dst = path + "firmware/" + versionout + os.rename(src, dst) + gzip_bin(dst, dst + ".gz") + + versionout = version[:-1] + "_esp32_ssd1306_" + sha + ".bin" + src = path + ".pio/build/esp32-wroom32-ssd1306/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/" dst = path + "firmware/" diff --git a/src/defines.h b/src/defines.h index d0c7a85f..d223d764 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 49 +#define VERSION_PATCH 50 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index dbea4957..9a890f86 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -236,7 +236,7 @@ class PubMqtt { publish("version", mVersion, true); publish("device", mDevName, true); - publish("uptime", "0"); + tickerMinute(); publish(mLwtTopic, mLwtOnline, true, false); subscribe("ctrl/#"); diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 5e7848b1..d10b56fd 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -42,6 +42,7 @@ namespace ah { mUptime = 0; mTimestamp = 0; mPrevMillis = millis(); + mDiffFraq = 0; } void loop(void) { @@ -53,7 +54,8 @@ namespace ah { return; } mDiffSeconds = mDiff / 1000; - mPrevMillis += (mPrevMillis * 1000); + mDiffFraq = mDiff % 1000; + mPrevMillis += (mDiffSeconds * 1000) - mDiffFraq; checkEvery(); checkAt(); mUptime += mDiffSeconds; @@ -132,6 +134,7 @@ namespace ah { uint32_t mMillis, mPrevMillis, mDiff; uint32_t mUptime; uint8_t mDiffSeconds; + uint16_t mDiffFraq; }; } diff --git a/src/web/html/index.html b/src/web/html/index.html index b5b55b9b..6833e10d 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -39,12 +39,11 @@

WiFi RSSI: dBm

- Statistics: + System Infos:


                     

                     

                 

-

Every seconds the values are updated

Discuss with us on Discord
@@ -79,6 +78,8 @@