From 19e86ebdb28ac1756865223798021435e4541253 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 1 Jan 2023 23:37:13 +0100 Subject: [PATCH 001/215] changed calculation of start / stop communication to 1 min after last comm. stop #515 moved payload send to `payload.h`, function `ivSend` #515 --- src/CHANGES.md | 39 +++------------------ src/app.cpp | 63 ++++++++-------------------------- src/defines.h | 2 +- src/hm/payload.h | 89 ++++++++++++++++++++++++++++++------------------ 4 files changed, 76 insertions(+), 117 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f8a2c5cab..e62f1333c 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,36 +1,7 @@ -# Changelog v0.5.66 +# Changelog -**Note:** Version `0.5.42` to `0.5.65` were development versions. Last release version was `0.5.41` -Detailed change log (development changes): https://github.com/lumapu/ahoy/blob/945a671d27d10d0f7c175ebbf2fbb2806f9cd79a/src/CHANGES.md +(starting from release version `0.5.66`) - -* updated REST API and MQTT (both of them use the same functionality) -* improved stability -* Regular expressions for input fields which are used for MQTT to be compliant to MQTT -* WiFi optimization (AP Mode and STA in parallel, reconnect if local STA is unavailable) -* improved display of `/system` -* fix Update button protection (prevent double click #527) -* optimized scheduler #515 -* fix of duplicates in API `/api/record/live` (#526) -* added update information to `index.html` (check for update with github.com) -* fix web logout (auto logout) -* switched MQTT library -* removed MQTT `available_text` (can be deducted from `available`) -* enhanced MQTT documentation in `User_Manual.md` -* changed MQTT topic `status` to nummeric value, check documentation in `User_Manual.md` -* added immediate (each minute) report of inverter status MQTT #522 -* increased MQTT user, pwd and topic length to 64 characters + `\0`. (The string end `\0` reduces the available size by one) #516 -* added disable night communication flag to MQTT #505 -* added MQTT /status to show status over all inverters -* added MQTT RX counter to index.html -* added protection mask to select which pages should be protected -* added monochrome display that show values also if nothing changed and in offline mode #498 -* added icons to index.html, added WiFi-strength symbol on each page -* refactored communication offset (adjustable in minutes now) -* factory reset formats entire little fs -* renamed sunrise / sunset on index.html to start / stop communication -* fixed static IP save -* fix NTP with static IP -* all values are displayed on /live even if they are 0 -* added NRF24 info to Systeminfo -* reordered enqueue commands after boot up to prevent same payload length for successive commands +## 0.5.67 +* changed calculation of start / stop communication to 1 min after last comm. stop #515 +* moved payload send to `payload.h`, function `ivSend` #515 diff --git a/src/app.cpp b/src/app.cpp index 29df1e648..b0e310c4b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -51,7 +51,7 @@ void app::setup() { #endif mSys->addInverters(&mConfig->inst); - mPayload.setup(mSys); + mPayload.setup(mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); mPayload.enableSerialDebug(mConfig->serial.debug); if(!mSys->Radio.isChipConnected()) @@ -115,7 +115,9 @@ void app::loop(void) { yield(); if (rxRdy) - mPayload.process(true, mConfig->nrf.maxRetransPerPyld, &mStat); + mPayload.process(true); + + mRxTicker = 0; } #if !defined(AP_ONLY) @@ -138,14 +140,18 @@ void app::tickNtpUpdate(void) { //----------------------------------------------------------------------------- void app::tickCalcSunrise(void) { - ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); + if (mSunrise == 0) // on boot/reboot calc sun values for current time + ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); + + if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) // current time is past communication stop, calc sun values for next day + ah::calculateSunriseSunset(mTimestamp + 86400, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); + tickIVCommunication(); - uint32_t nxtTrig = mTimestamp - ((mTimestamp + mCalculatedTimezoneOffset - 10) % 86400) + 86400;; // next midnight, -10 for safety that it is certain next day, local timezone + uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig); - if (mConfig->mqtt.broker[0] > 0) { + if (mConfig->mqtt.broker[0] > 0) mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom); - } } //----------------------------------------------------------------------------- @@ -187,49 +193,8 @@ void app::tickSend(void) { } while ((NULL == iv) && ((maxLoop--) > 0)); if (NULL != iv) { - if(iv->config->enabled) { - 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, mTimestamp); - 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); // read back power limit - } 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; - } - } + if(iv->config->enabled) + mPayload.ivSend(iv); } } else { if (mConfig->serial.debug) diff --git a/src/defines.h b/src/defines.h index ddfda35e1..301c9f17b 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 66 +#define VERSION_PATCH 67 //------------------------------------- typedef struct { diff --git a/src/hm/payload.h b/src/hm/payload.h index ef69c4fee..9a23fbfe7 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -34,45 +34,65 @@ class Payload : public Handler { public: Payload() : Handler() {} - void setup(HMSYSTEM *sys) { - mSys = sys; + void setup(HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { + mSys = sys; + mStat = stat; + mMaxRetrans = maxRetransmits; + mTimestamp = timestamp; memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t))); mLastPacketId = 0x00; - mSerialDebug = false; + mSerialDebug = false; } void enableSerialDebug(bool enable) { mSerialDebug = enable; } - bool isComplete(Inverter<> *iv) { - return mPayload[iv->id].complete; - } - - uint8_t getMaxPacketId(Inverter<> *iv) { - return mPayload[iv->id].maxPackId; - } - - uint8_t getRetransmits(Inverter<> *iv) { - return mPayload[iv->id].retransmits; + void notify(uint8_t val) { + for(typename std::list::iterator it = mList.begin(); it != mList.end(); ++it) { + (*it)(val); + } } - uint32_t getTs(Inverter<> *iv) { - return mPayload[iv->id].ts; - } + void ivSend(Inverter<> *iv) { + if (!mPayload[iv->id].complete) + process(false); - void request(Inverter<> *iv) { - mPayload[iv->id].requested = true; - } + if (!mPayload[iv->id].complete) { + if (0 == mPayload[iv->id].maxPackId) + mStat->rxFailNoAnser++; + else + mStat->rxFail++; + + iv->setQueuedCmdFinished(); // command failed + if (mSerialDebug) + DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); + if (mSerialDebug) { + DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); + DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); + } + } - void setTxCmd(Inverter<> *iv, uint8_t cmd) { - mPayload[iv->id].txCmd = cmd; - } + reset(iv); + mPayload[iv->id].requested = true; - void notify(uint8_t val) { - for(typename std::list::iterator it = mList.begin(); it != mList.end(); ++it) { - (*it)(val); - } + yield(); + if (mSerialDebug) + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX)); + + if (iv->devControlRequest) { + if (mSerialDebug) + 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[iv->id].txCmd = iv->devControlCmd; + iv->clearCmdQueue(); + iv->enqueCommand(SystemConfigPara); // read back power limit + } else { + uint8_t cmd = iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); + mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex); + mPayload[iv->id].txCmd = cmd; + } } void add(packet_t *p, uint8_t len) { @@ -134,7 +154,7 @@ class Payload : public Handler { return (crc == crcRcv) ? true : false; } - void process(bool retransmit, uint8_t maxRetransmits, statistics_t *stat) { + void process(bool retransmit) { for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if (NULL == iv) @@ -152,9 +172,9 @@ class Payload : public Handler { if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) { // This is required to prevent retransmissions without answer. DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); - mPayload[iv->id].retransmits = maxRetransmits; + mPayload[iv->id].retransmits = mMaxRetrans; } else { - if (mPayload[iv->id].retransmits < maxRetransmits) { + if (mPayload[iv->id].retransmits < mMaxRetrans) { mPayload[iv->id].retransmits++; if (mPayload[iv->id].maxPackId != 0) { for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { @@ -207,7 +227,7 @@ class Payload : public Handler { DPRINTLN(DBG_ERROR, F("record is NULL!")); } else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { if (mPayload[iv->id].txId == (TX_REQ_INFO + 0x80)) - stat->rxSuccess++; + mStat->rxSuccess++; rec->ts = mPayload[iv->id].ts; for (uint8_t i = 0; i < rec->length; i++) { @@ -218,7 +238,7 @@ class Payload : public Handler { notify(mPayload[iv->id].txCmd); } else { DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes")); - stat->rxFail++; + mStat->rxFail++; } iv->setQueuedCmdFinished(); @@ -230,7 +250,7 @@ class Payload : public Handler { } } - void reset(Inverter<> *iv, uint32_t utcTs) { + void reset(Inverter<> *iv) { DPRINTLN(DBG_INFO, "resetPayload: id: " + String(iv->id)); memset(mPayload[iv->id].len, 0, MAX_PAYLOAD_ENTRIES); mPayload[iv->id].txCmd = 0; @@ -238,11 +258,14 @@ class Payload : public Handler { mPayload[iv->id].maxPackId = 0; mPayload[iv->id].complete = false; mPayload[iv->id].requested = false; - mPayload[iv->id].ts = utcTs; + mPayload[iv->id].ts = *mTimestamp; } private: HMSYSTEM *mSys; + statistics_t *mStat; + uint8_t mMaxRetrans; + uint32_t *mTimestamp; invPayload_t mPayload[MAX_NUM_INVERTERS]; uint8_t mLastPacketId; bool mSerialDebug; From 2f19e70852463219635a8f92e6ee1f2e0217eed0 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 1 Jan 2023 23:49:31 +0100 Subject: [PATCH 002/215] payload: if last frame is missing, request all frames again --- src/CHANGES.md | 1 + src/hm/payload.h | 88 +++++++++++++++++++++++------------------------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index e62f1333c..0ba677a34 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,3 +5,4 @@ ## 0.5.67 * changed calculation of start / stop communication to 1 min after last comm. stop #515 * moved payload send to `payload.h`, function `ivSend` #515 +* payload: if last frame is missing, request all frames again diff --git a/src/hm/payload.h b/src/hm/payload.h index 9a23fbfe7..bc8b49493 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -21,6 +21,7 @@ typedef struct { uint8_t len[MAX_PAYLOAD_ENTRIES]; bool complete; uint8_t maxPackId; + bool lastFound; uint8_t retransmits; bool requested; } invPayload_t; @@ -56,43 +57,43 @@ class Payload : public Handler { void ivSend(Inverter<> *iv) { if (!mPayload[iv->id].complete) - process(false); + process(false); - if (!mPayload[iv->id].complete) { - if (0 == mPayload[iv->id].maxPackId) - mStat->rxFailNoAnser++; - else - mStat->rxFail++; - - iv->setQueuedCmdFinished(); // command failed - if (mSerialDebug) - DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); - if (mSerialDebug) { - DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); - DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); - } + if (!mPayload[iv->id].complete) { + if (0 == mPayload[iv->id].maxPackId) + mStat->rxFailNoAnser++; + else + mStat->rxFail++; + + iv->setQueuedCmdFinished(); // command failed + if (mSerialDebug) + DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); + if (mSerialDebug) { + DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); + DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); } + } - reset(iv); - mPayload[iv->id].requested = true; + reset(iv); + mPayload[iv->id].requested = true; - yield(); + yield(); + if (mSerialDebug) + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX)); + + if (iv->devControlRequest) { if (mSerialDebug) - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX)); - - if (iv->devControlRequest) { - if (mSerialDebug) - 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[iv->id].txCmd = iv->devControlCmd; - iv->clearCmdQueue(); - iv->enqueCommand(SystemConfigPara); // read back power limit - } else { - uint8_t cmd = iv->getQueuedCmd(); - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); - mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex); - mPayload[iv->id].txCmd = cmd; - } + 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[iv->id].txCmd = iv->devControlCmd; + iv->clearCmdQueue(); + iv->enqueCommand(SystemConfigPara); // read back power limit + } else { + uint8_t cmd = iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); + mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex); + mPayload[iv->id].txCmd = cmd; + } } void add(packet_t *p, uint8_t len) { @@ -115,7 +116,7 @@ class Payload : public Handler { if ((*pid & 0x7f) > mPayload[iv->id].maxPackId) { mPayload[iv->id].maxPackId = (*pid & 0x7f); if (*pid > 0x81) - mLastPacketId = *pid; + mPayload[iv->id].lastFound = true; } } } @@ -162,7 +163,6 @@ class Payload : public Handler { if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) { // no processing needed if txId is not 0x95 - // DPRINTLN(DBG_INFO, F("processPayload - set complete, txId: ") + String(mPayload[iv->id].txId, HEX)); mPayload[iv->id].complete = true; } @@ -176,24 +176,20 @@ class Payload : public Handler { } else { if (mPayload[iv->id].retransmits < mMaxRetrans) { mPayload[iv->id].retransmits++; - if (mPayload[iv->id].maxPackId != 0) { + if(false == mPayload[iv->id].lastFound) { + DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Complete Retransmit")); + mPayload[iv->id].txCmd = iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); + mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex); + } else if (mPayload[iv->id].maxPackId != 0) { for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { if (mPayload[iv->id].len[i] == 0) { DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i + 1) + F(" missing: Request Retransmit")); mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true); - break; // only retransmit one frame per loop + break; // only request retransmit one frame per loop } yield(); } - } else { - DPRINTLN(DBG_WARN, F("while retrieving data: last frame missing: Request Retransmit")); - if (0x00 != mLastPacketId) - mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, mLastPacketId, true); - else { - mPayload[iv->id].txCmd = iv->getQueuedCmd(); - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); - mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex); - } } mSys->Radio.switchRxCh(100); } @@ -256,6 +252,7 @@ class Payload : public Handler { mPayload[iv->id].txCmd = 0; mPayload[iv->id].retransmits = 0; mPayload[iv->id].maxPackId = 0; + mPayload[iv->id].lastFound = false; mPayload[iv->id].complete = false; mPayload[iv->id].requested = false; mPayload[iv->id].ts = *mTimestamp; @@ -267,7 +264,6 @@ class Payload : public Handler { uint8_t mMaxRetrans; uint32_t *mTimestamp; invPayload_t mPayload[MAX_NUM_INVERTERS]; - uint8_t mLastPacketId; bool mSerialDebug; }; From 4a00f90e3481c0e6e607dde8030414b5def3f95d Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 1 Jan 2023 23:57:02 +0100 Subject: [PATCH 003/215] fix compile --- src/hm/payload.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hm/payload.h b/src/hm/payload.h index bc8b49493..2a37be325 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -41,7 +41,6 @@ class Payload : public Handler { mMaxRetrans = maxRetransmits; mTimestamp = timestamp; memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t))); - mLastPacketId = 0x00; mSerialDebug = false; } From 27b3a9cd87b195109c7b2b5f7e22103213e2c46e Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 2 Jan 2023 12:26:03 +0100 Subject: [PATCH 004/215] Powerlimit is transfered immediately to inverter --- src/CHANGES.md | 4 ++++ src/app.cpp | 3 +-- src/app.h | 4 ++++ src/appInterface.h | 3 +++ src/defines.h | 2 +- src/hm/payload.h | 45 ++++++++++++++++++++++++++++++--------------- src/web/RestApi.h | 1 + 7 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 0ba677a34..4a0a2f94d 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,10 @@ (starting from release version `0.5.66`) +## 0.5.68 +* repaired receive payload +* Powerlimit is transfered immediately to inverter + ## 0.5.67 * changed calculation of start / stop communication to 1 min after last comm. stop #515 * moved payload send to `payload.h`, function `ivSend` #515 diff --git a/src/app.cpp b/src/app.cpp index b0e310c4b..7ad48f26b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -90,6 +90,7 @@ void app::loop(void) { ah::Scheduler::loop(); mSys->Radio.loop(); + mPayload.loop(); yield(); @@ -116,8 +117,6 @@ void app::loop(void) { if (rxRdy) mPayload.process(true); - - mRxTicker = 0; } #if !defined(AP_ONLY) diff --git a/src/app.h b/src/app.h index 54dca314c..8663c2a99 100644 --- a/src/app.h +++ b/src/app.h @@ -122,6 +122,10 @@ class app : public IApp, public ah::Scheduler { once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1); } + void ivSendHighPrio(Inverter<> *iv) { + mPayload.ivSendHighPrio(iv); + } + bool getMqttIsConnected() { return mMqtt.isConnected(); } diff --git a/src/appInterface.h b/src/appInterface.h index 94f753992..64acab6be 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -7,6 +7,7 @@ #define __IAPP_H__ #include "defines.h" +#include "hm/hmSystem.h" // abstract interface to App. Make members of App accessible from child class // like web or API without forward declaration @@ -34,6 +35,8 @@ class IApp { virtual bool getSettingsValid() = 0; virtual void setMqttDiscoveryFlag() = 0; + virtual void ivSendHighPrio(Inverter<> *iv) = 0; + virtual bool getMqttIsConnected() = 0; virtual uint32_t getMqttRxCnt() = 0; virtual uint32_t getMqttTxCnt() = 0; diff --git a/src/defines.h b/src/defines.h index 301c9f17b..616caa0d6 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 67 +#define VERSION_PATCH 68 //------------------------------------- typedef struct { diff --git a/src/hm/payload.h b/src/hm/payload.h index 2a37be325..b0ea74645 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -42,6 +42,7 @@ class Payload : public Handler { mTimestamp = timestamp; memset(mPayload, 0, (MAX_NUM_INVERTERS * sizeof(invPayload_t))); mSerialDebug = false; + mHighPrioIv = NULL; } void enableSerialDebug(bool enable) { @@ -54,22 +55,35 @@ class Payload : public Handler { } } - void ivSend(Inverter<> *iv) { - if (!mPayload[iv->id].complete) - process(false); + void loop() { + if(NULL != mHighPrioIv) { + ivSend(mHighPrioIv, true); + mHighPrioIv = NULL; + } + } - if (!mPayload[iv->id].complete) { - if (0 == mPayload[iv->id].maxPackId) - mStat->rxFailNoAnser++; - else - mStat->rxFail++; + void ivSendHighPrio(Inverter<> *iv) { + mHighPrioIv = iv; + } - iv->setQueuedCmdFinished(); // command failed - if (mSerialDebug) - DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); - if (mSerialDebug) { - DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); - DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); + void ivSend(Inverter<> *iv, bool highPrio = false) { + if(!highPrio) { + if (!mPayload[iv->id].complete) + process(false); + + if (!mPayload[iv->id].complete) { + if (0 == mPayload[iv->id].maxPackId) + mStat->rxFailNoAnser++; + else + mStat->rxFail++; + + iv->setQueuedCmdFinished(); // command failed + if (mSerialDebug) + DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout")); + if (mSerialDebug) { + DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") "); + DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")"); + } } } @@ -180,7 +194,7 @@ class Payload : public Handler { mPayload[iv->id].txCmd = iv->getQueuedCmd(); DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex); - } else if (mPayload[iv->id].maxPackId != 0) { + } else { for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { if (mPayload[iv->id].len[i] == 0) { DPRINTLN(DBG_WARN, F("while retrieving data: Frame ") + String(i + 1) + F(" missing: Request Retransmit")); @@ -264,6 +278,7 @@ class Payload : public Handler { uint32_t *mTimestamp; invPayload_t mPayload[MAX_NUM_INVERTERS]; bool mSerialDebug; + Inverter<> *mHighPrioIv; }; #endif /*__PAYLOAD_H_*/ diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 8ad55e26d..4246a8661 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -534,6 +534,7 @@ class RestApi { iv->powerLimit[1] = AbsolutNonPersistent; iv->devControlCmd = ActivePowerContr; iv->devControlRequest = true; + mApp->ivSendHighPrio(iv); } else if(F("dev") == jsonIn[F("cmd")]) { DPRINTLN(DBG_INFO, F("dev cmd")); From 94487641cc18f2a57b839c41d671fc869e72a55b Mon Sep 17 00:00:00 2001 From: dAjaY85 Date: Fri, 6 Jan 2023 12:08:05 +0100 Subject: [PATCH 005/215] =?UTF-8?q?Hinzuf=C3=BCgen=20vom=201,3"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.cpp | 2 +- src/app.h | 6 ++-- src/platformio.ini | 36 +++++++++++++++++++ .../MonochromeDisplay/MonochromeDisplay.h | 20 +++++++---- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 7ad48f26b..83be5a36d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -74,7 +74,7 @@ void app::setup() { mApi.setup(this, mSys, mWeb.getWebSrvPtr(), mConfig); // Plugins - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) + #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) mMonoDisplay.setup(mSys, &mTimestamp); everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay)); #endif diff --git a/src/app.h b/src/app.h index 8663c2a99..5a4bb4944 100644 --- a/src/app.h +++ b/src/app.h @@ -46,7 +46,7 @@ typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; // PLUGINS -#if defined(ENA_NOKIA) || defined(ENA_SSD1306) +#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) #include "plugins/MonochromeDisplay/MonochromeDisplay.h" typedef MonochromeDisplay MonoDisplayType; #endif @@ -180,7 +180,7 @@ class app : public IApp, public ah::Scheduler { #if !defined(AP_ONLY) mMqtt.payloadEventListener(cmd); #endif - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) + #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) mMonoDisplay.payloadEventListener(cmd); #endif } @@ -244,7 +244,7 @@ class app : public IApp, public ah::Scheduler { uint32_t mSunrise, mSunset; // plugins - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) + #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) MonoDisplayType mMonoDisplay; #endif }; diff --git a/src/platformio.ini b/src/platformio.ini index ba6c8be0b..d64a8c93a 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -125,6 +125,24 @@ lib_deps = https://github.com/ThingPulse/esp8266-oled-ssd1306.git https://github.com/JChristensen/Timezone +[env:esp8266-sh1106] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L +build_flags = -D RELEASE -DENA_SH1106 +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 + https://github.com/JChristensen/Timezone + [env:esp32-wroom32-release] platform = espressif32 board = lolin_d32 @@ -181,3 +199,21 @@ lib_deps = bblanchon/ArduinoJson https://github.com/ThingPulse/esp8266-oled-ssd1306.git https://github.com/JChristensen/Timezone + +[env:esp32-wroom32-sh1106] +platform = espressif32 +board = lolin_d32 +build_flags = -D RELEASE -std=gnu++14 -DENA_SH1106 +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 + https://github.com/JChristensen/Timezone \ No newline at end of file diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 832369921..3bfe94589 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -1,15 +1,19 @@ #ifndef __MONOCHROME_DISPLAY__ #define __MONOCHROME_DISPLAY__ -#if defined(ENA_NOKIA) || defined(ENA_SSD1306) +#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) #ifdef ENA_NOKIA #include #define DISP_PROGMEM U8X8_PROGMEM -#else // ENA_SSD1306 +#else // ENA_SSD1306 || ENA_SH1106 /* esp8266 : SCL = 5, SDA = 4 */ /* ewsp32 : SCL = 22, SDA = 21 */ #include - #include + #ifdef ENA_SSD1306 + #include + # else //ENA_SH1106 + #include + #endif #define DISP_PROGMEM PROGMEM #endif @@ -34,7 +38,7 @@ class MonochromeDisplay { mNewPayload = false; mExtra = 0; } - #else // ENA_SSD1306 + #else // ENA_SSD1306 || ENA_SH1106 MonochromeDisplay() : mDisplay(0x3c, SDA, SCL), mCE(CEST, CET) { mNewPayload = false; mExtra = 0; @@ -215,7 +219,7 @@ class MonochromeDisplay { mDisplay.sendBuffer(); } while( mDisplay.nextPage() ); mExtra++; - #else // ENA_SSD1306 + #else // ENA_SSD1306 || ENA_SH1106 if(mUp) { mRx += 2; if(mRx >= 20) @@ -289,7 +293,11 @@ class MonochromeDisplay { #if defined(ENA_NOKIA) U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay; #else // ENA_SSD1306 - SSD1306Wire mDisplay; + #ifdef ENA_SSD1306 + SSD1306Wire mDisplay; + # else //ENA_SH1106 + SH1106Wire mDisplay; + #endif int mRx; char mUp; #endif From 712b5af9b9a32ace2dd8460a0a0e1a96a94040cc Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 7 Jan 2023 01:15:25 +0100 Subject: [PATCH 006/215] merged SH1106 1.3" Display, thx @dAjaY85 added SH1106 to automatic build added IP address to MQTT (version, device and IP are retained and only transmitted once after boot) #556 added `set_power_limit` acknowledge MQTT publish #553 changed: version, device name are only published via MQTT once after boot added `Login` to menu if admin password is set #554 added `development` to second changelog link in `index.html` #543 added interval for MQTT (as option). With this settings MQTT live data is published in a fixed timing (only if inverter is available) #542, #523 added MQTT `comm_disabled` #529 --- .github/workflows/compile_development.yml | 2 +- .github/workflows/compile_release.yml | 2 +- User_Manual.md | 2 + src/CHANGES.md | 11 +++++ src/app.cpp | 9 ++-- src/app.h | 4 ++ src/appInterface.h | 1 + src/config/settings.h | 6 ++- src/defines.h | 2 +- src/hm/payload.h | 10 ++++- src/publisher/pubMqtt.h | 53 ++++++++++++++++++----- src/web/RestApi.h | 24 ++++++---- src/web/html/index.html | 2 +- src/web/html/setup.html | 9 ++-- src/web/web.h | 1 + 15 files changed, 107 insertions(+), 31 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 8d8ab45e5..203e23a77 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 --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 + run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp8266-sh1106 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 --environment esp32-wroom32-sh1106 - name: Rename Binary files id: rename-binary-files diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 20fcef872..91c4c8e3a 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -51,7 +51,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 --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 + run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp8266-nokia5110 --environment esp8266-ssd1306 --environment esp8266-sh1106 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 --environment esp32-wroom32-sh1106 - name: Rename Binary files id: rename-binary-files diff --git a/User_Manual.md b/User_Manual.md index 529125a0d..be7519ec6 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -29,6 +29,7 @@ The AhoyDTU will publish on the following topics | `uptime` | 73630 | uptime in seconds | false | | `version` | 0.5.61 | current installed verison of AhoyDTU | true | | `wifi_rssi` | -75 | WiFi signal strength | false | +| `ip_addr` | 192.168.178.25 | WiFi Station IP Address | true | | status code | Remarks | |---|---| @@ -43,6 +44,7 @@ The AhoyDTU will publish on the following topics |---|---|---|---| | `available` | 2 | see table below | true | | `last_success` | 1672155690 | UTC Timestamp | true | +| `ack_pwr_limit` | true | fast information if inverter has accepted power limit | false | | status code | Remarks | |---|---| diff --git a/src/CHANGES.md b/src/CHANGES.md index 4a0a2f94d..27fd1205b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,17 @@ (starting from release version `0.5.66`) +## 0.5.69 +* merged SH1106 1.3" Display, thx @dAjaY85 +* added SH1106 to automatic build +* added IP address to MQTT (version, device and IP are retained and only transmitted once after boot) #556 +* added `set_power_limit` acknowledge MQTT publish #553 +* changed: version, device name are only published via MQTT once after boot +* added `Login` to menu if admin password is set #554 +* added `development` to second changelog link in `index.html` #543 +* added interval for MQTT (as option). With this settings MQTT live data is published in a fixed timing (only if inverter is available) #542, #523 +* added MQTT `comm_disabled` #529 + ## 0.5.68 * repaired receive payload * Powerlimit is transfered immediately to inverter diff --git a/src/app.cpp b/src/app.cpp index 83be5a36d..3e8da14ff 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -51,7 +51,7 @@ void app::setup() { #endif mSys->addInverters(&mConfig->inst); - mPayload.setup(mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); + mPayload.setup(this, mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); mPayload.enableSerialDebug(mConfig->serial.debug); if(!mSys->Radio.isChipConnected()) @@ -162,14 +162,17 @@ void app::tickIVCommunication(void) { nxtTrig = mSunrise - mConfig->sun.offsetSec; } else { if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise - return; + nxtTrig = 0; } else { // current time lies within communication start/stop time, set next trigger to communication stop mIVCommunicationOn = true; nxtTrig = mSunset + mConfig->sun.offsetSec; } } - onceAt(std::bind(&app::tickIVCommunication, this), nxtTrig); + if (nxtTrig != 0) + onceAt(std::bind(&app::tickIVCommunication, this), nxtTrig); } + if (mConfig->mqtt.broker[0] > 0) + mMqtt.tickerComm(mIVCommunicationOn); } //----------------------------------------------------------------------------- diff --git a/src/app.h b/src/app.h index 5a4bb4944..b08b7d05e 100644 --- a/src/app.h +++ b/src/app.h @@ -122,6 +122,10 @@ class app : public IApp, public ah::Scheduler { once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1); } + void setMqttPowerLimitAck(Inverter<> *iv) { + mMqtt.setPowerLimitAck(iv); + } + void ivSendHighPrio(Inverter<> *iv) { mPayload.ivSendHighPrio(iv); } diff --git a/src/appInterface.h b/src/appInterface.h index 64acab6be..4c25c3a2c 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -34,6 +34,7 @@ class IApp { virtual bool getRebootRequestState() = 0; virtual bool getSettingsValid() = 0; virtual void setMqttDiscoveryFlag() = 0; + virtual void setMqttPowerLimitAck(Inverter<> *iv) = 0; virtual void ivSendHighPrio(Inverter<> *iv) = 0; diff --git a/src/config/settings.h b/src/config/settings.h index 47c59a8c7..921ebc966 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -96,6 +96,7 @@ typedef struct { char user[MQTT_USER_LEN]; char pwd[MQTT_PWD_LEN]; char topic[MQTT_TOPIC_LEN]; + uint16_t interval; } cfgMqtt_t; typedef struct { @@ -297,6 +298,7 @@ class settings { snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", DEF_MQTT_USER); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD); snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); + mCfg.mqtt.interval = 0; // off mCfg.led.led0 = DEF_LED0_PIN; mCfg.led.led1 = DEF_LED1_PIN; @@ -396,8 +398,10 @@ class settings { obj[F("user")] = mCfg.mqtt.user; obj[F("pwd")] = mCfg.mqtt.pwd; obj[F("topic")] = mCfg.mqtt.topic; + obj[F("intvl")] = mCfg.mqtt.interval; } else { - mCfg.mqtt.port = obj[F("port")]; + mCfg.mqtt.port = obj[F("port")]; + mCfg.mqtt.interval = obj[F("intvl")]; snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); diff --git a/src/defines.h b/src/defines.h index 616caa0d6..b37cb6c77 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 68 +#define VERSION_PATCH 69 //------------------------------------- typedef struct { diff --git a/src/hm/payload.h b/src/hm/payload.h index b0ea74645..1f112844e 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -35,7 +35,8 @@ class Payload : public Handler { public: Payload() : Handler() {} - void setup(HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { + void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { + mApp = app; mSys = sys; mStat = stat; mMaxRetrans = maxRetransmits; @@ -141,7 +142,11 @@ class Payload : public Handler { iv->devControlRequest = false; if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { - String msg = (p->packet[10] == 0x00 && p->packet[11] == 0x00) ? "" : "NOT "; + String msg = ""; + if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) { + msg = "NOT "; + mApp->setMqttPowerLimitAck(iv); + } DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has ") + msg + F("accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); } iv->devControlCmd = Init; @@ -272,6 +277,7 @@ class Payload : public Handler { } private: + IApp *mApp; HMSYSTEM *mSys; statistics_t *mStat; uint8_t mMaxRetrans; diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index bd7a709fd..382406f44 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -41,11 +41,13 @@ class PubMqtt { ~PubMqtt() { } void setup(cfgMqtt_t *cfg_mqtt, const char *devName, const char *version, HMSYSTEM *sys, uint32_t *utcTs) { - mCfgMqtt = cfg_mqtt; - mDevName = devName; - mVersion = version; - mSys = sys; - mUtcTimestamp = utcTs; + mCfgMqtt = cfg_mqtt; + mDevName = devName; + mVersion = version; + mSys = sys; + mUtcTimestamp = utcTs; + mExeOnce = true; + mIntervalTimeout = 1; snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); @@ -73,7 +75,16 @@ class PubMqtt { } void tickerSecond() { - sendIvData(); + if(0 == mCfgMqtt->interval) // no fixed interval, publish once new data were received (from inverter) + sendIvData(); + else { // send mqtt data in a fixed interval + if(--mIntervalTimeout == 0) { + mIntervalTimeout = mCfgMqtt->interval; + mSendList.push(RealTimeRunData_Debug); + sendIvData(); + } + } + } void tickerMinute() { @@ -98,9 +109,16 @@ class PubMqtt { publish("dis_night_comm", ((disNightCom) ? "true" : "false"), true); } + void tickerComm(bool disabled) { + publish("comm_disabled", ((disabled) ? "true" : "false"), true); + publish("comm_dis_ts", String(*mUtcTimestamp).c_str(), true); + } + void payloadEventListener(uint8_t cmd) { - if(mClient.connected()) // prevent overflow if MQTT broker is not reachable but set - mSendList.push(cmd); + if(mClient.connected()) { // prevent overflow if MQTT broker is not reachable but set + if((0 == mCfgMqtt->interval) || (RealTimeRunData_Debug != cmd)) // no interval or no live data + mSendList.push(cmd); + } } void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) { @@ -188,6 +206,15 @@ class PubMqtt { } } + void setPowerLimitAck(Inverter<> *iv) { + if (NULL != iv) { + char topic[7 + MQTT_TOPIC_LEN]; + + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ack_pwr_limit", iv->config->name); + publish(topic, "true", true); + } + } + private: #if defined(ESP8266) void onWifiConnect(const WiFiEventStationModeGotIP& event) { @@ -223,8 +250,12 @@ class PubMqtt { DPRINTLN(DBG_INFO, F("MQTT connected")); mEnReconnect = true; - publish("version", mVersion, true); - publish("device", mDevName, true); + if(mExeOnce) { + publish("version", mVersion, true); + publish("device", mDevName, true); + publish("ip_addr", WiFi.localIP().toString().c_str(), true); + mExeOnce = false; + } tickerMinute(); publish(mLwtTopic, mLwtOnline, true, false); @@ -494,6 +525,8 @@ class PubMqtt { subscriptionCb mSubscriptionCb; bool mIvAvail; // shows if at least one inverter is available uint8_t mLastIvState[MAX_NUM_INVERTERS]; + bool mExeOnce; + uint16_t mIntervalTimeout; // last will topic and payload must be available trough lifetime of 'espMqttClient' char mLwtTopic[MQTT_TOPIC_LEN+5]; diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 4246a8661..8986e76d9 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -276,11 +276,12 @@ class RestApi { } void getMqtt(JsonObject obj) { - obj[F("broker")] = String(mConfig->mqtt.broker); - obj[F("port")] = String(mConfig->mqtt.port); - obj[F("user")] = String(mConfig->mqtt.user); - obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); - obj[F("topic")] = String(mConfig->mqtt.topic); + obj[F("broker")] = String(mConfig->mqtt.broker); + obj[F("port")] = String(mConfig->mqtt.port); + obj[F("user")] = String(mConfig->mqtt.user); + obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); + obj[F("topic")] = String(mConfig->mqtt.topic); + obj[F("interval")] = String(mConfig->mqtt.interval); } void getNtp(JsonObject obj) { @@ -357,10 +358,15 @@ class RestApi { obj[F("name")][i] = "Documentation"; obj[F("link")][i] = "https://ahoydtu.de"; obj[F("trgt")][i++] = "_blank"; - if((strlen(mConfig->sys.adminPwd) > 0) && !mApp->getProtection()) { + if(strlen(mConfig->sys.adminPwd) > 0) { obj[F("name")][i++] = "-"; - obj[F("name")][i] = "Logout"; - obj[F("link")][i++] = "/logout"; + if(mApp->getProtection()) { + obj[F("name")][i] = "Login"; + obj[F("link")][i++] = "/login"; + } else { + obj[F("name")][i] = "Logout"; + obj[F("link")][i++] = "/logout"; + } } } @@ -411,6 +417,8 @@ class RestApi { JsonArray info = obj.createNestedArray(F("infos")); if(mApp->getMqttIsConnected()) info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); + if(mConfig->mqtt.interval > 0) + info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); } void getSetup(JsonObject obj) { diff --git a/src/web/html/index.html b/src/web/html/index.html index 0cd7f430d..6634197ee 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -51,7 +51,7 @@

Support this project:

  • Discuss with us on Discord
  • Report issues
  • Contribute to documentation
  • -
  • Download & Test development firmware, Changelog
  • +
  • Download & Test development firmware, Development Changelog
  • make a donation
  • diff --git a/src/web/html/setup.html b/src/web/html/setup.html index cf57f12ba..8377cede7 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -94,7 +94,7 @@

    General

    - + @@ -148,6 +148,9 @@ +

    Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

    + + @@ -170,7 +173,7 @@
    - + @@ -389,7 +392,7 @@ } function parseMqtt(obj) { - for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"]]) + for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"], ["Interval", "interval"]]) document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]]; } diff --git a/src/web/web.h b/src/web/web.h index d8922747f..b3667b64b 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -494,6 +494,7 @@ class Web { 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(); + mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); // serial console if(request->arg("serIntvl") != "") { From cfb74c6a4c54f3963274da754cc2dad42bda6160 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 7 Jan 2023 01:32:53 +0100 Subject: [PATCH 007/215] changed name of binaries, moved GIT-Sha to the front #538 --- scripts/getVersion.py | 24 +++++++++++++++++------- src/CHANGES.md | 3 +++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index f7c825ced..b53c82d4f 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -52,42 +52,52 @@ def readVersion(path, infile): os.mkdir(path + "firmware/") sha = os.getenv("SHA",default="sha") - versionout = version[:-1] + "_esp8266_" + sha + ".bin" + versionout = version[:-1] + "_" + sha + "_esp8266.bin" src = path + ".pio/build/esp8266-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) - versionout = version[:-1] + "_esp8266_nokia5110_" + sha + ".bin" + versionout = version[:-1] + "_" + sha + "_esp8266_nokia5110.bin" src = path + ".pio/build/esp8266-nokia5110/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) - versionout = version[:-1] + "_esp8266_ssd1306_" + sha + ".bin" + versionout = version[:-1] + "_" + sha + "_esp8266_ssd1306.bin" src = path + ".pio/build/esp8266-ssd1306/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) + + versionout = version[:-1] + "_" + sha + "_esp8266_sh1106.bin" + src = path + ".pio/build/esp8266_sh1106/firmware.bin" + dst = path + "firmware/" + versionout + os.rename(src, dst) - versionout = version[:-1] + "_esp8285_" + sha + ".bin" + versionout = version[:-1] + "_" + sha + "_esp8285.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" + versionout = version[:-1] + "_" + sha + "_esp32.bin" src = path + ".pio/build/esp32-wroom32-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) - versionout = version[:-1] + "_esp32_nokia5110_" + sha + ".bin" + versionout = version[:-1] + "_" + sha + "_esp32_nokia5110.bin" src = path + ".pio/build/esp32-wroom32-nokia5110/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) - versionout = version[:-1] + "_esp32_ssd1306_" + sha + ".bin" + versionout = version[:-1] + "_" + sha + "_esp32_ssd1306.bin" src = path + ".pio/build/esp32-wroom32-ssd1306/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) + versionout = version[:-1] + "_" + sha + "_esp32_sh1106.bin" + src = path + ".pio/build/esp32-wroom32-sh1106/firmware.bin" + dst = path + "firmware/" + versionout + os.rename(src, dst) + # other ESP32 bin files src = path + ".pio/build/esp32-wroom32-release/" dst = path + "firmware/" diff --git a/src/CHANGES.md b/src/CHANGES.md index 27fd1205b..8fd8d8add 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,8 @@ (starting from release version `0.5.66`) +## 0.5.70 + ## 0.5.69 * merged SH1106 1.3" Display, thx @dAjaY85 * added SH1106 to automatic build @@ -12,6 +14,7 @@ * added `development` to second changelog link in `index.html` #543 * added interval for MQTT (as option). With this settings MQTT live data is published in a fixed timing (only if inverter is available) #542, #523 * added MQTT `comm_disabled` #529 +* changed name of binaries, moved GIT-Sha to the front #538 ## 0.5.68 * repaired receive payload From 4265856814c59fccf4b6fe3e2bbf0a1647ad2779 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 7 Jan 2023 11:55:45 +0100 Subject: [PATCH 008/215] corrected github action corrected MQTT `comm_disabled` #529 --- scripts/getVersion.py | 2 +- src/CHANGES.md | 1 + src/app.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index b53c82d4f..5f96f37f6 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -68,7 +68,7 @@ def readVersion(path, infile): os.rename(src, dst) versionout = version[:-1] + "_" + sha + "_esp8266_sh1106.bin" - src = path + ".pio/build/esp8266_sh1106/firmware.bin" + src = path + ".pio/build/esp8266-sh1106/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) diff --git a/src/CHANGES.md b/src/CHANGES.md index 8fd8d8add..364b2b4d4 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,6 +3,7 @@ (starting from release version `0.5.66`) ## 0.5.70 +* corrected MQTT `comm_disabled` #529 ## 0.5.69 * merged SH1106 1.3" Display, thx @dAjaY85 diff --git a/src/app.cpp b/src/app.cpp index 3e8da14ff..6f53121b8 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -172,7 +172,7 @@ void app::tickIVCommunication(void) { onceAt(std::bind(&app::tickIVCommunication, this), nxtTrig); } if (mConfig->mqtt.broker[0] > 0) - mMqtt.tickerComm(mIVCommunicationOn); + mMqtt.tickerComm(!mIVCommunicationOn); } //----------------------------------------------------------------------------- From 1a435b4e7bab1e2777b6ae8fab3796d420723ab6 Mon Sep 17 00:00:00 2001 From: Sarge Date: Sat, 7 Jan 2023 21:34:53 +0100 Subject: [PATCH 009/215] MQTT topic input: allow more special chars (#$%&), removed needles escapes / minus at end as required by regex --- src/web/html/setup.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 8377cede7..c60020e13 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -147,7 +147,7 @@ - +

    Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

    From d8e255ddc2c9c5b09ee14e739a744ed0597c980c Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 8 Jan 2023 22:16:14 +0100 Subject: [PATCH 010/215] corrected MQTT `comm_disabled` #529 fix Prometheus and JSON endpoints (`config_override.h`) #561 publish MQTT with fixed interval even if inverter is not available #542 added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman improved potential issue with `checkTicker`, thx @cbscpe MQTT option for reset values on midnight / not avail / communication stop #539 small fix in `tickIVCommunication` #534 add `YieldTotal` correction, eg. to have the option to zero at year start #512 --- src/CHANGES.md | 8 +++ src/app.cpp | 14 +++- src/app.h | 7 +- src/appInterface.h | 1 + src/config/settings.h | 31 +++++++-- src/defines.h | 2 +- src/hm/hmInverter.h | 18 ++++- src/publisher/pubMqtt.h | 143 +++++++++++++++++++++++++++++---------- src/utils/ahoyTimer.h | 2 +- src/utils/llist.h | 110 ------------------------------ src/utils/scheduler.h | 1 + src/web/RestApi.h | 43 +++++++++--- src/web/html/setup.html | 47 ++++++++++--- src/web/html/update.html | 1 - src/web/web.h | 129 ++++++++++++++++++++++++++--------- src/wifi/ahoywifi.h | 3 +- 16 files changed, 344 insertions(+), 216 deletions(-) delete mode 100644 src/utils/llist.h diff --git a/src/CHANGES.md b/src/CHANGES.md index 364b2b4d4..ce304e4bb 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,14 @@ ## 0.5.70 * corrected MQTT `comm_disabled` #529 +* fix Prometheus and JSON endpoints (`config_override.h`) #561 +* publish MQTT with fixed interval even if inverter is not available #542 +* added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 +* MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman +* improved potential issue with `checkTicker`, thx @cbscpe +* MQTT option for reset values on midnight / not avail / communication stop #539 +* small fix in `tickIVCommunication` #534 +* add `YieldTotal` correction, eg. to have the option to zero at year start #512 ## 0.5.69 * merged SH1106 1.3" Display, thx @dAjaY85 diff --git a/src/app.cpp b/src/app.cpp index 6f53121b8..e42b8c2b2 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -62,6 +62,9 @@ void app::setup() { if (mConfig->mqtt.broker[0] > 0) { everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt)); everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt)); + uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight + if(mConfig->mqtt.rstYieldMidNight) + onceAt(std::bind(&app::tickMidnight, this), nxtTrig); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); } #endif @@ -161,7 +164,7 @@ void app::tickIVCommunication(void) { if (mTimestamp < (mSunrise - mConfig->sun.offsetSec)) { // current time is before communication start, set next trigger to communication start nxtTrig = mSunrise - mConfig->sun.offsetSec; } else { - if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise + if (mTimestamp >= (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise nxtTrig = 0; } else { // current time lies within communication start/stop time, set next trigger to communication stop mIVCommunicationOn = true; @@ -207,6 +210,15 @@ void app::tickSend(void) { updateLed(); } +//----------------------------------------------------------------------------- +void app::tickMidnight(void) { + // only used and enabled by MQTT (see setup()) + uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight + onceAt(std::bind(&app::tickMidnight, this), nxtTrig); + + mMqtt.tickerMidnight(); +} + //----------------------------------------------------------------------------- void app::handleIntr(void) { DPRINTLN(DBG_VERBOSE, F("app::handleIntr")); diff --git a/src/app.h b/src/app.h index b08b7d05e..fdbf96169 100644 --- a/src/app.h +++ b/src/app.h @@ -78,6 +78,10 @@ class app : public IApp, public ah::Scheduler { return mSettings.saveSettings(); } + bool readSettings(const char *path) { + return mSettings.readSettings(path); + } + bool eraseSettings(bool eraseWifi = false) { return mSettings.eraseSettings(eraseWifi); } @@ -95,7 +99,7 @@ class app : public IApp, public ah::Scheduler { } void setRebootFlag() { - once(std::bind(&app::tickReboot, this), 1); + once(std::bind(&app::tickReboot, this), 3); } const char *getVersion() { @@ -203,6 +207,7 @@ class app : public IApp, public ah::Scheduler { void tickCalcSunrise(void); void tickIVCommunication(void); void tickSend(void); + void tickMidnight(void); /*void tickSerial(void) { if(Serial.available() == 0) return; diff --git a/src/appInterface.h b/src/appInterface.h index 4c25c3a2c..c2d191b25 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -15,6 +15,7 @@ class IApp { public: virtual ~IApp() {} virtual bool saveSettings() = 0; + virtual bool readSettings(const char *path) = 0; virtual bool eraseSettings(bool eraseWifi) = 0; virtual void setRebootFlag() = 0; virtual const char *getVersion() = 0; diff --git a/src/config/settings.h b/src/config/settings.h index 921ebc966..50fbe01dd 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -97,6 +97,9 @@ typedef struct { char pwd[MQTT_PWD_LEN]; char topic[MQTT_TOPIC_LEN]; uint16_t interval; + bool rstYieldMidNight; + bool rstValsNotAvail; + bool rstValsCommStop; } cfgMqtt_t; typedef struct { @@ -105,6 +108,7 @@ typedef struct { serial_u serial; uint16_t chMaxPwr[4]; char chName[4][MAX_NAME_LENGTH]; + uint32_t yieldCor; // YieldTotal correction value } cfgIv_t; typedef struct { @@ -155,7 +159,7 @@ class settings { else DPRINTLN(DBG_INFO, F(" .. done")); - readSettings(); + readSettings("/settings.json"); } // should be used before OTA @@ -186,9 +190,10 @@ class settings { #endif } - void readSettings(void) { + bool readSettings(const char* path) { + bool success = false; loadDefaults(); - File fp = LittleFS.open("/settings.json", "r"); + File fp = LittleFS.open(path, "r"); if(!fp) DPRINTLN(DBG_WARN, F("failed to load json, using default config")); else { @@ -206,6 +211,7 @@ class settings { jsonMqtt(root["mqtt"]); jsonLed(root["led"]); jsonInst(root["inst"]); + success = true; } else { Serial.println(F("failed to parse json, using default config")); @@ -213,6 +219,7 @@ class settings { fp.close(); } + return success; } bool saveSettings(void) { @@ -299,6 +306,9 @@ class settings { snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", DEF_MQTT_PWD); snprintf(mCfg.mqtt.topic, MQTT_TOPIC_LEN, "%s", DEF_MQTT_TOPIC); mCfg.mqtt.interval = 0; // off + mCfg.mqtt.rstYieldMidNight = false; + mCfg.mqtt.rstValsNotAvail = false; + mCfg.mqtt.rstValsCommStop = false; mCfg.led.led0 = DEF_LED0_PIN; mCfg.led.led1 = DEF_LED1_PIN; @@ -399,9 +409,16 @@ class settings { obj[F("pwd")] = mCfg.mqtt.pwd; obj[F("topic")] = mCfg.mqtt.topic; obj[F("intvl")] = mCfg.mqtt.interval; + obj[F("rstMidNight")] = (bool)mCfg.mqtt.rstYieldMidNight; + obj[F("rstNotAvail")] = (bool)mCfg.mqtt.rstValsNotAvail; + obj[F("rstComStop")] = (bool)mCfg.mqtt.rstValsCommStop; + } else { mCfg.mqtt.port = obj[F("port")]; mCfg.mqtt.interval = obj[F("intvl")]; + mCfg.mqtt.rstYieldMidNight = (bool)obj["rstMidNight"]; + mCfg.mqtt.rstValsNotAvail = (bool)obj["rstNotAvail"]; + mCfg.mqtt.rstValsCommStop = (bool)obj["rstComStop"]; snprintf(mCfg.mqtt.broker, MQTT_ADDR_LEN, "%s", obj[F("broker")].as()); snprintf(mCfg.mqtt.user, MQTT_USER_LEN, "%s", obj[F("user")].as()); snprintf(mCfg.mqtt.pwd, MQTT_PWD_LEN, "%s", obj[F("pwd")].as()); @@ -438,9 +455,10 @@ class settings { void jsonIv(JsonObject obj, cfgIv_t *cfg, bool set = false) { if(set) { - obj[F("en")] = (bool)cfg->enabled; - obj[F("name")] = cfg->name; - obj[F("sn")] = cfg->serial.u64; + obj[F("en")] = (bool)cfg->enabled; + obj[F("name")] = cfg->name; + obj[F("sn")] = cfg->serial.u64; + obj[F("yield")] = cfg->yieldCor; for(uint8_t i = 0; i < 4; i++) { obj[F("pwr")][i] = cfg->chMaxPwr[i]; obj[F("chName")][i] = cfg->chName[i]; @@ -449,6 +467,7 @@ class settings { cfg->enabled = (bool)obj[F("en")]; snprintf(cfg->name, MAX_NAME_LENGTH, "%s", obj[F("name")].as()); cfg->serial.u64 = obj[F("sn")]; + cfg->yieldCor = obj[F("yield")]; for(uint8_t i = 0; i < 4; i++) { cfg->chMaxPwr[i] = obj[F("pwr")][i]; snprintf(cfg->chName[i], MAX_NAME_LENGTH, "%s", obj[F("chName")][i].as()); diff --git a/src/defines.h b/src/defines.h index b37cb6c77..8ab9ccdaa 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 69 +#define VERSION_PATCH 70 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 8c1fccc84..f02c6b9b5 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -233,11 +233,13 @@ class Inverter { val <<= 8; val |= buf[ptr]; } while(++ptr != end); - if(FLD_T == rec->assign[pos].fieldId) { + if (FLD_T == rec->assign[pos].fieldId) { // temperature is a signed value! rec->record[pos] = (REC_TYP)((int16_t)val) / (REC_TYP)(div); - } - else { + } else if ((FLD_YT == rec->assign[pos].fieldId) + && (config->yieldCor != 0)) { + rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div) - (REC_TYP)config->yieldCor; + } else { if ((REC_TYP)(div) > 1) rec->record[pos] = (REC_TYP)(val) / (REC_TYP)(div); else @@ -286,6 +288,16 @@ class Inverter { DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x")); } + bool setValue(uint8_t pos, record_t<> *rec, REC_TYP val) { + DPRINTLN(DBG_VERBOSE, F("hmInverter.h:setValue")); + if(NULL == rec) + return false; + if(pos > rec->length) + return false; + rec->record[pos] = val; + return true; + } + REC_TYP getValue(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue")); if(NULL == rec) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 382406f44..85742bd53 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -112,6 +112,27 @@ class PubMqtt { void tickerComm(bool disabled) { publish("comm_disabled", ((disabled) ? "true" : "false"), true); publish("comm_dis_ts", String(*mUtcTimestamp).c_str(), true); + + if(disabled && (mCfgMqtt->rstValsCommStop)) + zeroAllInverters(); + } + + void tickerMidnight() { + Inverter<> *iv; + record_t<> *rec; + + // set YieldDay to zero + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos = iv->getPosByChFld(CH0, FLD_YD, rec); + iv->setValue(pos, rec, 0.0f); + } + + mSendList.push(RealTimeRunData_Debug); + sendIvData(); } void payloadEventListener(uint8_t cmd) { @@ -394,18 +415,21 @@ class PubMqtt { allAvail = false; } } - else if (!iv->isProducing(*mUtcTimestamp, rec)) { + else { mIvAvail = true; - if (MQTT_STATUS_AVAIL_PROD == status) - status = MQTT_STATUS_AVAIL_NOT_PROD; + if (!iv->isProducing(*mUtcTimestamp, rec)) { + if (MQTT_STATUS_AVAIL_PROD == status) + status = MQTT_STATUS_AVAIL_NOT_PROD; + } } - else - mIvAvail = true; if(mLastIvState[id] != status) { mLastIvState[id] = status; changed = true; + if(mCfgMqtt->rstValsNotAvail) + zeroValues(iv); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); snprintf(val, 40, "%d", status); publish(topic, val, true); @@ -419,12 +443,13 @@ class PubMqtt { if(changed) { snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((mIvAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); publish("status", val, true); + sendIvData(false); // false prevents loop of same function } return totalComplete; } - void sendIvData(void) { + void sendIvData(bool sendTotals = true) { if(mSendList.empty()) return; @@ -442,49 +467,52 @@ class PubMqtt { record_t<> *rec = iv->getRecordStruct(mSendList.front()); // data - if(iv->isAvailable(*mUtcTimestamp, rec)) { - for (uint8_t i = 0; i < rec->length; i++) { - bool retained = false; - if (mSendList.front() == RealTimeRunData_Debug) { + //if(iv->isAvailable(*mUtcTimestamp, rec) || (0 != mCfgMqtt->interval)) { // is avail or fixed pulish interval was set + 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, "%g", ah::round3(iv->getValue(i, rec))); + publish(topic, val, retained); + + // calculate total values for RealTimeRunData_Debug + if (mSendList.front() == RealTimeRunData_Debug) { + if (CH0 == rec->assign[i].ch) { switch (rec->assign[i].fieldId) { + case FLD_PAC: + total[0] += iv->getValue(i, rec); + break; case FLD_YT: + total[1] += iv->getValue(i, rec); + break; case FLD_YD: - retained = true; + total[2] += iv->getValue(i, rec); + break; + case FLD_PDC: + total[3] += iv->getValue(i, rec); 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, "%g", ah::round3(iv->getValue(i, rec))); - publish(topic, val, retained); - - // calculate total values for RealTimeRunData_Debug - if (mSendList.front() == RealTimeRunData_Debug) { - if (CH0 == rec->assign[i].ch) { - switch (rec->assign[i].fieldId) { - case FLD_PAC: - total[0] += iv->getValue(i, rec); - break; - case FLD_YT: - total[1] += iv->getValue(i, rec); - break; - case FLD_YD: - total[2] += iv->getValue(i, rec); - break; - case FLD_PDC: - total[3] += iv->getValue(i, rec); - break; - } - } - sendTotal = true; - } - yield(); + sendTotal = true; } + yield(); } + //} } mSendList.pop(); // remove from list once all inverters were processed + if(!sendTotals) // skip total value calculation + continue; + if ((true == sendTotal) && processIvStatus()) { uint8_t fieldId; for (uint8_t i = 0; i < 4; i++) { @@ -511,6 +539,47 @@ class PubMqtt { } } + void zeroAllInverters() { + Inverter<> *iv; + + // set values to zero, exept yields + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + + zeroValues(iv); + } + sendIvData(); + } + + void zeroValues(Inverter<> *iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + for(uint8_t ch = 0; ch <= iv->channels; ch++) { + uint8_t pos = 0; + uint8_t fld = 0; + while(0xff != pos) { + switch(fld) { + case FLD_YD: + case FLD_YT: + case FLD_FW_VERSION: + case FLD_FW_BUILD_YEAR: + case FLD_FW_BUILD_MONTH_DAY: + case FLD_FW_BUILD_HOUR_MINUTE: + case FLD_HW_ID: + case FLD_ACT_ACTIVE_PWR_LIMIT: + continue; + break; + } + pos = iv->getPosByChFld(ch, fld, rec); + iv->setValue(pos, rec, 0.0f); + fld++; + } + } + + mSendList.push(RealTimeRunData_Debug); + } + espMqttClient mClient; cfgMqtt_t *mCfgMqtt; #if defined(ESP8266) diff --git a/src/utils/ahoyTimer.h b/src/utils/ahoyTimer.h index 5c960a346..08c09016c 100644 --- a/src/utils/ahoyTimer.h +++ b/src/utils/ahoyTimer.h @@ -15,7 +15,7 @@ namespace ah { *ticker = mil + interval; return true; } - else if(mil < (*ticker - interval)) { + else if((mil + interval) < (*ticker)) { *ticker = mil + interval; return true; } diff --git a/src/utils/llist.h b/src/utils/llist.h deleted file mode 100644 index 69750f19e..000000000 --- a/src/utils/llist.h +++ /dev/null @@ -1,110 +0,0 @@ -//----------------------------------------------------------------------------- -// 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; - uint8_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) && (p->nxt == p)) - root = NULL; - else - root = p->nxt; - 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 36dcdaaef..330ab080e 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -129,6 +129,7 @@ namespace ah { mTickerInUse[i] = false; else mTicker[i].timeout = mTicker[i].reload; + //DPRINTLN(DBG_INFO, "checkTick " + String(i) + " reload: " + String(mTicker[i].reload) + ", timeout: " + String(mTicker[i].timeout)); (mTicker[i].c)(); yield(); } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 8986e76d9..07c129944 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -134,17 +134,34 @@ class RestApi { ep[F("record/config")] = url + F("record/config"); ep[F("record/live")] = url + F("record/live"); } + void onDwnldSetup(AsyncWebServerRequest *request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); - JsonObject root = response->getRoot(); + AsyncWebServerResponse *response; - getSetup(root); + File fp = LittleFS.open("/settings.json", "r"); + if(!fp) { + DPRINTLN(DBG_ERROR, F("failed to load settings")); + response = request->beginResponse(200, F("application/json"), "{}"); + } + else { + String tmp = fp.readString(); + int i = 0; + // remove all passwords + while (i != -1) { + i = tmp.indexOf("\"pwd\":", i); + if(-1 != i) { + i+=7; + tmp.remove(i, tmp.indexOf("\"", i)-i); + } + } + response = request->beginResponse(200, F("application/json"), tmp); + } - response->setLength(); response->addHeader("Content-Type", "application/octet-stream"); response->addHeader("Content-Description", "File Transfer"); response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json"); request->send(response); + fp.close(); } void getGeneric(JsonObject obj) { @@ -165,7 +182,7 @@ class RestApi { obj[F("device_name")] = mConfig->sys.deviceName; obj[F("mac")] = WiFi.macAddress(); - obj[F("hostname")] = WiFi.getHostname(); + obj[F("hostname")] = mConfig->sys.deviceName; obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); obj[F("prot_mask")] = mConfig->sys.protectionMask; @@ -263,6 +280,7 @@ class RestApi { obj2[F("serial")] = String(iv->config->serial.u64, HEX); obj2[F("channels")] = iv->channels; obj2[F("version")] = String(iv->getFwVersion()); + obj2[F("yieldCor")] = iv->config->yieldCor; for(uint8_t j = 0; j < iv->channels; j ++) { obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; @@ -276,12 +294,15 @@ class RestApi { } void getMqtt(JsonObject obj) { - obj[F("broker")] = String(mConfig->mqtt.broker); - obj[F("port")] = String(mConfig->mqtt.port); - obj[F("user")] = String(mConfig->mqtt.user); - obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); - obj[F("topic")] = String(mConfig->mqtt.topic); - obj[F("interval")] = String(mConfig->mqtt.interval); + obj[F("broker")] = String(mConfig->mqtt.broker); + obj[F("port")] = String(mConfig->mqtt.port); + obj[F("user")] = String(mConfig->mqtt.user); + obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); + obj[F("topic")] = String(mConfig->mqtt.topic); + obj[F("interval")] = String(mConfig->mqtt.interval); + obj[F("rstMid")] = (bool)mConfig->mqtt.rstYieldMidNight; + obj[F("rstNAvail")] = (bool)mConfig->mqtt.rstValsNotAvail; + obj[F("rstComStop")] = (bool)mConfig->mqtt.rstValsCommStop; } void getNtp(JsonObject obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 8377cede7..aad46232e 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -31,7 +31,13 @@
    ERASE SETTINGS (not WiFi) - +
    + Upload JSON Settings +
    + + +
    +
    Device Host Name @@ -147,7 +153,13 @@ - + + +
    + +
    + +

    Send Inverter data in a fixed interval, even if there is no change. A value of '0' disables the fixed interval. The data is published once it was successfully received from inverter. (default: 0)

    @@ -184,7 +196,7 @@
    - Download your settings (JSON file) (only saved values) + Download your settings (JSON file) (only saved values, passwords will be removed!)
    @@ -212,8 +224,9 @@ const re = /11[2,4,6]1.*/; document.getElementById("btnAdd").addEventListener("click", function() { - if(highestId <= (maxInv-1)) - ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId + 1); + if(highestId <= (maxInv-1)) { + ivHtml(JSON.parse('{"enabled":true,"name":"","serial":"","channels":4,"ch_max_power":[0,0,0,0],"ch_name":["","","",""]}'), highestId); + } }); function apiCbWifi(obj) { @@ -268,6 +281,12 @@ getAjax("/api/setup", apiCbMqtt, "POST", JSON.stringify(obj)); } + function hide() { + document.getElementById("form").submit(); + var e = document.getElementById("content"); + e.replaceChildren(span("upload started")); + } + function delIv() { var id = this.id.substring(0,4); var e = document.getElementsByName(id + "Addr")[0]; @@ -278,8 +297,8 @@ } function ivHtml(obj, id) { - highestId = id; - if(highestId == (maxInv - 1)) + highestId = id + 1; + if(highestId == maxInv) setHide("btnAdd", true); iv = document.getElementById("inverter"); iv.appendChild(des("Inverter " + id)); @@ -292,7 +311,7 @@ iv.appendChild(br()); iv.appendChild(lbl(id + "Addr", "Serial Number (12 digits)*")); - var addr = inp(id + "Addr", obj["serial"], 12); + var addr = inp(id + "Addr", obj["serial"], 12, ["text"], null, "text", "[0-9]+", "Invalid input"); iv.appendChild(addr); ['keyup', 'change'].forEach(function(evt) { addr.addEventListener(evt, (e) => { @@ -323,7 +342,7 @@ iv.append( lbl(id + "Name", "Name*"), - inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9.\\-_\\+\\/]+", "Invalid input") + inp(id + "Name", obj["name"], 32, ["text"], null, "text", "[A-Za-z0-9./#$%&=+_-]+", "Invalid input") ); for(var j of [["ModPwr", "ch_max_power", "Max Module Power (Wp)", 4, "[0-9]+"], ["ModName", "ch_name", "Module Name", 16, null]]) { @@ -339,10 +358,15 @@ iv.appendChild(d); } + iv.append( + br(), + lbl(id + "YieldCor", "Yield Total Correction (will be subtracted) [kWh]"), + inp(id + "YieldCor", obj["yieldCor"], 32, ["text"], null, "text", "[0-9]+", "Invalid input") + ); + var del = inp(id+"del", "X", 0, ["btn", "btnDel"], id+"del", "button"); del.addEventListener("click", delIv); iv.append( - br(), lbl(id + "lbldel", "Delete"), del ); @@ -394,6 +418,9 @@ function parseMqtt(obj) { for(var i of [["Addr", "broker"], ["Port", "port"], ["User", "user"], ["Pwd", "pwd"], ["Topic", "topic"], ["Interval", "interval"]]) document.getElementsByName("mqtt"+i[0])[0].value = obj[i[1]]; + + for(var i of [["Mid", "rstMid"], ["ComStop", "rstNAvail"], ["NotAvail", "rstComStop"]]) + document.getElementsByName("mqttRst"+i[0])[0].checked = obj[i[1]]; } function parseNtp(obj) { diff --git a/src/web/html/update.html b/src/web/html/update.html index 215188db2..e9bcde87b 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -23,7 +23,6 @@ -
    - ERASE SETTINGS (not WiFi) -
    - Upload JSON Settings -
    - - -
    -
    Device Host Name @@ -196,7 +188,15 @@
    - Download your settings (JSON file) (only saved values, passwords will be removed!) + ERASE SETTINGS (not WiFi) +
    + Upload / Store JSON Settings + + + + +
    + Download settings (JSON file) (only saved values, passwords will be removed!)
    diff --git a/src/web/html/system.html b/src/web/html/system.html index 5419d7ba4..75e05765f 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -94,11 +94,12 @@ } main.append( + genTabRow("TX count", stat["tx_cnt"]), genTabRow("RX success", stat["rx_success"]), genTabRow("RX fail", stat["rx_fail"]), genTabRow("RX no answer", stat["rx_fail_answer"]), - genTabRow("RX frames received", stat["frame_cnt"]), - genTabRow("TX count", stat["tx_cnt"]) + genTabRow("RX fragments", stat["frame_cnt"]), + genTabRow("TX retransmits", stat["retransmits"]) ); } diff --git a/src/web/web.h b/src/web/web.h index 0756e009b..e80ff76f5 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -86,6 +86,7 @@ class Web { mWeb.on("/upload", HTTP_POST, std::bind(&Web::onUpload, this, std::placeholders::_1), std::bind(&Web::onUpload2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); mWeb.on("/serial", HTTP_GET, std::bind(&Web::onSerial, this, std::placeholders::_1)); + mWeb.on("/debug", HTTP_GET, std::bind(&Web::onDebug, this, std::placeholders::_1)); mEvts.onConnect(std::bind(&Web::onConnect, this, std::placeholders::_1)); @@ -189,12 +190,15 @@ class Web { msg.replace("\r\n", ""); if(mSerialAddTime) { if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { - strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9); - mSerialBufFill += 9; + if(mApp->getTimestamp() > 0) { + strncpy(&mSerialBuf[mSerialBufFill], mApp->getTimeStr(mApp->getTimezoneOffset()).c_str(), 9); + mSerialBufFill += 9; + } } else { mSerialBufFill = 0; - mEvts.send("webSerial, buffer overflow!", "serial", millis()); + mEvts.send("webSerial, buffer overflow!", "serial", millis()); + return; } mSerialAddTime = false; } @@ -209,7 +213,7 @@ class Web { } else { mSerialBufFill = 0; - mEvts.send("webSerial, buffer overflow!", "serial", millis()); + mEvts.send("webSerial, buffer overflow!", "serial", millis()); } } @@ -649,6 +653,12 @@ class Web { request->send(200, "text/json", "{success:true}"); }*/ + void onDebug(AsyncWebServerRequest *request) { + mApp->getSchedulerNames(); + AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), "ok"); + request->send(response); + } + void onSerial(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("onSerial")); diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index a743ddf5f..c4fba0442 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -11,7 +11,7 @@ // NTP CONFIG #define NTP_PACKET_SIZE 48 - +#define NTP_RETRIES 5 //----------------------------------------------------------------------------- ahoywifi::ahoywifi() : mApIp(192, 168, 4, 1) {} @@ -26,6 +26,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mStaConn = DISCONNECTED; mCnt = 0; mScanActive = false; + mRetries = NTP_RETRIES; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -148,7 +149,17 @@ void ahoywifi::setupStation(void) { //----------------------------------------------------------------------------- -bool ahoywifi::getNtpTime(void) { +bool ahoywifi::getNtpTime(uint32_t *nxtTrig) { + if(0 != mRetries) { + DPRINTLN(DBG_INFO, "try to getNtpTime"); + *nxtTrig = 43200; // check again in 12h (if NTP was successful) + mRetries--; + } else if(0 != *mUtcTimestamp) { // time is availabe, but NTP not + *nxtTrig = 5; // check again 5s + mRetries = NTP_RETRIES; + return true; // true is necessary to enable all timers even if NTP was not reachable + } + if(GOT_IP != mStaConn) return false; @@ -267,6 +278,7 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) { setupWifi(); // reconnect with AP / Station setup mAppWifiCb(false); DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); + mRetries = NTP_RETRIES; } break; diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index d8b503e4b..72a80d7bd 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -25,7 +25,7 @@ class ahoywifi { void setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb); void tickWifiLoop(void); - bool getNtpTime(void); + bool getNtpTime(uint32_t *nxtTrig); void scanAvailNetworks(void); void getAvailNetworks(JsonObject obj); @@ -68,6 +68,7 @@ class ahoywifi { uint8_t mLoopCnt; bool mScanActive; + uint8_t mRetries; }; #endif /*__AHOYWIFI_H__*/ From a0a40be9a6db3189820a8084b64b547b6c101a1e Mon Sep 17 00:00:00 2001 From: lumapu Date: Thu, 19 Jan 2023 22:51:13 +0100 Subject: [PATCH 027/215] fix compile --- src/app.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.cpp b/src/app.cpp index d698bba8d..643efb174 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -147,7 +147,7 @@ void app::regularTickers(void) { everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc"); // Plugins #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) - everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay)); + everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp"); #endif every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart"); } From 76b5818f487a58eb8526e5863bacb54bd75bd809 Mon Sep 17 00:00:00 2001 From: lumapu Date: Fri, 20 Jan 2023 07:21:28 +0100 Subject: [PATCH 028/215] fix compile --- src/plugins/MonochromeDisplay/MonochromeDisplay.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 7f050fe20..2e64844b1 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -138,7 +138,7 @@ class MonochromeDisplay { for (uint8_t fld = 0; fld < 3; fld++) { pos = iv->getPosByChFld(CH0, list[fld],rec); - int isprod = iv->isProducing(*mUtcTs,rec); + int isprod = iv->isProducing(*mUtcTs); if(fld == 1) { From 2b3f252bbf1ed03b6896646bfe32bf6a9a4b1e58 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 21 Jan 2023 00:34:39 +0100 Subject: [PATCH 029/215] fix wakeup issue, once wifi was lost during night the communication didn't start in the morning reenabled FlashStringHelper because of lacking RAM complete rewrite of monochrome display class, thx to @dAjaY85 -> displays are now configurable in setup --- .github/workflows/compile_development.yml | 2 +- .github/workflows/compile_release.yml | 2 +- src/CHANGES.md | 5 + src/app.cpp | 22 +- src/app.h | 14 +- src/config/config.h | 2 - src/config/settings.h | 64 ++- src/defines.h | 2 +- src/hm/hmInverter.h | 16 + src/platformio.ini | 112 +---- .../MonochromeDisplay/MonochromeDisplay.h | 429 +++++++----------- src/utils/dbg.h | 2 +- src/web/RestApi.h | 13 +- src/web/html/setup.html | 151 +++--- src/web/web.h | 11 + src/wifi/ahoywifi.cpp | 25 +- src/wifi/ahoywifi.h | 4 +- 17 files changed, 389 insertions(+), 487 deletions(-) diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 203e23a77..ec4358806 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 esp8266-sh1106 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 --environment esp32-wroom32-sh1106 + run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp32-wroom32-release - name: Rename Binary files id: rename-binary-files diff --git a/.github/workflows/compile_release.yml b/.github/workflows/compile_release.yml index 91c4c8e3a..91ae35bf4 100644 --- a/.github/workflows/compile_release.yml +++ b/.github/workflows/compile_release.yml @@ -51,7 +51,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 esp8266-sh1106 --environment esp32-wroom32-release --environment esp32-wroom32-nokia5110 --environment esp32-wroom32-ssd1306 --environment esp32-wroom32-sh1106 + run: pio run -d src --environment esp8266-release --environment esp8285-release --environment esp32-wroom32-release - name: Rename Binary files id: rename-binary-files diff --git a/src/CHANGES.md b/src/CHANGES.md index 1e9b48e15..40cf5f8b9 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,11 @@ (starting from release version `0.5.66`) +## 0.5.75 +* fix wakeup issue, once wifi was lost during night the communication didn't start in the morning +* reenabled FlashStringHelper because of lacking RAM +* complete rewrite of monochrome display class, thx to @dAjaY85 -> displays are now configurable in setup + ## 0.5.74 * improved payload handling (retransmit all fragments on CRC error) * improved `isAvailable`, checkes all record structs, inverter becomes available more early because version is check first diff --git a/src/app.cpp b/src/app.cpp index 643efb174..7f4071a2c 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -64,9 +64,8 @@ void app::setup() { mApi.setup(this, mSys, mWeb.getWebSrvPtr(), mConfig); // Plugins - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) - mMonoDisplay.setup(&mConfig->plugin.display, mSys, &mTimestamp); - #endif + if(mConfig->plugin.display.type != 0) + mMonoDisplay.setup(&mConfig->plugin.display, mSys, &mTimestamp, 0xff, mVersion); mPubSerial.setup(mConfig, mSys, &mTimestamp); @@ -133,6 +132,7 @@ void app::onWifi(bool gotIp) { mInnerLoopCb = std::bind(&app::loopStandard, this); mSendTickerId = every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend"); mMqttReconnect = true; + mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); } else { @@ -146,27 +146,27 @@ void app::regularTickers(void) { DPRINTLN(DBG_DEBUG, F("regularTickers")); everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc"); // Plugins - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) - everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp"); - #endif + if(mConfig->plugin.display.type != 0) + everySec(std::bind(&MonoDisplayType::tickerSecond, &mMonoDisplay), "disp"); every(std::bind(&PubSerialType::tick, &mPubSerial), mConfig->serial.interval, "uart"); } //----------------------------------------------------------------------------- void app::tickNtpUpdate(void) { uint32_t nxtTrig = 5; // default: check again in 5 sec - if (mWifi.getNtpTime(&nxtTrig)) { + if (mWifi.getNtpTime()) { if (mMqttReconnect && mMqttEnabled) { mMqtt.connect(); everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS"); everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM"); - uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight - if(mConfig->mqtt.rstYieldMidNight) - onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "midNi"); + if(mConfig->mqtt.rstYieldMidNight) { + uint32_t midTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight + onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi"); + } mMqttReconnect = false; } - nxtTrig = 43200; + nxtTrig = 43200; // check again in 12h if((mSunrise == 0) && (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; diff --git a/src/app.h b/src/app.h index 7a0cd38e3..0466daf87 100644 --- a/src/app.h +++ b/src/app.h @@ -46,10 +46,8 @@ typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; // PLUGINS -#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) - #include "plugins/MonochromeDisplay/MonochromeDisplay.h" - typedef MonochromeDisplay MonoDisplayType; -#endif +#include "plugins/MonochromeDisplay/MonochromeDisplay.h" +typedef MonochromeDisplay MonoDisplayType; class app : public IApp, public ah::Scheduler { @@ -180,10 +178,8 @@ class app : public IApp, public ah::Scheduler { void setTimestamp(uint32_t newTime) { DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime)); - if(0 == newTime) { - uint32_t tmp; - mWifi.getNtpTime(&tmp); - } + if(0 == newTime) + mWifi.getNtpTime(); else Scheduler::setTimestamp(newTime); } @@ -269,9 +265,7 @@ class app : public IApp, public ah::Scheduler { uint32_t mSunrise, mSunset; // plugins - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) MonoDisplayType mMonoDisplay; - #endif }; #endif /*__APP_H__*/ diff --git a/src/config/config.h b/src/config/config.h index 2b5a06882..69e7193bf 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -52,8 +52,6 @@ #define DEF_CE_PIN 2 #define DEF_IRQ_PIN 0 #endif -#define DEF_LED0_PIN 255 // off -#define DEF_LED1_PIN 255 // off // default NRF24 power, possible values (0 - 3) #define DEF_AMPLIFIERPOWER 1 diff --git a/src/config/settings.h b/src/config/settings.h index 5a83c6997..dbf39d6c8 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -17,6 +17,7 @@ * More info: * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ +#define DEF_PIN_OFF 255 #define PROT_MASK_INDEX 0x0001 @@ -117,10 +118,15 @@ typedef struct { } cfgInst_t; typedef struct { + uint8_t type; bool pwrSaveAtIvOffline; - uint32_t wakeUp; - uint32_t sleepAt; + bool logoEn; + bool pxShift; + uint16_t wakeUp; + uint16_t sleepAt; uint8_t contrast; + uint8_t pin0; + uint8_t pin1; } display_t; typedef struct { @@ -216,14 +222,15 @@ class settings { DeserializationError err = deserializeJson(root, fp); if(!err && (root.size() > 0)) { mCfg.valid = true; - jsonWifi(root["wifi"]); - jsonNrf(root["nrf"]); - jsonNtp(root["ntp"]); - jsonSun(root["sun"]); - jsonSerial(root["serial"]); - jsonMqtt(root["mqtt"]); - jsonLed(root["led"]); - jsonInst(root["inst"]); + jsonWifi(root[F("wifi")]); + jsonNrf(root[F("nrf")]); + jsonNtp(root[F("ntp")]); + jsonSun(root[F("sun")]); + jsonSerial(root[F("serial")]); + jsonMqtt(root[F("mqtt")]); + jsonLed(root[F("led")]); + jsonPlugin(root[F("plugin")]); + jsonInst(root[F("inst")]); success = true; } else { @@ -252,6 +259,7 @@ class settings { jsonSerial(root.createNestedObject(F("serial")), true); jsonMqtt(root.createNestedObject(F("mqtt")), true); jsonLed(root.createNestedObject(F("led")), true); + jsonPlugin(root.createNestedObject(F("plugin")), true); jsonInst(root.createNestedObject(F("inst")), true); if(0 == serializeJson(root, fp)) { @@ -323,13 +331,17 @@ class settings { mCfg.mqtt.rstValsNotAvail = false; mCfg.mqtt.rstValsCommStop = false; - mCfg.led.led0 = DEF_LED0_PIN; - mCfg.led.led1 = DEF_LED1_PIN; + mCfg.led.led0 = DEF_PIN_OFF; + mCfg.led.led1 = DEF_PIN_OFF; memset(&mCfg.inst, 0, sizeof(cfgInst_t)); mCfg.plugin.display.pwrSaveAtIvOffline = false; - mCfg.plugin.display.contrast = 60; + mCfg.plugin.display.contrast = 60; + mCfg.plugin.display.logoEn = true; + mCfg.plugin.display.pxShift = true; + mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL + mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA } void jsonWifi(JsonObject obj, bool set = false) { @@ -452,6 +464,32 @@ class settings { } } + void jsonPlugin(JsonObject obj, bool set = false) { + if(set) { + JsonObject disp = obj.createNestedObject("disp"); + disp[F("type")] = mCfg.plugin.display.type; + disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; + disp[F("logo")] = (bool)mCfg.plugin.display.logoEn; + disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; + disp[F("wake")] = mCfg.plugin.display.wakeUp; + disp[F("sleep")] = mCfg.plugin.display.sleepAt; + disp[F("contrast")] = mCfg.plugin.display.contrast; + disp[F("pin0")] = mCfg.plugin.display.pin0; + disp[F("pin1")] = mCfg.plugin.display.pin1; + } else { + JsonObject disp = obj["disp"]; + mCfg.plugin.display.type = disp[F("type")]; + mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")]; + mCfg.plugin.display.logoEn = (bool) disp[F("logo")]; + mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")]; + mCfg.plugin.display.wakeUp = disp[F("wake")]; + mCfg.plugin.display.sleepAt = disp[F("sleep")]; + mCfg.plugin.display.contrast = disp[F("contrast")]; + mCfg.plugin.display.pin0 = disp[F("pin0")]; + mCfg.plugin.display.pin1 = disp[F("pin1")]; + } + } + void jsonInst(JsonObject obj, bool set = false) { if(set) obj[F("en")] = (bool)mCfg.inst.enabled; diff --git a/src/defines.h b/src/defines.h index d0fe6bf1f..544507aeb 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 74 +#define VERSION_PATCH 75 //------------------------------------- typedef struct { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 18b0869cf..ab86965df 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -315,6 +315,22 @@ class Inverter { return true; } + REC_TYP getChannelFieldValue(uint8_t channel, uint8_t fieldId, record_t<> *rec) { + uint8_t pos = 0; + if(NULL != rec) { + for(; pos < rec->length; pos++) { + if((rec->assign[pos].ch == channel) && (rec->assign[pos].fieldId == fieldId)) + break; + } + if(pos >= rec->length) + return 0; + + return rec->record[pos]; + } + else + return 0; + } + REC_TYP getValue(uint8_t pos, record_t<> *rec) { DPRINTLN(DBG_VERBOSE, F("hmInverter.h:getValue")); if(NULL == rec) diff --git a/src/platformio.ini b/src/platformio.ini index 897cd451b..60245f694 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -38,6 +38,8 @@ lib_deps = paulstoffregen/Time https://github.com/bertmelis/espMqttClient#v1.3.3 bblanchon/ArduinoJson + https://github.com/JChristensen/Timezone + olikraus/U8g2 ;esp8266/DNSServer ;esp8266/EEPROM ;esp8266/ESP8266WiFi @@ -89,60 +91,6 @@ 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 - https://github.com/JChristensen/Timezone - 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/JChristensen/Timezone - olikraus/U8g2 - -[env:esp8266-sh1106] -platform = espressif8266 -board = esp12e -board_build.f_cpu = 80000000L -build_flags = -D RELEASE -DENA_SH1106 -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/JChristensen/Timezone - olikraus/U8g2 - [env:esp32-wroom32-release] platform = espressif32 board = lolin_d32 @@ -163,59 +111,3 @@ 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 - https://github.com/JChristensen/Timezone - 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 - https://github.com/JChristensen/Timezone - olikraus/U8g2 - -[env:esp32-wroom32-sh1106] -platform = espressif32 -board = lolin_d32 -build_flags = -D RELEASE -std=gnu++14 -DENA_SH1106 -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 - https://github.com/JChristensen/Timezone - olikraus/U8g2 \ No newline at end of file diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 2e64844b1..7666abfbe 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -1,46 +1,41 @@ #ifndef __MONOCHROME_DISPLAY__ #define __MONOCHROME_DISPLAY__ -/* esp8266 : SCL = 5, SDA = 4 */ -/* ewsp32 : SCL = 22, SDA = 21 */ -#if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) #include -#ifdef ENA_NOKIA - #define DISP_PROGMEM U8X8_PROGMEM -#else // ENA_SSD1306 || ENA_SH1106 - #define DISP_PROGMEM PROGMEM -#endif - #include #include "../../utils/helper.h" #include "../../hm/hmSystem.h" +#define DISP_DEFAULT_TIMEOUT 60 // in seconds + static uint8_t bmp_logo[] PROGMEM = { - B00000000,B00000000, // ................ - B11101100,B00110111, // ..##.######.##.. - B11101100,B00110111, // ..##.######.##.. - B11100000,B00000111, // .....######..... - B11010000,B00001011, // ....#.####.#.... - B10011000,B00011001, // ...##..##..##... - B10000000,B00000001, // .......##....... - B00000000,B00000000, // ................ - B01111000,B00011110, // ...####..####... - B11111100,B00111111, // ..############.. - B01111100,B00111110, // ..#####..#####.. - B00000000,B00000000, // ................ - B11111100,B00111111, // ..############.. - B11111110,B01111111, // .##############. - B01111110,B01111110, // .######..######. - B00000000,B00000000 // ................ + B00000000, B00000000, // ................ + B11101100, B00110111, // ..##.######.##.. + B11101100, B00110111, // ..##.######.##.. + B11100000, B00000111, // .....######..... + B11010000, B00001011, // ....#.####.#.... + B10011000, B00011001, // ...##..##..##... + B10000000, B00000001, // .......##....... + B00000000, B00000000, // ................ + B01111000, B00011110, // ...####..####... + B11111100, B00111111, // ..############.. + B01111100, B00111110, // ..#####..#####.. + B00000000, B00000000, // ................ + B11111100, B00111111, // ..############.. + B11111110, B01111111, // .##############. + B01111110, B01111110, // .######..######. + B00000000, B00000000 // ................ }; -static uint8_t bmp_arrow[] DISP_PROGMEM = { +static uint8_t bmp_arrow[] PROGMEM = { B00000000, B00011100, B00011100, B00001110, B00001110, B11111110, B01111111, B01110000, B01110000, B00110000, B00111000, B00011000, B01111111, B00111111, - B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000}; + B00011110, B00001110, B00000110, B00000000, B00000000, B00000000, B00000000 +}; + static TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time static TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Tim @@ -48,31 +43,41 @@ static TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central Eu template class MonochromeDisplay { public: - #ifdef ENA_NOKIA - MonochromeDisplay() : mDisplay(U8G2_R0, 5, 4, 16), mCE(CEST, CET) { - mNewPayload = false; - mExtra = 0; - } - #else // ENA_SSD1306 || ENA_SH1106 - MonochromeDisplay() : mDisplay(U8G2_R0, SCL, SDA, U8X8_PIN_NONE), mCE(CEST, CET) { - mNewPayload = false; - mExtra = 0; - } - #endif + uint8_t dispContrast = 60; - void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs) { + MonochromeDisplay() : mCE(CEST, CET) {} + + void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) { mCfg = cfg; mSys = sys; mUtcTs = utcTs; - memset( mToday, 0, sizeof(float)*MAX_NUM_INVERTERS ); - memset( mTotal, 0, sizeof(float)*MAX_NUM_INVERTERS ); - mLastHour = 25; - mDisplay.begin(); - ShowInfoText("booting..."); - } + mNewPayload = false; + mLoopCnt = 0; + + mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline) + if(mCfg->type) { + switch(mCfg->type) { + case 1: + mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(U8G2_R0, mCfg->pin0, mCfg->pin1, disp_reset); + break; + case 2: + mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, disp_reset, mCfg->pin0, mCfg->pin1); + break; + case 3: + mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, disp_reset, mCfg->pin0, mCfg->pin1); + break; + } + mDisplay->begin(); - void loop(void) { + mIsLarge = ((mDisplay->getWidth() > 120) && (mDisplay->getHeight() > 60)); + calcLineHeights(); + mDisplay->clearBuffer(); + mDisplay->setContrast(mCfg->contrast); + printText("Ahoy!", 0, 35); + printText(version, 3, 46); + mDisplay->sendBuffer(); + } } void payloadEventListener(uint8_t cmd) { @@ -80,244 +85,136 @@ class MonochromeDisplay { } void tickerSecond() { - static int cnt=1; - if(mNewPayload || !(cnt % 10)) { - cnt=1; + if(mCfg->pwrSaveAtIvOffline) { + if(mTimeout != 0) + mTimeout--; + } + if(mNewPayload || ((++mLoopCnt % 10) == 0)) { mNewPayload = false; + mLoopCnt = 0; DataScreen(); } - else - cnt++; } private: - 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; + void DataScreen() { + if (mCfg->type == 0) + return; + if(*mUtcTs == 0) + return; + + float totalPower = 0; + float totalYieldDay = 0; + float totalYieldTotal = 0; + + bool isprod = false; + + Inverter<> *iv; + record_t<> *rec; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + + if (iv->isProducing(*mUtcTs)) + isprod = true; + + totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); + totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + totalYieldTotal += iv->getChannelFieldValue(CH0, FLD_YT, rec); + } + + mDisplay->clearBuffer(); + + // Logos + // pxMovement +x (0 - 6 px) + uint8_t ex = (_mExtra % 7); + if (isprod) { + mDisplay->drawXBMP(5 + ex, 1, 8, 17, bmp_arrow); + if (mCfg->logoEn) + mDisplay->drawXBMP(mDisplay->getWidth() - 24 + ex, 2, 16, 16, bmp_logo); + } + + if ((totalPower > 0) && isprod) { + mTimeout = DISP_DEFAULT_TIMEOUT; + mDisplay->setPowerSave(false); + mDisplay->setContrast(mCfg->contrast); + if (totalPower > 999) + snprintf(_fmtText, sizeof(_fmtText), "%2.1f kW", (totalPower / 1000)); + else + snprintf(_fmtText, sizeof(_fmtText), "%3.0f W", totalPower); + printText(_fmtText, 0, 20); + } else { + printText("offline", 0, 25); + if(mCfg->pwrSaveAtIvOffline) { + if(mTimeout == 0) + mDisplay->setPowerSave(true); } - mDisplay.sendBuffer(); - } while( mDisplay.nextPage() ); - } + } + + snprintf(_fmtText, sizeof(_fmtText), "today: %4.0f Wh", totalYieldDay); + printText(_fmtText, 1); + + snprintf(_fmtText, sizeof(_fmtText), "total: %.1f kWh", totalYieldTotal); + printText(_fmtText, 2); - void DataScreen(void) { - String timeStr = ah::getDateTimeStr(mCE.toLocal(*mUtcTs)).substring(2, 16); - int hr = timeStr.substring(9,2).toInt(); IPAddress ip = WiFi.localIP(); - float totalYield = 0.0, totalYieldToday = 0.0, totalActual = 0.0; - char fmtText[32]; - int ucnt=0, num_inv=0; - unsigned int pow_i[ MAX_NUM_INVERTERS ]; - - memset( pow_i, 0, sizeof(unsigned int)* MAX_NUM_INVERTERS ); - if ( hr < mLastHour ) // next day ? reset today-values - memset( mToday, 0, sizeof(float)*MAX_NUM_INVERTERS ); - mLastHour = hr; - - 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}; - - for (uint8_t fld = 0; fld < 3; fld++) { - pos = iv->getPosByChFld(CH0, list[fld],rec); - int isprod = iv->isProducing(*mUtcTs); - - if(fld == 1) - { - if ( isprod ) - mTotal[num_inv] = iv->getValue(pos,rec); - totalYield += mTotal[num_inv]; - } - if(fld == 2) - { - if ( isprod ) - mToday[num_inv] = iv->getValue(pos,rec); - totalYieldToday += mToday[num_inv]; - } - if((fld == 0) && isprod ) - { - pow_i[num_inv] = iv->getValue(pos,rec); - totalActual += iv->getValue(pos,rec); - ucnt++; - } - } - num_inv++; - } + if (!(_mExtra % 10) && (ip)) { + printText(ip.toString().c_str(), 3); + } else { + // Get current time + if(mIsLarge) + printText(ah::getDateTimeStr(mCE.toLocal(*mUtcTs)).c_str(), 3); + else + printText(ah::getTimeStr(mCE.toLocal(*mUtcTs)).c_str(), 3); + } + mDisplay->sendBuffer(); + + _mExtra++; + } + + void calcLineHeights() { + uint8_t yOff = 0; + for(uint8_t i = 0; i < 4; i++) { + setFont(i); + yOff += (mDisplay->getMaxCharHeight() + 1); + mLineOffsets[i] = yOff; + } + } + + inline void setFont(uint8_t line) { + switch (line) { + case 0: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB14_tr : u8g2_font_lubBI14_tr); break; + case 3: mDisplay->setFont(u8g2_font_5x8_tr); break; + default: mDisplay->setFont((mIsLarge) ? u8g2_font_ncenB10_tr : u8g2_font_5x8_tr); break; } - /* u8g2_font_open_iconic_embedded_2x_t 'D' + 'G' + 'J' */ - mDisplay.clear(); - mDisplay.firstPage(); - do { - #ifdef ENA_NOKIA - if(ucnt) { - //=====> Actual Production - mDisplay.drawXBMP(10,1,8,17,bmp_arrow); - mDisplay.setFont(u8g2_font_logisoso16_tr); - mDisplay.setCursor(25,17); - if (totalActual>999){ - sprintf(fmtText,"%2.1f",(totalActual/1000)); - mDisplay.print(String(fmtText)+F(" kW")); - } else { - sprintf(fmtText,"%3.0f",totalActual); - mDisplay.print(String(fmtText)+F(" W")); - } - //<======================= - } - else - { - //=====> Offline - mDisplay.setFont(u8g2_font_logisoso16_tr ); - mDisplay.setCursor(10,17); - mDisplay.print(String(F("offline"))); - //<======================= - - } - mDisplay.drawHLine(2,20,78); - mDisplay.setFont(u8g2_font_5x8_tr); - mDisplay.setCursor(5,29); - if (( num_inv < 2 ) || !(mExtra%2)) - { - 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 - { - int id1=(mExtra/2)%(num_inv-1); - if( pow_i[id1] ) - mDisplay.print(F("#")+String(id1+1)+F(" ")+String(pow_i[id1])+F(" W")); - else - mDisplay.print(F("#")+String(id1+1)+F(" -----")); - mDisplay.setCursor(5,37); - if( pow_i[id1+1] ) - mDisplay.print(F("#")+String(id1+2)+F(" ")+String(pow_i[id1+1])+F(" W")); - else - mDisplay.print(F("#")+String(id1+2)+F(" -----")); - } - if ( !(mExtra%10) && ip ) { - mDisplay.setCursor(5,47); - mDisplay.print(ip.toString()); - } - else { - mDisplay.setCursor(5,47); - mDisplay.print(timeStr); - } - #else // ENA_SSD1306 - mDisplay.setContrast(mCfg->contrast); - // pxZittern in +x (0 - 8 px) - int ex = 2*( mExtra % 5 ); - mDisplay.drawXBM(100+ex,2,16,16,bmp_logo); - mDisplay.setFont(u8g2_font_ncenB08_tr); - if(ucnt) { - //=====> Actual Production - mDisplay.setPowerSave(false); - displaySleep=false; - mDisplay.setFont(u8g2_font_logisoso18_tr); - mDisplay.drawXBM(10+ex,2,8,17,bmp_arrow); - mDisplay.setCursor(25+ex,20); - if (totalActual>999){ - sprintf(fmtText,"%2.1f",(totalActual/1000)); - mDisplay.print(String(fmtText)+F(" kW")); - } else { - sprintf(fmtText,"%3.0f",totalActual); - mDisplay.print(String(fmtText)+F(" W")); - } - //<======================= - } - else - { - //=====> Offline - if(!displaySleep) { - displaySleepTimer = millis(); - displaySleep=true; - } - mDisplay.setFont(u8g2_font_logisoso18_tr); - mDisplay.setCursor(10+ex,20); - mDisplay.print(String(F("offline"))); - if(mCfg->pwrSaveAtIvOffline) { - if ((millis() - displaySleepTimer) > displaySleepDelay) - mDisplay.setPowerSave(true); - } - //<======================= - } - mDisplay.drawLine(2+ex, 23, 123, 23); - mDisplay.setFont(u8g2_font_ncenB10_tr); - mDisplay.setCursor(2+ex,36); - if (( num_inv < 2 ) || !(mExtra%2)) - { - //=====> Today & Total Production - sprintf(fmtText,"%5.0f",totalYieldToday); - mDisplay.print(F("today: ")+String(fmtText)+F(" Wh")); - mDisplay.setCursor(2+ex,50); - sprintf(fmtText,"%.1f",totalYield); - mDisplay.print(F("total: ")+String(fmtText)+F(" kWh")); - //<======================= - } else { - int id1=(mExtra/2)%(num_inv-1); - if( pow_i[id1] ) - mDisplay.print(F("#")+String(id1+1)+F(" ")+String(pow_i[id1])+F(" W")); - else - mDisplay.print(F("#")+String(id1+1)+F(" -----")); - mDisplay.setCursor(5+ex,50); - if( pow_i[id1+1] ) - mDisplay.print(F("#")+String(id1+2)+F(" ")+String(pow_i[id1+1])+F(" W")); - else - mDisplay.print(F("#")+String(id1+2)+F(" -----")); - } - mDisplay.setFont(u8g2_font_5x8_tr); - mDisplay.setCursor(5+ex,63); - if ( !(mExtra%10) && ip ) - mDisplay.print(ip.toString()); - else - mDisplay.print(timeStr); - #endif - mDisplay.sendBuffer(); - } while( mDisplay.nextPage() ); - delay(200); - mExtra++; + } + + void printText(const char* text, uint8_t line, uint8_t dispX = 5) { + if(!mIsLarge) + dispX = 5; + setFont(line); + if(mCfg->pxShift) + dispX += (_mExtra % 7); // add pixel movement + mDisplay->drawStr(dispX, mLineOffsets[line], text); } // private member variables - #ifdef ENA_NOKIA - U8G2_PCD8544_84X48_1_4W_HW_SPI mDisplay; - #elif defined(ENA_SSD1306) - U8G2_SSD1306_128X64_NONAME_F_HW_I2C mDisplay; - #elif defined(ENA_SH1106) - U8G2_SH1106_128X64_NONAME_F_HW_I2C mDisplay; - #endif - int mExtra; + U8G2* mDisplay; + + uint8_t _mExtra; + uint16_t mTimeout; // interval at which to power save (milliseconds) + char _fmtText[32]; + bool mNewPayload; - float mTotal[ MAX_NUM_INVERTERS ]; - float mToday[ MAX_NUM_INVERTERS ]; + bool mIsLarge; + uint8_t mLoopCnt; uint32_t *mUtcTs; - int mLastHour; + uint8_t mLineOffsets[5]; display_t *mCfg; HMSYSTEM *mSys; Timezone mCE; - bool displaySleep; - uint32_t displaySleepTimer; - const uint32_t displaySleepDelay= 60000; }; -#endif #endif /*__MONOCHROME_DISPLAY__*/ diff --git a/src/utils/dbg.h b/src/utils/dbg.h index 7c956ef69..4f765ca64 100644 --- a/src/utils/dbg.h +++ b/src/utils/dbg.h @@ -5,7 +5,7 @@ #ifndef __DBG_H__ #define __DBG_H__ -#if defined(F) //defined(ESP32) && +#if defined(F) && defined(ESP32) #undef F #define F(sl) (sl) #endif diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 5461746dc..c80254645 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -14,7 +14,7 @@ #include "../appInterface.h" -#if defined(F) //defined(ESP32) && +#if defined(F) && defined(ESP32) #undef F #define F(sl) (sl) #endif @@ -354,6 +354,16 @@ class RestApi { ah::ip2Char(mConfig->sys.ip.gateway, buf); obj[F("gateway")] = String(buf); } + void getDisplay(JsonObject obj) { + obj[F("disp_type")] = (uint8_t)mConfig->plugin.display.type; + obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; + obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn; + obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift; + obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast; + obj[F("pinDisp0")] = mConfig->plugin.display.pin0; + obj[F("pinDisp1")] = mConfig->plugin.display.pin1; + } + void getMenu(JsonObject obj) { uint8_t i = 0; uint16_t mask = (mApp->getProtection()) ? mConfig->sys.protectionMask : 0; @@ -461,6 +471,7 @@ class RestApi { getRadio(obj.createNestedObject(F("radio"))); getSerial(obj.createNestedObject(F("serial"))); getStaticIp(obj.createNestedObject(F("static_ip"))); + getDisplay(obj.createNestedObject(F("display"))); } void getNetworks(JsonObject obj) { diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 1f9f81ead..025b3ce76 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -165,7 +165,7 @@
    System Config -

    Pinout (Wemos)

    +

    Pinout

    Radio (NRF24L01+)

    @@ -181,6 +181,25 @@
    + +
    +
    + Display Config +
    + +
    + +
    + +
    + + + +

    Pinout

    +
    +
    +
    +
    @@ -221,6 +240,56 @@ var highestId = 0; var maxInv = 0; + var esp8266pins = [ + [255, "off / default"], + [0, "D3 (GPIO0)"], + [1, "TX (GPIO1)"], + [2, "D4 (GPIO2)"], + [3, "RX (GPIO3)"], + [4, "D2 (GPIO4, SDA)"], + [5, "D1 (GPIO5, SCL)"], + [6, "GPIO6"], + [7, "GPIO7"], + [8, "GPIO8"], + [9, "GPIO9"], + [10, "GPIO10"], + [11, "GPIO11"], + [12, "D6 (GPIO12)"], + [13, "D7 (GPIO13)"], + [14, "D5 (GPIO14)"], + [15, "D8 (GPIO15)"], + [16, "D0 (GPIO16 - no IRQ!)"] + ]; + var esp32pins = [ + [255, "off / default"], + [0, "GPIO0"], + [1, "TX (GPIO1)"], + [2, "GPIO2 (LED)"], + [3, "RX (GPIO3)"], + [4, "GPIO4"], + [5, "GPIO5"], + [12, "GPIO12"], + [13, "GPIO13"], + [14, "GPIO14"], + [15, "GPIO15"], + [16, "GPIO16"], + [17, "GPIO17"], + [18, "GPIO18"], + [19, "GPIO19"], + [21, "GPIO21 (SDA)"], + [22, "GPIO22 (SCL)"], + [23, "GPIO23"], + [25, "GPIO25"], + [26, "GPIO26"], + [27, "GPIO27"], + [32, "GPIO32"], + [33, "GPIO33"], + [34, "GPIO34"], + [35, "GPIO35"], + [36, "VP (GPIO36)"], + [39, "VN (GPIO39)"] + ]; + const re = /11[2,4,6]1.*/; document.getElementById("btnAdd").addEventListener("click", function() { @@ -443,59 +512,7 @@ pins = [['cs', 'pinCs'], ['ce', 'pinCe'], ['irq', 'pinIrq'], ['led0', 'pinLed0'], ['led1', 'pinLed1']]; for(p of pins) { e.appendChild(lbl(p[1], p[0].toUpperCase())); - if("ESP8266" == type) { - e.appendChild(sel(p[1], [ - [255, "off / default"], - [0, "D3 (GPIO0)"], - [1, "TX (GPIO1)"], - [2, "D4 (GPIO2)"], - [3, "RX (GPIO3)"], - [4, "D2 (GPIO4)"], - [5, "D1 (GPIO5)"], - [6, "GPIO6"], - [7, "GPIO7"], - [8, "GPIO8"], - [9, "GPIO9"], - [10, "GPIO10"], - [11, "GPIO11"], - [12, "D6 (GPIO12)"], - [13, "D7 (GPIO13)"], - [14, "D5 (GPIO14)"], - [15, "D8 (GPIO15)"], - [16, "D0 (GPIO16 - no IRQ!)"] - ], obj[p[0]])); - } - else { - e.appendChild(sel(p[1], [ - [255, "off / default"], - [0, "GPIO0"], - [1, "TX (GPIO1)"], - [2, "GPIO2 (LED)"], - [3, "RX (GPIO3)"], - [4, "GPIO4"], - [5, "GPIO5"], - [12, "GPIO12"], - [13, "GPIO13"], - [14, "GPIO14"], - [15, "GPIO15"], - [16, "GPIO16"], - [17, "GPIO17"], - [18, "GPIO18"], - [19, "GPIO19"], - [21, "GPIO21"], - [22, "GPIO22"], - [23, "GPIO23"], - [25, "GPIO25"], - [26, "GPIO26"], - [27, "GPIO27"], - [32, "GPIO32"], - [33, "GPIO33"], - [34, "GPIO34"], - [35, "GPIO35"], - [36, "VP (GPIO36)"], - [39, "VN (GPIO39)"] - ], obj[p[0]])); - } + e.appendChild(sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[0]])); } } @@ -516,6 +533,29 @@ document.getElementsByName("serIntvl")[0].value = obj["interval"]; } + function parseDisplay(obj, type) { + for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"]]) + document.getElementsByName(i[0])[0].checked = obj[i[1]]; + + var e = document.getElementById("dispPins"); + pins = [['SCL / CS', 'pinDisp0'], ['SDA / DC', 'pinDisp1']]; + for(p of pins) { + e.appendChild(lbl(p[1], p[0].toUpperCase())); + e.appendChild(sel(p[1], ("ESP8266" == type) ? esp8266pins : esp32pins, obj[p[1]])); + } + + var opts = [[0, "None"], [1, "Nokia5110"], [2, "SSD1306 0.96\""], [3, "SH1106 1.3\""]]; + document.getElementById("dispType").append( + lbl("dispType", "Type"), + sel("dispType", opts, obj["disp_type"]) + ); + + e = document.getElementById("contrast"); + for(var i = 30; i < 101; i += 2) { + e.appendChild(opt(i, i, (i == obj["contrast"]))); + } + } + function parse(root) { if(null != root) { parseMenu(root["menu"]); @@ -529,6 +569,7 @@ parsePinout(root["pinout"], root["system"]["esp_type"]); parseRadio(root["radio"]); parseSerial(root["serial"]); + parseDisplay(root["display"], root["system"]["esp_type"]); } } @@ -555,7 +596,7 @@ hiddenInput = document.getElementById("disclaimer") hiddenInput.value = sessionStorage.getItem("gDisclaimer"); - getAjax("/api/setup", parse); + getAjax("http://10.20.3.44/api/setup", parse); diff --git a/src/web/web.h b/src/web/web.h index e80ff76f5..4c96f8b90 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -562,6 +562,17 @@ class Web { // Needed to log TX buffers to serial console mSys->Radio.mSerialDebug = mConfig->serial.debug; } + + // display + mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on"); + mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on"); + mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on"); + mConfig->plugin.display.type = request->arg("dispType").toInt(); + mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); + mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt(); + mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt(); + + mApp->saveSettings(); if(request->arg("reboot") == "on") diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index c4fba0442..11bf5ae05 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -11,7 +11,6 @@ // NTP CONFIG #define NTP_PACKET_SIZE 48 -#define NTP_RETRIES 5 //----------------------------------------------------------------------------- ahoywifi::ahoywifi() : mApIp(192, 168, 4, 1) {} @@ -26,7 +25,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mStaConn = DISCONNECTED; mCnt = 0; mScanActive = false; - mRetries = NTP_RETRIES; + mLastNtpFailed = false; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -149,26 +148,25 @@ void ahoywifi::setupStation(void) { //----------------------------------------------------------------------------- -bool ahoywifi::getNtpTime(uint32_t *nxtTrig) { - if(0 != mRetries) { - DPRINTLN(DBG_INFO, "try to getNtpTime"); - *nxtTrig = 43200; // check again in 12h (if NTP was successful) - mRetries--; - } else if(0 != *mUtcTimestamp) { // time is availabe, but NTP not - *nxtTrig = 5; // check again 5s - mRetries = NTP_RETRIES; +bool ahoywifi::getNtpTime() { + if(mLastNtpFailed && (0 != *mUtcTimestamp)) { // time is available, but NTP not maybe it was set by "sync with browser" + mLastNtpFailed = false; return true; // true is necessary to enable all timers even if NTP was not reachable } - if(GOT_IP != mStaConn) + if(GOT_IP != mStaConn) { + mLastNtpFailed = true; return false; + } IPAddress timeServer; uint8_t buf[NTP_PACKET_SIZE]; uint8_t retry = 0; - if (WiFi.hostByName(mConfig->ntp.addr, timeServer) != 1) + if (WiFi.hostByName(mConfig->ntp.addr, timeServer) != 1) { + mLastNtpFailed = true; return false; + } mUdp.begin(mConfig->ntp.port); sendNTPpacket(timeServer); @@ -193,6 +191,7 @@ bool ahoywifi::getNtpTime(uint32_t *nxtTrig) { } DPRINTLN(DBG_INFO, F("[NTP]: getNtpTime failed")); + mLastNtpFailed = true; return false; } @@ -275,10 +274,10 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) { if(mStaConn != CONNECTING) { mStaConn = DISCONNECTED; mCnt = 5; // try to reconnect in 5 sec + mLastNtpFailed = false; setupWifi(); // reconnect with AP / Station setup mAppWifiCb(false); DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); - mRetries = NTP_RETRIES; } break; diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 72a80d7bd..292c60747 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -25,7 +25,7 @@ class ahoywifi { void setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb); void tickWifiLoop(void); - bool getNtpTime(uint32_t *nxtTrig); + bool getNtpTime(); void scanAvailNetworks(void); void getAvailNetworks(JsonObject obj); @@ -68,7 +68,7 @@ class ahoywifi { uint8_t mLoopCnt; bool mScanActive; - uint8_t mRetries; + bool mLastNtpFailed; }; #endif /*__AHOYWIFI_H__*/ From f78e417a94299b5992b886f12d552380dac617b0 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 21 Jan 2023 00:35:53 +0100 Subject: [PATCH 030/215] fix ip --- src/web/html/setup.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 025b3ce76..3bd8ef6ec 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -596,7 +596,7 @@ hiddenInput = document.getElementById("disclaimer") hiddenInput.value = sessionStorage.getItem("gDisclaimer"); - getAjax("http://10.20.3.44/api/setup", parse); + getAjax("/api/setup", parse); From 67043285bcfd53fca093e0065e7145b297cbfd5e Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 21 Jan 2023 00:42:45 +0100 Subject: [PATCH 031/215] fix script --- scripts/getVersion.py | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/scripts/getVersion.py b/scripts/getVersion.py index 5f96f37f6..4ecb714eb 100644 --- a/scripts/getVersion.py +++ b/scripts/getVersion.py @@ -56,21 +56,6 @@ def readVersion(path, infile): src = path + ".pio/build/esp8266-release/firmware.bin" dst = path + "firmware/" + versionout os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp8266_nokia5110.bin" - src = path + ".pio/build/esp8266-nokia5110/firmware.bin" - dst = path + "firmware/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp8266_ssd1306.bin" - src = path + ".pio/build/esp8266-ssd1306/firmware.bin" - dst = path + "firmware/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp8266_sh1106.bin" - src = path + ".pio/build/esp8266-sh1106/firmware.bin" - dst = path + "firmware/" + versionout - os.rename(src, dst) versionout = version[:-1] + "_" + sha + "_esp8285.bin" src = path + ".pio/build/esp8285-release/firmware.bin" @@ -83,21 +68,6 @@ def readVersion(path, infile): dst = path + "firmware/" + versionout os.rename(src, dst) - versionout = version[:-1] + "_" + sha + "_esp32_nokia5110.bin" - src = path + ".pio/build/esp32-wroom32-nokia5110/firmware.bin" - dst = path + "firmware/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32_ssd1306.bin" - src = path + ".pio/build/esp32-wroom32-ssd1306/firmware.bin" - dst = path + "firmware/" + versionout - os.rename(src, dst) - - versionout = version[:-1] + "_" + sha + "_esp32_sh1106.bin" - src = path + ".pio/build/esp32-wroom32-sh1106/firmware.bin" - dst = path + "firmware/" + versionout - os.rename(src, dst) - # other ESP32 bin files src = path + ".pio/build/esp32-wroom32-release/" dst = path + "firmware/" From 2dbf732ddcbcadf653e3e797a81537d2007be2b4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 21 Jan 2023 00:52:16 +0100 Subject: [PATCH 032/215] fix power limit not possible #607 --- src/CHANGES.md | 1 + src/hm/hmInverter.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 40cf5f8b9..89fd46329 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -6,6 +6,7 @@ * fix wakeup issue, once wifi was lost during night the communication didn't start in the morning * reenabled FlashStringHelper because of lacking RAM * complete rewrite of monochrome display class, thx to @dAjaY85 -> displays are now configurable in setup +* fix power limit not possible #607 ## 0.5.74 * improved payload handling (retransmit all fragments on CRC error) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index ab86965df..3cdeb101f 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -282,6 +282,7 @@ class Inverter { else if (rec->assign == InfoAssignment) { DPRINTLN(DBG_DEBUG, "add info"); // eg. fw version ... + isConnected = true; } else if (rec->assign == SystemConfigParaAssignment) { DPRINTLN(DBG_DEBUG, "add config"); @@ -289,7 +290,6 @@ class Inverter { if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ actPowerLimit = rec->record[pos]; DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit, 1)); - isConnected = true; } } else if (rec->assign == AlarmDataAssignment) { From 7ceaa7944f5c0841acdc6cd680e82019b4457544 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 21 Jan 2023 23:25:17 +0100 Subject: [PATCH 033/215] reduce MQTT retry interval from maximum speed to one second fixed homeassistant autodiscovery #565 --- src/CHANGES.md | 4 ++ src/defines.h | 2 +- .../MonochromeDisplay/MonochromeDisplay.h | 5 +-- src/publisher/pubMqtt.h | 41 ++++++++++++++----- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 89fd46329..f1b217ddd 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,10 @@ (starting from release version `0.5.66`) +## 0.5.76 +* reduce MQTT retry interval from maximum speed to one second +* fixed homeassistant autodiscovery #565 + ## 0.5.75 * fix wakeup issue, once wifi was lost during night the communication didn't start in the morning * reenabled FlashStringHelper because of lacking RAM diff --git a/src/defines.h b/src/defines.h index 544507aeb..a5f108de2 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 75 +#define VERSION_PATCH 76 //------------------------------------- typedef struct { diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index 7666abfbe..f4e1a69c5 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -43,8 +43,6 @@ static TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central Eu template class MonochromeDisplay { public: - uint8_t dispContrast = 60; - MonochromeDisplay() : mCE(CEST, CET) {} void setup(display_t *cfg, HMSYSTEM *sys, uint32_t *utcTs, uint8_t disp_reset, const char *version) { @@ -75,6 +73,7 @@ class MonochromeDisplay { mDisplay->clearBuffer(); mDisplay->setContrast(mCfg->contrast); printText("Ahoy!", 0, 35); + printText("ahoydtu.de", 2, 20); printText(version, 3, 46); mDisplay->sendBuffer(); } @@ -193,7 +192,7 @@ class MonochromeDisplay { void printText(const char* text, uint8_t line, uint8_t dispX = 5) { if(!mIsLarge) - dispX = 5; + dispX = (line == 0) ? 10 : 5; setFont(line); if(mCfg->pxShift) dispX += (_mExtra % 7); // add pixel movement diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index bc3ecc7a8..2cd175e4c 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -46,6 +46,7 @@ class PubMqtt { mSys = sys; mUtcTimestamp = utcTs; mIntervalTimeout = 1; + mReconnectRequest = false; snprintf(mLwtTopic, MQTT_TOPIC_LEN + 5, "%s/mqtt", mCfgMqtt->topic); @@ -66,6 +67,7 @@ class PubMqtt { } void connect() { + mReconnectRequest = false; if(!mClient.connected()) mClient.connect(); } @@ -80,7 +82,10 @@ class PubMqtt { sendIvData(); } } - + if(mReconnectRequest) { + connect(); + return; + } } void tickerMinute() { @@ -147,12 +152,21 @@ class PubMqtt { if(!mClient.connected()) return; - if(addTopic) { - String topic = String(mCfgMqtt->topic) + "/" + String(subTopic); - mClient.publish(topic.c_str(), QOS_0, retained, payload); - } - else - mClient.publish(subTopic, QOS_0, retained, payload); + String topic = ""; + if(addTopic) + topic = String(mCfgMqtt->topic) + "/"; + topic += String(subTopic); + + do { + if(0 != mClient.publish(topic.c_str(), QOS_0, retained, payload)) + break; + if(!mClient.connected()) + break; + + mClient.loop(); + yield(); + } while(1); + mTxCnt++; } @@ -181,7 +195,7 @@ class PubMqtt { void sendDiscoveryConfig(void) { DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); - char topic[64], buffer[512], name[32], uniq_id[32]; + char topic[64], name[32], uniq_id[32]; DynamicJsonDocument doc(512); for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); @@ -221,8 +235,12 @@ class PubMqtt { doc[F("stat_cla")] = String(stateCls); snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); - serializeJson(doc, buffer); - publish(topic, buffer, true, false); + size_t size = measureJson(doc) + 1; + char *buf = new char[size]; + memset(buf, 0, size); + serializeJson(doc, buf, size); + publish(topic, buf, true, false); + delete[] buf; } yield(); @@ -258,7 +276,7 @@ class PubMqtt { switch (reason) { case espMqttClientTypes::DisconnectReason::TCP_DISCONNECTED: DBGPRINTLN(F("TCP disconnect")); - connect(); + mReconnectRequest = true; break; case espMqttClientTypes::DisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION: DBGPRINTLN(F("wrong protocol version")); @@ -561,6 +579,7 @@ class PubMqtt { std::queue mSendList; subscriptionCb mSubscriptionCb; bool mIvAvail; // shows if at least one inverter is available + bool mReconnectRequest; uint8_t mLastIvState[MAX_NUM_INVERTERS]; uint16_t mIntervalTimeout; From 9cedb41ff4ec19049dfde7f9e478a6a5c78d3854 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 22 Jan 2023 01:04:33 +0100 Subject: [PATCH 034/215] implemented `getNTPTime` improvements #609 partially #611 added alarm messages to MQTT #177, #600, #608 --- src/CHANGES.md | 2 ++ src/app.cpp | 10 +++++++--- src/app.h | 5 ++--- src/hm/hmInverter.h | 13 ++++++------- src/hm/payload.h | 33 +++++++++++++++++++++++++-------- src/publisher/pubMqtt.h | 27 +++++++++++++++++++++++++++ src/utils/handler.h | 33 --------------------------------- src/wifi/ahoywifi.cpp | 19 ++++--------------- src/wifi/ahoywifi.h | 7 +++++-- 9 files changed, 78 insertions(+), 71 deletions(-) delete mode 100644 src/utils/handler.h diff --git a/src/CHANGES.md b/src/CHANGES.md index f1b217ddd..1d3591298 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,6 +5,8 @@ ## 0.5.76 * reduce MQTT retry interval from maximum speed to one second * fixed homeassistant autodiscovery #565 +* implemented `getNTPTime` improvements #609 partially #611 +* added alarm messages to MQTT #177, #600, #608 ## 0.5.75 * fix wakeup issue, once wifi was lost during night the communication didn't start in the morning diff --git a/src/app.cpp b/src/app.cpp index 7f4071a2c..0d88847e2 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -28,7 +28,7 @@ void app::setup() { mSys = new HmSystemType(); mSys->enableDebug(); mSys->setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); - mPayload.addListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1)); + mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1)); #if defined(AP_ONLY) mInnerLoopCb = std::bind(&app::loopStandard, this); @@ -54,6 +54,7 @@ void app::setup() { if (mMqttEnabled) { mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mTimestamp); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); + mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } #endif setupLed(); @@ -134,6 +135,8 @@ void app::onWifi(bool gotIp) { mMqttReconnect = true; mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); + if(WIFI_AP == WiFi.getMode()) + everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); } else { mInnerLoopCb = std::bind(&app::loopWifi, this); @@ -154,7 +157,8 @@ void app::regularTickers(void) { //----------------------------------------------------------------------------- void app::tickNtpUpdate(void) { uint32_t nxtTrig = 5; // default: check again in 5 sec - if (mWifi.getNtpTime()) { + bool isOK = mWifi.getNtpTime(); + if (isOK || mTimestamp != 0) { if (mMqttReconnect && mMqttEnabled) { mMqtt.connect(); everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS"); @@ -166,7 +170,7 @@ void app::tickNtpUpdate(void) { mMqttReconnect = false; } - nxtTrig = 43200; // check again in 12h + nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min if((mSunrise == 0) && (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; diff --git a/src/app.h b/src/app.h index 0466daf87..d51c9209e 100644 --- a/src/app.h +++ b/src/app.h @@ -195,9 +195,8 @@ class app : public IApp, public ah::Scheduler { #if !defined(AP_ONLY) mMqtt.payloadEventListener(cmd); #endif - #if defined(ENA_NOKIA) || defined(ENA_SSD1306) || defined(ENA_SH1106) - mMonoDisplay.payloadEventListener(cmd); - #endif + if(mConfig->plugin.display.type != 0) + mMonoDisplay.payloadEventListener(cmd); } void mqttSubRxCb(JsonObject obj); diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 3cdeb101f..640aeae39 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -286,7 +286,6 @@ class Inverter { } else if (rec->assign == SystemConfigParaAssignment) { DPRINTLN(DBG_DEBUG, "add config"); - // get at least the firmware version and save it to the inverter object if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ actPowerLimit = rec->record[pos]; DPRINT(DBG_DEBUG, F("Inverter actual power limit: ") + String(actPowerLimit, 1)); @@ -451,10 +450,10 @@ class Inverter { } } - bool parseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len) { + uint16_t parseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len, uint32_t *start, uint32_t *endTime) { uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE; if((startOff + ALARM_LOG_ENTRY_SIZE) > len) - return false; + return 0; uint16_t wCode = ((uint16_t)pyld[startOff]) << 8 | pyld[startOff+1]; uint32_t startTimeOffset = 0, endTimeOffset = 0; @@ -464,11 +463,11 @@ class Inverter { if (((wCode >> 12) & 0x01) == 1) // check if is AM or PM endTimeOffset = 12 * 60 * 60; - uint32_t start = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset; - uint32_t end = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset; + *start = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset; + *endTime = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset; - DPRINTLN(DBG_INFO, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(start) + ", end: " + ah::getTimeStr(end)); - return true; + DPRINTLN(DBG_INFO, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(*start) + ", end: " + ah::getTimeStr(*endTime)); + return pyld[startOff+1]; } String getAlarmStr(uint16_t alarmCode) { diff --git a/src/hm/payload.h b/src/hm/payload.h index 093edbd3e..7e66eb764 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -8,7 +8,6 @@ #include "../utils/dbg.h" #include "../utils/crc.h" -#include "../utils/handler.h" #include "../config/config.h" #include @@ -29,12 +28,13 @@ typedef struct { typedef std::function payloadListenerType; +typedef std::function alarmListenerType; template -class Payload : public Handler { +class Payload { public: - Payload() : Handler() {} + Payload() {} void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { mApp = app; @@ -53,10 +53,12 @@ class Payload : public Handler { mSerialDebug = enable; } - void notify(uint8_t val) { - for(typename std::list::iterator it = mList.begin(); it != mList.end(); ++it) { - (*it)(val); - } + void addPayloadListener(payloadListenerType cb) { + mCbPayload = cb; + } + + void addAlarmListener(alarmListenerType cb) { + mCbAlarm = cb; } void loop() { @@ -258,9 +260,13 @@ class Payload : public Handler { if(AlarmData == mPayload[iv->id].txCmd) { uint8_t i = 0; + uint16_t code; + uint32_t start, end; while(1) { - if(!iv->parseAlarmLog(i++, payload, payloadLen)) + code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); + if(0 == code) break; + (mCbAlarm)(code, start, end); yield(); } } @@ -279,6 +285,14 @@ class Payload : public Handler { } private: + void notify(uint8_t val) { + (mCbPayload)(val); + } + + void notify(uint16_t code, uint32_t start, uint32_t endTime) { + (mCbAlarm)(code, start, endTime); + } + bool build(uint8_t id, bool *complete) { DPRINTLN(DBG_VERBOSE, F("build")); uint16_t crc = 0xffff, crcRcv = 0x0000; @@ -329,6 +343,9 @@ class Payload : public Handler { invPayload_t mPayload[MAX_NUM_INVERTERS]; bool mSerialDebug; Inverter<> *mHighPrioIv; + + alarmListenerType mCbAlarm; + payloadListenerType mCbPayload; }; #endif /*__PAYLOAD_H_*/ diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 2cd175e4c..11744ab67 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -26,6 +26,13 @@ typedef std::function subscriptionCb; +struct alarm_t { + uint16_t code; + uint32_t start; + uint32_t end; + alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {} +}; + template class PubMqtt { public: @@ -148,6 +155,12 @@ class PubMqtt { } } + void alarmEventListener(uint16_t code, uint32_t start, uint32_t endTime) { + if(mClient.connected()) { + mAlarmList.push(alarm_t(code, start, endTime)); + } + } + void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) { if(!mClient.connected()) return; @@ -436,6 +449,19 @@ class PubMqtt { return totalComplete; } + void sendAlarmData() { + if(mAlarmList.empty()) + return; + Inverter<> *iv = mSys->getInverterByPos(0, false); + while(!mAlarmList.empty()) { + alarm_t alarm = mAlarmList.front(); + publish("alarm", iv->getAlarmStr(alarm.code).c_str()); + publish("alarm_start", String(alarm.start).c_str()); + publish("alarm_end", String(alarm.end).c_str()); + mAlarmList.pop(); + } + } + void sendIvData(bool sendTotals = true) { if(mSendList.empty()) return; @@ -577,6 +603,7 @@ class PubMqtt { uint32_t *mUtcTimestamp; uint32_t mRxCnt, mTxCnt; std::queue mSendList; + std::queue mAlarmList; subscriptionCb mSubscriptionCb; bool mIvAvail; // shows if at least one inverter is available bool mReconnectRequest; diff --git a/src/utils/handler.h b/src/utils/handler.h deleted file mode 100644 index 51d64c0d2..000000000 --- a/src/utils/handler.h +++ /dev/null @@ -1,33 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://ahoydtu.de -// Lukas Pusch, lukas@lpusch.de -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- - -#ifndef __HANDLER_H__ -#define __HANDLER_H__ - -#include -#include -#include - -template -class Handler { - public: - Handler() {} - - void addListener(TYPE f) { - mList.push_back(f); - } - - /*virtual void notify(void) { - for(typename std::list::iterator it = mList.begin(); it != mList.end(); ++it) { - (*it)(); - } - }*/ - - protected: - std::list mList; -}; - -#endif /*__HANDLER_H__*/ diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 11bf5ae05..6f7725ebf 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -25,7 +25,6 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mStaConn = DISCONNECTED; mCnt = 0; mScanActive = false; - mLastNtpFailed = false; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -70,6 +69,7 @@ void ahoywifi::tickWifiLoop() { DBGPRINTLN(F("AP client connected")); welcome(mApIp.toString()); WiFi.mode(WIFI_AP); + mAppWifiCb(true); } return; } @@ -148,25 +148,16 @@ void ahoywifi::setupStation(void) { //----------------------------------------------------------------------------- -bool ahoywifi::getNtpTime() { - if(mLastNtpFailed && (0 != *mUtcTimestamp)) { // time is available, but NTP not maybe it was set by "sync with browser" - mLastNtpFailed = false; - return true; // true is necessary to enable all timers even if NTP was not reachable - } - - if(GOT_IP != mStaConn) { - mLastNtpFailed = true; +bool ahoywifi::getNtpTime(void) { + if(GOT_IP != mStaConn) return false; - } IPAddress timeServer; uint8_t buf[NTP_PACKET_SIZE]; uint8_t retry = 0; - if (WiFi.hostByName(mConfig->ntp.addr, timeServer) != 1) { - mLastNtpFailed = true; + if (WiFi.hostByName(mConfig->ntp.addr, timeServer) != 1) return false; - } mUdp.begin(mConfig->ntp.port); sendNTPpacket(timeServer); @@ -191,7 +182,6 @@ bool ahoywifi::getNtpTime() { } DPRINTLN(DBG_INFO, F("[NTP]: getNtpTime failed")); - mLastNtpFailed = true; return false; } @@ -274,7 +264,6 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) { if(mStaConn != CONNECTING) { mStaConn = DISCONNECTED; mCnt = 5; // try to reconnect in 5 sec - mLastNtpFailed = false; setupWifi(); // reconnect with AP / Station setup mAppWifiCb(false); DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 292c60747..7155a427c 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -25,7 +25,7 @@ class ahoywifi { void setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb); void tickWifiLoop(void); - bool getNtpTime(); + bool getNtpTime(void); void scanAvailNetworks(void); void getAvailNetworks(JsonObject obj); @@ -68,7 +68,10 @@ class ahoywifi { uint8_t mLoopCnt; bool mScanActive; - bool mLastNtpFailed; + + void sortRSSI(int *sort, int n); + void getBSSIDs(void); + std::list mBSSIDList; }; #endif /*__AHOYWIFI_H__*/ From 2610f1adfba51f54225067de33854ab49d853ae1 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 22 Jan 2023 01:10:54 +0100 Subject: [PATCH 035/215] fix ESP32 compile --- src/publisher/pubMqtt.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 11744ab67..f066a38af 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -175,8 +175,9 @@ class PubMqtt { break; if(!mClient.connected()) break; - + #if defined(ESP8266) mClient.loop(); + #endif yield(); } while(1); From 22ae6d2e22059ebf1d1eaf69c77433459040aaa5 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 24 Jan 2023 22:12:23 +0100 Subject: [PATCH 036/215] fix wrong filename for automatically created manifest (online installer) #620 added rotate display feature #619 improved Prometheus endpoint #615, thx to fsck-block --- scripts/buildManifest.py | 4 +- src/CHANGES.md | 5 ++ src/config/settings.h | 4 ++ src/defines.h | 2 +- .../MonochromeDisplay/MonochromeDisplay.h | 10 +-- src/web/RestApi.h | 1 + src/web/html/setup.html | 4 +- src/web/web.h | 69 +++++++++++++++---- 8 files changed, 78 insertions(+), 21 deletions(-) diff --git a/scripts/buildManifest.py b/scripts/buildManifest.py index 9a5411d57..db29b3527 100644 --- a/scripts/buildManifest.py +++ b/scripts/buildManifest.py @@ -36,13 +36,13 @@ def buildManifest(path, infile, outfile): esp32["parts"].append({"path": "bootloader.bin", "offset": 4096}) esp32["parts"].append({"path": "partitions.bin", "offset": 32768}) esp32["parts"].append({"path": "ota.bin", "offset": 57344}) - esp32["parts"].append({"path": version[1] + "_esp32_" + sha + ".bin", "offset": 65536}) + esp32["parts"].append({"path": version[1] + "_" + sha + "_esp32.bin", "offset": 65536}) data["builds"].append(esp32) esp8266 = {} esp8266["chipFamily"] = "ESP8266" esp8266["parts"] = [] - esp8266["parts"].append({"path": version[1] + "_esp8266_" + sha + ".bin", "offset": 0}) + esp8266["parts"].append({"path": version[1] + "_" + sha + "_esp8266.bin", "offset": 0}) data["builds"].append(esp8266) jsonString = json.dumps(data, indent=2) diff --git a/src/CHANGES.md b/src/CHANGES.md index 1d3591298..9405fb152 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,11 @@ (starting from release version `0.5.66`) +## 0.5.77 +* fix wrong filename for automatically created manifest (online installer) #620 +* added rotate display feature #619 +* improved Prometheus endpoint #615, thx to fsck-block + ## 0.5.76 * reduce MQTT retry interval from maximum speed to one second * fixed homeassistant autodiscovery #565 diff --git a/src/config/settings.h b/src/config/settings.h index dbf39d6c8..4a922962f 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -122,6 +122,7 @@ typedef struct { bool pwrSaveAtIvOffline; bool logoEn; bool pxShift; + bool rot180; uint16_t wakeUp; uint16_t sleepAt; uint8_t contrast; @@ -340,6 +341,7 @@ class settings { mCfg.plugin.display.contrast = 60; mCfg.plugin.display.logoEn = true; mCfg.plugin.display.pxShift = true; + mCfg.plugin.display.rot180 = false; mCfg.plugin.display.pin0 = DEF_PIN_OFF; // SCL mCfg.plugin.display.pin1 = DEF_PIN_OFF; // SDA } @@ -471,6 +473,7 @@ class settings { disp[F("pwrSafe")] = (bool)mCfg.plugin.display.pwrSaveAtIvOffline; disp[F("logo")] = (bool)mCfg.plugin.display.logoEn; disp[F("pxShift")] = (bool)mCfg.plugin.display.pxShift; + disp[F("rot180")] = (bool)mCfg.plugin.display.rot180; disp[F("wake")] = mCfg.plugin.display.wakeUp; disp[F("sleep")] = mCfg.plugin.display.sleepAt; disp[F("contrast")] = mCfg.plugin.display.contrast; @@ -482,6 +485,7 @@ class settings { mCfg.plugin.display.pwrSaveAtIvOffline = (bool) disp[F("pwrSafe")]; mCfg.plugin.display.logoEn = (bool) disp[F("logo")]; mCfg.plugin.display.pxShift = (bool) disp[F("pxShift")]; + mCfg.plugin.display.rot180 = (bool) disp[F("rot180")]; mCfg.plugin.display.wakeUp = disp[F("wake")]; mCfg.plugin.display.sleepAt = disp[F("sleep")]; mCfg.plugin.display.contrast = disp[F("contrast")]; diff --git a/src/defines.h b/src/defines.h index a5f108de2..ecb161bcb 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 76 +#define VERSION_PATCH 77 //------------------------------------- typedef struct { diff --git a/src/plugins/MonochromeDisplay/MonochromeDisplay.h b/src/plugins/MonochromeDisplay/MonochromeDisplay.h index f4e1a69c5..5cfd85f02 100644 --- a/src/plugins/MonochromeDisplay/MonochromeDisplay.h +++ b/src/plugins/MonochromeDisplay/MonochromeDisplay.h @@ -51,18 +51,20 @@ class MonochromeDisplay { mUtcTs = utcTs; mNewPayload = false; mLoopCnt = 0; - mTimeout = DISP_DEFAULT_TIMEOUT; // power off timeout (after inverters go offline) + + u8g2_cb_t *rot = (u8g2_cb_t *)((mCfg->rot180) ? U8G2_R2 : U8G2_R0); + if(mCfg->type) { switch(mCfg->type) { case 1: - mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(U8G2_R0, mCfg->pin0, mCfg->pin1, disp_reset); + mDisplay = new U8G2_PCD8544_84X48_F_4W_HW_SPI(rot, mCfg->pin0, mCfg->pin1, disp_reset); break; case 2: - mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, disp_reset, mCfg->pin0, mCfg->pin1); + mDisplay = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1); break; case 3: - mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, disp_reset, mCfg->pin0, mCfg->pin1); + mDisplay = new U8G2_SH1106_128X64_NONAME_F_HW_I2C(rot, disp_reset, mCfg->pin0, mCfg->pin1); break; } mDisplay->begin(); diff --git a/src/web/RestApi.h b/src/web/RestApi.h index c80254645..ec4fd61fc 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -359,6 +359,7 @@ class RestApi { obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; obj[F("logo_en")] = (bool)mConfig->plugin.display.logoEn; obj[F("px_shift")] = (bool)mConfig->plugin.display.pxShift; + obj[F("rot180")] = (bool)mConfig->plugin.display.rot180; obj[F("contrast")] = (uint8_t)mConfig->plugin.display.contrast; obj[F("pinDisp0")] = mConfig->plugin.display.pin0; obj[F("pinDisp1")] = mConfig->plugin.display.pin1; diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 3bd8ef6ec..fe5144493 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -192,6 +192,8 @@

    + +
    @@ -534,7 +536,7 @@ } function parseDisplay(obj, type) { - for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"]]) + for(var i of [["logoEn", "logo_en"], ["dispPwr", "disp_pwr"], ["dispPxSh", "px_shift"], ["disp180", "rot180"]]) document.getElementsByName(i[0])[0].checked = obj[i[1]]; var e = document.getElementById("dispPins"); diff --git a/src/web/web.h b/src/web/web.h index 4c96f8b90..a661c987c 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -565,12 +565,13 @@ class Web { // display mConfig->plugin.display.pwrSaveAtIvOffline = (request->arg("dispPwr") == "on"); - mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on"); + mConfig->plugin.display.logoEn = (request->arg("logoEn") == "on"); mConfig->plugin.display.pxShift = (request->arg("dispPxSh") == "on"); - mConfig->plugin.display.type = request->arg("dispType").toInt(); - mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); - mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt(); - mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt(); + mConfig->plugin.display.rot180 = (request->arg("disp180") == "on"); + mConfig->plugin.display.type = request->arg("dispType").toInt(); + mConfig->plugin.display.contrast = request->arg("dispCont").toInt(); + mConfig->plugin.display.pin0 = request->arg("pinDisp0").toInt(); + mConfig->plugin.display.pin1 = request->arg("pinDisp1").toInt(); mApp->saveSettings(); @@ -735,36 +736,78 @@ class Web { void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); String metrics; - char headline[80]; + char infoline[90]; - snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mConfig->sys.deviceName); - metrics += "# TYPE ahoy_solar_info gauge\n" + String(headline) + "\n"; + // System info + snprintf(infoline, sizeof(infoline), "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mConfig->sys.deviceName); + metrics += "# TYPE ahoy_solar_info gauge\n" + String(infoline) + "\n"; Inverter<> *iv; record_t<> *rec; - char type[60], topic[60], val[25]; + char type[60], topic[80], val[25]; for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { iv = mSys->getInverterByPos(id); if(NULL == iv) continue; + // Inverter info + snprintf(infoline, sizeof(infoline), "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\",enabled=\"%d\"} 1", + iv->config->name, iv->config->serial.u64,iv->config->enabled); + metrics += "# TYPE ahoy_solar_inverter_info gauge\n" + String(infoline) + "\n"; + // AC rec = iv->getRecordStruct(RealTimeRunData_Debug); for(uint8_t i = 0; i < rec->length; i++) { uint8_t channel = rec->assign[i].ch; if(channel == 0) { String promUnit, promType; std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(i, rec)); - snprintf(type, 60, "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i, rec), promUnit.c_str(), promType.c_str()); - snprintf(topic, 60, "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i, rec), promUnit.c_str(), iv->config->name); - snprintf(val, 25, "%.3f", iv->getValue(i, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i, rec), promUnit.c_str(), promType.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val), "%.3f", iv->getValue(i, rec)); + metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; + } + } + // channels DC + for(uint8_t j = 1; j <= iv->channels; j ++) { + uint8_t pos; + for (uint8_t k = 0; k < 6; k++) { + switch(k) { + default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; + case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; + case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; + case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; + case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; + case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; + } + String promUnit, promType; + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(pos, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(pos, rec), promUnit.c_str(), promType.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_%s_%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(pos, rec), promUnit.c_str(), iv->config->name, iv->config->chName[j-1]); + snprintf(val, sizeof(val), "%.3f", iv->getValue(pos, rec)); metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; } } } - AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), metrics); + // NRF Statistics + statistics_t *stat = mApp->getStatistics(); + metrics += radioStatistic(F("rx_success"), stat->rxSuccess); + metrics += radioStatistic(F("rx_fail"), stat->rxFail); + metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); + metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); + metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); + + AsyncWebServerResponse *response = request->beginResponse(200, F("text/plain"), metrics); request->send(response); } + String radioStatistic(String statistic, uint32_t value) { + char type[60], topic[80], val[25]; + snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s gauge",statistic.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_radio_%s",statistic.c_str()); + snprintf(val, sizeof(val), "%d", value); + return ( String(type) + "\n" + String(topic) + " " + String(val) + "\n"); + } + std::pair convertToPromUnits(String shortUnit) { if(shortUnit == "A") return {"ampere", "gauge"}; if(shortUnit == "V") return {"volt", "gauge"}; From 6721c8bbc11258abed2b066dd23f9ff6de496bff Mon Sep 17 00:00:00 2001 From: lumapu Date: Wed, 25 Jan 2023 22:28:10 +0100 Subject: [PATCH 037/215] wifi debug --- src/CHANGES.md | 3 +- src/web/RestApi.h | 1 + src/wifi/ahoywifi.cpp | 121 ++++++++++++++++++++++++++++++++++-------- src/wifi/ahoywifi.h | 6 +-- 4 files changed, 105 insertions(+), 26 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 9405fb152..4d492205c 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,7 +5,8 @@ ## 0.5.77 * fix wrong filename for automatically created manifest (online installer) #620 * added rotate display feature #619 -* improved Prometheus endpoint #615, thx to fsck-block +* improved Prometheus endpoint #615, thx to @fsck-block +* improved wifi to connect always to strongest RSSI, thx to @beegee3 #611 ## 0.5.76 * reduce MQTT retry interval from maximum speed to one second diff --git a/src/web/RestApi.h b/src/web/RestApi.h index ec4fd61fc..3a5ba0b11 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -601,6 +601,7 @@ class RestApi { bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { if(F("scan_wifi") == jsonIn[F("cmd")]) { + DPRINTLN(DBG_INFO, F("rqst scan")); mApp->scanAvailNetworks(); } else if(F("set_time") == jsonIn[F("cmd")]) diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 6f7725ebf..6b5c13a57 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -25,6 +25,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mStaConn = DISCONNECTED; mCnt = 0; mScanActive = false; + mLastApClients = 0; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -64,21 +65,44 @@ void ahoywifi::tickWifiLoop() { #if !defined(AP_ONLY) if(mStaConn != GOT_IP) { if (WiFi.softAPgetStationNum() > 0) { // do not reconnect if any AP connection exists - mDns.processNextRequest(); - if((WIFI_AP_STA == WiFi.getMode()) && !mScanActive) { - DBGPRINTLN(F("AP client connected")); - welcome(mApIp.toString()); - WiFi.mode(WIFI_AP); - mAppWifiCb(true); + if(WIFI_AP_STA == WiFi.getMode()) { + if(mScanActive && (mLastApClients != WiFi.softAPgetStationNum())) + mScanActive = false; + + if(mLastApClients != WiFi.softAPgetStationNum()) { + mLastApClients = WiFi.softAPgetStationNum(); + WiFi.scanDelete(); + WiFi.mode(WIFI_AP); + //mDns.start(53, "*", mApIp); + mAppWifiCb(true); + DBGPRINTLN(F("AP client connected")); + welcome(mApIp.toString()); + } } + mDns.processNextRequest(); return; } else if(WIFI_AP == WiFi.getMode()) { + mLastApClients = 0; mCnt = 0; + DPRINTLN(DBG_INFO, "DNS stop"); + mDns.stop(); WiFi.mode(WIFI_AP_STA); } mCnt++; + if(!mScanActive && mBSSIDList.empty()) { // start scanning APs with the given SSID + DBGPRINT(F("scanning APs with SSID ")); + DBGPRINTLN(String(mConfig->sys.stationSsid)); + mScanActive = true; + #if defined(ESP8266) + WiFi.scanNetworks(true, false, 0U, (uint8_t *)mConfig->sys.stationSsid); + #else + WiFi.scanNetworks(true, false, false, 300U, 0U, mConfig->sys.stationSsid); + #endif + return; + } + uint8_t timeout = 10; // seconds if (mStaConn == CONNECTED) // connected but no ip @@ -87,10 +111,27 @@ void ahoywifi::tickWifiLoop() { DBGPRINT(F("reconnect in ")); DBGPRINT(String(timeout-mCnt)); DBGPRINTLN(F(" seconds")); + if(mScanActive) { + getBSSIDs(); + if(!mScanActive) // scan completed + if ((mCnt % timeout) < 8) + mCnt = timeout - 2; + } if((mCnt % timeout) == 0) { // try to reconnect after x sec without connection if(mStaConn != CONNECTED) mStaConn = CONNECTING; - WiFi.reconnect(); + if(mBSSIDList.size() > 0) { // get first BSSID in list + DBGPRINT("try to connect to AP with BSSID:"); + uint8_t bssid[6]; + for (int j = 0; j < 6; j++) { + bssid[j] = mBSSIDList.front(); + mBSSIDList.pop_front(); + DBGPRINT(" " + String(bssid[j], HEX)); + } + DBGPRINTLN(""); + WiFi.disconnect(); + WiFi.begin(mConfig->sys.stationSsid, mConfig->sys.stationPwd, 0, &bssid[0]); + } mCnt = 0; } } @@ -135,7 +176,7 @@ void ahoywifi::setupStation(void) { if(!WiFi.config(ip, gateway, mask, dns1, dns2)) DPRINTLN(DBG_ERROR, F("failed to set static IP!")); } - mStaConn = (WiFi.begin(mConfig->sys.stationSsid, mConfig->sys.stationPwd) != WL_CONNECTED) ? DISCONNECTED : CONNECTED; + mBSSIDList.clear(); if(String(mConfig->sys.deviceName) != "") WiFi.hostname(mConfig->sys.deviceName); WiFi.mode(WIFI_AP_STA); @@ -206,40 +247,72 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { mUdp.endPacket(); } +//----------------------------------------------------------------------------- +void ahoywifi::sortRSSI(int *sort, int n) { + for (int i = 0; i < n; i++) + sort[i] = i; + for (int i = 0; i < n; i++) + for (int j = i + 1; j < n; j++) + if (WiFi.RSSI(sort[j]) > WiFi.RSSI(sort[i])) + std::swap(sort[i], sort[j]); +} //----------------------------------------------------------------------------- void ahoywifi::scanAvailNetworks(void) { + DPRINTLN(DBG_INFO, "start scan"); if(-2 == WiFi.scanComplete()) { mScanActive = true; if(WIFI_AP == WiFi.getMode()) WiFi.mode(WIFI_AP_STA); WiFi.scanNetworks(true); } + else + DPRINTLN(DBG_INFO, "complete!"); } - //----------------------------------------------------------------------------- void ahoywifi::getAvailNetworks(JsonObject obj) { JsonArray nets = obj.createNestedArray("networks"); + DPRINTLN(DBG_INFO, "getAvailNetworks"); int n = WiFi.scanComplete(); + if (n < 0) + return; if(n > 0) { int sort[n]; - for (int i = 0; i < n; i++) - sort[i] = i; - for (int i = 0; i < n; i++) - for (int j = i + 1; j < n; j++) - if (WiFi.RSSI(sort[j]) > WiFi.RSSI(sort[i])) - std::swap(sort[i], sort[j]); + sortRSSI(&sort[0], n); for (int i = 0; i < n; ++i) { - nets[i]["ssid"] = WiFi.SSID(sort[i]); - nets[i]["rssi"] = WiFi.RSSI(sort[i]); + DPRINTLN(DBG_INFO, "found network: " + WiFi.SSID(sort[i])); + nets[i]["ssid"] = WiFi.SSID(sort[i]); + nets[i]["rssi"] = WiFi.RSSI(sort[i]); } - mScanActive = false; - WiFi.scanDelete(); } + mScanActive = false; + WiFi.scanDelete(); } +//----------------------------------------------------------------------------- +void ahoywifi::getBSSIDs() { + int n = WiFi.scanComplete(); + if (n < 0) + return; + if(n > 0) { + mBSSIDList.clear(); + int sort[n]; + sortRSSI(&sort[0], n); + for (int i = 0; i < n; i++) { + DBGPRINT("BSSID " + String(i) + ":"); + uint8_t *bssid = WiFi.BSSID(sort[i]); + for (int j = 0; j < 6; j++){ + DBGPRINT(" " + String(bssid[j], HEX)); + mBSSIDList.push_back(bssid[j]); + } + DBGPRINTLN(""); + } + } + mScanActive = false; + WiFi.scanDelete(); +} //----------------------------------------------------------------------------- void ahoywifi::connectionEvent(WiFiStatus_t status) { @@ -248,15 +321,19 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) { if(mStaConn != CONNECTED) { mStaConn = CONNECTED; DBGPRINTLN(F("\n[WiFi] Connected")); - WiFi.mode(WIFI_STA); - DBGPRINTLN(F("[WiFi] AP disabled")); - mDns.stop(); } break; case GOT_IP: mStaConn = GOT_IP; + if (mScanActive) { // maybe another scan has started + WiFi.scanDelete(); + mScanActive = false; + } welcome(WiFi.localIP().toString() + F(" (Station)")); + mDns.stop(); + WiFi.mode(WIFI_STA); + DBGPRINTLN(F("[WiFi] AP disabled")); mAppWifiCb(true); break; diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 7155a427c..549bf716f 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -50,7 +50,8 @@ class ahoywifi { void onWiFiEvent(WiFiEvent_t event); #endif void welcome(String msg); - + void sortRSSI(int *sort, int n); + void getBSSIDs(void); settings_t *mConfig; appWifiCb mAppWifiCb; @@ -68,9 +69,8 @@ class ahoywifi { uint8_t mLoopCnt; bool mScanActive; + uint8_t mLastApClients; - void sortRSSI(int *sort, int n); - void getBSSIDs(void); std::list mBSSIDList; }; From 3bd8fe61b55acc800070b8ca5ddbdcbeded39e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20K?= Date: Sat, 28 Jan 2023 05:58:32 +0100 Subject: [PATCH 038/215] Fix attempt for HA autodiscovery --- src/publisher/pubMqtt.h | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index f066a38af..dc80d06d0 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -236,23 +236,24 @@ class PubMqtt { const char *devCls = getFieldDeviceClass(rec->assign[i].fieldId); const char *stateCls = getFieldStateClass(rec->assign[i].fieldId); - doc.clear(); - doc[F("name")] = name; - doc[F("stat_t")] = String(mCfgMqtt->topic) + "/" + String(iv->config->name) + String(topic); - 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!? + DynamicJsonDocument doc2(512); + doc2.clear(); + doc2[F("name")] = name; + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + String(iv->config->name) + String(topic); + doc2[F("unit_of_meas")] = iv->getUnit(i, rec); + doc2[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id; + doc2[F("dev")] = deviceObj; + doc2[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[F("dev_cla")] = String(devCls); + doc2[F("dev_cla")] = String(devCls); if (stateCls != NULL) - doc[F("stat_cla")] = String(stateCls); + doc2[F("stat_cla")] = String(stateCls); snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); - size_t size = measureJson(doc) + 1; + size_t size = measureJson(doc2) + 1; char *buf = new char[size]; memset(buf, 0, size); - serializeJson(doc, buf, size); + serializeJson(doc2, buf, size); publish(topic, buf, true, false); delete[] buf; } From 9456937baf8bdef0f182f0814d00d3128e9dd5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20K?= Date: Sat, 28 Jan 2023 10:39:09 +0100 Subject: [PATCH 039/215] Add Autodiscovery for total topic --- src/publisher/pubMqtt.h | 74 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index dc80d06d0..e59eb9f4b 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -258,8 +258,72 @@ class PubMqtt { delete[] buf; } - yield(); + yield(); + } +//Début modif + + String node_mac = WiFi.macAddress().substring(12,14)+ WiFi.macAddress().substring(15,17); + String node_id = "AHOY_DTU_" + node_mac; + doc.clear(); + doc[F("name")] = node_id; + doc[F("ids")] = node_id; + doc[F("cu")] = F("http://") + String(WiFi.localIP().toString()); + doc[F("mf")] = F("Hoymiles"); + doc[F("mdl")] = "AHOY_DTU"; + JsonObject deviceObj = doc.as(); + + uint8_t fieldId; + String fieldUnit; + for (uint8_t i = 0; i < 4; i++) { + switch (i) { + default: + case 0: + fieldId = FLD_PAC; + fieldUnit = "W"; + break; + case 1: + fieldId = FLD_YT; + fieldUnit = "kWh"; + break; + case 2: + fieldId = FLD_YD; + fieldUnit = "Wh"; + break; + case 3: + fieldId = FLD_PDC; + fieldUnit = "W"; + break; + } + + snprintf(name, 32, "Total %s", fields[fieldId]); + snprintf(topic, 64, "/%s", fields[fieldId]); + + + const char *devCls = getFieldDeviceClass(fieldId); + const char *stateCls = getFieldStateClass(fieldId); + + DynamicJsonDocument doc2(512); + doc2.clear(); + doc2[F("name")] = String(name); + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/total" + String(topic); + doc2[F("unit_of_meas")] = fieldUnit; + doc2[F("uniq_id")] = String(node_id) + "_" + String(fields[fieldId]); + doc2[F("dev")] = deviceObj; + doc2[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) + doc2[F("dev_cla")] = String(devCls); + if (stateCls != NULL) + doc2[F("stat_cla")] = String(stateCls); + + snprintf(topic, 64, "%s/sensor/%s/Total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(),fields[fieldId]); + size_t size = measureJson(doc2) + 1; + char *buf = new char[size]; + memset(buf, 0, size); + serializeJson(doc2, buf, size); + publish(topic, buf, true, false); + delete[] buf; } + yield(); } void setPowerLimitAck(Inverter<> *iv) { @@ -388,6 +452,14 @@ class PubMqtt { return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : stateClasses[deviceFieldAssignment[pos].stateClsId]; } + const char *getFieldUnit(uint8_t fieldId) { + uint8_t pos = 0; + for (; pos < DEVICE_CLS_ASSIGN_LIST_LEN; pos++) { + if (deviceFieldAssignment[pos].fieldId == fieldId) + break; + } + return (pos >= DEVICE_CLS_ASSIGN_LIST_LEN) ? NULL : deviceClasses[deviceFieldAssignment[pos].deviceClsId]; + } bool processIvStatus() { // returns true if all inverters are available bool allAvail = true; From f8d4b4f5ae90d4fc4447eb05cfbe258bd696b398 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sat, 28 Jan 2023 23:37:40 +0100 Subject: [PATCH 040/215] fixed Wifi scan during first configuration (client connected to AP of Ahoy) #611 --- src/web/RestApi.h | 4 +--- src/wifi/ahoywifi.cpp | 26 ++++++++++++++++---------- src/wifi/ahoywifi.h | 1 + 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 3a5ba0b11..43d535008 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -600,10 +600,8 @@ class RestApi { } bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { - if(F("scan_wifi") == jsonIn[F("cmd")]) { - DPRINTLN(DBG_INFO, F("rqst scan")); + if(F("scan_wifi") == jsonIn[F("cmd")]) mApp->scanAvailNetworks(); - } else if(F("set_time") == jsonIn[F("cmd")]) mApp->setTimestamp(jsonIn[F("val")]); else if(F("sync_ntp") == jsonIn[F("cmd")]) diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 6b5c13a57..3fcbe22be 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -26,6 +26,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mCnt = 0; mScanActive = false; mLastApClients = 0; + mScanCnt = 0; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -66,15 +67,21 @@ void ahoywifi::tickWifiLoop() { if(mStaConn != GOT_IP) { if (WiFi.softAPgetStationNum() > 0) { // do not reconnect if any AP connection exists if(WIFI_AP_STA == WiFi.getMode()) { + // first time switch to AP Mode if(mScanActive && (mLastApClients != WiFi.softAPgetStationNum())) mScanActive = false; + // scan is finished + if(!mScanActive) { + WiFi.mode(WIFI_AP); + mDns.start(53, "*", mApIp); + } + + // only once a client connects to AP if(mLastApClients != WiFi.softAPgetStationNum()) { mLastApClients = WiFi.softAPgetStationNum(); WiFi.scanDelete(); - WiFi.mode(WIFI_AP); - //mDns.start(53, "*", mApIp); - mAppWifiCb(true); + mAppWifiCb(false); DBGPRINTLN(F("AP client connected")); welcome(mApIp.toString()); } @@ -94,6 +101,7 @@ void ahoywifi::tickWifiLoop() { if(!mScanActive && mBSSIDList.empty()) { // start scanning APs with the given SSID DBGPRINT(F("scanning APs with SSID ")); DBGPRINTLN(String(mConfig->sys.stationSsid)); + mScanCnt = 0; mScanActive = true; #if defined(ESP8266) WiFi.scanNetworks(true, false, 0U, (uint8_t *)mConfig->sys.stationSsid); @@ -259,21 +267,17 @@ void ahoywifi::sortRSSI(int *sort, int n) { //----------------------------------------------------------------------------- void ahoywifi::scanAvailNetworks(void) { - DPRINTLN(DBG_INFO, "start scan"); if(-2 == WiFi.scanComplete()) { mScanActive = true; if(WIFI_AP == WiFi.getMode()) WiFi.mode(WIFI_AP_STA); WiFi.scanNetworks(true); } - else - DPRINTLN(DBG_INFO, "complete!"); } //----------------------------------------------------------------------------- void ahoywifi::getAvailNetworks(JsonObject obj) { JsonArray nets = obj.createNestedArray("networks"); - DPRINTLN(DBG_INFO, "getAvailNetworks"); int n = WiFi.scanComplete(); if (n < 0) @@ -282,7 +286,6 @@ void ahoywifi::getAvailNetworks(JsonObject obj) { int sort[n]; sortRSSI(&sort[0], n); for (int i = 0; i < n; ++i) { - DPRINTLN(DBG_INFO, "found network: " + WiFi.SSID(sort[i])); nets[i]["ssid"] = WiFi.SSID(sort[i]); nets[i]["rssi"] = WiFi.RSSI(sort[i]); } @@ -294,8 +297,11 @@ void ahoywifi::getAvailNetworks(JsonObject obj) { //----------------------------------------------------------------------------- void ahoywifi::getBSSIDs() { int n = WiFi.scanComplete(); - if (n < 0) - return; + if (n < 0){ + mScanCnt++; + if (mScanCnt < 20) + return; + } if(n > 0) { mBSSIDList.clear(); int sort[n]; diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 549bf716f..321dbb86e 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -70,6 +70,7 @@ class ahoywifi { uint8_t mLoopCnt; bool mScanActive; uint8_t mLastApClients; + uint8_t mScanCnt; std::list mBSSIDList; }; From 8457f46b0c6d24d41671ff52fb78a95cdc394a45 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 29 Jan 2023 00:55:10 +0100 Subject: [PATCH 041/215] further improvements regarding wifi #611, fix connection if only one AP with same SSID is there fix endless loop in `zerovalues` #564 fix auto discover again #565 added total values to autodiscover #630 improved zero at midnight #625 --- src/CHANGES.md | 7 ++++ src/defines.h | 2 +- src/publisher/pubMqtt.h | 78 ++++++++++++++++++++++++++--------------- src/wifi/ahoywifi.cpp | 19 +++++----- 4 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 4d492205c..9e31ada25 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,13 @@ (starting from release version `0.5.66`) +## 0.5.78 +* further improvements regarding wifi #611, fix connection if only one AP with same SSID is there +* fix endless loop in `zerovalues` #564 +* fix auto discover again #565 +* added total values to autodiscover #630 +* improved zero at midnight #625 + ## 0.5.77 * fix wrong filename for automatically created manifest (online installer) #620 * added rotate display feature #619 diff --git a/src/defines.h b/src/defines.h index ecb161bcb..5c626e028 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 77 +#define VERSION_PATCH 78 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index f066a38af..021a29eb9 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -133,6 +133,7 @@ class PubMqtt { void tickerMidnight() { Inverter<> *iv; record_t<> *rec; + char topic[7 + MQTT_TOPIC_LEN], val[4]; // set YieldDay to zero for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { @@ -142,10 +143,11 @@ class PubMqtt { rec = iv->getRecordStruct(RealTimeRunData_Debug); uint8_t pos = iv->getPosByChFld(CH0, FLD_YD, rec); iv->setValue(pos, rec, 0.0f); - } - mSendList.push(RealTimeRunData_Debug); - sendIvData(); + snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch0/%s", iv->config->name, fields[FLD_YD]); + snprintf(val, 4, "0.0"); + publish(topic, val, true); + } } void payloadEventListener(uint8_t cmd) { @@ -210,7 +212,10 @@ class PubMqtt { DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); char topic[64], name[32], uniq_id[32]; - DynamicJsonDocument doc(512); + DynamicJsonDocument doc(128); + + uint8_t fldTotal[4] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC}; + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); if (NULL == iv) @@ -218,41 +223,55 @@ class PubMqtt { record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); doc.clear(); + doc[F("name")] = iv->config->name; doc[F("ids")] = String(iv->config->serial.u64, HEX); doc[F("cu")] = F("http://") + String(WiFi.localIP().toString()); doc[F("mf")] = F("Hoymiles"); doc[F("mdl")] = iv->config->name; - JsonObject deviceObj = doc.as(); - - for (uint8_t i = 0; i < rec->length; i++) { - if (rec->assign[i].ch == CH0) - snprintf(name, 32, "%s %s", iv->config->name, iv->getFieldName(i, rec)); - else - snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); - snprintf(topic, 64, "/ch%d/%s", rec->assign[i].ch, iv->getFieldName(i, rec)); - snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec)); - - const char *devCls = getFieldDeviceClass(rec->assign[i].fieldId); - const char *stateCls = getFieldStateClass(rec->assign[i].fieldId); - - doc.clear(); - doc[F("name")] = name; - doc[F("stat_t")] = String(mCfgMqtt->topic) + "/" + String(iv->config->name) + String(topic); - 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!? + JsonObject deviceObj = doc.as(); // deviceObj is only pointer!? + + for (uint8_t i = 0; i < (rec->length + 4); i++) { + const char *devCls, *stateCls; + if(i < rec->length) { + if (rec->assign[i].ch == CH0) + snprintf(name, 32, "%s %s", iv->config->name, iv->getFieldName(i, rec)); + else + snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(topic, 64, "/ch%d/%s", rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec)); + + devCls = getFieldDeviceClass(rec->assign[i].fieldId); + stateCls = getFieldStateClass(rec->assign[i].fieldId); + } + else { // total values + snprintf(name, 32, "Total %s", fields[fldTotal[i-rec->length]]); + snprintf(topic, 64, "/%s", fields[fldTotal[i-rec->length]]); + snprintf(uniq_id, 32, "total_%s", fields[fldTotal[i-rec->length]]); + devCls = getFieldDeviceClass(fldTotal[i-rec->length]); + stateCls = getFieldStateClass(fldTotal[i-rec->length]); + } + + DynamicJsonDocument doc2(512); + doc2[F("name")] = name; + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + String(iv->config->name) + String(topic); + doc2[F("unit_of_meas")] = iv->getUnit(((i < rec->length) ? i : (i - rec->length)), rec); + doc2[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id; + doc2[F("dev")] = deviceObj; + doc2[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[F("dev_cla")] = String(devCls); + doc2[F("dev_cla")] = String(devCls); if (stateCls != NULL) - doc[F("stat_cla")] = String(stateCls); + doc2[F("stat_cla")] = String(stateCls); - snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); - size_t size = measureJson(doc) + 1; + if(i < rec->length) + snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + else // total values + snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, fields[fldTotal[i-rec->length]]); + size_t size = measureJson(doc2) + 1; char *buf = new char[size]; memset(buf, 0, size); - serializeJson(doc, buf, size); + serializeJson(doc2, buf, size); publish(topic, buf, true, false); delete[] buf; } @@ -582,6 +601,7 @@ class PubMqtt { case FLD_FW_BUILD_HOUR_MINUTE: case FLD_HW_ID: case FLD_ACT_ACTIVE_PWR_LIMIT: + fld++; continue; break; } diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 3fcbe22be..4ca7f290f 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -98,7 +98,12 @@ void ahoywifi::tickWifiLoop() { } mCnt++; - if(!mScanActive && mBSSIDList.empty()) { // start scanning APs with the given SSID + uint8_t timeout = 10; // seconds + if (mStaConn == CONNECTED) // connected but no ip + timeout = 20; + + + if(!mScanActive && mBSSIDList.empty() && ((mCnt % timeout) == 0)) { // start scanning APs with the given SSID DBGPRINT(F("scanning APs with SSID ")); DBGPRINTLN(String(mConfig->sys.stationSsid)); mScanCnt = 0; @@ -110,20 +115,14 @@ void ahoywifi::tickWifiLoop() { #endif return; } - - uint8_t timeout = 10; // seconds - - if (mStaConn == CONNECTED) // connected but no ip - timeout = 20; - DBGPRINT(F("reconnect in ")); DBGPRINT(String(timeout-mCnt)); DBGPRINTLN(F(" seconds")); if(mScanActive) { getBSSIDs(); - if(!mScanActive) // scan completed - if ((mCnt % timeout) < 8) - mCnt = timeout - 2; + //if(!mScanActive) // scan completed + // if ((mCnt % timeout) < 8) + // mCnt = timeout - 2; } if((mCnt % timeout) == 0) { // try to reconnect after x sec without connection if(mStaConn != CONNECTED) From bd8de5f8f93267357c00d8b42b4829230e3f6d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20K?= Date: Sun, 29 Jan 2023 11:43:39 +0100 Subject: [PATCH 042/215] Send totals values once --- src/publisher/pubMqtt.h | 54 ++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 1b3effd3e..c0bcff5e8 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -217,24 +217,39 @@ class PubMqtt { uint8_t fldTotal[4] = {FLD_PAC, FLD_YT, FLD_YD, FLD_PDC}; const char* unitTotal[4] = {"W", "kWh", "Wh", "W"}; - for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { - Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL == iv) + String node_mac = WiFi.macAddress().substring(12,14)+ WiFi.macAddress().substring(15,17); + String node_id = "AHOY_DTU_" + node_mac; + bool total = false; + + for (uint8_t id = 0; id < mSys->getNumInverters() ; id++) { + doc.clear(); + + if (total) // total become true at iv = NULL next cycle continue; + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL == iv) + total = true; record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - doc.clear(); - doc[F("name")] = iv->config->name; - doc[F("ids")] = String(iv->config->serial.u64, HEX); + if (!total) { + doc[F("name")] = iv->config->name; + doc[F("ids")] = String(iv->config->serial.u64, HEX); + doc[F("mdl")] = iv->config->name; + } + else { + doc[F("name")] = node_id; + doc[F("ids")] = node_id; + doc[F("mdl")] = node_id; + } + doc[F("cu")] = F("http://") + String(WiFi.localIP().toString()); doc[F("mf")] = F("Hoymiles"); - doc[F("mdl")] = iv->config->name; JsonObject deviceObj = doc.as(); // deviceObj is only pointer!? - for (uint8_t i = 0; i < (rec->length + 4); i++) { + for (uint8_t i = 0; i < ((!total) ? (rec->length) : (4) ) ; i++) { const char *devCls, *stateCls; - if(i < rec->length) { + if (!total) { if (rec->assign[i].ch == CH0) snprintf(name, 32, "%s %s", iv->config->name, iv->getFieldName(i, rec)); else @@ -245,19 +260,20 @@ class PubMqtt { devCls = getFieldDeviceClass(rec->assign[i].fieldId); stateCls = getFieldStateClass(rec->assign[i].fieldId); } + else { // total values - snprintf(name, 32, "Total %s", fields[fldTotal[i-rec->length]]); - snprintf(topic, 64, "/%s", fields[fldTotal[i-rec->length]]); - snprintf(uniq_id, 32, "total_%s", fields[fldTotal[i-rec->length]]); - devCls = getFieldDeviceClass(fldTotal[i-rec->length]); - stateCls = getFieldStateClass(fldTotal[i-rec->length]); + snprintf(name, 32, "Total %s", fields[fldTotal[i]]); + snprintf(topic, 64, "/%s", fields[fldTotal[i]]); + snprintf(uniq_id, 32, "total_%s", fields[fldTotal[i]]); + devCls = getFieldDeviceClass(fldTotal[i]); + stateCls = getFieldStateClass(fldTotal[i]); } DynamicJsonDocument doc2(512); doc2[F("name")] = name; - doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((i < rec->length) ? String(iv->config->name) : "total" ) + String(topic); - doc2[F("unit_of_meas")] = ((i < rec->length) ? (iv->getUnit(i,rec)) : (unitTotal[i-rec->length])); - doc2[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id; + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic); + doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(i,rec)) : (unitTotal[i])); + doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id; doc2[F("dev")] = deviceObj; doc2[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) @@ -265,10 +281,10 @@ class PubMqtt { if (stateCls != NULL) doc2[F("stat_cla")] = String(stateCls); - if(i < rec->length) + if (!total) snprintf(topic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); else // total values - snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, fields[fldTotal[i-rec->length]]); + snprintf(topic, 64, "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(),fields[fldTotal[i]]); size_t size = measureJson(doc2) + 1; char *buf = new char[size]; memset(buf, 0, size); From a37a9d4cc0d6f9544a6471b95f6833e82a3da685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20K?= Date: Sun, 29 Jan 2023 19:08:18 +0100 Subject: [PATCH 043/215] Ignore "exp_aft" for yieldtotal and yieldday. --- src/publisher/pubMqtt.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index c0bcff5e8..8788cb995 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -275,7 +275,8 @@ class PubMqtt { doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(i,rec)) : (unitTotal[i])); doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id; doc2[F("dev")] = deviceObj; - doc2[F("exp_aft")] = MQTT_INTERVAL + 5; // add 5 sec if connection is bad or ESP too slow @TODO: stimmt das wirklich als expire!? + if (!(total && String(stateCls) == String("total_increasing"))) + doc2[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) doc2[F("dev_cla")] = String(devCls); if (stateCls != NULL) From 9610e71a2c2cc77184146ec58669568f21b14621 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 30 Jan 2023 18:59:34 +0100 Subject: [PATCH 044/215] chunked response for prometheus metrics --- doc/prometheus_ep_description.md | 47 +++++++++ src/web/web.h | 159 ++++++++++++++++++------------- 2 files changed, 138 insertions(+), 68 deletions(-) create mode 100644 doc/prometheus_ep_description.md diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md new file mode 100644 index 000000000..bce0525f4 --- /dev/null +++ b/doc/prometheus_ep_description.md @@ -0,0 +1,47 @@ +# Prometheus Endpoint +Metrics available for AhoyDTU device, inverters and channels. + +Prometheus metrics provided at `/metrics`. + +## Labels +| Label name | Description | +|:-----------|:--------------------------------------| +| version | current installed version of AhoyDTU | +| image | currently not used | +| devicename | Device name from setup | +| name | Inverter name from setup | +| serial | Serial number of inverter | +| enabled | Communication enable for inverter | +| inverter | Inverter name from setup | +| channel | Channel name from setup | + + +## Exported Metrics +| Metric name | Type | Description | Labels | +|----------------------------------------|---------|--------------------------------------------------------|--------------| +| `ahoy_solar_info` | Gauge | Information about the AhoyDTU device | version, image, devicename | +| `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial, enabled | +| `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter | +| `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter | +| `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | +| `ahoy_solar_Q_AC_var` | Gauge | AC reactive power[var] | inverter | +| `ahoy_solar_F_AC_hertz` | Gauge | AC frequency [Hz] | inverter | +| `ahoy_solar_PF_AC` | Gauge | AC Power factor | inverter | +| `ahoy_solar_Temp_celsius` | Gauge | Temperature of inverter | inverter | +| `ahoy_solar_ALARM_MES_ID` | Gauge | Last alarm message id of inverter | inverter | +| `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter | +| `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter | +| `ahoy_solar_P_DC_watt` | Gauge | DC power of inverter [W] | inverter | +| `ahoy_solar_Efficiency_ratio` | Gauge | ration AC Power over DC Power [%] | inverter | +| `ahoy_solar_U_DC_volt` | Gauge | DC voltage of channel [V] | inverter, channel | +| `ahoy_solar_I_DC_ampere` | Gauge | DC current of channel [A] | inverter, channel | +| `ahoy_solar_P_DC_watt` | Gauge | DC power of channel [P] | inverter, channel | +| `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter, channel | +| `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter, channel | +| `ahoy_solar_Irradiation_ratio` | Gauge | ratio DC Power over set maximum power per channel [%] | inverter, channel | +| `ahoy_solar_radio_rx_success` | Gauge | NRF24 statistic | | +| `ahoy_solar_radio_rx_fail` | Gauge | NRF24 statistic | | +| `ahoy_solar_radio_rx_fail_answer` | Gauge | NRF24 statistic | | +| `ahoy_solar_radio_frame_cnt` | Gauge | NRF24 statistic | | +| `ahoy_solar_radio_tx_cnt` | Gauge | NRF24 statistic | | + diff --git a/src/web/web.h b/src/web/web.h index a661c987c..5641ef0d6 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -733,70 +733,93 @@ class Web { #endif #ifdef ENABLE_PROMETHEUS_EP + enum { + metricsStateStart, metricsStateInverter, metricStateChannel,metricsStateEnd + } metricsStep; + int metricsInverterId,metricsChannelId; + void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); - String metrics; - char infoline[90]; - // System info - snprintf(infoline, sizeof(infoline), "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mConfig->sys.deviceName); - metrics += "# TYPE ahoy_solar_info gauge\n" + String(infoline) + "\n"; - Inverter<> *iv; - record_t<> *rec; - char type[60], topic[80], val[25]; - for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { - iv = mSys->getInverterByPos(id); - if(NULL == iv) - continue; - // Inverter info - snprintf(infoline, sizeof(infoline), "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\",enabled=\"%d\"} 1", - iv->config->name, iv->config->serial.u64,iv->config->enabled); - metrics += "# TYPE ahoy_solar_inverter_info gauge\n" + String(infoline) + "\n"; - - // AC - rec = iv->getRecordStruct(RealTimeRunData_Debug); - for(uint8_t i = 0; i < rec->length; i++) { - uint8_t channel = rec->assign[i].ch; - if(channel == 0) { - String promUnit, promType; - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(i, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i, rec), promUnit.c_str(), promType.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i, rec), promUnit.c_str(), iv->config->name); - snprintf(val, sizeof(val), "%.3f", iv->getValue(i, rec)); - metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; - } - } - // channels DC - for(uint8_t j = 1; j <= iv->channels; j ++) { - uint8_t pos; - for (uint8_t k = 0; k < 6; k++) { - switch(k) { - default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; - case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; - case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; - case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; - case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; - case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; + metricsStep = metricsStateStart; + AsyncWebServerResponse *response = request->beginChunkedResponse(F("text/plain"), + [this](uint8_t *buffer, size_t maxLen, size_t filledLength) -> size_t + { + Inverter<> *iv; + record_t<> *rec; + statistics_t *stat; + String metrics; + char type[60], topic[100], val[25]; + size_t len = 0; + + switch (metricsStep) { + case metricsStateStart: // System Info & NRF Statistics : fit to one packet + snprintf(topic,sizeof(topic),"# TYPE ahoy_solar_info gauge\nahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", + mApp->getVersion(), mConfig->sys.deviceName); + metrics = topic; + // NRF Statistics + stat = mApp->getStatistics(); + metrics += radioStatistic(F("rx_success"), stat->rxSuccess); + metrics += radioStatistic(F("rx_fail"), stat->rxFail); + metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); + metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); + metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); + + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + // Start Inverter loop + metricsInverterId = 0; + metricsStep = metricsStateInverter; + break; + + case metricsStateInverter: // Inverter loop + if (metricsInverterId < mSys->getNumInverters()) { + iv = mSys->getInverterByPos(metricsInverterId); + if(NULL != iv) { + // Inverter info + len = snprintf((char *)buffer, maxLen, "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\",enabled=\"%d\"} 1\n", + iv->config->name, iv->config->serial.u64,iv->config->enabled); + // Start Channel loop for this inverter + metricsChannelId = 0; + metricsStep = metricStateChannel; + } + } else { + metricsStep = metricsStateEnd; } - String promUnit, promType; - std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(pos, rec)); - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(pos, rec), promUnit.c_str(), promType.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_%s_%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(pos, rec), promUnit.c_str(), iv->config->name, iv->config->chName[j-1]); - snprintf(val, sizeof(val), "%.3f", iv->getValue(pos, rec)); - metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; - } - } - } - - // NRF Statistics - statistics_t *stat = mApp->getStatistics(); - metrics += radioStatistic(F("rx_success"), stat->rxSuccess); - metrics += radioStatistic(F("rx_fail"), stat->rxFail); - metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); - metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); - metrics += radioStatistic(F("tx_cnt"), mSys->Radio.mSendCnt); + break; + + case metricStateChannel: // Channel loop + iv = mSys->getInverterByPos(metricsInverterId); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (metricsChannelId < rec->length) { + uint8_t channel = rec->assign[metricsChannelId].ch; + String promUnit, promType; + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + if (0 == channel) { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name); + } else { + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,iv->config->chName[channel-1]); + } + snprintf(val, sizeof(val), "%.3f", iv->getValue(metricsChannelId, rec)); + len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); + + metricsChannelId++; + } else { + len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. + + // All channels processed --> try next inverter + metricsInverterId++; + metricsStep = metricsStateInverter; + } + break; - AsyncWebServerResponse *response = request->beginResponse(200, F("text/plain"), metrics); + case metricsStateEnd: + default: // end of transmission + len = 0; + break; + } + return len; + }); request->send(response); } @@ -809,18 +832,18 @@ class Web { } std::pair convertToPromUnits(String shortUnit) { - if(shortUnit == "A") return {"ampere", "gauge"}; - if(shortUnit == "V") return {"volt", "gauge"}; - if(shortUnit == "%") return {"ratio", "gauge"}; - if(shortUnit == "W") return {"watt", "gauge"}; - if(shortUnit == "Wh") return {"watt_daily", "counter"}; - if(shortUnit == "kWh") return {"watt_total", "counter"}; - if(shortUnit == "°C") return {"celsius", "gauge"}; - + if(shortUnit == "A") return {"_ampere", "gauge"}; + if(shortUnit == "V") return {"_volt", "gauge"}; + if(shortUnit == "%") return {"_ratio", "gauge"}; + if(shortUnit == "W") return {"_watt", "gauge"}; + if(shortUnit == "Wh") return {"_wattHours", "counter"}; + if(shortUnit == "kWh") return {"_kilowattHours", "counter"}; + if(shortUnit == "°C") return {"_celsius", "gauge"}; + if(shortUnit == "var") return {"_var", "gauge"}; + if(shortUnit == "Hz") return {"_hertz", "gauge"}; return {"", "gauge"}; } #endif - AsyncWebServer mWeb; AsyncEventSource mEvts; bool mProtected; From 146c7612f698de4d0322ba77eb497cf79cd0de20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20K?= Date: Tue, 31 Jan 2023 06:35:37 +0100 Subject: [PATCH 045/215] Remove exp_aft for all YD & YT. Add reset YD total at midnight. Publish YD & YT on CH0 only when iv is producing to avoids 0 at restart. --- src/config/config.h | 2 +- src/publisher/pubMqtt.h | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/config/config.h b/src/config/config.h index 69e7193bf..722e53236 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -99,7 +99,7 @@ #define NTP_REFRESH_INTERVAL 12 * 3600 * 1000 // default mqtt interval -#define MQTT_INTERVAL 60 +#define MQTT_INTERVAL 90 // default MQTT broker uri #define DEF_MQTT_BROKER "\0" diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 8788cb995..ec3465d90 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -148,7 +148,10 @@ class PubMqtt { snprintf(val, 4, "0.0"); publish(topic, val, true); } - } + // set Total YieldDay to zero + snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[FLD_YD]); + snprintf(val, 4, "0.0"); + publish(topic, val, true); } void payloadEventListener(uint8_t cmd) { if(mClient.connected()) { // prevent overflow if MQTT broker is not reachable but set @@ -275,7 +278,7 @@ class PubMqtt { doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(i,rec)) : (unitTotal[i])); doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id; doc2[F("dev")] = deviceObj; - if (!(total && String(stateCls) == String("total_increasing"))) + if (!(String(stateCls) == String("total_increasing"))) doc2[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) doc2[F("dev_cla")] = String(devCls); @@ -525,6 +528,8 @@ class PubMqtt { switch (rec->assign[i].fieldId) { case FLD_YT: case FLD_YD: + if ((rec->assign[i].ch == CH0) && (!iv->isProducing(*mUtcTimestamp))) // avoids returns to 0 on restart + continue; retained = true; break; } From a8b9ca0b4cfcde22e528f2897a730f45666efe53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20K?= Date: Tue, 31 Jan 2023 11:51:21 +0100 Subject: [PATCH 046/215] don't send "0" for CH0 YT YD at reboot --- src/publisher/pubMqtt.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index ec3465d90..8b5b7dce0 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -145,12 +145,12 @@ class PubMqtt { iv->setValue(pos, rec, 0.0f); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ch0/%s", iv->config->name, fields[FLD_YD]); - snprintf(val, 4, "0.0"); + snprintf(val, 2, "0"); publish(topic, val, true); } // set Total YieldDay to zero snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[FLD_YD]); - snprintf(val, 4, "0.0"); + snprintf(val, 2, "0"); publish(topic, val, true); } void payloadEventListener(uint8_t cmd) { @@ -484,7 +484,7 @@ class PubMqtt { if(changed) { snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((mIvAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); publish("status", val, true); - sendIvData(false); // false prevents loop of same function + //sendIvData(false); // false prevents loop of same function } return totalComplete; From 802205ad911641bb14941eb0b9ac76ed2d5adce6 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 14:46:31 +0100 Subject: [PATCH 047/215] Update appInterface.h --- src/appInterface.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/appInterface.h b/src/appInterface.h index a8e37a759..83a4f769b 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -17,6 +17,7 @@ class IApp { virtual bool saveSettings() = 0; virtual bool readSettings(const char *path) = 0; virtual bool eraseSettings(bool eraseWifi) = 0; + virtual void setOnUpdate() = 0; virtual void setRebootFlag() = 0; virtual const char *getVersion() = 0; virtual statistics_t *getStatistics() = 0; From 139129ea8b0c3fbac9b4dd0ecc74227eb1dd1842 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 14:49:49 +0100 Subject: [PATCH 048/215] Update app.h --- src/app.h | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/app.h b/src/app.h index d51c9209e..d3709b894 100644 --- a/src/app.h +++ b/src/app.h @@ -18,10 +18,8 @@ #include "config/settings.h" #include "defines.h" #include "utils/crc.h" -#include "utils/ahoyTimer.h" #include "utils/scheduler.h" -#include "hm/CircularBuffer.h" #include "hm/hmSystem.h" #include "hm/payload.h" #include "wifi/ahoywifi.h" @@ -61,10 +59,6 @@ class app : public IApp, public ah::Scheduler { void loopWifi(void); void onWifi(bool gotIp); void regularTickers(void); - void handleIntr(void); - void cbMqtt(char* topic, byte* payload, unsigned int length); - void saveValues(void); - bool getWifiApActive(void); uint32_t getUptime() { return Scheduler::getUptime(); @@ -99,6 +93,10 @@ class app : public IApp, public ah::Scheduler { mWifi.getAvailNetworks(obj); } + void setOnUpdate() { + onWifi(false); + } + void setRebootFlag() { once(std::bind(&app::tickReboot, this), 3, "rboot"); } @@ -206,6 +204,9 @@ class app : public IApp, public ah::Scheduler { void tickReboot(void) { DPRINTLN(DBG_INFO, F("Rebooting...")); + onWifi(false); + ah::Scheduler::resetTicker(); + WiFi.disconnect(); ESP.restart(); } @@ -247,13 +248,9 @@ class app : public IApp, public ah::Scheduler { settings_t *mConfig; uint8_t mSendLastIvId; - uint8_t mSendTickerId; statistics_t mStat; - // timer - uint32_t mRxTicker; - // mqtt PubMqttType mMqtt; bool mMqttReconnect; From 5fb45f1b20b1e7de0f3f4edb98ffe6339bdeeccc Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 14:53:01 +0100 Subject: [PATCH 049/215] Update app.cpp --- src/app.cpp | 77 ++++++++++++++++++++--------------------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 0d88847e2..f5478f0fd 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -82,40 +82,26 @@ void app::loop(void) { void app::loopStandard(void) { ah::Scheduler::loop(); - mSys->Radio.loop(); - mPayload.loop(); - - yield(); - - if (ah::checkTicker(&mRxTicker, 4)) { - bool rxRdy = mSys->Radio.switchRxCh(); - - if (!mSys->BufCtrl.empty()) { - uint8_t len; - packet_t *p = mSys->BufCtrl.getBack(); + if (mSys->Radio.loop()) { + while (!mSys->Radio.mBufCtrl.empty()) { + packet_t *p = &mSys->Radio.mBufCtrl.front(); - if (mSys->Radio.checkPaketCrc(p->packet, &len, p->rxCh)) { - if (mConfig->serial.debug) { - DPRINT(DBG_INFO, "RX " + String(len) + "B Ch" + String(p->rxCh) + " | "); - mSys->Radio.dumpBuf(NULL, p->packet, len); - } - mStat.frmCnt++; - - if (0 != len) - mPayload.add(p, len); + if (mConfig->serial.debug) { + DPRINT(DBG_INFO, "RX " + String(p->len) + "B Ch" + String(p->ch) + " | "); + mSys->Radio.dumpBuf(p->packet, p->len); } - mSys->BufCtrl.popBack(); - } - yield(); + mStat.frmCnt++; - if (rxRdy) - mPayload.process(true); + mPayload.add(p); + mSys->Radio.mBufCtrl.pop(); + yield(); + } + mPayload.process(true); } + mPayload.loop(); -#if !defined(AP_ONLY) if(mMqttEnabled) mMqtt.loop(); -#endif } //----------------------------------------------------------------------------- @@ -131,12 +117,14 @@ void app::onWifi(bool gotIp) { regularTickers(); // reinstall regular tickers if (gotIp) { mInnerLoopCb = std::bind(&app::loopStandard, this); - mSendTickerId = every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend"); + every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend"); mMqttReconnect = true; mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); - if(WIFI_AP == WiFi.getMode()) + if(WIFI_AP == WiFi.getMode()) { + mMqttEnabled = false; everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); + } } else { mInnerLoopCb = std::bind(&app::loopWifi, this); @@ -232,6 +220,15 @@ void app::tickComm(void) { once(std::bind(&app::tickComm, this), 1, "mqCom"); // MQTT not connected, retry } +//----------------------------------------------------------------------------- +void app::tickMidnight(void) { + // only used and enabled by MQTT (see setup()) + uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight + onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2"); + + mMqtt.tickerMidnight(); +} + //----------------------------------------------------------------------------- void app::tickSend(void) { if(!mSys->Radio.isChipConnected()) { @@ -239,9 +236,9 @@ void app::tickSend(void) { return; } if (mIVCommunicationOn) { - if (!mSys->BufCtrl.empty()) { + if (!mSys->Radio.mBufCtrl.empty()) { if (mConfig->serial.debug) - DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->BufCtrl.getFill())); + DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->Radio.mBufCtrl.size())); } int8_t maxLoop = MAX_NUM_INVERTERS; @@ -264,21 +261,6 @@ void app::tickSend(void) { updateLed(); } -//----------------------------------------------------------------------------- -void app::tickMidnight(void) { - // only used and enabled by MQTT (see setup()) - uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight - onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2"); - - mMqtt.tickerMidnight(); -} - -//----------------------------------------------------------------------------- -void app::handleIntr(void) { - DPRINTLN(DBG_VERBOSE, F("app::handleIntr")); - mSys->Radio.handleIntr(); -} - //----------------------------------------------------------------------------- void app::resetSystem(void) { snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); @@ -290,8 +272,7 @@ void app::resetSystem(void) { mSunrise = 0; mSunset = 0; - mRxTicker = 0; - mSendTickerId = 0xff; // invalid id + mMqttEnabled = false; mSendLastIvId = 0; mShowRebootRequest = false; From 447a43388eb20a7a9250af0d4c5e5c68ce49a62a Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:01:08 +0100 Subject: [PATCH 050/215] Update main.cpp --- src/main.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c585d0f25..42ed55f43 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,21 +7,11 @@ #include "app.h" #include "config/config.h" - app myApp; -//----------------------------------------------------------------------------- -IRAM_ATTR void handleIntr(void) { - myApp.handleIntr(); -} - - //----------------------------------------------------------------------------- void setup() { myApp.setup(); - - // TODO: move to HmRadio - attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING); } From 975ee667f2dd29a269200ad82a42c179e6522689 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:03:26 +0100 Subject: [PATCH 051/215] Update defines.h --- src/defines.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/defines.h b/src/defines.h index 5c626e028..d5f0fcb3f 100644 --- a/src/defines.h +++ b/src/defines.h @@ -17,7 +17,8 @@ //------------------------------------- typedef struct { - uint8_t rxCh; + uint8_t ch; + uint8_t len; uint8_t packet[MAX_RF_PAYLOAD_SIZE]; } packet_t; From 7177d54d736e11fa5cc2e574ab6fe9232c39260a Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:10:15 +0100 Subject: [PATCH 052/215] Update pubMqtt.h --- src/publisher/pubMqtt.h | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 021a29eb9..938499eae 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -15,7 +15,6 @@ #endif #include "../utils/dbg.h" -#include "../utils/ahoyTimer.h" #include "../config/config.h" #include #include @@ -40,8 +39,7 @@ class PubMqtt { mRxCnt = 0; mTxCnt = 0; mSubscriptionCb = NULL; - mIvAvail = true; - memset(mLastIvState, 0xff, MAX_NUM_INVERTERS); + memset(mLastIvState, MQTT_STATUS_NOT_AVAIL_NOT_PROD, MAX_NUM_INVERTERS); } ~PubMqtt() { } @@ -70,6 +68,7 @@ class PubMqtt { void loop() { #if defined(ESP8266) mClient.loop(); + yield(); #endif } @@ -408,13 +407,12 @@ class PubMqtt { bool processIvStatus() { // returns true if all inverters are available - bool allAvail = true; - bool first = true; + bool allAvail = true; // shows if all enabled inverters are available + bool anyAvail = false; // shows if at least one enabled inverter is available bool changed = false; char topic[7 + MQTT_TOPIC_LEN], val[40]; Inverter<> *iv; record_t<> *rec; - bool totalComplete = true; for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { iv = mSys->getInverterByPos(id); @@ -422,32 +420,21 @@ class PubMqtt { continue; // skip to next inverter rec = iv->getRecordStruct(RealTimeRunData_Debug); - if(first) - mIvAvail = false; - first = false; // inverter status - uint8_t status = MQTT_STATUS_AVAIL_PROD; - if ((!iv->isAvailable(*mUtcTimestamp)) || (!iv->config->enabled)) { - status = MQTT_STATUS_NOT_AVAIL_NOT_PROD; - if(iv->config->enabled) { // only change all-avail if inverter is enabled! - totalComplete = false; + uint8_t status = MQTT_STATUS_NOT_AVAIL_NOT_PROD; + if (iv->config->enabled) { + if (iv->isAvailable(*mUtcTimestamp)) + status = (iv->isProducing(*mUtcTimestamp)) ? MQTT_STATUS_AVAIL_PROD : MQTT_STATUS_AVAIL_NOT_PROD; + else // inverter is enabled but not available allAvail = false; - } - } - else { - mIvAvail = true; - if (!iv->isProducing(*mUtcTimestamp)) { - if (MQTT_STATUS_AVAIL_PROD == status) - status = MQTT_STATUS_AVAIL_NOT_PROD; - } } if(mLastIvState[id] != status) { mLastIvState[id] = status; changed = true; - if(mCfgMqtt->rstValsNotAvail) + if((MQTT_STATUS_NOT_AVAIL_NOT_PROD == status) && (mCfgMqtt->rstValsNotAvail)) zeroValues(iv); snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name); @@ -461,12 +448,12 @@ class PubMqtt { } if(changed) { - snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((mIvAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); + snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE))); publish("status", val, true); sendIvData(false); // false prevents loop of same function } - return totalComplete; + return allAvail; } void sendAlarmData() { @@ -494,7 +481,7 @@ class PubMqtt { memset(total, 0, sizeof(float) * 4); for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { Inverter<> *iv = mSys->getInverterByPos(id); - if (NULL == iv) + if ((NULL == iv) || (MQTT_STATUS_NOT_AVAIL_NOT_PROD == mLastIvState[id])) continue; // skip to next inverter record_t<> *rec = iv->getRecordStruct(mSendList.front()); @@ -626,7 +613,6 @@ class PubMqtt { std::queue mSendList; std::queue mAlarmList; subscriptionCb mSubscriptionCb; - bool mIvAvail; // shows if at least one inverter is available bool mReconnectRequest; uint8_t mLastIvState[MAX_NUM_INVERTERS]; uint16_t mIntervalTimeout; From 8f49aae4f35d5fd6629d57afc82105a25e47837e Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:15:53 +0100 Subject: [PATCH 053/215] Delete ahoyTimer.h --- src/utils/ahoyTimer.h | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/utils/ahoyTimer.h diff --git a/src/utils/ahoyTimer.h b/src/utils/ahoyTimer.h deleted file mode 100644 index 08c09016c..000000000 --- a/src/utils/ahoyTimer.h +++ /dev/null @@ -1,27 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://ahoydtu.de -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- - -#ifndef __AHOY_TIMER_H__ -#define __AHOY_TIMER_H__ - -#include - -namespace ah { - inline bool checkTicker(uint32_t *ticker, uint32_t interval) { - uint32_t mil = millis(); - if(mil >= *ticker) { - *ticker = mil + interval; - return true; - } - else if((mil + interval) < (*ticker)) { - *ticker = mil + interval; - return true; - } - - return false; - } -} - -#endif /*__AHOY_TIMER_H__*/ From 48c4884e80c0e1ab8253da7d0db5d608c00dc793 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:22:34 +0100 Subject: [PATCH 054/215] Update web.h --- src/web/web.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/web/web.h b/src/web/web.h index 5641ef0d6..89d2dfb0d 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -18,7 +18,6 @@ #include "../appInterface.h" #include "../hm/hmSystem.h" -#include "../utils/ahoyTimer.h" #include "../utils/helper.h" #include "html/h/index_html.h" @@ -132,6 +131,8 @@ class Web { } void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + mApp->setOnUpdate(); + if(!index) { Serial.printf("Update Start: %s\n", filename.c_str()); #ifndef ESP32 @@ -246,7 +247,7 @@ class Web { AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html); response->addHeader("Connection", "close"); request->send(response); - if(reboot) + //if(reboot) mApp->setRebootFlag(); } @@ -263,7 +264,7 @@ class Web { AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html); response->addHeader("Connection", "close"); request->send(response); - if(reboot) + //if(reboot) mApp->setRebootFlag(); } @@ -550,8 +551,8 @@ class Web { mConfig->mqtt.port = request->arg("mqttPort").toInt(); mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); mConfig->mqtt.rstYieldMidNight = (request->arg("mqttRstMid") == "on"); - mConfig->mqtt.rstValsNotAvail = (request->arg("mqttRstComStop") == "on"); - mConfig->mqtt.rstValsCommStop = (request->arg("mqttRstNotAvail") == "on"); + mConfig->mqtt.rstValsNotAvail = (request->arg("mqttRstNotAvail") == "on"); + mConfig->mqtt.rstValsCommStop = (request->arg("mqttRstComStop") == "on"); // serial console if(request->arg("serIntvl") != "") { From 5056d7c729f2babd5df113188e864b01a23f30ee Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:25:54 +0100 Subject: [PATCH 055/215] Update ahoywifi.h --- src/wifi/ahoywifi.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 321dbb86e..ac8c0dd05 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -34,6 +34,7 @@ class ahoywifi { DISCONNECTED = 0, CONNECTING, CONNECTED, + IN_AP_MODE, GOT_IP } WiFiStatus_t; @@ -41,6 +42,8 @@ class ahoywifi { void setupAp(void); void setupStation(void); void sendNTPpacket(IPAddress& address); + void sortRSSI(int *sort, int n); + void getBSSIDs(void); void connectionEvent(WiFiStatus_t status); #if defined(ESP8266) void onConnect(const WiFiEventStationModeConnected& event); @@ -50,8 +53,7 @@ class ahoywifi { void onWiFiEvent(WiFiEvent_t event); #endif void welcome(String msg); - void sortRSSI(int *sort, int n); - void getBSSIDs(void); + settings_t *mConfig; appWifiCb mAppWifiCb; @@ -67,11 +69,8 @@ class ahoywifi { uint8_t mCnt; uint32_t *mUtcTimestamp; - uint8_t mLoopCnt; - bool mScanActive; - uint8_t mLastApClients; uint8_t mScanCnt; - + bool mScanActive; std::list mBSSIDList; }; From bbee9abd28a35b4e40effe3ae3df8e9da7732707 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:28:22 +0100 Subject: [PATCH 056/215] Update ahoywifi.cpp --- src/wifi/ahoywifi.cpp | 64 ++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 4ca7f290f..8384c1182 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -25,8 +25,6 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { mStaConn = DISCONNECTED; mCnt = 0; mScanActive = false; - mLastApClients = 0; - mScanCnt = 0; #if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeConnected(std::bind(&ahoywifi::onConnect, this, std::placeholders::_1)); @@ -66,44 +64,31 @@ void ahoywifi::tickWifiLoop() { #if !defined(AP_ONLY) if(mStaConn != GOT_IP) { if (WiFi.softAPgetStationNum() > 0) { // do not reconnect if any AP connection exists - if(WIFI_AP_STA == WiFi.getMode()) { - // first time switch to AP Mode - if(mScanActive && (mLastApClients != WiFi.softAPgetStationNum())) - mScanActive = false; - - // scan is finished - if(!mScanActive) { - WiFi.mode(WIFI_AP); - mDns.start(53, "*", mApIp); - } - - // only once a client connects to AP - if(mLastApClients != WiFi.softAPgetStationNum()) { - mLastApClients = WiFi.softAPgetStationNum(); + if(mStaConn != IN_AP_MODE) { + mStaConn = IN_AP_MODE; + // first time switch to AP Mode + if (mScanActive) { WiFi.scanDelete(); - mAppWifiCb(false); - DBGPRINTLN(F("AP client connected")); - welcome(mApIp.toString()); + mScanActive = false; } + DBGPRINTLN(F("AP client connected")); + welcome(mApIp.toString()); + WiFi.mode(WIFI_AP); + mDns.start(53, "*", mApIp); + mAppWifiCb(true); } mDns.processNextRequest(); return; } - else if(WIFI_AP == WiFi.getMode()) { - mLastApClients = 0; + else if(mStaConn == IN_AP_MODE) { mCnt = 0; - DPRINTLN(DBG_INFO, "DNS stop"); mDns.stop(); WiFi.mode(WIFI_AP_STA); + mStaConn = DISCONNECTED; } mCnt++; - uint8_t timeout = 10; // seconds - if (mStaConn == CONNECTED) // connected but no ip - timeout = 20; - - - if(!mScanActive && mBSSIDList.empty() && ((mCnt % timeout) == 0)) { // start scanning APs with the given SSID + if(!mScanActive && mBSSIDList.empty()) { // start scanning APs with the given SSID DBGPRINT(F("scanning APs with SSID ")); DBGPRINTLN(String(mConfig->sys.stationSsid)); mScanCnt = 0; @@ -115,14 +100,20 @@ void ahoywifi::tickWifiLoop() { #endif return; } + + uint8_t timeout = 10; // seconds + + if (mStaConn == CONNECTED) // connected but no ip + timeout = 20; + DBGPRINT(F("reconnect in ")); DBGPRINT(String(timeout-mCnt)); DBGPRINTLN(F(" seconds")); if(mScanActive) { getBSSIDs(); - //if(!mScanActive) // scan completed - // if ((mCnt % timeout) < 8) - // mCnt = timeout - 2; + if(!mScanActive) // scan completed + if ((mCnt % timeout) < timeout - 2) + mCnt = timeout - 2; } if((mCnt % timeout) == 0) { // try to reconnect after x sec without connection if(mStaConn != CONNECTED) @@ -166,8 +157,6 @@ void ahoywifi::setupAp(void) { WiFi.mode(WIFI_AP_STA); WiFi.softAPConfig(mApIp, mApIp, IPAddress(255, 255, 255, 0)); WiFi.softAP(WIFI_AP_SSID, WIFI_AP_PWD); - - mDns.start(53, "*", mApIp); } @@ -266,10 +255,10 @@ void ahoywifi::sortRSSI(int *sort, int n) { //----------------------------------------------------------------------------- void ahoywifi::scanAvailNetworks(void) { - if(-2 == WiFi.scanComplete()) { + if(!mScanActive) { mScanActive = true; if(WIFI_AP == WiFi.getMode()) - WiFi.mode(WIFI_AP_STA); + WiFi.mode(WIFI_AP_STA); WiFi.scanNetworks(true); } } @@ -291,12 +280,14 @@ void ahoywifi::getAvailNetworks(JsonObject obj) { } mScanActive = false; WiFi.scanDelete(); + if(mStaConn == IN_AP_MODE) + WiFi.mode(WIFI_AP); } //----------------------------------------------------------------------------- void ahoywifi::getBSSIDs() { int n = WiFi.scanComplete(); - if (n < 0){ + if (n < 0) { mScanCnt++; if (mScanCnt < 20) return; @@ -336,7 +327,6 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) { mScanActive = false; } welcome(WiFi.localIP().toString() + F(" (Station)")); - mDns.stop(); WiFi.mode(WIFI_STA); DBGPRINTLN(F("[WiFi] AP disabled")); mAppWifiCb(true); From 155d602e6e56e5d5dbf6bba2d6eeccc3aaa35f13 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:29:24 +0100 Subject: [PATCH 057/215] Delete CircularBuffer.h --- src/hm/CircularBuffer.h | 161 ---------------------------------------- 1 file changed, 161 deletions(-) delete mode 100644 src/hm/CircularBuffer.h diff --git a/src/hm/CircularBuffer.h b/src/hm/CircularBuffer.h deleted file mode 100644 index 65c9e7685..000000000 --- a/src/hm/CircularBuffer.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - CircularBuffer - An Arduino circular buffering library for arbitrary types. - - Created by Ivo Pullens, Emmission, 2014 -- www.emmission.nl - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#ifndef CircularBuffer_h -#define CircularBuffer_h - -#if defined(ESP8266) || defined(ESP32) -#define DISABLE_IRQ noInterrupts() -#define RESTORE_IRQ interrupts() -#else -#define DISABLE_IRQ \ - uint8_t sreg = SREG; \ - cli(); - -#define RESTORE_IRQ \ - SREG = sreg; -#endif - -template -class CircularBuffer { - - typedef BUFFERTYPE BufferType; - BufferType Buffer[BUFFERSIZE]; - - public: - CircularBuffer() : m_buff(Buffer) { - m_size = BUFFERSIZE; - clear(); - } - - /** Clear all entries in the circular buffer. */ - void clear(void) - { - m_front = 0; - m_fill = 0; - } - - /** Test if the circular buffer is empty */ - inline bool empty(void) const - { - return !m_fill; - } - - /** Return the number of records stored in the buffer */ - inline uint8_t available(void) const - { - return m_fill; - } - - /** Test if the circular buffer is full */ - inline bool full(void) const - { - return m_fill == m_size; - } - - inline uint8_t getFill(void) const { - return m_fill; - } - - /** Aquire record on front of the buffer, for writing. - * After filling the record, it has to be pushed to actually - * add it to the buffer. - * @return Pointer to record, or NULL when buffer is full. - */ - BUFFERTYPE* getFront(void) const - { - DISABLE_IRQ; - BUFFERTYPE* f = NULL; - if (!full()) - f = get(m_front); - RESTORE_IRQ; - return f; - } - - /** Push record to front of the buffer - * @param record Record to push. If record was aquired previously (using getFront) its - * data will not be copied as it is already present in the buffer. - * @return True, when record was pushed successfully. - */ - bool pushFront(BUFFERTYPE* record) - { - bool ok = false; - DISABLE_IRQ; - if (!full()) - { - BUFFERTYPE* f = get(m_front); - if (f != record) - *f = *record; - m_front = (m_front+1) % m_size; - m_fill++; - ok = true; - } - RESTORE_IRQ; - return ok; - } - - /** Aquire record on back of the buffer, for reading. - * After reading the record, it has to be pop'ed to actually - * remove it from the buffer. - * @return Pointer to record, or NULL when buffer is empty. - */ - BUFFERTYPE* getBack(void) const - { - BUFFERTYPE* b = NULL; - DISABLE_IRQ; - if (!empty()) - b = get(back()); - RESTORE_IRQ; - return b; - } - - /** Remove record from back of the buffer. - * @return True, when record was pop'ed successfully. - */ - bool popBack(void) - { - bool ok = false; - DISABLE_IRQ; - if (!empty()) - { - m_fill--; - ok = true; - } - RESTORE_IRQ; - return ok; - } - - protected: - inline BUFFERTYPE * get(const uint8_t idx) const - { - return &(m_buff[idx]); - } - inline uint8_t back(void) const - { - return (m_front - m_fill + m_size) % m_size; - } - - uint8_t m_size; // Total number of records that can be stored in the buffer. - BUFFERTYPE* const m_buff; - volatile uint8_t m_front; // Index of front element (not pushed yet). - volatile uint8_t m_fill; // Amount of records currently pushed. -}; - -#endif // CircularBuffer_h From 6159c0755973bf458fa4916cca684fab703ec32d Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:41:47 +0100 Subject: [PATCH 058/215] Update hmRadio.h --- src/hm/hmRadio.h | 331 +++++++++++++++++++---------------------------- 1 file changed, 132 insertions(+), 199 deletions(-) diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index ee84263b8..ea440b8cb 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -9,28 +9,10 @@ #include "../utils/dbg.h" #include #include "../utils/crc.h" -#ifndef DISABLE_IRQ - #if defined(ESP8266) || defined(ESP32) - #define DISABLE_IRQ noInterrupts() - #define RESTORE_IRQ interrupts() - #else - #define DISABLE_IRQ \ - uint8_t sreg = SREG; \ - cli(); - #define RESTORE_IRQ \ - SREG = sreg; - #endif -#endif -//#define CHANNEL_HOP // switch between channels or use static channel to send +#define SPI_SPEED 1000000 -#define DEFAULT_RECV_CHANNEL 3 -#define SPI_SPEED 1000000 - -#define DUMMY_RADIO_ID ((uint64_t)0xDEADBEEF01ULL) - -#define RF_CHANNELS 5 -#define RF_LOOP_CNT 300 +#define RF_CHANNELS 5 #define TX_REQ_INFO 0x15 #define TX_REQ_DEVCONTROL 0x51 @@ -61,11 +43,12 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"}; #define BIT_CNT(x) ((x)<<3) +static volatile bool mIrqRcvd; //----------------------------------------------------------------------------- // HM Radio class //----------------------------------------------------------------------------- -template +template class HmRadio { public: HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) { @@ -84,23 +67,22 @@ class HmRadio { mRfChLst[3] = 61; mRfChLst[4] = 75; + // default channels mTxChIdx = 2; // Start TX with 40 mRxChIdx = 0; // Start RX with 03 - mRxLoopCnt = RF_LOOP_CNT; - mSendCnt = 0; - mRetransmits = 0; + mSendCnt = 0; + mRetransmits = 0; - mSerialDebug = false; - mIrqRcvd = false; + mSerialDebug = false; + mIrqRcvd = false; } ~HmRadio() {} - void setup(BUFFER *ctrl, uint8_t ampPwr = RF24_PA_LOW, uint8_t irq = IRQ_PIN, uint8_t ce = CE_PIN, uint8_t cs = CS_PIN) { + void setup(uint8_t ampPwr = RF24_PA_LOW, uint8_t irq = IRQ_PIN, uint8_t ce = CE_PIN, uint8_t cs = CS_PIN) { DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setup")); pinMode(irq, INPUT_PULLUP); - mBufCtrl = ctrl; - + attachInterrupt(digitalPinToInterrupt(irq), []()IRAM_ATTR{ mIrqRcvd = true; }, FALLING); uint32_t dtuSn = 0x87654321; uint32_t chipID = 0; // will be filled with last 3 bytes of MAC @@ -121,27 +103,22 @@ class HmRadio { DTU_RADIO_ID = ((uint64_t)(((dtuSn >> 24) & 0xFF) | ((dtuSn >> 8) & 0xFF00) | ((dtuSn << 8) & 0xFF0000) | ((dtuSn << 24) & 0xFF000000)) << 8) | 0x01; mNrf24.begin(ce, cs); - mNrf24.setRetries(0, 0); + mNrf24.setRetries(3, 15); // 3*250us + 250us and 15 loops -> 15ms - mNrf24.setChannel(DEFAULT_RECV_CHANNEL); + mNrf24.setChannel(mRfChLst[mRxChIdx]); mNrf24.setDataRate(RF24_250KBPS); + mNrf24.setAutoAck(true); + mNrf24.enableDynamicPayloads(); mNrf24.setCRCLength(RF24_CRC_16); - mNrf24.setAutoAck(false); - mNrf24.setPayloadSize(MAX_RF_PAYLOAD_SIZE); mNrf24.setAddressWidth(5); mNrf24.openReadingPipe(1, DTU_RADIO_ID); - mNrf24.enableDynamicPayloads(); - // enable only receiving interrupts - mNrf24.maskIRQ(true, true, false); + // enable all receiving interrupts + mNrf24.maskIRQ(false, false, false); DPRINT(DBG_INFO, F("RF24 Amp Pwr: RF24_PA_")); DPRINTLN(DBG_INFO, String(rf24AmpPowerNames[ampPwr])); mNrf24.setPALevel(ampPwr & 0x03); - mNrf24.startListening(); - - - mTxCh = setDefaultChannels(); if(mNrf24.isChipConnected()) { DPRINTLN(DBG_INFO, F("Radio Config:")); @@ -151,77 +128,70 @@ class HmRadio { DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); } - void loop(void) { - if(mIrqRcvd) { - DISABLE_IRQ; - mIrqRcvd = false; - bool tx_ok, tx_fail, rx_ready; - mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH - RESTORE_IRQ; - uint8_t pipe, len; - packet_t *p; - while(mNrf24.available(&pipe)) { - if(!mBufCtrl->full()) { - p = mBufCtrl->getFront(); - p->rxCh = mRfChLst[mRxChIdx]; - len = mNrf24.getPayloadSize(); - if(len > MAX_RF_PAYLOAD_SIZE) - len = MAX_RF_PAYLOAD_SIZE; - - mNrf24.read(p->packet, len); - mBufCtrl->pushFront(p); - yield(); + bool loop(void) { + if (!mIrqRcvd) + return false; // nothing to do + mIrqRcvd = false; + bool tx_ok, tx_fail, rx_ready; + mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH + mNrf24.flush_tx(); // empty TX FIFO + //DBGPRINTLN("TX whatHappened Ch" + String(mRfChLst[mTxChIdx]) + " " + String(tx_ok) + String(tx_fail) + String(rx_ready)); + + // start listening on the default RX channel + mRxChIdx = 0; + mNrf24.setChannel(mRfChLst[mRxChIdx]); + mNrf24.startListening(); + + //uint32_t debug_ms = millis(); + uint16_t cnt = 300; // that is 60 times 5 channels + while (0 < cnt--) { + uint32_t startMillis = millis(); + while (millis()-startMillis < 4) { // listen 4ms to each channel + if (mIrqRcvd) { + mIrqRcvd = false; + if (getReceived()) { // everything received + //DBGPRINTLN("RX finished Cnt: " + String(300-cnt) + " time used: " + String(millis()-debug_ms)+ " ms"); + mNrf24.stopListening(); + return true; + } } - else - break; + yield(); } - mNrf24.flush_rx(); // drop the packet - RESTORE_IRQ; + switchRxCh(); // switch to next RX channel + yield(); } + // not finished but time is over + //DBGPRINTLN("RX not finished: 300 time used: " + String(millis()-debug_ms)+ " ms"); + mNrf24.stopListening(); + return true; } + bool isChipConnected(void) { + //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:isChipConnected")); + return mNrf24.isChipConnected(); + } void enableDebug() { mSerialDebug = true; } - void handleIntr(void) { - mIrqRcvd = true; - } - - uint8_t setDefaultChannels(void) { - //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setDefaultChannels")); - mTxChIdx = 2; // Start TX with 40 - mRxChIdx = 0; // Start RX with 03 - return mRfChLst[mTxChIdx]; - } - void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data, bool isRetransmit) { DPRINTLN(DBG_INFO, F("sendControlPacket cmd: 0x") + String(cmd, HEX)); - sendCmdPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME, false); - uint8_t cnt = 0; - mTxBuf[10 + cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor - mTxBuf[10 + cnt++] = 0x00; + initPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME); + uint8_t cnt = 10; + mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor + mTxBuf[cnt++] = 0x00; if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet - mTxBuf[10 + cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[10 + cnt++] = ((data[0] * 10) ) & 0xff; // power limit - mTxBuf[10 + cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings - mTxBuf[10 + cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling + mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit + mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit + mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings + mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling } - - // crc control data - uint16_t crc = ah::crc16(&mTxBuf[10], cnt); - mTxBuf[10 + cnt++] = (crc >> 8) & 0xff; - mTxBuf[10 + cnt++] = (crc ) & 0xff; - - // crc over all - mTxBuf[10 + cnt] = ah::crc8(mTxBuf, 10 + cnt); - - sendPacket(invId, mTxBuf, 10 + cnt + 1, isRetransmit, true); + sendPacket(invId, cnt, isRetransmit, true); } void sendTimePacket(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit) { DPRINTLN(DBG_DEBUG, F("sendTimePacket 0x") + String(cmd, HEX)); - sendCmdPacket(invId, TX_REQ_INFO, ALL_FRAMES, isRetransmit, false); + initPacket(invId, TX_REQ_INFO, ALL_FRAMES); mTxBuf[10] = cmd; // cid mTxBuf[11] = 0x00; CP_U32_LittleEndian(&mTxBuf[12], ts); @@ -229,61 +199,16 @@ class HmRadio { mTxBuf[18] = (alarmMesId >> 8) & 0xff; mTxBuf[19] = (alarmMesId ) & 0xff; } - uint16_t crc = ah::crc16(&mTxBuf[10], 14); - mTxBuf[24] = (crc >> 8) & 0xff; - mTxBuf[25] = (crc ) & 0xff; - mTxBuf[26] = ah::crc8(mTxBuf, 26); - - sendPacket(invId, mTxBuf, 27, isRetransmit, true); + sendPacket(invId, 24, isRetransmit, true); } - void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool isRetransmit, bool calcCrc = true) { - DPRINTLN(DBG_VERBOSE, F("sendCmdPacket, mid: ") + String(mid, HEX) + F(" pid: ") + String(pid, HEX)); - memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE); - mTxBuf[0] = mid; // message id - CP_U32_BigEndian(&mTxBuf[1], (invId >> 8)); - CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8)); - mTxBuf[9] = pid; - if(calcCrc) { - mTxBuf[10] = ah::crc8(mTxBuf, 10); - sendPacket(invId, mTxBuf, 11, isRetransmit, false); - } + void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool isRetransmit) { + initPacket(invId, mid, pid); + sendPacket(invId, 10, isRetransmit, false); } - bool checkPaketCrc(uint8_t buf[], uint8_t *len, uint8_t rxCh) { - //DPRINTLN(DBG_INFO, F("hmRadio.h:checkPaketCrc")); - *len = (buf[0] >> 2); - if(*len > (MAX_RF_PAYLOAD_SIZE - 2)) - *len = MAX_RF_PAYLOAD_SIZE - 2; - for(uint8_t i = 1; i < (*len + 1); i++) { - buf[i-1] = (buf[i] << 1) | (buf[i+1] >> 7); - } - - uint8_t crc = ah::crc8(buf, *len-1); - bool valid = (crc == buf[*len-1]); - - return valid; - } - - bool switchRxCh(uint16_t addLoop = 0) { - if(!mNrf24.isChipConnected()) - return true; - mRxLoopCnt += addLoop; - if(mRxLoopCnt != 0) { - mRxLoopCnt--; - DISABLE_IRQ; - mNrf24.stopListening(); - mNrf24.setChannel(getRxNxtChannel()); - mNrf24.startListening(); - RESTORE_IRQ; - } - return (0 == mRxLoopCnt); // receive finished - } - - void dumpBuf(const char *info, uint8_t buf[], uint8_t len) { + void dumpBuf(uint8_t buf[], uint8_t len) { //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:dumpBuf")); - if(NULL != info) - DBGPRINT(String(info)); for(uint8_t i = 0; i < len; i++) { DHEX(buf[i]); DBGPRINT(" "); @@ -291,11 +216,6 @@ class HmRadio { DBGPRINTLN(""); } - bool isChipConnected(void) { - //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:isChipConnected")); - return mNrf24.isChipConnected(); - } - uint8_t getDataRate(void) { if(!mNrf24.isChipConnected()) return 3; // unkown @@ -306,7 +226,7 @@ class HmRadio { return mNrf24.isPVariant(); } - + std::queue mBufCtrl; uint32_t mSendCnt; uint32_t mRetransmits; @@ -314,78 +234,91 @@ class HmRadio { bool mSerialDebug; private: - void sendPacket(uint64_t invId, uint8_t buf[], uint8_t len, bool isRetransmit, bool clear=false) { + bool getReceived(void) { + bool tx_ok, tx_fail, rx_ready; + mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH + DBGPRINTLN("RX whatHappened Ch" + String(mRfChLst[mRxChIdx]) + " " + String(tx_ok) + String(tx_fail) + String(rx_ready)); + + bool isLastPackage = false; + while(mNrf24.available()) { + uint8_t len; + len = mNrf24.getDynamicPayloadSize(); // if payload size > 32, corrupt payload has been flushed + if (len > 0) { + packet_t p; + p.ch = mRfChLst[mRxChIdx]; + p.len = len; + mNrf24.read(p.packet, len); + mBufCtrl.push(p); + if (p.packet[0] == (TX_REQ_INFO + ALL_FRAMES)) // response from get information command + isLastPackage = (p.packet[9] > 0x81); // > 0x81 indicates last packet received + else if (p.packet[0] != 0x00) // ignore fragment number zero + isLastPackage = true; // response from dev control command + yield(); + } + } + return isLastPackage; + } + + void switchRxCh() { + mNrf24.stopListening(); + // get next channel index + if(++mRxChIdx >= RF_CHANNELS) + mRxChIdx = 0; + mNrf24.setChannel(mRfChLst[mRxChIdx]); + mNrf24.startListening(); + } + + void initPacket(uint64_t invId, uint8_t mid, uint8_t pid) { + DPRINTLN(DBG_VERBOSE, F("initPacket, mid: ") + String(mid, HEX) + F(" pid: ") + String(pid, HEX)); + memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE); + mTxBuf[0] = mid; // message id + CP_U32_BigEndian(&mTxBuf[1], (invId >> 8)); + CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8)); + mTxBuf[9] = pid; + } + + void sendPacket(uint64_t invId, uint8_t len, bool isRetransmit, bool clear=false) { //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendPacket")); //DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mSendCnt)); - //dumpBuf("SEN ", buf, len); + + // append crc's + if (len > 10) { + // crc control data + uint16_t crc = ah::crc16(&mTxBuf[10], len - 10); + mTxBuf[len++] = (crc >> 8) & 0xff; + mTxBuf[len++] = (crc ) & 0xff; + } + // crc over all + mTxBuf[len++] = ah::crc8(mTxBuf, len); + if(mSerialDebug) { DPRINT(DBG_INFO, "TX " + String(len) + "B Ch" + String(mRfChLst[mTxChIdx]) + " | "); - dumpBuf(NULL, buf, len); + dumpBuf(mTxBuf, len); } - DISABLE_IRQ; - mNrf24.stopListening(); - - if(clear) - mRxLoopCnt = RF_LOOP_CNT; - mNrf24.setChannel(mRfChLst[mTxChIdx]); - mTxCh = getTxNxtChannel(); // switch channel for next packet - mNrf24.openWritingPipe(invId); // TODO: deprecated - mNrf24.setCRCLength(RF24_CRC_16); - mNrf24.enableDynamicPayloads(); - mNrf24.setAutoAck(true); - mNrf24.setRetries(3, 15); // 3*250us and 15 loops -> 11.25ms - mNrf24.write(buf, len); + mNrf24.openWritingPipe(reinterpret_cast(&invId)); + mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response - // Try to avoid zero payload acks (has no effect) - mNrf24.openWritingPipe(DUMMY_RADIO_ID); // TODO: why dummy radio id?, deprecated - mRxChIdx = 0; - mNrf24.setChannel(mRfChLst[mRxChIdx]); - mNrf24.setAutoAck(false); - mNrf24.setRetries(0, 0); - mNrf24.disableDynamicPayloads(); - mNrf24.setCRCLength(RF24_CRC_DISABLED); - mNrf24.startListening(); + // switch TX channel for next packet + if(++mTxChIdx >= RF_CHANNELS) + mTxChIdx = 0; - RESTORE_IRQ; if(isRetransmit) mRetransmits++; else mSendCnt++; } - uint8_t getTxNxtChannel(void) { - - if(++mTxChIdx >= RF_CHANNELS) - mTxChIdx = 0; - return mRfChLst[mTxChIdx]; - } - - uint8_t getRxNxtChannel(void) { - - if(++mRxChIdx >= RF_CHANNELS) - mRxChIdx = 0; - return mRfChLst[mRxChIdx]; - } - uint64_t DTU_RADIO_ID; - uint8_t mTxCh; - uint8_t mTxChIdx; - uint8_t mRfChLst[RF_CHANNELS]; + uint8_t mTxChIdx; uint8_t mRxChIdx; - uint16_t mRxLoopCnt; RF24 mNrf24; - BUFFER *mBufCtrl; uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE]; - - DevControlCmdType DevControlCmd; - - volatile bool mIrqRcvd; }; #endif /*__RADIO_H__*/ From 9638414ce29abcf80d51b6cb95c7cd1708ad8867 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:44:19 +0100 Subject: [PATCH 059/215] Update hmSystem.h --- src/hm/hmSystem.h | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index 85976cbd5..58099d401 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -8,19 +8,11 @@ #include "hmInverter.h" #include "hmRadio.h" -#include "CircularBuffer.h" -typedef CircularBuffer BufferType; -typedef HmRadio RadioType; - -template > +template > class HmSystem { public: - typedef RADIO RadioType; - RadioType Radio; - typedef BUFFER BufferType; - BufferType BufCtrl; - //DevControlCmdType DevControlCmd; + HmRadio<> Radio; HmSystem() { mNumInv = 0; @@ -30,11 +22,11 @@ class HmSystem { } void setup() { - Radio.setup(&BufCtrl); + Radio.setup(); } void setup(uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin) { - Radio.setup(&BufCtrl, ampPwr, irqPin, cePin, csPin); + Radio.setup(ampPwr, irqPin, cePin, csPin); } void addInverters(cfgInst_t *config) { From c241f356a0549cd2d9514ad07c2b843f02ff3c85 Mon Sep 17 00:00:00 2001 From: beegee3 <119520001+beegee3@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:47:29 +0100 Subject: [PATCH 060/215] Update payload.h --- src/hm/payload.h | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/hm/payload.h b/src/hm/payload.h index 7e66eb764..721663900 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -46,7 +46,8 @@ class Payload { reset(i); } mSerialDebug = false; - mHighPrioIv = NULL; + mHighPrioIv = NULL; + mCbAlarm = NULL; } void enableSerialDebug(bool enable) { @@ -117,7 +118,7 @@ class Payload { } } - void add(packet_t *p, uint8_t len) { + void add(packet_t *p) { Inverter<> *iv = mSys->findInverter(&p->packet[1]); if(NULL == iv) @@ -132,8 +133,8 @@ class Payload { } else { DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX)); if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) { - memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], len - 11); - mPayload[iv->id].len[(*pid & 0x7F) - 1] = len - 11; + memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], p->len - 11); + mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->len - 11; mPayload[iv->id].gotFragment = true; } @@ -194,21 +195,24 @@ class Payload { if (mPayload[iv->id].retransmits < mMaxRetrans) { mPayload[iv->id].retransmits++; if(false == mPayload[iv->id].gotFragment) { + /* DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit")); mPayload[iv->id].txCmd = iv->getQueuedCmd(); DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket 0x") + String(mPayload[iv->id].txCmd, HEX)); mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); + */ + DPRINTLN(DBG_WARN, F("(#") + String(iv->id) + F(") nothing received")); + mPayload[iv->id].retransmits = mMaxRetrans; } else { for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { if (mPayload[iv->id].len[i] == 0) { DPRINTLN(DBG_WARN, F("Frame ") + String(i + 1) + F(" missing: Request Retransmit")); - mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true, true); + mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true); break; // only request retransmit one frame per loop } yield(); } } - mSys->Radio.switchRxCh(100); } } } @@ -241,13 +245,13 @@ class Payload { if (mSerialDebug) { DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): "); - mSys->Radio.dumpBuf(NULL, payload, payloadLen); + mSys->Radio.dumpBuf(payload, payloadLen); } if (NULL == rec) { DPRINTLN(DBG_ERROR, F("record is NULL!")); } else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { - if (mPayload[iv->id].txId == (TX_REQ_INFO + 0x80)) + if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES)) mStat->rxSuccess++; rec->ts = mPayload[iv->id].ts; @@ -266,7 +270,8 @@ class Payload { code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); if(0 == code) break; - (mCbAlarm)(code, start, end); + if (NULL != mCbAlarm) + (mCbAlarm)(code, start, end); yield(); } } @@ -278,9 +283,7 @@ class Payload { iv->setQueuedCmdFinished(); } } - yield(); - } } @@ -290,7 +293,8 @@ class Payload { } void notify(uint16_t code, uint32_t start, uint32_t endTime) { - (mCbAlarm)(code, start, endTime); + if (NULL != mCbAlarm) + (mCbAlarm)(code, start, endTime); } bool build(uint8_t id, bool *complete) { From a9179ec1eab666a9466c6366fa1bbc114d4bf1c5 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 5 Feb 2023 22:14:59 +0100 Subject: [PATCH 061/215] fixed mixed reset flags #648 fixed `mCbAlarm` if MQTT is not used #653 --- src/CHANGES.md | 4 ++++ src/hm/payload.h | 7 +++++-- src/web/web.h | 4 ++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 9e31ada25..7167da6b7 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,10 @@ (starting from release version `0.5.66`) +## 0.5.79 +* fixed mixed reset flags #648 +* fixed `mCbAlarm` if MQTT is not used #653 + ## 0.5.78 * further improvements regarding wifi #611, fix connection if only one AP with same SSID is there * fix endless loop in `zerovalues` #564 diff --git a/src/hm/payload.h b/src/hm/payload.h index 7e66eb764..bc21a5175 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -47,6 +47,7 @@ class Payload { } mSerialDebug = false; mHighPrioIv = NULL; + mCbAlarm = NULL; } void enableSerialDebug(bool enable) { @@ -266,7 +267,8 @@ class Payload { code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); if(0 == code) break; - (mCbAlarm)(code, start, end); + if(NULL != mCbAlarm) + (mCbAlarm)(code, start, end); yield(); } } @@ -290,7 +292,8 @@ class Payload { } void notify(uint16_t code, uint32_t start, uint32_t endTime) { - (mCbAlarm)(code, start, endTime); + if(NULL != mCbAlarm) + (mCbAlarm)(code, start, endTime); } bool build(uint8_t id, bool *complete) { diff --git a/src/web/web.h b/src/web/web.h index 5641ef0d6..1c8af2950 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -550,8 +550,8 @@ class Web { mConfig->mqtt.port = request->arg("mqttPort").toInt(); mConfig->mqtt.interval = request->arg("mqttInterval").toInt(); mConfig->mqtt.rstYieldMidNight = (request->arg("mqttRstMid") == "on"); - mConfig->mqtt.rstValsNotAvail = (request->arg("mqttRstComStop") == "on"); - mConfig->mqtt.rstValsCommStop = (request->arg("mqttRstNotAvail") == "on"); + mConfig->mqtt.rstValsCommStop = (request->arg("mqttRstComStop") == "on"); + mConfig->mqtt.rstValsNotAvail = (request->arg("mqttRstNotAvail") == "on"); // serial console if(request->arg("serIntvl") != "") { From 3ed81513d5ed4b8ad2096aef49a012918d67b6c9 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 5 Feb 2023 22:44:10 +0100 Subject: [PATCH 062/215] next changes from @beegee many thanks for your contribution! replaced `CircularBuffer` by `std::queue` reworked `hmRadio.h` completely (interrupts, packaging) fix exception while `reboot` cleanup MQTT coding --- src/CHANGES.md | 5 +++++ src/defines.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 7167da6b7..94d31e44e 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,6 +5,11 @@ ## 0.5.79 * fixed mixed reset flags #648 * fixed `mCbAlarm` if MQTT is not used #653 +* next changes from @beegee many thanks for your contribution! +* replaced `CircularBuffer` by `std::queue` +* reworked `hmRadio.h` completely (interrupts, packaging) +* fix exception while `reboot` +* cleanup MQTT coding ## 0.5.78 * further improvements regarding wifi #611, fix connection if only one AP with same SSID is there diff --git a/src/defines.h b/src/defines.h index d5f0fcb3f..2a12e0e62 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 78 +#define VERSION_PATCH 79 //------------------------------------- typedef struct { From a85ed6c0897ddc6883da28d8882820abb0333795 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 5 Feb 2023 23:14:33 +0100 Subject: [PATCH 063/215] fixed MQTT `autodiscover` #630 thanks to @antibill51 --- src/CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CHANGES.md b/src/CHANGES.md index 94d31e44e..09df4b0b1 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -5,6 +5,7 @@ ## 0.5.79 * fixed mixed reset flags #648 * fixed `mCbAlarm` if MQTT is not used #653 +* fixed MQTT `autodiscover` #630 thanks to @antibill51 * next changes from @beegee many thanks for your contribution! * replaced `CircularBuffer` by `std::queue` * reworked `hmRadio.h` completely (interrupts, packaging) From edefcf1c830b5ea7cd202a9d27395811a6f5a03b Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 6 Feb 2023 00:07:58 +0100 Subject: [PATCH 064/215] fix Interrupt fix connect to WiFi with only one AP --- src/app.cpp | 1 + src/app.h | 5 +++++ src/hm/hmRadio.h | 5 ++++- src/main.cpp | 10 ++++++++++ src/wifi/ahoywifi.cpp | 13 ++++++------- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index f5478f0fd..2f1bd58d1 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,6 +13,7 @@ app::app() : ah::Scheduler() {} //----------------------------------------------------------------------------- void app::setup() { + mSys = NULL; Serial.begin(115200); while (!Serial) yield(); diff --git a/src/app.h b/src/app.h index d3709b894..36a77f9ac 100644 --- a/src/app.h +++ b/src/app.h @@ -60,6 +60,11 @@ class app : public IApp, public ah::Scheduler { void onWifi(bool gotIp); void regularTickers(void); + void handleIntr(void) { + if(NULL != mSys) + mSys->Radio.handleIntr(); + } + uint32_t getUptime() { return Scheduler::getUptime(); } diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index ea440b8cb..9f671c54b 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -82,7 +82,6 @@ class HmRadio { void setup(uint8_t ampPwr = RF24_PA_LOW, uint8_t irq = IRQ_PIN, uint8_t ce = CE_PIN, uint8_t cs = CS_PIN) { DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setup")); pinMode(irq, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(irq), []()IRAM_ATTR{ mIrqRcvd = true; }, FALLING); uint32_t dtuSn = 0x87654321; uint32_t chipID = 0; // will be filled with last 3 bytes of MAC @@ -166,6 +165,10 @@ class HmRadio { return true; } + void handleIntr(void) { + mIrqRcvd = true; + } + bool isChipConnected(void) { //DPRINTLN(DBG_VERBOSE, F("hmRadio.h:isChipConnected")); return mNrf24.isChipConnected(); diff --git a/src/main.cpp b/src/main.cpp index 42ed55f43..c585d0f25 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,11 +7,21 @@ #include "app.h" #include "config/config.h" + app myApp; +//----------------------------------------------------------------------------- +IRAM_ATTR void handleIntr(void) { + myApp.handleIntr(); +} + + //----------------------------------------------------------------------------- void setup() { myApp.setup(); + + // TODO: move to HmRadio + attachInterrupt(digitalPinToInterrupt(myApp.getIrqPin()), handleIntr, FALLING); } diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 8384c1182..dc8f03b55 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -88,7 +88,12 @@ void ahoywifi::tickWifiLoop() { } mCnt++; - if(!mScanActive && mBSSIDList.empty()) { // start scanning APs with the given SSID + uint8_t timeout = 10; // seconds + if (mStaConn == CONNECTED) // connected but no ip + timeout = 20; + + + if(!mScanActive && mBSSIDList.empty() && ((mCnt % timeout) == 0)) { // start scanning APs with the given SSID DBGPRINT(F("scanning APs with SSID ")); DBGPRINTLN(String(mConfig->sys.stationSsid)); mScanCnt = 0; @@ -100,12 +105,6 @@ void ahoywifi::tickWifiLoop() { #endif return; } - - uint8_t timeout = 10; // seconds - - if (mStaConn == CONNECTED) // connected but no ip - timeout = 20; - DBGPRINT(F("reconnect in ")); DBGPRINT(String(timeout-mCnt)); DBGPRINTLN(F(" seconds")); From e7fff27a4a6f4213fb0cf22ad80be109c6fc14f0 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 6 Feb 2023 18:43:09 +0100 Subject: [PATCH 065/215] added further inverter and alarm info to prometheus endpoint --- doc/prometheus_ep_description.md | 28 ++++++------ src/web/web.h | 73 ++++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md index bce0525f4..9d274f1e3 100644 --- a/doc/prometheus_ep_description.md +++ b/doc/prometheus_ep_description.md @@ -4,23 +4,24 @@ Metrics available for AhoyDTU device, inverters and channels. Prometheus metrics provided at `/metrics`. ## Labels -| Label name | Description | -|:-----------|:--------------------------------------| -| version | current installed version of AhoyDTU | -| image | currently not used | -| devicename | Device name from setup | -| name | Inverter name from setup | -| serial | Serial number of inverter | -| enabled | Communication enable for inverter | -| inverter | Inverter name from setup | -| channel | Channel name from setup | - +| Label name | Description | +|:-------------|:--------------------------------------| +| version | current installed version of AhoyDTU | +| image | currently not used | +| devicename | Device name from setup | +| name | Inverter name from setup | +| serial | Serial number of inverter | +| inverter | Inverter name from setup | +| channel | Channel name from setup | ## Exported Metrics | Metric name | Type | Description | Labels | |----------------------------------------|---------|--------------------------------------------------------|--------------| | `ahoy_solar_info` | Gauge | Information about the AhoyDTU device | version, image, devicename | -| `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial, enabled | +| `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial | +| `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter | +| `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter | +| `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter | | `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter | | `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter | | `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | @@ -28,7 +29,8 @@ Prometheus metrics provided at `/metrics`. | `ahoy_solar_F_AC_hertz` | Gauge | AC frequency [Hz] | inverter | | `ahoy_solar_PF_AC` | Gauge | AC Power factor | inverter | | `ahoy_solar_Temp_celsius` | Gauge | Temperature of inverter | inverter | -| `ahoy_solar_ALARM_MES_ID` | Gauge | Last alarm message id of inverter | inverter | +| `ahoy_solar_ALARM_MES_ID` | Gauge | Alarm message index of inverter | inverter | +| `ahoy_solar_LastAlarmCode` | Gauge | Last alarm code from inverter | inverter | | `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter | | `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter | | `ahoy_solar_P_DC_watt` | Gauge | DC power of inverter [W] | inverter | diff --git a/src/web/web.h b/src/web/web.h index be4df73b3..887f65aaa 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -132,7 +132,7 @@ class Web { void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { mApp->setOnUpdate(); - + if(!index) { Serial.printf("Update Start: %s\n", filename.c_str()); #ifndef ESP32 @@ -735,7 +735,7 @@ class Web { #ifdef ENABLE_PROMETHEUS_EP enum { - metricsStateStart, metricsStateInverter, metricStateChannel,metricsStateEnd + metricsStateStart, metricsStateInverter, metricStateRealtimeData,metricsStateAlarmData,metricsStateEnd } metricsStep; int metricsInverterId,metricsChannelId; @@ -749,15 +749,22 @@ class Web { Inverter<> *iv; record_t<> *rec; statistics_t *stat; + String promUnit, promType; String metrics; char type[60], topic[100], val[25]; size_t len = 0; + int alarmChannelId; switch (metricsStep) { case metricsStateStart: // System Info & NRF Statistics : fit to one packet - snprintf(topic,sizeof(topic),"# TYPE ahoy_solar_info gauge\nahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", + snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", mApp->getVersion(), mConfig->sys.deviceName); - metrics = topic; + metrics = String(type) + String(topic); + + snprintf(topic,sizeof(topic),"# TYPE ahoy_solar_freeheap gauge\nahoy_solar_freeheap{devicename=\"%s\"} %u\n",mConfig->sys.deviceName,ESP.getFreeHeap()); + metrics += String(topic); + // NRF Statistics stat = mApp->getStatistics(); metrics += radioStatistic(F("rx_success"), stat->rxSuccess); @@ -776,24 +783,40 @@ class Web { if (metricsInverterId < mSys->getNumInverters()) { iv = mSys->getInverterByPos(metricsInverterId); if(NULL != iv) { - // Inverter info - len = snprintf((char *)buffer, maxLen, "ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\",enabled=\"%d\"} 1\n", - iv->config->name, iv->config->serial.u64,iv->config->enabled); - // Start Channel loop for this inverter + // Inverter info : fit to one packet + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_info gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", + iv->config->name, iv->config->serial.u64); + metrics = String(type) + String(topic); + + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_enabled gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n",iv->config->name,iv->config->enabled); + metrics += String(type) + String(topic); + + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_available gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n",iv->config->name,iv->isAvailable(mApp->getTimestamp())); + metrics += String(type) + String(topic); + + snprintf(type,sizeof(type),"# TYPE ahoy_solar_inverter_is_producing gauge\n"); + snprintf(topic,sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n",iv->config->name,iv->isProducing(mApp->getTimestamp())); + metrics += String(type) + String(topic); + + len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); + + // Start Realtime Data Channel loop for this inverter metricsChannelId = 0; - metricsStep = metricStateChannel; + metricsStep = metricStateRealtimeData; } } else { metricsStep = metricsStateEnd; } break; - case metricStateChannel: // Channel loop + case metricStateRealtimeData: // Realtime Data Channel loop iv = mSys->getInverterByPos(metricsInverterId); rec = iv->getRecordStruct(RealTimeRunData_Debug); if (metricsChannelId < rec->length) { uint8_t channel = rec->assign[metricsChannelId].ch; - String promUnit, promType; std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); if (0 == channel) { @@ -808,12 +831,34 @@ class Web { } else { len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. - // All channels processed --> try next inverter - metricsInverterId++; - metricsStep = metricsStateInverter; + // All realtime data channels processed --> try alarm data + metricsStep = metricsStateAlarmData; } break; + case metricsStateAlarmData: // Alarm Info loop + iv = mSys->getInverterByPos(metricsInverterId); + rec = iv->getRecordStruct(AlarmData); + // simple hack : there is only one channel with alarm data + // TODO: find the right one channel with the alarm id + alarmChannelId = 0; + // printf("AlarmData Length %d\n",rec->length); + if (alarmChannelId < rec->length) + { + //uint8_t channel = rec->assign[alarmChannelId].ch; + std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); + snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), promType.c_str()); + snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(val, sizeof(val), "%.3f", iv->getValue(alarmChannelId, rec)); + len = snprintf((char*)buffer,maxLen,"%s\n%s %s\n",type,topic,val); + } else { + len = snprintf((char*)buffer,maxLen,"#\n"); // At least one char to send otherwise the transmission ends. + } + // alarm channel processed --> try next inverter + metricsInverterId++; + metricsStep = metricsStateInverter; + break; + case metricsStateEnd: default: // end of transmission len = 0; From 0e6faa79e3e699066c0512a8e14ecf148745cda7 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 6 Feb 2023 21:47:12 +0100 Subject: [PATCH 066/215] fixed communication #656 --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- src/hm/hmRadio.h | 4 ++-- src/hm/payload.h | 1 - 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 09df4b0b1..ae341ba6b 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,9 @@ (starting from release version `0.5.66`) +## 0.5.80 +* fixed communication #656 + ## 0.5.79 * fixed mixed reset flags #648 * fixed `mCbAlarm` if MQTT is not used #653 diff --git a/src/defines.h b/src/defines.h index 2a12e0e62..9dfb78840 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 79 +#define VERSION_PATCH 80 //------------------------------------- typedef struct { diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 9f671c54b..b3466d002 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -9,6 +9,7 @@ #include "../utils/dbg.h" #include #include "../utils/crc.h" +#include "../config/config.h" #define SPI_SPEED 1000000 @@ -43,8 +44,6 @@ const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"}; #define BIT_CNT(x) ((x)<<3) -static volatile bool mIrqRcvd; - //----------------------------------------------------------------------------- // HM Radio class //----------------------------------------------------------------------------- @@ -313,6 +312,7 @@ class HmRadio { mSendCnt++; } + volatile bool mIrqRcvd; uint64_t DTU_RADIO_ID; uint8_t mRfChLst[RF_CHANNELS]; diff --git a/src/hm/payload.h b/src/hm/payload.h index 071dfe0c9..721663900 100644 --- a/src/hm/payload.h +++ b/src/hm/payload.h @@ -48,7 +48,6 @@ class Payload { mSerialDebug = false; mHighPrioIv = NULL; mCbAlarm = NULL; - mCbAlarm = NULL; } void enableSerialDebug(bool enable) { From 67ff0209e86906e26a0c3f2edd41171979c5f34e Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 6 Feb 2023 22:12:12 +0100 Subject: [PATCH 067/215] renamed payload to hmPayload --- src/app.h | 4 ++-- src/hm/{payload.h => hmPayload.h} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/hm/{payload.h => hmPayload.h} (99%) diff --git a/src/app.h b/src/app.h index 36a77f9ac..c60bb2950 100644 --- a/src/app.h +++ b/src/app.h @@ -21,7 +21,7 @@ #include "utils/scheduler.h" #include "hm/hmSystem.h" -#include "hm/payload.h" +#include "hm/hmPayload.h" #include "wifi/ahoywifi.h" #include "web/web.h" #include "web/RestApi.h" @@ -37,7 +37,7 @@ #define ACOS(x) (degrees(acos(x))) typedef HmSystem HmSystemType; -typedef Payload PayloadType; +typedef HmPayload PayloadType; typedef Web WebType; typedef RestApi RestApiType; typedef PubMqtt PubMqttType; diff --git a/src/hm/payload.h b/src/hm/hmPayload.h similarity index 99% rename from src/hm/payload.h rename to src/hm/hmPayload.h index 721663900..2e599a6c4 100644 --- a/src/hm/payload.h +++ b/src/hm/hmPayload.h @@ -32,9 +32,9 @@ typedef std::function al template -class Payload { +class HmPayload { public: - Payload() {} + HmPayload() {} void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { mApp = app; From 75539c5daf968598c7e336b78ae31e61bdf00fba Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 6 Feb 2023 23:37:05 +0100 Subject: [PATCH 068/215] started implementation of MI inverters (setup.html, own processing `MiPayload.h`) --- src/CHANGES.md | 3 + src/app.cpp | 65 ++++--- src/app.h | 8 +- src/defines.h | 2 +- src/hm/hmDefines.h | 3 + src/hm/hmInverter.h | 2 + src/hm/hmPayload.h | 17 +- src/hm/hmRadio.h | 8 +- src/hm/hmSystem.h | 39 +++-- src/hm/miPayload.h | 298 ++++++++++++++++++++++++++++++++ src/web/html/index.html | 2 +- src/web/html/login.html | 2 +- src/web/html/serial.html | 2 +- src/web/html/setup.html | 17 +- src/web/html/system.html | 2 +- src/web/html/update.html | 2 +- src/web/html/visualization.html | 2 +- 17 files changed, 408 insertions(+), 66 deletions(-) create mode 100644 src/hm/miPayload.h diff --git a/src/CHANGES.md b/src/CHANGES.md index ae341ba6b..2e31c0e10 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -2,6 +2,9 @@ (starting from release version `0.5.66`) +## 0.5.81 +* started implementation of MI inverters (setup.html, own processing `MiPayload.h`) + ## 0.5.80 * fixed communication #656 diff --git a/src/app.cpp b/src/app.cpp index 2f1bd58d1..d64eefdb9 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,7 +13,6 @@ app::app() : ah::Scheduler() {} //----------------------------------------------------------------------------- void app::setup() { - mSys = NULL; Serial.begin(115200); while (!Serial) yield(); @@ -26,9 +25,8 @@ void app::setup() { 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.enableDebug(); + mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs); mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1)); #if defined(AP_ONLY) @@ -42,34 +40,37 @@ void app::setup() { everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); #endif - mSys->addInverters(&mConfig->inst); - mPayload.setup(this, mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); + mSys.addInverters(&mConfig->inst); + mPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); mPayload.enableSerialDebug(mConfig->serial.debug); - if(!mSys->Radio.isChipConnected()) + mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp); + mMiPayload.enableSerialDebug(mConfig->serial.debug); + + if(!mSys.Radio.isChipConnected()) DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); // when WiFi is in client mode, then enable mqtt broker #if !defined(AP_ONLY) mMqttEnabled = (mConfig->mqtt.broker[0] > 0); if (mMqttEnabled) { - mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, mSys, &mTimestamp); + mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } #endif setupLed(); - mWeb.setup(this, mSys, mConfig); + mWeb.setup(this, &mSys, mConfig); mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0); - mApi.setup(this, mSys, mWeb.getWebSrvPtr(), mConfig); + mApi.setup(this, &mSys, mWeb.getWebSrvPtr(), mConfig); // Plugins if(mConfig->plugin.display.type != 0) - mMonoDisplay.setup(&mConfig->plugin.display, mSys, &mTimestamp, 0xff, mVersion); + mMonoDisplay.setup(&mConfig->plugin.display, &mSys, &mTimestamp, 0xff, mVersion); - mPubSerial.setup(mConfig, mSys, &mTimestamp); + mPubSerial.setup(mConfig, &mSys, &mTimestamp); regularTickers(); } @@ -83,23 +84,31 @@ void app::loop(void) { void app::loopStandard(void) { ah::Scheduler::loop(); - if (mSys->Radio.loop()) { - while (!mSys->Radio.mBufCtrl.empty()) { - packet_t *p = &mSys->Radio.mBufCtrl.front(); + if (mSys.Radio.loop()) { + while (!mSys.Radio.mBufCtrl.empty()) { + packet_t *p = &mSys.Radio.mBufCtrl.front(); if (mConfig->serial.debug) { DPRINT(DBG_INFO, "RX " + String(p->len) + "B Ch" + String(p->ch) + " | "); - mSys->Radio.dumpBuf(p->packet, p->len); + mSys.Radio.dumpBuf(p->packet, p->len); } mStat.frmCnt++; - mPayload.add(p); - mSys->Radio.mBufCtrl.pop(); + Inverter<> *iv = mSys.findInverter(&p->packet[1]); + if(NULL == iv) { + if(IV_HM == iv->ivGen) + mPayload.add(iv, p); + else + mMiPayload.add(iv, p); + } + mSys.Radio.mBufCtrl.pop(); yield(); } mPayload.process(true); + mMiPayload.process(true); } mPayload.loop(); + mMiPayload.loop(); if(mMqttEnabled) mMqtt.loop(); @@ -232,26 +241,30 @@ void app::tickMidnight(void) { //----------------------------------------------------------------------------- void app::tickSend(void) { - if(!mSys->Radio.isChipConnected()) { + if(!mSys.Radio.isChipConnected()) { DPRINTLN(DBG_WARN, "NRF24 not connected!"); return; } if (mIVCommunicationOn) { - if (!mSys->Radio.mBufCtrl.empty()) { + if (!mSys.Radio.mBufCtrl.empty()) { if (mConfig->serial.debug) - DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys->Radio.mBufCtrl.size())); + DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys.Radio.mBufCtrl.size())); } int8_t maxLoop = MAX_NUM_INVERTERS; - Inverter<> *iv = mSys->getInverterByPos(mSendLastIvId); + Inverter<> *iv = mSys.getInverterByPos(mSendLastIvId); do { mSendLastIvId = ((MAX_NUM_INVERTERS - 1) == mSendLastIvId) ? 0 : mSendLastIvId + 1; - iv = mSys->getInverterByPos(mSendLastIvId); + iv = mSys.getInverterByPos(mSendLastIvId); } while ((NULL == iv) && ((maxLoop--) > 0)); if (NULL != iv) { - if(iv->config->enabled) - mPayload.ivSend(iv); + if(iv->config->enabled) { + if(iv->ivGen == IV_HM) + mPayload.ivSend(iv); + else + mMiPayload.ivSend(iv); + } } } else { if (mConfig->serial.debug) @@ -307,7 +320,7 @@ void app::setupLed(void) { //----------------------------------------------------------------------------- void app::updateLed(void) { if(mConfig->led.led0 != 0xff) { - Inverter<> *iv = mSys->getInverterByPos(0); + Inverter<> *iv = mSys.getInverterByPos(0); if (NULL != iv) { if(iv->isProducing(mTimestamp)) digitalWrite(mConfig->led.led0, LOW); // LED on diff --git a/src/app.h b/src/app.h index c60bb2950..7e9499b75 100644 --- a/src/app.h +++ b/src/app.h @@ -22,6 +22,7 @@ #include "hm/hmSystem.h" #include "hm/hmPayload.h" +#include "hm/miPayload.h" #include "wifi/ahoywifi.h" #include "web/web.h" #include "web/RestApi.h" @@ -38,6 +39,7 @@ typedef HmSystem HmSystemType; typedef HmPayload PayloadType; +typedef MiPayload MiPayloadType; typedef Web WebType; typedef RestApi RestApiType; typedef PubMqtt PubMqttType; @@ -61,8 +63,7 @@ class app : public IApp, public ah::Scheduler { void regularTickers(void); void handleIntr(void) { - if(NULL != mSys) - mSys->Radio.handleIntr(); + mSys.Radio.handleIntr(); } uint32_t getUptime() { @@ -187,7 +188,7 @@ class app : public IApp, public ah::Scheduler { Scheduler::setTimestamp(newTime); } - HmSystemType *mSys; + HmSystemType mSys; private: typedef std::function innerLoopCb; @@ -246,6 +247,7 @@ class app : public IApp, public ah::Scheduler { WebType mWeb; RestApiType mApi; PayloadType mPayload; + MiPayloadType mMiPayload; PubSerialType mPubSerial; char mVersion[12]; diff --git a/src/defines.h b/src/defines.h index 9dfb78840..cf16e86be 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 80 +#define VERSION_PATCH 81 //------------------------------------- typedef struct { diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index fa7e08b26..aa9fe24fb 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -9,6 +9,9 @@ #include "../utils/dbg.h" #include +// inverter generations +enum {IV_HM = 0, IV_MI}; + // units enum {UNIT_V = 0, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_HZ, UNIT_C, UNIT_PCT, UNIT_VAR, UNIT_NONE}; const char* const units[] = {"V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", ""}; diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 640aeae39..d50f341a0 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -105,6 +105,7 @@ const calcFunc_t calcFunctions[] = { template class Inverter { public: + uint8_t ivGen; // generation of inverter (HM / MI) cfgIv_t *config; // stored settings uint8_t id; // unique id uint8_t type; // integer which refers to inverter type @@ -123,6 +124,7 @@ class Inverter { bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) Inverter() { + ivGen = IV_HM; powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited powerLimit[1] = AbsolutNonPersistent; // default power limit setting actPowerLimit = 0xffff; // init feedback from inverter to -1 diff --git a/src/hm/hmPayload.h b/src/hm/hmPayload.h index 2e599a6c4..a85f24e3e 100644 --- a/src/hm/hmPayload.h +++ b/src/hm/hmPayload.h @@ -3,8 +3,8 @@ // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- -#ifndef __PAYLOAD_H__ -#define __PAYLOAD_H__ +#ifndef __HM_PAYLOAD_H__ +#define __HM_PAYLOAD_H__ #include "../utils/dbg.h" #include "../utils/crc.h" @@ -48,6 +48,7 @@ class HmPayload { mSerialDebug = false; mHighPrioIv = NULL; mCbAlarm = NULL; + mCbPayload = NULL; } void enableSerialDebug(bool enable) { @@ -118,12 +119,7 @@ class HmPayload { } } - void add(packet_t *p) { - Inverter<> *iv = mSys->findInverter(&p->packet[1]); - - if(NULL == iv) - return; - + void add(Inverter<> *iv, packet_t *p) { if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command mPayload[iv->id].txId = p->packet[0]; DPRINTLN(DBG_DEBUG, F("Response from info request received")); @@ -289,7 +285,8 @@ class HmPayload { private: void notify(uint8_t val) { - (mCbPayload)(val); + if(NULL != mCbPayload) + (mCbPayload)(val); } void notify(uint16_t code, uint32_t start, uint32_t endTime) { @@ -352,4 +349,4 @@ class HmPayload { payloadListenerType mCbPayload; }; -#endif /*__PAYLOAD_H_*/ +#endif /*__HM_PAYLOAD_H__*/ diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index b3466d002..5a6edb0e4 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -291,16 +291,16 @@ class HmRadio { mTxBuf[len++] = (crc ) & 0xff; } // crc over all - mTxBuf[len++] = ah::crc8(mTxBuf, len); + mTxBuf[len+1] = ah::crc8(mTxBuf, len); if(mSerialDebug) { - DPRINT(DBG_INFO, "TX " + String(len) + "B Ch" + String(mRfChLst[mTxChIdx]) + " | "); - dumpBuf(mTxBuf, len); + DPRINT(DBG_INFO, "TX " + String(len+1) + "B Ch" + String(mRfChLst[mTxChIdx]) + " | "); + dumpBuf(mTxBuf, len+1); } mNrf24.setChannel(mRfChLst[mTxChIdx]); mNrf24.openWritingPipe(reinterpret_cast(&invId)); - mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response + mNrf24.startWrite(mTxBuf, len+1, false); // false = request ACK response // switch TX channel for next packet if(++mTxChIdx >= RF_CHANNELS) diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index 58099d401..3f3b29a78 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -14,18 +14,15 @@ class HmSystem { public: HmRadio<> Radio; - HmSystem() { - mNumInv = 0; - } - ~HmSystem() { - // TODO: cleanup - } + HmSystem() {} void setup() { + mNumInv = 0; Radio.setup(); } void setup(uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin) { + mNumInv = 0; Radio.setup(ampPwr, irqPin, cePin, csPin); } @@ -34,8 +31,19 @@ class HmSystem { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { iv = addInverter(&config->iv[i]); if (0ULL != config->iv[i].serial.u64) { - if (NULL != iv) - DPRINTLN(DBG_INFO, "added inverter " + String(iv->config->serial.u64, HEX)); + if (NULL != iv) { + DPRINT(DBG_INFO, "added inverter "); + if(iv->config->serial.b[5] == 0x11) + DBGPRINT("HM"); + else { + DBGPRINT(((iv->config->serial.b[4] & 0x03) == 0x01) ? " (2nd Gen) " : " (3rd Gen) "); + } + + DBGPRINTLN(String(iv->config->serial.u64, HEX)); + + if((iv->config->serial.b[5] == 0x10) && ((iv->config->serial.b[4] & 0x03) == 0x01)) + DPRINTLN(DBG_WARN, F("MI Inverter are not fully supported now!!!")); + } } } } @@ -51,16 +59,25 @@ class HmSystem { p->config = config; DPRINT(DBG_VERBOSE, "SERIAL: " + String(p->config->serial.b[5], HEX)); DPRINTLN(DBG_VERBOSE, " " + String(p->config->serial.b[4], HEX)); - if(p->config->serial.b[5] == 0x11) { + if((p->config->serial.b[5] == 0x11) || (p->config->serial.b[5] == 0x10)) { switch(p->config->serial.b[4]) { + case 0x22: case 0x21: p->type = INV_TYPE_1CH; break; + case 0x42: case 0x41: p->type = INV_TYPE_2CH; break; + case 0x62: case 0x61: p->type = INV_TYPE_4CH; break; default: - DPRINT(DBG_ERROR, F("unknown inverter type: 11")); - DPRINTLN(DBG_ERROR, String(p->config->serial.b[4], HEX)); + DPRINTLN(DBG_ERROR, F("unknown inverter type")); break; } + + if(p->config->serial.b[5] == 0x11) + p->ivGen = IV_HM; + else if((p->config->serial.b[4] & 0x03) == 0x02) // MI 3rd Gen -> same as HM + p->ivGen = IV_HM; + else // MI 2nd Gen + p->ivGen = IV_MI; } else if(p->config->serial.u64 != 0ULL) DPRINTLN(DBG_ERROR, F("inverter type can't be detected!")); diff --git a/src/hm/miPayload.h b/src/hm/miPayload.h new file mode 100644 index 000000000..f578652ca --- /dev/null +++ b/src/hm/miPayload.h @@ -0,0 +1,298 @@ +//----------------------------------------------------------------------------- +// 2023 Ahoy, https://ahoydtu.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +#ifndef __MI_PAYLOAD_H__ +#define __MI_PAYLOAD_H__ + +#include "../utils/dbg.h" +#include "../utils/crc.h" +#include "../config/config.h" +#include + +typedef struct { + uint32_t ts; + bool requested; + uint8_t txCmd; + uint8_t len[MAX_PAYLOAD_ENTRIES]; + /* + uint8_t txId; + uint8_t invId; + uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE]; + bool complete; + uint8_t maxPackId; + bool lastFound; + uint8_t retransmits; + bool gotFragment;*/ +} miPayload_t; + + +typedef std::function miPayloadListenerType; + + +template +class MiPayload { + public: + MiPayload() {} + + void setup(IApp *app, HMSYSTEM *sys, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) { + mApp = app; + mSys = sys; + mStat = stat; + mMaxRetrans = maxRetransmits; + mTimestamp = timestamp; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { + reset(i); + } + mSerialDebug = false; + mCbMiPayload = NULL; + } + + void enableSerialDebug(bool enable) { + mSerialDebug = enable; + } + + void addPayloadListener(miPayloadListenerType cb) { + mCbMiPayload = cb; + } + + void loop() {} + + + void ivSend(Inverter<> *iv) { + reset(iv->id); + mPayload[iv->id].requested = true; + + yield(); + if (mSerialDebug) + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX)); + + uint8_t cmd = 0x09; //iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); + mSys->Radio.sendTimePacket(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false); + mPayload[iv->id].txCmd = cmd; + } + + void add(Inverter<> *iv, packet_t *p) { + DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX)); + + /*if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command + mPayload[iv->id].txId = p->packet[0]; + DPRINTLN(DBG_DEBUG, F("Response from info request received")); + uint8_t *pid = &p->packet[9]; + if (*pid == 0x00) { + DPRINT(DBG_DEBUG, F("fragment number zero received and ignored")); + } else { + DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX)); + if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) { + memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->packet[10], p->len - 11); + mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->len - 11; + mPayload[iv->id].gotFragment = true; + } + + if ((*pid & ALL_FRAMES) == ALL_FRAMES) { + // Last packet + if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) { + mPayload[iv->id].maxPackId = (*pid & 0x7f); + if (*pid > 0x81) + mPayload[iv->id].lastFound = true; + } + } + } + } else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command + DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received")); + + mPayload[iv->id].txId = p->packet[0]; + iv->clearDevControlRequest(); + + if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { + String msg = ""; + if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) + mApp->setMqttPowerLimitAck(iv); + else + msg = "NOT "; + DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has ") + msg + F("accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1])); + iv->clearCmdQueue(); + iv->enqueCommand(SystemConfigPara); // read back power limit + } + iv->devControlCmd = Init; + }*/ + } + + void process(bool retransmit) { + for (uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter + + /*if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) { + // no processing needed if txId is not 0x95 + mPayload[iv->id].complete = true; + continue; // skip to next inverter + } + + if (!mPayload[iv->id].complete) { + bool crcPass, pyldComplete; + crcPass = build(iv->id, &pyldComplete); + if (!crcPass && !pyldComplete) { // payload not complete + if ((mPayload[iv->id].requested) && (retransmit)) { + if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) { + // This is required to prevent retransmissions without answer. + DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm...")); + mPayload[iv->id].retransmits = mMaxRetrans; + } else if(iv->devControlCmd == ActivePowerContr) { + DPRINTLN(DBG_INFO, F("retransmit power limit")); + mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true); + } else { + if (mPayload[iv->id].retransmits < mMaxRetrans) { + mPayload[iv->id].retransmits++; + if(false == mPayload[iv->id].gotFragment) { + DPRINTLN(DBG_WARN, F("(#") + String(iv->id) + F(") nothing received")); + mPayload[iv->id].retransmits = mMaxRetrans; + } else { + for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { + if (mPayload[iv->id].len[i] == 0) { + DPRINTLN(DBG_WARN, F("Frame ") + String(i + 1) + F(" missing: Request Retransmit")); + mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true); + break; // only request retransmit one frame per loop + } + yield(); + } + } + } + } + } + } else if(!crcPass && pyldComplete) { // crc error on complete Payload + if (mPayload[iv->id].retransmits < mMaxRetrans) { + mPayload[iv->id].retransmits++; + DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit")); + mPayload[iv->id].txCmd = iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket 0x") + String(mPayload[iv->id].txCmd, HEX)); + mSys->Radio.sendTimePacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); + } + } else { // payload complete + DPRINTLN(DBG_INFO, F("procPyld: cmd: 0x") + String(mPayload[iv->id].txCmd, HEX)); + DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX)); + DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId)); + record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser + mPayload[iv->id].complete = true; + + uint8_t payload[128]; + uint8_t payloadLen = 0; + + memset(payload, 0, 128); + + for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) { + memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); + payloadLen += (mPayload[iv->id].len[i]); + yield(); + } + payloadLen -= 2; + + if (mSerialDebug) { + DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): "); + mSys->Radio.dumpBuf(payload, payloadLen); + } + + if (NULL == rec) { + DPRINTLN(DBG_ERROR, F("record is NULL!")); + } else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) { + if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES)) + mStat->rxSuccess++; + + rec->ts = mPayload[iv->id].ts; + for (uint8_t i = 0; i < rec->length; i++) { + iv->addValue(i, payload, rec); + yield(); + } + iv->doCalculations(); + notify(mPayload[iv->id].txCmd); + + if(AlarmData == mPayload[iv->id].txCmd) { + uint8_t i = 0; + uint16_t code; + uint32_t start, end; + while(1) { + code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); + if(0 == code) + break; + if (NULL != mCbAlarm) + (mCbAlarm)(code, start, end); + yield(); + } + } + } else { + DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes")); + mStat->rxFail++; + } + + iv->setQueuedCmdFinished(); + } + }*/ + yield(); + } + } + + private: + void notify(uint8_t val) { + if(NULL != mCbMiPayload) + (mCbMiPayload)(val); + } + + bool build(uint8_t id, bool *complete) { + /*DPRINTLN(DBG_VERBOSE, F("build")); + uint16_t crc = 0xffff, crcRcv = 0x0000; + if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES) + mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; + + // check if all fragments are there + *complete = true; + for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { + if(mPayload[id].len[i] == 0) + *complete = false; + } + if(!*complete) + return false; + + for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) { + if (mPayload[id].len[i] > 0) { + if (i == (mPayload[id].maxPackId - 1)) { + crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 2, crc); + crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]); + } else + crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc); + } + yield(); + } + + return (crc == crcRcv) ? true : false;*/ + return true; + } + + void reset(uint8_t id) { + DPRINTLN(DBG_INFO, "resetPayload: id: " + String(id)); + memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES); + /* + mPayload[id].gotFragment = false; + mPayload[id].retransmits = 0; + mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; + mPayload[id].lastFound = false; + mPayload[id].complete = false;*/ + mPayload[id].txCmd = 0; + mPayload[id].requested = false; + mPayload[id].ts = *mTimestamp; + } + + IApp *mApp; + HMSYSTEM *mSys; + statistics_t *mStat; + uint8_t mMaxRetrans; + uint32_t *mTimestamp; + miPayload_t mPayload[MAX_NUM_INVERTERS]; + bool mSerialDebug; + + payloadListenerType mCbMiPayload; +}; + +#endif /*__MI_PAYLOAD_H__*/ diff --git a/src/web/html/index.html b/src/web/html/index.html index 6634197ee..0e71fcf79 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -62,7 +62,7 @@

    Support this project: