diff --git a/patches/AsyncWeb_Prometheus.patch b/patches/AsyncWeb_Prometheus.patch deleted file mode 100644 index 3c7deac4..00000000 --- a/patches/AsyncWeb_Prometheus.patch +++ /dev/null @@ -1,26 +0,0 @@ -diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp -index 6e88da9..09359c3 100644 ---- a/src/AsyncWebSocket.cpp -+++ b/src/AsyncWebSocket.cpp -@@ -827,7 +827,7 @@ void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) - - IPAddress AsyncWebSocketClient::remoteIP() { - if(!_client) { -- return IPAddress((uint32_t)0); -+ return IPAddress(); - } - return _client->remoteIP(); - } -diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp -index a22e991..babef18 100644 ---- a/src/WebResponses.cpp -+++ b/src/WebResponses.cpp -@@ -317,7 +317,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u - free(buf); - return 0; - } -- outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen; -+ outLen = sprintf((char*)buf+headLen, "%04x", readLen) + headLen; - while(outLen < headLen + 4) buf[outLen++] = ' '; - buf[outLen++] = '\r'; - buf[outLen++] = '\n'; diff --git a/patches/espMqttClientSemaphore.patch b/patches/espMqttClientSemaphore.patch new file mode 100644 index 00000000..51376218 --- /dev/null +++ b/patches/espMqttClientSemaphore.patch @@ -0,0 +1,123 @@ +diff --git a/src/Helpers.h b/src/Helpers.h +index 05ab136..50b4c2f 100644 +--- a/src/Helpers.h ++++ b/src/Helpers.h +@@ -1,7 +1,7 @@ + /* + Copyright (c) 2022 Bert Melis. All rights reserved. + +-This work is licensed under the terms of the MIT license. ++This work is licensed under the terms of the MIT license. + For a copy, see or + the LICENSE file. + */ +@@ -13,6 +13,7 @@ the LICENSE file. + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_task_wdt.h" ++ #define EMC_SEMAPHORE_TAKE_CHECK() if(pdTRUE == xSemaphoreTake(_xSemaphore, portMAX_DELAY)) + #define EMC_SEMAPHORE_TAKE() xSemaphoreTake(_xSemaphore, portMAX_DELAY) + #define EMC_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + #define EMC_GET_FREE_MEMORY() std::max(ESP.getMaxAllocHeap(), ESP.getMaxAllocPsram()) +@@ -25,9 +26,11 @@ the LICENSE file. + // _xSemaphore defined as std::atomic + #define EMC_SEMAPHORE_TAKE() while (_xSemaphore) { /*ESP.wdtFeed();*/ } _xSemaphore = true + #define EMC_SEMAPHORE_GIVE() _xSemaphore = false ++ #define EMC_SEMAPHORE_TAKE_CHECK() EMC_SEMAPHORE_TAKE + #else + #define EMC_SEMAPHORE_TAKE() + #define EMC_SEMAPHORE_GIVE() ++ #define EMC_SEMAPHORE_TAKE_CHECK() + #endif + #define EMC_GET_FREE_MEMORY() ESP.getMaxFreeBlockSize() + // no need to yield for ESP8266, the Arduino framework does this internally +diff --git a/src/MqttClient.cpp b/src/MqttClient.cpp +index dc21f74..d524e50 100644 +--- a/src/MqttClient.cpp ++++ b/src/MqttClient.cpp +@@ -1,7 +1,7 @@ + /* + Copyright (c) 2022 Bert Melis. All rights reserved. + +-This work is licensed under the terms of the MIT license. ++This work is licensed under the terms of the MIT license. + For a copy, see or + the LICENSE file. + */ +@@ -148,16 +148,20 @@ uint16_t MqttClient::publish(const char* topic, uint8_t qos, bool retain, const + #endif + return 0; + } +- EMC_SEMAPHORE_TAKE(); +- uint16_t packetId = (qos > 0) ? _getNextPacketId() : 1; +- if (!_addPacket(packetId, topic, payload, length, qos, retain)) { +- emc_log_e("Could not create PUBLISH packet"); ++ uint16_t packetId = 0; ++ EMC_SEMAPHORE_TAKE_CHECK() { ++ packetId = (qos > 0) ? _getNextPacketId() : 1; ++ if (!_addPacket(packetId, topic, payload, length, qos, retain)) { ++ emc_log_e("Could not create PUBLISH packet"); ++ EMC_SEMAPHORE_GIVE(); ++ _onError(packetId, Error::OUT_OF_MEMORY); ++ EMC_SEMAPHORE_TAKE_CHECK() { ++ packetId = 0; ++ } ++ } + EMC_SEMAPHORE_GIVE(); +- _onError(packetId, Error::OUT_OF_MEMORY); +- EMC_SEMAPHORE_TAKE(); +- packetId = 0; ++ yield(); + } +- EMC_SEMAPHORE_GIVE(); + return packetId; + } + +@@ -174,16 +178,20 @@ uint16_t MqttClient::publish(const char* topic, uint8_t qos, bool retain, espMqt + #endif + return 0; + } +- EMC_SEMAPHORE_TAKE(); +- uint16_t packetId = (qos > 0) ? _getNextPacketId() : 1; +- if (!_addPacket(packetId, topic, callback, length, qos, retain)) { +- emc_log_e("Could not create PUBLISH packet"); ++ uint16_t packetId = 0; ++ EMC_SEMAPHORE_TAKE_CHECK() { ++ packetId = (qos > 0) ? _getNextPacketId() : 1; ++ if (!_addPacket(packetId, topic, callback, length, qos, retain)) { ++ emc_log_e("Could not create PUBLISH packet"); ++ EMC_SEMAPHORE_GIVE(); ++ _onError(packetId, Error::OUT_OF_MEMORY); ++ EMC_SEMAPHORE_TAKE_CHECK() { ++ packetId = 0; ++ } ++ } + EMC_SEMAPHORE_GIVE(); +- _onError(packetId, Error::OUT_OF_MEMORY); +- EMC_SEMAPHORE_TAKE(); +- packetId = 0; ++ yield(); + } +- EMC_SEMAPHORE_GIVE(); + return packetId; + } + +@@ -237,11 +245,13 @@ void MqttClient::loop() { + case State::connectingMqtt: + #if EMC_WAIT_FOR_CONNACK + if (_transport->connected()) { +- EMC_SEMAPHORE_TAKE(); +- _sendPacket(); +- _checkIncoming(); +- _checkPing(); +- EMC_SEMAPHORE_GIVE(); ++ EMC_SEMAPHORE_TAKE_CHECK() { ++ _sendPacket(); ++ _checkIncoming(); ++ _checkPing(); ++ EMC_SEMAPHORE_GIVE(); ++ yield(); ++ } + } else { + _setState(State::disconnectingTcp1); + _disconnectReason = DisconnectReason::TCP_DISCONNECTED; diff --git a/scripts/applyPatches.py b/scripts/applyPatches.py index 1672ab2f..dcd3098f 100644 --- a/scripts/applyPatches.py +++ b/scripts/applyPatches.py @@ -25,9 +25,9 @@ def applyPatch(libName, patchFile): os.chdir(start) -# list of patches to apply (relative to /src) -applyPatch("ESPAsyncWebServer-esphome", "../patches/AsyncWeb_Prometheus.patch") +applyPatch("espMqttClient", "../patches/espMqttClientSemaphore.patch") +# list of patches to apply (relative to /src) if (env['PIOENV'][:5] == "esp32") or (env['PIOENV'][:13] == "opendtufusion"): applyPatch("GxEPD2", "../patches/GxEPD2_HAL.patch") diff --git a/scripts/auto_firmware_version.py b/scripts/auto_firmware_version.py index 75bf7379..1a69c8dc 100644 --- a/scripts/auto_firmware_version.py +++ b/scripts/auto_firmware_version.py @@ -2,16 +2,14 @@ # # Copyright (C) 2022 Thomas Basler and others # -import pkg_resources Import("env") -required_pkgs = {'dulwich'} -installed_pkgs = {pkg.key for pkg in pkg_resources.working_set} -missing_pkgs = required_pkgs - installed_pkgs - -if missing_pkgs: +try: + from dulwich import porcelain +except ModuleNotFoundError: env.Execute('"$PYTHONEXE" -m pip install dulwich') + from dulwich import porcelain from dulwich import porcelain diff --git a/src/CHANGES.md b/src/CHANGES.md index 8fd61673..0b333651 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,4 +1,4 @@ -Changelog v0.8.154 +Changelog v0.8.155 * fix display IP in ePaper display (ETH or WiFi, static or DHCP) * fix German translation @@ -16,4 +16,4 @@ Changelog v0.8.154 * increased maximum number of alarms to 50 for ESP32 * updated libraries -full version log: [Development Log](https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md) \ No newline at end of file +full version log: [Development Log](https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md) diff --git a/src/app.cpp b/src/app.cpp index c6a689ee..7c6a8aef 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,7 +13,11 @@ //----------------------------------------------------------------------------- -app::app() : ah::Scheduler {} { +app::app() + : ah::Scheduler {} + , mSunrise {0} + , mSunset {0} +{ memset(mVersion, 0, sizeof(char) * 12); memset(mVersionModules, 0, sizeof(char) * 12); } @@ -51,7 +55,7 @@ void app::setup() { #else mNetwork = static_cast(new AhoyWifi()); #endif - mNetwork->setup(mConfig, &mTimestamp, [this](bool gotIp) { this->onNetwork(gotIp); }, [this](bool gotTime) { this->onNtpUpdate(gotTime); }); + mNetwork->setup(mConfig, [this](bool gotIp) { this->onNetwork(gotIp); }, [this](uint32_t gotTime) { this->onNtpUpdate(gotTime); }); mNetwork->begin(); esp_task_wdt_reset(); @@ -78,7 +82,7 @@ void app::setup() { mMqttEnabled = (mConfig->mqtt.broker[0] > 0); if (mMqttEnabled) { mMqtt.setup(this, &mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp, &mUptime); - mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); + mMqtt.setSubscriptionCb([this](JsonObject obj) { mqttSubRxCb(obj); }); mCommunication.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); }); } #endif @@ -148,101 +152,90 @@ void app::loop(void) { } //----------------------------------------------------------------------------- -void app::onNetwork(bool gotIp) { - mNetworkConnected = gotIp; - if(gotIp) { - ah::Scheduler::resetTicker(); - regularTickers(); //reinstall regular tickers - every(std::bind(&app::tickSend, this), mConfig->inst.sendInterval, "tSend"); - mTickerInstallOnce = true; - mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! - once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); +void app::onNetwork(bool connected) { + mNetworkConnected = connected; + #if defined(ENABLE_MQTT) + if (mMqttEnabled) { + resetTickerByName("mqttS"); + resetTickerByName("mqttM"); + } + #endif + + if(connected) { + mNetwork->updateNtpTime(); + + resetTickerByName("tSend"); + every([this]() { tickSend(); }, mConfig->inst.sendInterval, "tSend"); + + #if defined(ENABLE_MQTT) + if (mMqttEnabled) { + everySec([this]() { mMqtt.tickerSecond(); }, "mqttS"); + everyMin([this]() { mMqtt.tickerMinute(); }, "mqttM"); + } + #endif /*ENABLE_MQTT*/ } } //----------------------------------------------------------------------------- void app::regularTickers(void) { DPRINTLN(DBG_DEBUG, F("regularTickers")); - everySec(std::bind(&WebType::tickSecond, &mWeb), "webSc"); + everySec([this]() { mWeb.tickSecond(); }, "webSc"); everySec([this]() { mProtection->tickSecond(); }, "prot"); - everySec([this]() {mNetwork->tickNetworkLoop(); }, "net"); + everySec([this]() { mNetwork->tickNetworkLoop(); }, "net"); + + if(mConfig->inst.startWithoutTime) + every([this]() { tickSend(); }, mConfig->inst.sendInterval, "tSend"); + - if(mConfig->inst.startWithoutTime && !mNetworkConnected) - every(std::bind(&app::tickSend, this), mConfig->inst.sendInterval, "tSend"); + every([this]() { mNetwork->updateNtpTime(); }, mConfig->ntp.interval * 60, "ntp"); + + if (mConfig->inst.rstValsNotAvail) + everyMin([this]() { tickMinute(); }, "tMin"); // Plugins #if defined(PLUGIN_DISPLAY) if (DISP_TYPE_T0_NONE != mConfig->plugin.display.type) - everySec(std::bind(&DisplayType::tickerSecond, &mDisplay), "disp"); + everySec([this]() { mDisplay.tickerSecond(); }, "disp"); #endif - every(std::bind(&PubSerialType::tick, &mPubSerial), 5, "uart"); + every([this]() { mPubSerial.tick(); }, 5, "uart"); //everySec([this]() { mImprov.tickSerial(); }, "impro"); #if defined(ENABLE_HISTORY) - everySec(std::bind(&HistoryType::tickerSecond, &mHistory), "hist"); + everySec([this]() { mHistory.tickerSecond(); }, "hist"); #endif /*ENABLE_HISTORY*/ #if defined(ENABLE_SIMULATOR) - every(std::bind(&SimulatorType::tick, &mSimulator), 5, "sim"); + every([this]() {mSimulator->tick(); }, 5, "sim"); #endif /*ENABLE_SIMULATOR*/ } //----------------------------------------------------------------------------- -void app::onNtpUpdate(bool gotTime) { - mNtpReceived = true; - if ((0 == mSunrise) && (0.0 != mConfig->sun.lat) && (0.0 != mConfig->sun.lon)) { - mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; - tickCalcSunrise(); - } - - if (mTickerInstallOnce) { - mTickerInstallOnce = false; - #if defined(ENABLE_MQTT) - if (mMqttEnabled) { - mMqtt.tickerSecond(); - everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS"); - everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM"); +void app::onNtpUpdate(uint32_t utcTimestamp) { + if(0 == utcTimestamp) { + // try again in 5s + once([this]() { mNetwork->updateNtpTime(); }, 5, "ntp"); + } else { + mTimestamp = utcTimestamp; + DPRINTLN(DBG_INFO, "[NTP]: " + ah::getDateTimeStr(mTimestamp) + " UTC"); + + uint32_t localTime = gTimezone.toLocal(mTimestamp); + uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time + resetTickerByName("midNi"); + onceAt([this]() { tickMidnight(); }, midTrig, "midNi"); + + if (mConfig->sys.schedReboot) { + uint32_t rebootTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86410); // reboot 10 secs after midnght + resetTickerByName("midRe"); + onceAt([this]() { tickReboot(); }, rebootTrig, "midRe"); } - #endif /*ENABLE_MQTT*/ - - if (mConfig->inst.rstValsNotAvail) - everyMin(std::bind(&app::tickMinute, this), "tMin"); - if(mNtpReceived) { - uint32_t localTime = gTimezone.toLocal(mTimestamp); - uint32_t midTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time - onceAt(std::bind(&app::tickMidnight, this), midTrig, "midNi"); - - if (mConfig->sys.schedReboot) { - uint32_t rebootTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86410); // reboot 10 secs after midnght - onceAt(std::bind(&app::tickReboot, this), rebootTrig, "midRe"); - } + if ((0 == mSunrise) && (0.0 != mConfig->sun.lat) && (0.0 != mConfig->sun.lon)) { + mCalculatedTimezoneOffset = (int8_t)((mConfig->sun.lon >= 0 ? mConfig->sun.lon + 7.5 : mConfig->sun.lon - 7.5) / 15) * 3600; + tickCalcSunrise(); } } } -//----------------------------------------------------------------------------- -void app::updateNtp(void) { - if(mNtpReceived) - onNtpUpdate(true); -} - -//----------------------------------------------------------------------------- -void app::tickNtpUpdate(void) { - uint32_t nxtTrig = 5; // default: check again in 5 sec - - if (!mNtpReceived) - mNetwork->updateNtpTime(); - else { - nxtTrig = mConfig->ntp.interval * 60; // check again in configured interval - mNtpReceived = false; - } - - updateNtp(); - - once(std::bind(&app::tickNtpUpdate, this), nxtTrig, "ntp"); -} - //----------------------------------------------------------------------------- void app::tickCalcSunrise(void) { if (mSunrise == 0) // on boot/reboot calc sun values for current time @@ -254,11 +247,11 @@ void app::tickCalcSunrise(void) { tickIVCommunication(); uint32_t nxtTrig = mSunset + mConfig->sun.offsetSecEvening + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop - onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig, "Sunri"); + onceAt([this]() { tickCalcSunrise(); }, nxtTrig, "Sunri"); if (mMqttEnabled) { tickSun(); nxtTrig = mSunrise + mConfig->sun.offsetSecMorning + 1; // one second safety to trigger correctly - onceAt(std::bind(&app::tickSunrise, this), nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' + onceAt([this]() { tickSunrise(); }, nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' } } @@ -296,10 +289,10 @@ void app::tickIVCommunication(void) { } if(restartTick) // at least one inverter - onceAt(std::bind(&app::tickIVCommunication, this), nxtTrig, "ivCom"); + onceAt([this]() { tickIVCommunication(); }, nxtTrig, "ivCom"); if (zeroValues) // at least one inverter - once(std::bind(&app::tickZeroValues, this), mConfig->inst.sendInterval, "tZero"); + once([this]() { tickZeroValues(); }, mConfig->inst.sendInterval, "tZero"); } //----------------------------------------------------------------------------- @@ -307,7 +300,7 @@ void app::tickSun(void) { // only used and enabled by MQTT (see setup()) #if defined(ENABLE_MQTT) if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening)) - once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry + once([this]() { tickSun(); }, 1, "mqSun"); // MQTT not connected, retry #endif } @@ -316,7 +309,7 @@ void app::tickSunrise(void) { // only used and enabled by MQTT (see setup()) #if defined(ENABLE_MQTT) if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening, true)) - once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry + once([this]() { tickSun(); }, 1, "mqSun"); // MQTT not connected, retry #endif } @@ -344,7 +337,8 @@ void app::tickMinute(void) { void app::tickMidnight(void) { uint32_t localTime = gTimezone.toLocal(mTimestamp); uint32_t nxtTrig = gTimezone.toUTC(localTime - (localTime % 86400) + 86400); // next midnight local time - onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2"); + resetTickerByName("midNi"); + onceAt([this]() { tickMidnight(); }, nxtTrig, "midNi"); Inverter<> *iv; for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { @@ -387,7 +381,7 @@ void app::tickSend(void) { } if(mAllIvNotAvail != notAvail) - once(std::bind(&app::notAvailChanged, this), 1, "avail"); + once([this]() { notAvailChanged(); }, 1, "avail"); mAllIvNotAvail = notAvail; updateLed(); @@ -524,9 +518,6 @@ void app::resetSystem(void) { mAllIvNotAvail = true; - mSunrise = 0; - mSunset = 0; - mMqttEnabled = false; mSendLastIvId = 0; @@ -535,8 +526,6 @@ void app::resetSystem(void) { mSaveReboot = false; mNetworkConnected = false; - mNtpReceived = false; - mTickerInstallOnce = false; } //----------------------------------------------------------------------------- diff --git a/src/app.h b/src/app.h index d23f307e..58101a59 100644 --- a/src/app.h +++ b/src/app.h @@ -89,7 +89,7 @@ class app : public IApp, public ah::Scheduler { void setup(void); void loop(void) override; - void onNetwork(bool gotIp); + void onNetwork(bool connected); void regularTickers(void); void handleIntr(void) { @@ -195,6 +195,10 @@ class app : public IApp, public ah::Scheduler { return mNetwork->isApActive(); } + bool isNetworkConnected() override { + return mNetwork->isConnected(); + } + void setRebootFlag() override { once(std::bind(&app::tickReboot, this), 3, "rboot"); } @@ -287,6 +291,29 @@ class app : public IApp, public ah::Scheduler { return mConfig->cmt.enabled; } + bool cmtSearch(uint8_t id, uint8_t toCh) override { + #if defined(ESP32) + Inverter<> *iv; + + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { + iv = mSys.getInverterByPos(i, true); + if(nullptr != iv) { + if(i == id) + break; + else + iv = nullptr; + } + } + + if(nullptr != iv) { + mCmtRadio.catchInverter(iv, toCh); + return true; + } + #endif + + return false; + } + uint8_t getNrfIrqPin(void) { return mConfig->nrf.pinIrq; } @@ -403,10 +430,7 @@ class app : public IApp, public ah::Scheduler { setRebootFlag(); } - void tickNtpUpdate(void); - void onNtpUpdate(bool gotTime); - bool mNtpReceived = false; - void updateNtp(void); + void onNtpUpdate(uint32_t utcTimestamp); void triggerTickSend(uint8_t id) override { once([this, id]() { @@ -461,7 +485,6 @@ class app : public IApp, public ah::Scheduler { #if defined(ENABLE_MQTT) PubMqttType mMqtt; #endif - bool mTickerInstallOnce = false; bool mMqttEnabled = false; // sun diff --git a/src/appInterface.h b/src/appInterface.h index d49f907e..e03bc84f 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -34,6 +34,7 @@ class IApp { virtual String getIp(void) = 0; virtual String getMac(void) = 0; virtual bool isApActive(void) = 0; + virtual bool isNetworkConnected() = 0; virtual uint32_t getUptime() = 0; virtual uint32_t getTimestamp() = 0; @@ -55,6 +56,8 @@ class IApp { virtual bool getNrfEnabled() = 0; virtual bool getCmtEnabled() = 0; + virtual bool cmtSearch(uint8_t id, uint8_t toCh) = 0; + virtual uint32_t getMqttRxCnt() = 0; virtual uint32_t getMqttTxCnt() = 0; diff --git a/src/defines.h b/src/defines.h index afc168cf..65104df2 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 154 +#define VERSION_PATCH 155 //------------------------------------- typedef struct { uint8_t ch; diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index bf6f6861..8f11c31c 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -11,36 +11,133 @@ #include "hmInverter.h" #include "../utils/dbg.h" -#define DEFAULT_ATTEMPS 5 -#define MORE_ATTEMPS_ALARMDATA 3 // 8 -#define MORE_ATTEMPS_GRIDONPROFILEPARA 0 // 5 - +#if !defined(ESP32) + #if !defined(vSemaphoreDelete) + #define vSemaphoreDelete(a) + #define xSemaphoreTake(a, b) { while(a) { yield(); } a = true; } + #define xSemaphoreGive(a) { a = false; } + #endif +#endif + +#if defined(CONFIG_IDF_TARGET_ESP32S3) +template +#else template +#endif class CommQueue { + protected: /* types */ + static constexpr uint8_t DefaultAttempts = 5; + static constexpr uint8_t MoreAttemptsAlarmData = 3; + static constexpr uint8_t MoreAttemptsGridProfile = 0; + + protected: + struct QueueElement { + Inverter<> *iv; + uint8_t cmd; + uint8_t attempts; + uint8_t attemptsMax; + uint32_t ts; + bool isDevControl; + + QueueElement() + : iv {nullptr} + , cmd {0} + , attempts {0} + , attemptsMax {0} + , ts {0} + , isDevControl {false} + {} + + QueueElement(Inverter<> *iv, uint8_t cmd, bool devCtrl) + : iv {iv} + , cmd {cmd} + , attempts {DefaultAttempts} + , attemptsMax {DefaultAttempts} + , ts {0} + , isDevControl {devCtrl} + {} + + QueueElement(const QueueElement&) = delete; + + QueueElement(QueueElement&& other) : QueueElement{} { + this->swap(other); + } + + void changeCmd(uint8_t cmd) { + this->cmd = cmd; + this->isDevControl = false; + } + + void setTs(const uint32_t ts) { + this->ts = ts; + } + + void setAttempt() { + if(this->attempts) + this->attempts--; + } + + void incrAttempt(uint8_t attempts = 1) { + this->attempts += attempts; + if (this->attempts > this->attemptsMax) + this->attemptsMax = this->attempts; + } + + QueueElement& operator=(const QueueElement&) = delete; + + QueueElement& operator = (QueueElement&& other) { + this->swap(other); + return *this; + } + + void swap(QueueElement& other) { + std::swap(this->iv, other.iv); + std::swap(this->cmd, other.cmd); + std::swap(this->attempts, other.attempts); + std::swap(this->attemptsMax, other.attemptsMax); + std::swap(this->ts, other.ts); + std::swap(this->isDevControl, other.isDevControl); + } + }; + public: + CommQueue() + : wrPtr {0} + , rdPtr {0} + { + #if defined(ESP32) + this->mutex = xSemaphoreCreateBinaryStatic(&this->mutex_buffer); + xSemaphoreGive(this->mutex); + #endif + } + + ~CommQueue() { + vSemaphoreDelete(this->mutex); + } + void addImportant(Inverter<> *iv, uint8_t cmd) { - queue_s q(iv, cmd, true); + QueueElement q(iv, cmd, true); + xSemaphoreTake(this->mutex, portMAX_DELAY); if(!isIncluded(&q)) { - dec(&mRdPtr); - mQueue[mRdPtr] = q; + dec(&this->rdPtr); + mQueue[this->rdPtr] = std::move(q); } + xSemaphoreGive(this->mutex); } void add(Inverter<> *iv, uint8_t cmd) { - queue_s q(iv, cmd, false); + QueueElement q(iv, cmd, false); + xSemaphoreTake(this->mutex, portMAX_DELAY); if(!isIncluded(&q)) { - mQueue[mWrPtr] = q; - inc(&mWrPtr); + mQueue[this->wrPtr] = std::move(q); + inc(&this->wrPtr); } - } - - void chgCmd(Inverter<> *iv, uint8_t cmd) { - mQueue[mWrPtr] = queue_s(iv, cmd, false); + xSemaphoreGive(this->mutex); } uint8_t getFillState(void) const { - //DPRINTLN(DBG_INFO, "wr: " + String(mWrPtr) + ", rd: " + String(mRdPtr)); - return abs(mRdPtr - mWrPtr); + //DPRINTLN(DBG_INFO, "wr: " + String(this->wrPtr) + ", rd: " + String(this->rdPtr)); + return abs(this->rdPtr - this->wrPtr); } uint8_t getMaxFill(void) const { @@ -48,70 +145,44 @@ class CommQueue { } protected: - struct queue_s { - Inverter<> *iv; - uint8_t cmd; - uint8_t attempts; - uint8_t attemptsMax; - uint32_t ts; - bool isDevControl; - queue_s() {} - queue_s(Inverter<> *i, uint8_t c, bool dev) : - iv(i), cmd(c), attempts(DEFAULT_ATTEMPS), attemptsMax(DEFAULT_ATTEMPS), ts(0), isDevControl(dev) {} - }; - - protected: - void add(queue_s q) { - mQueue[mWrPtr] = q; - inc(&mWrPtr); + void add(QueueElement q) { + xSemaphoreTake(this->mutex, portMAX_DELAY); + mQueue[this->wrPtr] = q; + inc(&this->wrPtr); + xSemaphoreGive(this->mutex); } - void add(const queue_s *q, bool rstAttempts = false) { - mQueue[mWrPtr] = *q; + void add(QueueElement *q, bool rstAttempts = false) { + xSemaphoreTake(this->mutex, portMAX_DELAY); if(rstAttempts) { - mQueue[mWrPtr].attempts = DEFAULT_ATTEMPS; - mQueue[mWrPtr].attemptsMax = DEFAULT_ATTEMPS; - } - inc(&mWrPtr); - } - - void chgCmd(uint8_t cmd) { - mQueue[mRdPtr].cmd = cmd; - mQueue[mRdPtr].isDevControl = false; - } - - void get(std::function cb) { - if(mRdPtr == mWrPtr) { - cb(false, &mQueue[mRdPtr]); // empty - return; + q->attempts = DefaultAttempts; + q->attemptsMax = DefaultAttempts; } - cb(true, &mQueue[mRdPtr]); - } - - void cmdDone(bool keep = false) { - if(keep) { - mQueue[mRdPtr].attempts = DEFAULT_ATTEMPS; - mQueue[mRdPtr].attemptsMax = DEFAULT_ATTEMPS; - add(mQueue[mRdPtr]); // add to the end again + mQueue[this->wrPtr] = std::move(*q); + inc(&this->wrPtr); + xSemaphoreGive(this->mutex); + } + + void get(std::function cb) { + xSemaphoreTake(this->mutex, portMAX_DELAY); + if(this->rdPtr == this->wrPtr) { + xSemaphoreGive(this->mutex); + cb(false, nullptr); // empty + } else { + QueueElement el = std::move(mQueue[this->rdPtr]); + inc(&this->rdPtr); + xSemaphoreGive(this->mutex); + cb(true, &el); } - inc(&mRdPtr); - } - - void setTs(const uint32_t *ts) { - mQueue[mRdPtr].ts = *ts; - } - - void setAttempt(void) { - if(mQueue[mRdPtr].attempts) - mQueue[mRdPtr].attempts--; } - void incrAttempt(uint8_t attempts = 1) { - mQueue[mRdPtr].attempts += attempts; - if (mQueue[mRdPtr].attempts > mQueue[mRdPtr].attemptsMax) - mQueue[mRdPtr].attemptsMax = mQueue[mRdPtr].attempts; + void cmdReset(QueueElement *q) { + q->attempts = DefaultAttempts; + q->attemptsMax = DefaultAttempts; + add(q); // add to the end again } + private: void inc(uint8_t *ptr) { if(++(*ptr) >= N) *ptr = 0; @@ -123,13 +194,14 @@ class CommQueue { --(*ptr); } - private: - bool isIncluded(const queue_s *q) { - uint8_t ptr = mRdPtr; - while (ptr != mWrPtr) { + bool isIncluded(const QueueElement *q) { + uint8_t ptr = this->rdPtr; + while (ptr != this->wrPtr) { if(mQueue[ptr].cmd == q->cmd) { - if(mQueue[ptr].iv->id == q->iv->id) - return true; + if(mQueue[ptr].iv->id == q->iv->id) { + if(mQueue[ptr].isDevControl == q->isDevControl) + return true; + } } inc(&ptr); } @@ -137,9 +209,17 @@ class CommQueue { } protected: - std::array mQueue; - uint8_t mWrPtr = 0; - uint8_t mRdPtr = 0; + std::array mQueue; + + private: + uint8_t wrPtr; + uint8_t rdPtr; + #if defined(ESP32) + SemaphoreHandle_t mutex; + StaticSemaphore_t mutex_buffer; + #else + bool mutex; + #endif }; diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 3192c513..6290fc31 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -21,6 +21,12 @@ typedef std::function *)> alarmListenerType; class Communication : public CommQueue<> { public: + Communication() + : CommQueue() + {} + + ~Communication() {} + void setup(uint32_t *timestamp, bool *serialDebug, bool *privacyMode, bool *printWholeTrace) { mTimestamp = timestamp; mPrivacyMode = privacyMode; @@ -28,11 +34,6 @@ class Communication : public CommQueue<> { mPrintWholeTrace = printWholeTrace; } - void addImportant(Inverter<> *iv, uint8_t cmd) { - mState = States::RESET; // cancel current operation - CommQueue::addImportant(iv, cmd); - } - void addPayloadListener(payloadListenerType cb) { mCbPayload = cb; } @@ -46,29 +47,39 @@ class Communication : public CommQueue<> { } void loop() { - get([this](bool valid, const queue_s *q) { - if(!valid) { - if(mPrintSequenceDuration) { - mPrintSequenceDuration = false; - DPRINT(DBG_INFO, F("com loop duration: ")); - DBGPRINT(String(millis() - mLastEmptyQueueMillis)); - DBGPRINTLN(F("ms")); - DBGPRINTLN(F("-----")); + if(States::IDLE == mState) { + get([this](bool valid, QueueElement *q) { + if(!valid) { + if(mPrintSequenceDuration) { + mPrintSequenceDuration = false; + DPRINT(DBG_INFO, F("com loop duration: ")); + DBGPRINT(String(millis() - mLastEmptyQueueMillis)); + DBGPRINTLN(F("ms")); + DBGPRINTLN(F("-----")); + } + return; // empty } - return; // empty - } - if(!mPrintSequenceDuration) // entry was added to the queue - mLastEmptyQueueMillis = millis(); - mPrintSequenceDuration = true; - innerLoop(q); - }); + el = std::move(*q); + mState = States::INIT; + if(!mPrintSequenceDuration) // entry was added to the queue + mLastEmptyQueueMillis = millis(); + mPrintSequenceDuration = true; + }); + } + + if(nullptr != el.iv) + innerLoop(&el); } private: - inline void innerLoop(const queue_s *q) { + inline void innerLoop(QueueElement *q) { switch(mState) { - case States::RESET: + default: + case States::IDLE: + break; + + case States::INIT: if (!mWaitTime.isTimeout()) return; @@ -85,21 +96,20 @@ class Communication : public CommQueue<> { q->iv->curFrmCnt = 0; q->iv->radioStatistics.txCnt++; mIsRetransmit = false; - if(NULL == q->iv->radio) - cmdDone(false); // can't communicate while radio is not defined! mFirstTry = (INV_RADIO_TYPE_NRF == q->iv->ivRadioType) && (q->iv->isAvailable()); q->iv->mCmd = q->cmd; q->iv->mIsSingleframeReq = false; mFramesExpected = getFramesExpected(q); // function to get expected frame count. mTimeout = DURATION_TXFRAME + mFramesExpected*DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]; if((q->iv->ivGen == IV_MI) && ((q->cmd == MI_REQ_CH1) || (q->cmd == MI_REQ_4CH))) - incrAttempt(q->iv->channels); // 2 more attempts for 2ch, 4 more for 4ch + q->incrAttempt(q->iv->channels); // 2 more attempts for 2ch, 4 more for 4ch - mState = States::START; + if(NULL != q->iv->radio) + mState = States::START; break; case States::START: - setTs(mTimestamp); + q->setTs(*mTimestamp); if(INV_RADIO_TYPE_CMT == q->iv->ivRadioType) { // frequency was changed during runtime if(q->iv->curCmtFreq != q->iv->config->frequency) { @@ -118,10 +128,10 @@ class Communication : public CommQueue<> { //q->iv->radioStatistics.txCnt++; q->iv->radio->mRadioWaitTime.startTimeMonitor(mTimeout); if((!mIsRetransmit && (q->cmd == AlarmData)) || (q->cmd == GridOnProFilePara)) - incrAttempt((q->cmd == AlarmData)? MORE_ATTEMPS_ALARMDATA : MORE_ATTEMPS_GRIDONPROFILEPARA); + q->incrAttempt((q->cmd == AlarmData)? CommQueue::MoreAttemptsAlarmData : CommQueue::MoreAttemptsGridProfile); mIsRetransmit = false; - setAttempt(); + q->setAttempt(); mState = States::WAIT; break; @@ -182,17 +192,17 @@ class Communication : public CommQueue<> { q->iv->mDtuRxCnt++; if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command - if(parseFrame(p)) { + if(parseFrame(q, p)) { q->iv->curFrmCnt++; if(!mIsRetransmit && ((p->packet[9] == 0x02) || (p->packet[9] == 0x82)) && (p->millis < LIMIT_FAST_IV)) mHeu.setIvRetriesGood(q->iv,p->millis < LIMIT_VERYFAST_IV); } } else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command + q->iv->radio->mBufCtrl.pop(); if(parseDevCtrl(p, q)) closeRequest(q, true); else closeRequest(q, false); - q->iv->radio->mBufCtrl.pop(); return; // don't wait for empty buffer } else if(IV_MI == q->iv->ivGen) { parseMiFrame(p, q); @@ -278,12 +288,12 @@ class Communication : public CommQueue<> { q->iv->radioStatistics.txCnt--; q->iv->radioStatistics.retransmits++; mCompleteRetry = true; - mState = States::RESET; + mState = States::IDLE; return; } } - setAttempt(); + q->setAttempt(); if(*mSerialDebug) { DPRINT_IVID(DBG_WARN, q->iv->id); @@ -312,7 +322,7 @@ class Communication : public CommQueue<> { } } - inline void printRxInfo(const queue_s *q, packet_t *p) { + inline void printRxInfo(QueueElement *q, packet_t *p) { DPRINT_IVID(DBG_INFO, q->iv->id); DBGPRINT(F("RX ")); if(p->millis < 100) @@ -346,7 +356,7 @@ class Communication : public CommQueue<> { } - inline uint8_t getFramesExpected(const queue_s *q) { + inline uint8_t getFramesExpected(QueueElement *q) { if(q->isDevControl) return 1; @@ -410,7 +420,7 @@ class Communication : public CommQueue<> { return (ah::crc8(buf, len - 1) == buf[len-1]); } - inline bool parseFrame(packet_t *p) { + inline bool parseFrame(QueueElement *q, packet_t *p) { uint8_t *frameId = &p->packet[9]; if(0x00 == *frameId) { DPRINTLN(DBG_WARN, F("invalid frameId 0x00")); @@ -429,7 +439,7 @@ class Communication : public CommQueue<> { if((*frameId & ALL_FRAMES) == ALL_FRAMES) { mMaxFrameId = (*frameId & 0x7f); if(mMaxFrameId > 8) // large payloads, e.g. AlarmData - incrAttempt(mMaxFrameId - 6); + q->incrAttempt(mMaxFrameId - 6); } frame_t *f = &mLocalBuf[(*frameId & 0x7f) - 1]; @@ -440,7 +450,7 @@ class Communication : public CommQueue<> { return true; } - inline void parseMiFrame(packet_t *p, const queue_s *q) { + inline void parseMiFrame(packet_t *p, QueueElement *q) { if((!mIsRetransmit && p->packet[9] == 0x00) && (p->millis < LIMIT_FAST_IV_MI)) //first frame is fast? mHeu.setIvRetriesGood(q->iv,p->millis < LIMIT_VERYFAST_IV_MI); if ((p->packet[0] == MI_REQ_CH1 + ALL_FRAMES) @@ -464,7 +474,7 @@ class Communication : public CommQueue<> { } } - inline bool parseDevCtrl(const packet_t *p, const queue_s *q) { + inline bool parseDevCtrl(const packet_t *p, QueueElement *q) { switch(p->packet[12]) { case ActivePowerContr: if(p->packet[13] != 0x00) @@ -502,7 +512,7 @@ class Communication : public CommQueue<> { return accepted; } - inline bool compilePayload(const queue_s *q) { + inline bool compilePayload(QueueElement *q) { uint16_t crc = 0xffff, crcRcv = 0x0000; for(uint8_t i = 0; i < mMaxFrameId; i++) { if(i == (mMaxFrameId - 1)) { @@ -522,7 +532,7 @@ class Communication : public CommQueue<> { } else DBGPRINTLN(F("-> complete retransmit")); mCompleteRetry = true; - mState = States::RESET; + mState = States::IDLE; return false; } @@ -602,7 +612,7 @@ class Communication : public CommQueue<> { return true; } - void sendRetransmit(const queue_s *q, uint8_t i) { + void sendRetransmit(QueueElement *q, uint8_t i) { mFramesExpected = 1; q->iv->radio->setExpectedFrames(mFramesExpected); q->iv->radio->sendCmdPacket(q->iv, TX_REQ_INFO, (SINGLE_FRAME + i), true); @@ -613,7 +623,7 @@ class Communication : public CommQueue<> { } private: - void closeRequest(const queue_s *q, bool crcPass) { + void closeRequest(QueueElement *q, bool crcPass) { mHeu.evalTxChQuality(q->iv, crcPass, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); if(crcPass) q->iv->radioStatistics.rxSuccess++; @@ -627,17 +637,20 @@ class Communication : public CommQueue<> { if(q->isDevControl) keep = !crcPass; - cmdDone(keep); q->iv->mGotFragment = false; q->iv->mGotLastMsg = false; q->iv->miMultiParts = 0; + + if(keep) + cmdReset(q); // q will be zero'ed after that command + mIsRetransmit = false; mCompleteRetry = false; - mState = States::RESET; + mState = States::IDLE; DBGPRINTLN(F("-----")); } - inline void miHwDecode(packet_t *p, const queue_s *q) { + inline void miHwDecode(packet_t *p, QueueElement *q) { record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_All); // choose the record structure rec->ts = q->ts; /* @@ -751,7 +764,7 @@ class Communication : public CommQueue<> { } } - inline void miGPFDecode(packet_t *p, const queue_s *q) { + inline void miGPFDecode(packet_t *p, QueueElement *q) { record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure rec->ts = q->ts; rec->mqttSentStatus = MqttSentStatus::NEW_DATA; @@ -777,10 +790,10 @@ class Communication : public CommQueue<> { q->iv->miMultiParts = 7; // indicate we are ready } - inline void miDataDecode(packet_t *p, const queue_s *q) { + inline void miDataDecode(packet_t *p, QueueElement *q) { record_t<> *rec = q->iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser rec->ts = q->ts; - //mState = States::RESET; + //mState = States::IDLE; if(q->iv->miMultiParts < 6) q->iv->miMultiParts += 6; @@ -830,7 +843,7 @@ class Communication : public CommQueue<> { q->iv->miMultiParts += 6; // indicate we are ready } - void miNextRequest(uint8_t cmd, const queue_s *q) { + void miNextRequest(uint8_t cmd, QueueElement *q) { mHeu.evalTxChQuality(q->iv, true, (q->attemptsMax - 1 - q->attempts), q->iv->curFrmCnt); mHeu.getTxCh(q->iv); q->iv->radioStatistics.ivSent++; @@ -850,12 +863,12 @@ class Communication : public CommQueue<> { DBGHEXLN(cmd); } mIsRetransmit = true; - chgCmd(cmd); + q->changeCmd(cmd); //mState = States::WAIT; } - void miRepeatRequest(const queue_s *q) { - setAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types... + void miRepeatRequest(QueueElement *q) { + q->setAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types... q->iv->radio->sendCmdPacket(q->iv, q->cmd, 0x00, true); q->iv->radioStatistics.retransmits++; q->iv->radio->mRadioWaitTime.startTimeMonitor(DURATION_TXFRAME + DURATION_ONEFRAME + duration_reserve[q->iv->ivRadioType]); @@ -869,7 +882,7 @@ class Communication : public CommQueue<> { //mIsRetransmit = false; } - void miStsConsolidate(const queue_s *q, uint8_t stschan, record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) { + void miStsConsolidate(QueueElement *q, uint8_t stschan, record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) { //uint8_t status = (p->packet[11] << 8) + p->packet[12]; uint16_t statusMi = 3; // regular status for MI, change to 1 later? if ( uState == 2 ) { @@ -1014,7 +1027,7 @@ class Communication : public CommQueue<> { private: enum class States : uint8_t { - RESET, START, WAIT, CHECK_FRAMES, CHECK_PACKAGE + IDLE, INIT, START, WAIT, CHECK_FRAMES, CHECK_PACKAGE }; typedef struct { @@ -1024,8 +1037,9 @@ class Communication : public CommQueue<> { } frame_t; private: - States mState = States::RESET; + States mState = States::IDLE; uint32_t *mTimestamp = nullptr; + QueueElement el; bool *mPrivacyMode = nullptr, *mSerialDebug = nullptr, *mPrintWholeTrace = nullptr; TimeMonitor mWaitTime = TimeMonitor(0, true); // start as expired (due to code in RESET state) std::array mLocalBuf; diff --git a/src/hm/Radio.h b/src/hm/Radio.h index 12e80850..f6a9b8d3 100644 --- a/src/hm/Radio.h +++ b/src/hm/Radio.h @@ -29,6 +29,7 @@ class Radio { virtual void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit) = 0; virtual bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) { return true; } virtual bool switchFrequencyCh(Inverter<> *iv, uint8_t fromCh, uint8_t toCh) { return true; } + virtual void catchInverter(Inverter<> *iv, uint8_t toCh) {} virtual bool isChipConnected(void) const { return false; } virtual uint16_t getBaseFreqMhz() { return 0; } virtual uint16_t getBootFreqMhz() { return 0; } diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 026bf6d8..d74fdc6d 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -116,6 +116,13 @@ const calcFunc_t calcFunctions[] = { template class Inverter { + public: /*types*/ + #if defined(ESP32) + constexpr static uint8_t MaxAlarmNum = 50; + #else + constexpr static uint8_t MaxAlarmNum = 10; + #endif + public: uint8_t ivGen = IV_UNKNOWN; // generation of inverter (HM / MI) uint8_t ivRadioType = INV_RADIO_TYPE_UNKNOWN; // refers to used radio (nRF24 / CMT) @@ -135,7 +142,7 @@ class Inverter { record_t recordConfig; // structure for system config values record_t recordAlarm; // structure for alarm values InverterStatus status = InverterStatus::OFF; // indicates the current inverter status - std::array lastAlarm; // holds last 10 alarms + std::array lastAlarm; // holds last x alarms int8_t rssi = 0; // RSSI uint16_t alarmCnt = 0; // counts the total number of occurred alarms uint16_t alarmLastId = 0; // lastId which was received @@ -822,9 +829,9 @@ class Inverter { if(start > end) end = 0; - for(; i < 10; i++) { + for(; i < MaxAlarmNum; i++) { ++mAlarmNxtWrPos; - mAlarmNxtWrPos = mAlarmNxtWrPos % 10; + mAlarmNxtWrPos = mAlarmNxtWrPos % MaxAlarmNum; if(lastAlarm[mAlarmNxtWrPos].code == code && lastAlarm[mAlarmNxtWrPos].start == start) { // replace with same or update end time @@ -834,11 +841,11 @@ class Inverter { } } - if(alarmCnt < 10 && alarmCnt <= mAlarmNxtWrPos) + if(alarmCnt < MaxAlarmNum && alarmCnt <= mAlarmNxtWrPos) alarmCnt = mAlarmNxtWrPos + 1; lastAlarm[mAlarmNxtWrPos] = alarm_t(code, start, end); - if(++mAlarmNxtWrPos >= 10) // rolling buffer + if(++mAlarmNxtWrPos >= MaxAlarmNum) // rolling buffer mAlarmNxtWrPos = 0; } diff --git a/src/hms/CmtRadio.h b/src/hms/CmtRadio.h index b6405329..a5684a36 100644 --- a/src/hms/CmtRadio.h +++ b/src/hms/CmtRadio.h @@ -35,6 +35,11 @@ class CmtRadio : public Radio { return; mCmt.loop(); + if(nullptr != mCatchIv) { + if(mCmt.isTxReady()) + catchInverterLoop(); + } + if((!mIrqRcvd) && (!mRqstGetRx)) return; getRx(); @@ -53,7 +58,8 @@ class CmtRadio : public Radio { if(!mCfg->enabled) return; - DPRINT(DBG_INFO, F("sendControlPacket cmd: ")); + DPRINT_IVID(DBG_INFO, iv->id); + DBGPRINT(F("sendControlPacket cmd: ")); DBGHEXLN(cmd); initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME); uint8_t cnt = 10; @@ -92,6 +98,28 @@ class CmtRadio : public Radio { return true; } + void catchInverter(Inverter<> *iv, uint8_t toCh) override { + if(!isChipConnected()) + return; + + mCatchIv = iv; + mCatchIvCh = 1; + mCatchIvToCh = toCh; + + mCmt.switchChannel(0); + sendSwitchChCmd(iv, toCh); + } + + void catchInverterLoop() { + mCmt.switchChannel(mCatchIvCh); + sendSwitchChCmd(mCatchIv, mCatchIvToCh); + + if(++mCatchIvCh == 0x29) { + mCmt.switchChannel(mCatchIvToCh); + mCatchIv = nullptr; + } + } + uint16_t getBaseFreqMhz(void) override { return mCmt.getBaseFreqMhz(); } @@ -167,10 +195,6 @@ class CmtRadio : public Radio { } inline void sendSwitchChCmd(Inverter<> *iv, uint8_t ch) { - //if(CMT_SWITCH_CHANNEL_CYCLE > ++mSwitchCycle) - // return; - //mSwitchCycle = 0; - /** ch: * 0x00: 860.00 MHz * 0x01: 860.25 MHz @@ -193,7 +217,6 @@ class CmtRadio : public Radio { packet_t p; p.millis = millis() - mMillis; if(CmtStatus::SUCCESS == mCmt.getRx(p.packet, &p.len, 28, &p.rssi)) { - //mSwitchCycle = 0; p.ch = 0; // not used for CMT inverters mBufCtrl.push(p); } @@ -209,7 +232,10 @@ class CmtRadio : public Radio { bool mCmtAvail = false; bool mRqstGetRx = false; uint32_t mMillis = 0; - //uint8_t mSwitchCycle = 0; + + Inverter<> *mCatchIv = nullptr; + uint8_t mCatchIvCh = 0; + uint8_t mCatchIvToCh = 0; }; #endif /*__HMS_RADIO_H__*/ diff --git a/src/hms/cmt2300a.h b/src/hms/cmt2300a.h index ed3aab54..ece6fbc3 100644 --- a/src/hms/cmt2300a.h +++ b/src/hms/cmt2300a.h @@ -168,8 +168,13 @@ enum class CmtStatus : uint8_t { #define CMT2300A_MASK_PKT_OK_FLG 0x01 class Cmt2300a { + private: /*types*/ + static constexpr uint8_t CmtTimeoutMs = 40; + public: - Cmt2300a() {} + Cmt2300a() + : lastMillis {0} + {} void setup(uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb) { mSpi.init(pinSdio, pinSclk, pinCsb, pinFcsb); @@ -182,12 +187,17 @@ class Cmt2300a { if(CMT2300A_MASK_TX_DONE_FLG == mSpi.readReg(CMT2300A_CUS_INT_CLR1)) { if(cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY)) { mTxPending = false; + lastMillis = 0; goRx(); } } } } + bool isTxReady() { + return !mTxPending; + } + CmtStatus goRx(void) { if(mTxPending) return CmtStatus::ERR_TX_PENDING; @@ -222,9 +232,6 @@ class Cmt2300a { } CmtStatus getRx(uint8_t buf[], uint8_t *rxLen, uint8_t maxlen, int8_t *rssi) { - if(mTxPending) - return CmtStatus::ERR_TX_PENDING; - if(0x1b != (mSpi.readReg(CMT2300A_CUS_INT_FLAG) & 0x1b)) return CmtStatus::FIFO_EMPTY; @@ -248,8 +255,19 @@ class Cmt2300a { } CmtStatus tx(uint8_t buf[], uint8_t len) { - if(mTxPending) - return CmtStatus::ERR_TX_PENDING; + if(mTxPending) { + if(CmtTimeoutMs < (millis() - lastMillis)) { + DPRINT(DBG_ERROR, "CMT, last TX timeout: "); + DBGPRINT(String(millis() - lastMillis)); + DBGPRINTLN("ms"); + } + + while(mTxPending && (CmtTimeoutMs > (millis() - lastMillis))) { + vTaskDelay(10); + } + mTxPending = false; + goRx(); + } if(mInRxMode) { mInRxMode = false; @@ -280,6 +298,7 @@ class Cmt2300a { if(!cmtSwitchStatus(CMT2300A_GO_TX, CMT2300A_STA_TX)) return CmtStatus::ERR_SWITCH_STATE; + lastMillis = millis(); // wait for tx done mTxPending = true; @@ -556,6 +575,8 @@ class Cmt2300a { uint8_t mCusIntFlag = 0; uint8_t mRqstCh = 0, mCurCh = 0; RegionCfg mRegionCfg = RegionCfg::EUROPE; + + uint32_t lastMillis; }; #endif /*__CMT2300A_H__*/ diff --git a/src/network/AhoyNetwork.h b/src/network/AhoyNetwork.h index e64a13b1..1945fbb4 100644 --- a/src/network/AhoyNetwork.h +++ b/src/network/AhoyNetwork.h @@ -11,21 +11,22 @@ #include "../utils/helper.h" #include "AhoyWifiAp.h" #include "AsyncJson.h" +#include #define NTP_PACKET_SIZE 48 - class AhoyNetwork { public: typedef std::function OnNetworkCB; - typedef std::function OnTimeCB; + typedef std::function OnTimeCB; public: - void setup(settings_t *config, uint32_t *utcTimestamp, OnNetworkCB onNetworkCB, OnTimeCB onTimeCB) { + void setup(settings_t *config, OnNetworkCB onNetworkCB, OnTimeCB onTimeCB) { mConfig = config; - mUtcTimestamp = utcTimestamp; mOnNetworkCB = onNetworkCB; mOnTimeCB = onTimeCB; + mNtpIp = IPADDR_NONE; + if('\0' == mConfig->sys.deviceName[0]) snprintf(mConfig->sys.deviceName, DEVNAME_LEN, "%s", DEF_DEVICE_NAME); @@ -51,20 +52,67 @@ class AhoyNetwork { #endif } + virtual void tickNetworkLoop() { + if(mDnsCallbackReady) { + mDnsCallbackReady = false; + startNtpUpdate(); + } + + if(mNtpTimeoutSec) { + mNtpTimeoutSec--; + if(!mNtpTimeoutSec) + mOnTimeCB(0); // timeout + } + } + bool isConnected() const { - return (mStatus == NetworkState::CONNECTED); + return ((mStatus == NetworkState::CONNECTED) || (mStatus == NetworkState::GOT_IP)); } - bool updateNtpTime(void) { - if(NetworkState::GOT_IP != mStatus) - return false; + static void dnsCallback(const char *name, const ip_addr_t *ipaddr, void *pClass) { + AhoyNetwork *obj = static_cast(pClass); + if (ipaddr) { + #if defined(ESP32) + obj->mNtpIp = ipaddr->u_addr.ip4.addr; + #else + obj->mNtpIp = ipaddr->addr; + #endif + } + obj->mDnsCallbackReady = true; + } + + void updateNtpTime() { + if(mNtpIp != IPADDR_NONE) { + startNtpUpdate(); + return; + } + + mNtpTimeoutSec = 30; + + ip_addr_t ipaddr; + mNtpIp = WiFi.gatewayIP(); + // dns_gethostbyname runs asynchronous and sets the member mNtpIp which is then checked on + // next call of updateNtpTime + err_t err = dns_gethostbyname(mConfig->ntp.addr, &ipaddr, dnsCallback, this); + + if (err == ERR_OK) { + #if defined(ESP32) + mNtpIp = ipaddr.u_addr.ip4.addr; + #else + mNtpIp = ipaddr.addr; + #endif + startNtpUpdate(); + } + } + protected: + void startNtpUpdate() { + DPRINTLN(DBG_INFO, F("get time from: ") + mNtpIp.toString()); if (!mUdp.connected()) { - IPAddress timeServer; - if (!WiFi.hostByName(mConfig->ntp.addr, timeServer)) - return false; - if (!mUdp.connect(timeServer, mConfig->ntp.port)) - return false; + if (!mUdp.connect(mNtpIp, mConfig->ntp.port)) { + mOnTimeCB(0); + return; + } } mUdp.onPacket([this](AsyncUDPPacket packet) { @@ -72,12 +120,12 @@ class AhoyNetwork { }); sendNTPpacket(); - return true; + // reset to start with DNS lookup next time again + mNtpIp = IPADDR_NONE; } public: virtual void begin() = 0; - virtual void tickNetworkLoop() = 0; virtual String getIp(void) = 0; virtual String getMac(void) = 0; @@ -185,7 +233,7 @@ class AhoyNetwork { std::swap(sort[i], sort[j]); } - private: + protected: void sendNTPpacket(void) { uint8_t buf[NTP_PACKET_SIZE]; memset(buf, 0, NTP_PACKET_SIZE); @@ -194,11 +242,6 @@ class AhoyNetwork { buf[1] = 0; // Stratum buf[2] = 6; // Max Interval between messages in seconds buf[3] = 0xEC; // Clock Precision - // bytes 4 - 11 are for Root Delay and Dispersion and were set to 0 by memset - buf[12] = 49; // four-byte reference ID identifying - buf[13] = 0x4E; - buf[14] = 49; - buf[15] = 52; mUdp.write(buf, NTP_PACKET_SIZE); } @@ -215,10 +258,9 @@ class AhoyNetwork { // this is NTP time (seconds since Jan 1 1900): unsigned long secsSince1900 = highWord << 16 | lowWord; - *mUtcTimestamp = secsSince1900 - 2208988800UL; // UTC time - DPRINTLN(DBG_INFO, "[NTP]: " + ah::getDateTimeStr(*mUtcTimestamp) + " UTC"); - mOnTimeCB(true); mUdp.close(); + mNtpTimeoutSec = 0; // clear timeout + mOnTimeCB(secsSince1900 - 2208988800UL); } protected: @@ -230,12 +272,15 @@ class AhoyNetwork { CONNECTING // ESP8266 }; + public: + bool mDnsCallbackReady = false; + protected: settings_t *mConfig = nullptr; - uint32_t *mUtcTimestamp = nullptr; bool mConnected = false; bool mScanActive = false; bool mWifiConnecting = false; + uint8_t mNtpTimeoutSec = 0; OnNetworkCB mOnNetworkCB; OnTimeCB mOnTimeCB; @@ -245,6 +290,8 @@ class AhoyNetwork { AhoyWifiAp mAp; DNSServer mDns; + IPAddress mNtpIp; + AsyncUDP mUdp; // for time server #if defined(ESP8266) WiFiEventHandler wifiConnectHandler, wifiDisconnectHandler, wifiGotIPHandler; diff --git a/src/network/AhoyWifiEsp32.h b/src/network/AhoyWifiEsp32.h index 70017518..70d8119c 100644 --- a/src/network/AhoyWifiEsp32.h +++ b/src/network/AhoyWifiEsp32.h @@ -37,6 +37,7 @@ class AhoyWifi : public AhoyNetwork { } void tickNetworkLoop() override { + AhoyNetwork::tickNetworkLoop(); if(mAp.isEnabled()) mAp.tickLoop(); } diff --git a/src/network/AhoyWifiEsp8266.h b/src/network/AhoyWifiEsp8266.h index c72f06b5..03cd54b1 100644 --- a/src/network/AhoyWifiEsp8266.h +++ b/src/network/AhoyWifiEsp8266.h @@ -23,6 +23,7 @@ class AhoyWifi : public AhoyNetwork { } void tickNetworkLoop() override { + AhoyNetwork::tickNetworkLoop(); if(mAp.isEnabled()) mAp.tickLoop(); diff --git a/src/platformio.ini b/src/platformio.ini index 0639d16f..f5c75a10 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -26,7 +26,6 @@ extra_scripts = post:../scripts/add_littlefs_binary.py lib_deps = - https://github.com/esphome/ESPAsyncWebServer @ ^3.2.2 https://github.com/nRF24/RF24.git#v1.4.8 paulstoffregen/Time @ ^1.6.1 https://github.com/bertmelis/espMqttClient#v1.7.0 @@ -38,6 +37,7 @@ lib_deps = build_flags = -std=c++17 -std=gnu++17 + -DEMC_ALLOW_NOT_CONNECTED_PUBLISH build_unflags = -std=gnu++11 @@ -48,9 +48,12 @@ board = esp12e board_build.f_cpu = 80000000L lib_deps = ${env.lib_deps} + https://github.com/esphome/ESPAsyncWebServer @ ^3.2.2 https://github.com/me-no-dev/ESPAsyncUDP build_flags = ${env.build_flags} -DEMC_MIN_FREE_MEMORY=4096 + + -D CONFIG_ASYNC_TCP_STACK_SIZE=4096 ;-Wl,-Map,output.map monitor_filters = esp8266_exception_decoder @@ -149,16 +152,20 @@ monitor_filters = esp8266_exception_decoder [env:esp32-wroom32-minimal] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_d32 +lib_deps = + ${env.lib_deps} + https://github.com/mathieucarbou/ESPAsyncWebServer#v3.3.12 build_flags = ${env.build_flags} -DSPI_HAL monitor_filters = esp32_exception_decoder [env:esp32-wroom32] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_d32 +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:esp32-wroom32-minimal.build_flags} -DUSE_HSPI_FOR_EPD -DENABLE_MQTT @@ -186,32 +193,36 @@ monitor_filters = esp32_exception_decoder [env:esp32-wroom32-de] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_d32 +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:esp32-wroom32.build_flags} -DLANG_DE monitor_filters = esp32_exception_decoder [env:esp32-wroom32-prometheus] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_d32 +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:esp32-wroom32.build_flags} -DENABLE_PROMETHEUS_EP monitor_filters = esp32_exception_decoder [env:esp32-wroom32-prometheus-de] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_d32 +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:esp32-wroom32-prometheus.build_flags} -DLANG_DE monitor_filters = esp32_exception_decoder [env:esp32-s2-mini] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_s2_mini +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD -DSPI_HAL @@ -233,16 +244,18 @@ monitor_filters = esp32_exception_decoder [env:esp32-s2-mini-de] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_s2_mini +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:esp32-s2-mini.build_flags} -DLANG_DE monitor_filters = esp32_exception_decoder [env:esp32-c3-mini] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_c3_mini +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env.build_flags} -DUSE_HSPI_FOR_EPD -DSPI_HAL @@ -264,17 +277,19 @@ monitor_filters = esp32_exception_decoder [env:esp32-c3-mini-de] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = lolin_c3_mini +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:esp32-c3-mini.build_flags} -DLANG_DE monitor_filters = esp32_exception_decoder [env:opendtufusion-minimal] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env.build_flags} -DSPI_HAL -DDEF_NRF_CS_PIN=37 @@ -297,9 +312,10 @@ monitor_filters = esp32_exception_decoder, colorize [env:opendtufusion] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:opendtufusion-minimal.build_flags} -DETHERNET -DENABLE_MQTT @@ -315,28 +331,31 @@ monitor_filters = esp32_exception_decoder, colorize [env:opendtufusion-de] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:opendtufusion.build_flags} -DLANG_DE monitor_filters = esp32_exception_decoder, colorize [env:opendtufusion-16MB] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = esp32-s3-devkitc-1 board_upload.flash_size = 16MB board_build.partitions = default_16MB.csv upload_protocol = esp-builtin +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:opendtufusion.build_flags} monitor_filters = esp32_exception_decoder, colorize [env:opendtufusion-16MB-de] -platform = espressif32@6.7.0 +platform = espressif32@6.9.0 board = esp32-s3-devkitc-1 upload_protocol = esp-builtin +lib_deps = ${env:esp32-wroom32-minimal.lib_deps} build_flags = ${env:opendtufusion-16MB.build_flags} -DLANG_DE monitor_filters = diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 4654a4f4..c2d96aae 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -195,7 +195,7 @@ class Display { } #if defined(ESP32) else if (DISP_TYPE_T10_EPAPER == mCfg->type) { - mEpaper.loop((totalPower), totalYieldDay, totalYieldTotal, nrprod); + mEpaper.loop((totalPower), totalYieldDay, totalYieldTotal, nrprod, mApp->getIp(), mApp->isNetworkConnected()); mRefreshCycle++; if (mRefreshCycle > 2880) { // 15 * 2280 = 44300s = 12h diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp index 654627d2..d9f64796 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -1,7 +1,6 @@ #include "Display_ePaper.h" #if defined(ESP32) -#include #include "../../utils/helper.h" #include "imagedata.h" #include "defines.h" @@ -13,7 +12,9 @@ static const uint32_t spiClk = 4000000; // 4 MHz SPIClass hspi(HSPI); #endif -DisplayEPaper::DisplayEPaper() { +DisplayEPaper::DisplayEPaper() + : mNetworkConnected {false} +{ mDisplayRotation = 2; mHeadFootPadding = 16; memset(_fmtText, 0, EPAPER_MAX_TEXT_LEN); @@ -122,8 +123,8 @@ void DisplayEPaper::headlineIP() { _display->fillScreen(GxEPD_BLACK); do { - if ((WiFi.isConnected() == true) && (WiFi.localIP() > 0)) { - snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "%s", WiFi.localIP().toString().c_str()); + if (mNetworkConnected == true) { + snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "%s", _settedIP.c_str()); } else { snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, STR_NO_WIFI); } @@ -289,14 +290,15 @@ void DisplayEPaper::actualPowerPaged(float totalPower, float totalYieldDay, floa } while (_display->nextPage()); } //*************************************************************************** -void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod) { +void DisplayEPaper::loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod, String ip, bool networkConnected) { + mNetworkConnected = networkConnected; if(RefreshStatus::DONE != mRefreshState) return; // check if the IP has changed - if (_settedIP != WiFi.localIP().toString()) { + if (_settedIP != ip) { // save the new IP and call the Headline Function to adapt the Headline - _settedIP = WiFi.localIP().toString(); + _settedIP = ip; headlineIP(); } diff --git a/src/plugins/Display/Display_ePaper.h b/src/plugins/Display/Display_ePaper.h index 2106c492..e3ab2f41 100644 --- a/src/plugins/Display/Display_ePaper.h +++ b/src/plugins/Display/Display_ePaper.h @@ -33,7 +33,7 @@ class DisplayEPaper { void fullRefresh(); void init(uint8_t type, uint8_t _CS, uint8_t _DC, uint8_t _RST, uint8_t _BUSY, uint8_t _SCK, uint8_t _MOSI, uint32_t* utcTs, const char* version); void config(uint8_t rotation, bool enPowerSave); - void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod); + void loop(float totalPower, float totalYieldDay, float totalYieldTotal, uint8_t isprod, String ip, bool networkConnected); void refreshLoop(); void tickerSecond(); @@ -66,6 +66,7 @@ class DisplayEPaper { uint8_t mSecondCnt; bool mLogoDisplayed; + bool mNetworkConnected; #if defined(SPI_HAL) epdHal hal; #endif diff --git a/src/plugins/plugin_lang.h b/src/plugins/plugin_lang.h index 8d7a987f..e4f1db23 100644 --- a/src/plugins/plugin_lang.h +++ b/src/plugins/plugin_lang.h @@ -12,7 +12,7 @@ #define STR_OFFLINE "aus" #define STR_ONLINE "aktiv" #define STR_NO_INVERTER "kein inverter" - #define STR_NO_WIFI "WLAN nicht verbunden" + #define STR_NO_WIFI "Netzwerk nicht verbunden" #define STR_VERSION "Version" #define STR_ACTIVE_INVERTERS "aktive WR" #define STR_TODAY "heute" @@ -23,7 +23,7 @@ #define STR_OFFLINE "eteint" #define STR_ONLINE "online" #define STR_NO_INVERTER "pas d'onduleur" - #define STR_NO_WIFI "WiFi not connected" + #define STR_NO_WIFI "Network not connected" #define STR_VERSION "Version" #define STR_ACTIVE_INVERTERS "active Inv" #define STR_TODAY "today" @@ -34,7 +34,7 @@ #define STR_OFFLINE "offline" #define STR_ONLINE "online" #define STR_NO_INVERTER "no inverter" - #define STR_NO_WIFI "WiFi not connected" + #define STR_NO_WIFI "Network not connected" #define STR_VERSION "Version" #define STR_ACTIVE_INVERTERS "active Inv" #define STR_TODAY "today" diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 6e021bed..721cc2ff 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -11,8 +11,11 @@ #if defined(ENABLE_MQTT) #ifdef ESP8266 #include - #define xSemaphoreTake(a, b) { while(a) { yield(); } a = true; } - #define xSemaphoreGive(a) { a = false; } + #if !defined(vSemaphoreDelete) + #define vSemaphoreDelete(a) + #define xSemaphoreTake(a, b) { while(a) { yield(); } a = true; } + #define xSemaphoreGive(a) { a = false; } + #endif #elif defined(ESP32) #include #endif @@ -158,7 +161,10 @@ class PubMqtt { publish(subtopics[MQTT_UPTIME], mVal.data()); publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str()); publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str()); - #ifndef ESP32 + #if defined(ESP32) + snprintf(mVal.data(), mVal.size(), "%.2f", ah::readTemperature()); + publish(subtopics[MQTT_TEMP_SENS_C], mVal.data()); + #else publish(subtopics[MQTT_HEAP_FRAG], String(ESP.getHeapFragmentation()).c_str()); #endif } @@ -406,26 +412,25 @@ class PubMqtt { bool total = (mDiscovery.lastIvId == MAX_NUM_INVERTERS); Inverter<> *iv = mSys->getInverterByPos(mDiscovery.lastIvId); - record_t<> *rec = NULL; - if (NULL != iv) { + record_t<> *rec = nullptr; + if (nullptr != iv) { rec = iv->getRecordStruct(RealTimeRunData_Debug); if(0 == mDiscovery.sub) - mDiscovery.foundIvCnt++; + mDiscovery.foundIvCnt++; } - if ((NULL != iv) || total) { + if ((nullptr != iv) || total) { if (!total) { doc[F("name")] = iv->config->name; doc[F("ids")] = String(iv->config->serial.u64, HEX); doc[F("mdl")] = iv->config->name; - } - else { + } 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("cu")] = F("http://") + mApp->getIp(); doc[F("mf")] = F("Hoymiles"); JsonObject deviceObj = doc.as(); // deviceObj is only pointer!? @@ -438,18 +443,21 @@ class PubMqtt { uniq_id.fill(0); buf.fill(0); const char *devCls, *stateCls; + if (!total) { if (rec->assign[mDiscovery.sub].ch == CH0) snprintf(name.data(), name.size(), "%s", iv->getFieldName(mDiscovery.sub, rec)); else snprintf(name.data(), name.size(), "CH%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); - snprintf(topic.data(), name.size(), "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + if (!mCfgMqtt->json) + snprintf(topic.data(), name.size(), "/ch%d/%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); + else + snprintf(topic.data(), name.size(), "/ch%d", rec->assign[mDiscovery.sub].ch); snprintf(uniq_id.data(), uniq_id.size(), "ch%d_%s", rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); devCls = getFieldDeviceClass(rec->assign[mDiscovery.sub].fieldId); stateCls = getFieldStateClass(rec->assign[mDiscovery.sub].fieldId); } - else { // total values snprintf(name.data(), name.size(), "Total %s", fields[fldTotal[mDiscovery.sub]]); snprintf(topic.data(), topic.size(), "/%s", fields[fldTotal[mDiscovery.sub]]); @@ -461,24 +469,43 @@ class PubMqtt { DynamicJsonDocument doc2(512); constexpr static const char* unitTotal[] = {"W", "kWh", "Wh", "W"}; doc2[F("name")] = String(name.data()); - doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic.data()); + + if (mCfgMqtt->json) { + if (total) { + doc2[F("val_tpl")] = String("{{ value_json.") + fields[fldTotal[mDiscovery.sub]] + String(" }}"); + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + "total"; + } + else { + doc2[F("val_tpl")] = String("{{ value_json.") + iv->getFieldName(mDiscovery.sub, rec) + String(" }}"); + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + String(iv->config->name) + String(topic.data()); + } + } + else { + doc2[F("stat_t")] = String(mCfgMqtt->topic) + "/" + ((!total) ? String(iv->config->name) : "total" ) + String(topic.data()); + } doc2[F("unit_of_meas")] = ((!total) ? (iv->getUnit(mDiscovery.sub, rec)) : (unitTotal[mDiscovery.sub])); doc2[F("uniq_id")] = ((!total) ? (String(iv->config->serial.u64, HEX)) : (node_id)) + "_" + uniq_id.data(); doc2[F("dev")] = deviceObj; + 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) + if (devCls != nullptr) doc2[F("dev_cla")] = String(devCls); - if (stateCls != NULL) + if (stateCls != nullptr) doc2[F("stat_cla")] = String(stateCls); if (!total) snprintf(topic.data(), topic.size(), "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[mDiscovery.sub].ch, iv->getFieldName(mDiscovery.sub, rec)); else // total values snprintf(topic.data(), topic.size(), "%s/sensor/%s/total_%s/config", MQTT_DISCOVERY_PREFIX, node_id.c_str(), fields[fldTotal[mDiscovery.sub]]); + size_t size = measureJson(doc2) + 1; serializeJson(doc2, buf.data(), size); - if(FLD_EVT != rec->assign[mDiscovery.sub].fieldId) + + if(nullptr != rec) { + if(FLD_EVT != rec->assign[mDiscovery.sub].fieldId) + publish(topic.data(), buf.data(), true, false); + } else if(total) publish(topic.data(), buf.data(), true, false); if(++mDiscovery.sub == ((!total) ? (rec->length) : 4)) { @@ -656,7 +683,7 @@ class PubMqtt { size_t index; size_t total; - message_s() + message_s() : topic { nullptr } , payload { nullptr } , len { 0 } diff --git a/src/publisher/pubMqttDefs.h b/src/publisher/pubMqttDefs.h index c97daa78..691bb098 100644 --- a/src/publisher/pubMqttDefs.h +++ b/src/publisher/pubMqttDefs.h @@ -56,7 +56,8 @@ enum { MQTT_STATUS, MQTT_LWT_ONLINE, MQTT_LWT_OFFLINE, - MQTT_ACK_PWR_LMT + MQTT_ACK_PWR_LMT, + MQTT_TEMP_SENS_C }; const char* const subtopics[] PROGMEM = { @@ -76,7 +77,8 @@ const char* const subtopics[] PROGMEM = { "status", "connected", "not_connected", - "ack_pwr_limit" + "ack_pwr_limit", + "cpu_temp" }; enum { diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index edb9b9b9..b30f58e0 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -2,7 +2,7 @@ // 2023 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- - +#include #include "helper.h" #include "dbg.h" #include "../plugins/plugin_lang.h" @@ -142,4 +142,24 @@ namespace ah { } DBGPRINTLN(""); } + + float readTemperature() { + /*// ADC1 channel 0 is GPIO36 + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0); + int adc_reading = adc1_get_raw(ADC1_CHANNEL_0); + // Convert the raw ADC reading to a voltage in mV + esp_adc_cal_characteristics_t characteristics; + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_0, ADC_WIDTH_BIT_12, 1100, &characteristics); + uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &characteristics); + // Convert the voltage to a temperature in Celsius + // This formula is an approximation and might need to be calibrated for your specific use case. + float temperature = (voltage - 500) / 10.0;*/ + + #if defined(ESP32) + return temperatureRead(); + #else + return 0; + #endif + } } diff --git a/src/utils/helper.h b/src/utils/helper.h index ff1a9aed..c09bfb9c 100644 --- a/src/utils/helper.h +++ b/src/utils/helper.h @@ -49,6 +49,8 @@ namespace ah { String getTimeStrMs(uint64_t t); uint64_t Serial2u64(const char *val); void dumpBuf(uint8_t buf[], uint8_t len, uint8_t firstRepl = 0, uint8_t lastRepl = 0); + + float readTemperature(); } #endif /*__HELPER_H__*/ diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 5ce43a36..13208942 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -73,25 +73,32 @@ namespace ah { } - void once(scdCb c, uint32_t timeout, const char *name) { addTicker(c, timeout, 0, false, name); } - void onceAt(scdCb c, uint32_t timestamp, const char *name) { addTicker(c, timestamp, 0, true, name); } - uint8_t every(scdCb c, uint32_t interval, const char *name){ return addTicker(c, interval, interval, false, name); } + uint8_t once(scdCb c, uint32_t timeout, const char *name) { return addTicker(c, timeout, 0, false, name); } + uint8_t onceAt(scdCb c, uint32_t timestamp, const char *name) { return addTicker(c, timestamp, 0, true, name); } + uint8_t every(scdCb c, uint32_t interval, const char *name) { return addTicker(c, interval, interval, false, name); } - void everySec(scdCb c, const char *name) { every(c, SCD_SEC, name); } - void everyMin(scdCb c, const char *name) { every(c, SCD_MIN, name); } - void everyHour(scdCb c, const char *name) { every(c, SCD_HOUR, name); } - void every12h(scdCb c, const char *name) { every(c, SCD_12H, name); } - void everyDay(scdCb c, const char *name) { every(c, SCD_DAY, name); } + uint8_t everySec(scdCb c, const char *name) { return every(c, SCD_SEC, name); } + uint8_t everyMin(scdCb c, const char *name) { return every(c, SCD_MIN, name); } + uint8_t everyHour(scdCb c, const char *name) { return every(c, SCD_HOUR, name); } + uint8_t every12h(scdCb c, const char *name) { return every(c, SCD_12H, name); } + uint8_t everyDay(scdCb c, const char *name) { return every(c, SCD_DAY, name); } virtual void setTimestamp(uint32_t ts) { mTimestamp = ts; } - bool resetEveryById(uint8_t id) { - if (mTickerInUse[id] == false) - return false; - mTicker[id].timeout = mTicker[id].reload; - return true; + bool resetTickerByName(const char* name) { + for (uint8_t id = 0; id < MAX_NUM_TICKER; id++) { + if (mTickerInUse[id]) { + if(strncmp(name, mTicker[id].name, strlen(name)) == 0) { + mTicker[id].timeout = mTicker[id].reload; + mTickerInUse[id] = false; + return true; + } + } + } + + return false; } uint32_t getUptime(void) { diff --git a/src/web/RestApi.h b/src/web/RestApi.h index eac91cb9..c6859386 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -7,11 +7,6 @@ #define __WEB_API_H__ #include "../utils/dbg.h" -#ifdef ESP32 -#include "AsyncTCP.h" -#else -#include "ESPAsyncTCP.h" -#endif #include "../appInterface.h" #include "../hm/hmSystem.h" #include "../utils/helper.h" @@ -674,15 +669,15 @@ class RestApi { // find oldest alarm uint8_t offset = 0; uint32_t oldestStart = 0xffffffff; - for(uint8_t i = 0; i < 10; i++) { + for(uint8_t i = 0; i < Inverter<>::MaxAlarmNum; i++) { if((iv->lastAlarm[i].start != 0) && (iv->lastAlarm[i].start < oldestStart)) { offset = i; oldestStart = iv->lastAlarm[i].start; } } - for(uint8_t i = 0; i < 10; i++) { - uint8_t pos = (i + offset) % 10; + for(uint8_t i = 0; i < Inverter<>::MaxAlarmNum; i++) { + uint8_t pos = (i + offset) % Inverter<>::MaxAlarmNum; alarm[pos][F("code")] = iv->lastAlarm[pos].code; alarm[pos][F("str")] = iv->getAlarmStr(iv->lastAlarm[pos].code); alarm[pos][F("start")] = iv->lastAlarm[pos].start; @@ -822,7 +817,9 @@ class RestApi { void getChipInfo(JsonObject obj) { obj[F("cpu_freq")] = ESP.getCpuFreqMHz(); obj[F("sdk")] = ESP.getSdkVersion(); + #if defined(ESP32) + obj[F("temp_sensor_c")] = ah::readTemperature(); obj[F("revision")] = ESP.getChipRevision(); obj[F("model")] = ESP.getChipModel(); obj[F("cores")] = ESP.getChipCores(); @@ -1134,6 +1131,11 @@ class RestApi { iv->setDevCommand(jsonIn[F("val")].as()); } else if(F("restart_ahoy") == jsonIn[F("cmd")]) { mApp->setRebootFlag(); + } else if(F("cmt_search") == jsonIn[F("cmd")]) { + if(!mApp->cmtSearch(jsonIn[F("id")], jsonIn[F("to_ch")])) { + jsonOut[F("error")] = F("ERR_INVERTER_NOT_FOUND"); + return false; + } } else { jsonOut[F("error")] = F("ERR_UNKNOWN_CMD"); return false; diff --git a/src/web/html/about.html b/src/web/html/about.html index 1b27ac9d..b03aff78 100644 --- a/src/web/html/about.html +++ b/src/web/html/about.html @@ -14,7 +14,7 @@
Used Libraries
- + diff --git a/src/web/html/index.html b/src/web/html/index.html index ee78ac8e..33887e86 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -152,7 +152,7 @@ text = "{#INVERTER} #"; p.append( svg(icon, 30, 30, "icon " + cl), - span(text + i["id"] + ": " + i["name"] + " {#IS} " + avail), + span(text + i["id"] + ": " + i["name"] + " " + avail), br() ); diff --git a/src/web/html/setup.html b/src/web/html/setup.html index 2b59146c..ef9e06f5 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -736,7 +736,7 @@ lines.push(ml("tr", {}, [ ml("th", {style: "width: 10%; text-align: center;"}, ""), ml("th", {}, "Name"), - ml("th", {}, "Serial"), + ml("th", {class: "d-none d-sm-cell"}, "Serial"), ml("th", {style: "width: 10%; text-align: center;"}, "{#INV_EDIT}"), ml("th", {style: "width: 10%; text-align: center;"}, "{#INV_DELETE}") ])); @@ -745,7 +745,7 @@ lines.push(ml("tr", {}, [ ml("td", {}, badge(obj.inverter[i].enabled, (obj.inverter[i].enabled) ? "{#ENABLED}" : "{#DISABLED}")), ml("td", {}, obj.inverter[i].name), - ml("td", {}, String(obj.inverter[i].serial)), + ml("td", {class: "d-none d-sm-cell"}, String(obj.inverter[i].serial)), ml("td", {style: "text-align: center;", onclick: function() {ivModal(obj.inverter[i]);}}, svg(iconGear, 25, 25, "icon icon-fg pointer")), ml("td", {style: "text-align: center; ", onclick: function() {ivDel(obj.inverter[i]);}}, svg(iconDel, 25, 25, "icon icon-fg pointer")) ])); @@ -817,7 +817,8 @@ ml("input", {type: "hidden", name: "isnrf"}, null), ml("div", {id: "setcmt"}, [ divRow("{#INV_FREQUENCY}", sel("freq", esp32cmtFreq, obj.freq)), - divRow("{#INV_POWER_LEVEL}", sel("cmtpa", esp32cmtPa, obj.pa)) + divRow("{#INV_POWER_LEVEL}", sel("cmtpa", esp32cmtPa, obj.pa)), + divRow("{#INV_SEARCH}", ml("input", {type: "button", value: "{#BTN_SEARCH}", class: "btn", onclick: function() { cmtSearch(); }}, null)) ]), ml("div", {id: "setnrf"}, divRow("{#INV_POWER_LEVEL}", sel("nrfpa", nrfPa, obj.pa)) @@ -902,6 +903,16 @@ getAjax("/api/setup", cb, "POST", JSON.stringify(o)); } + function cmtSearch() { + var o = {} + o.cmd = "cmt_search" + o.token = "*" + o.id = obj.id + o.to_ch = document.getElementsByName("freq")[0].value; + + getAjax("/api/ctrl", cb, "POST", JSON.stringify(o)); + } + function convHerf(sn) { let sn_int = 0n; const CHARS = "0123456789ABCDEFGHJKLMNPRSTUVWXY"; diff --git a/src/web/html/style.css b/src/web/html/style.css index 6f05ea77..05c58168 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -339,6 +339,7 @@ p { .fs-sm-8 { font-size: 1rem; } .d-sm-block { display: block !important;} + .d-sm-cell { display: table-cell !important;} .d-sm-none { display: none !important; } } diff --git a/src/web/html/system.html b/src/web/html/system.html index 477b3a7e..a6223455 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -45,6 +45,9 @@ tr("Version", obj.generic.version + " - " + obj.generic.build), tr("Chip", "CPU: " + obj.chip.cpu_freq + "MHz, " + obj.chip.cores + " Core(s)"), tr("Chip Model", obj.chip.model) + /*IF_ESP32*/ + ,tr("Chip temp.", Math.round(obj.chip.temp_sensor_c * 10) / 10 + "°C") + /*ENDIF_ESP32*/ ] document.getElementById("info").append( @@ -62,8 +65,8 @@ function irqBadge(state) { switch(state) { case 0: return badge(false, "unknown", "warning"); break; - case 1: return badge(true, "true"); break; - default: return badge(false, "false"); break; + case 1: return badge(true, "{#TRUE}"); break; + default: return badge(false, "{#FALSE}"); break; } } @@ -125,13 +128,13 @@ function parseMqtt(obj) { if(obj.enabled) { lines = [ - tr("{#CONNECTED}", badge(obj.connected, ((obj.connected) ? "true" : "false"))), + tr("{#CONNECTED}", badge(obj.connected, ((obj.connected) ? "{#TRUE}" : "{#FALSE}"))), tr("#TX", obj.tx_cnt), tr("#RX", obj.rx_cnt) ] } else - lines = tr("enabled", badge(false, "false")); + lines = tr("{#ENABLED}", badge(false, "{#FALSE}")); document.getElementById("info").append( headline("MqTT"), @@ -161,7 +164,7 @@ function parseIndex(obj) { if(obj.ts_sunrise > 0) { document.getElementById("info").append( - headline("Sun"), + headline("{#SUN}"), ml("table", {class: "table"}, ml("tbody", {}, [ tr("{#SUNRISE}", new Date(obj.ts_sunrise * 1000).toLocaleString('de-DE')), diff --git a/src/web/html/update.html b/src/web/html/update.html index 4b670d45..410fe452 100644 --- a/src/web/html/update.html +++ b/src/web/html/update.html @@ -50,7 +50,9 @@ } function hide() { - var bin = document.getElementsByName("update")[0].value.slice(-env.length-4, -4) + let fw = document.getElementsByName("update")[0].value + var bin = fw.slice(-env.length-4, -4) + let ver = fw.split("_")[2].split(".") if (bin !== env) { var html = ml("div", {class: "row"}, [ ml("div", {class: "row my-3"}, "{#WARN_DIFF_ENV}"), @@ -60,8 +62,20 @@ ]) ]) modal("{#UPDATE_MODAL}", html) - } else - start() + } else { + if(ver[1] != "9") + start() + else { + var html = ml("div", {class: "row"}, [ + ml("div", {class: "row my-3"}, "{#ERROR_UPGRADE_NOT_POSSIBLE}"), + ml("div", {class: "row"}, [ + ml("div", {class: "col-6"}, ml("input", {type: "button", class: "btn", value: "{#CANCEL}", onclick: function() { modalClose(); }}, null)) + ]) + ]) + modal("{#UPDATE_MODAL}", html) + } + } + } function start() { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 507a6f9f..c04425d9 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -208,8 +208,11 @@ if(obj.rssi > -127) { if(obj.generation < 2) ageInfo += " (RSSI: " + ((obj.rssi == -64) ? ">=" : "<") + " -64 dBm)"; - else + else { + if(obj.rssi == 0) + obj.rssi = "--"; ageInfo += " (RSSI: " + obj.rssi + " dBm)"; + } } return ml("div", {class: "mb-5"}, [ diff --git a/src/web/lang.json b/src/web/lang.json index 179c3e4e..96f60a54 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -6,7 +6,7 @@ { "token": "NAV_WIZARD", "en": "Setup Wizard", - "de": "Einrichtungsassitent" + "de": "Einrichtungsassistent" }, { "token": "NAV_LIVE", @@ -236,7 +236,7 @@ { "token": "LOG_PRINT_TRACES", "en": "Print whole traces in Log", - "de": "alle Informationen in Log schreiben" + "de": "alle Informationen ins Log schreiben" }, { "token": "LOG_TO_MQTT", @@ -698,6 +698,16 @@ "en": "Pause communication during night (lat. and lon. need to be set)", "de": "Kommunikation während der Nacht pausieren (Breiten- und Längengrad müssen gesetzt sein" }, + { + "token": "INV_SEARCH", + "en": "Catch Inverter", + "de": "Wechselrichter suchen" + }, + { + "token": "BTN_SEARCH", + "en": "start", + "de": "starten" + }, { "token": "BTN_SAVE", "en": "save", @@ -1052,6 +1062,11 @@ "token": "COMMUNICATING", "en": "communicating", "de": "kommunizierend" + }, + { + "token": "SUN", + "en": "Sun", + "de": "Sonne" } ] }, @@ -1240,7 +1255,7 @@ }, { "token": "PRODUCING", - "en": "producing", + "en": "is producing", "de": "produziert" }, { @@ -1248,11 +1263,6 @@ "en": "Inverter", "de": "Wechselrichter" }, - { - "token": "IS", - "en": "is", - "de": "ist" - }, { "token": "LAST_SUCCESS", "en": "last successful transmission", @@ -1323,6 +1333,11 @@ "en": "your environment may not match the update file!", "de": "Die ausgewählte Firmware passt u.U. nicht zum Chipsatz!" }, + { + "token": "ERROR_UPGRADE_NOT_POSSIBLE", + "en": "OTA updade to version 0.9.x not possible, partition layout changed", + "de": "Aktualisierung auf Version 0.9.x nicht per Update möglich (Partition Layout geändert), bitte per Websinstaller neu installieren" + }, { "token": "CONTIUE", "en": "continue", diff --git a/src/web/web.h b/src/web/web.h index b337228a..efabe587 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -8,10 +8,7 @@ #include "../utils/dbg.h" #ifdef ESP32 -#include "AsyncTCP.h" #include "Update.h" -#else -#include "ESPAsyncTCP.h" #endif #include "../appInterface.h" #include "../hm/hmSystem.h" @@ -219,33 +216,38 @@ class Web { } private: - inline void checkRedirect(AsyncWebServerRequest *request) { - if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) - request->redirect(F("/index")); - else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) - request->redirect(F("/live")); - else if ((mConfig->sys.protectionMask & PROT_MASK_HISTORY) != PROT_MASK_HISTORY) - request->redirect(F("/history")); - else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) - request->redirect(F("/serial")); - else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) - request->redirect(F("/system")); - else - request->redirect(F("/login")); - } - - void checkProtection(AsyncWebServerRequest *request) { + bool checkProtection(AsyncWebServerRequest *request) { if(mApp->isProtected(request->client()->remoteIP().toString().c_str(), "", true)) { - checkRedirect(request); - return; + if ((mConfig->sys.protectionMask & PROT_MASK_INDEX) != PROT_MASK_INDEX) { + request->redirect(F("/index")); + return true; + } else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE) { + request->redirect(F("/live")); + return true; + } else if ((mConfig->sys.protectionMask & PROT_MASK_HISTORY) != PROT_MASK_HISTORY) { + request->redirect(F("/history")); + return true; + } else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL) { + request->redirect(F("/serial")); + return true; + } else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM) { + request->redirect(F("/system")); + return true; + } else { + request->redirect(F("/login")); + return true; + } } + + return false; } void getPage(AsyncWebServerRequest *request, uint16_t mask, const uint8_t *zippedHtml, uint32_t len) { if (CHECK_MASK(mConfig->sys.protectionMask, mask)) - checkProtection(request); + if(checkProtection(request)) + return; - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), zippedHtml, len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), zippedHtml, len); response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("content-type"), "text/html; charset=UTF-8"); if(request->hasParam("v")) @@ -339,11 +341,12 @@ class Web { if (request->args() > 0) { if (String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { mApp->unlock(request->client()->remoteIP().toString().c_str(), true); - request->redirect("/"); + request->redirect("/index"); + return; } } - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), login_html, login_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), login_html, login_html_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } @@ -354,7 +357,7 @@ class Web { checkProtection(request); mApp->lock(true); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), system_html, system_html_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } @@ -363,9 +366,9 @@ class Web { DPRINTLN(DBG_VERBOSE, F("onColor")); AsyncWebServerResponse *response; if (mConfig->sys.darkMode) - response = request->beginResponse_P(200, F("text/css"), colorDark_css, colorDark_css_len); + response = beginResponse(request, 200, F("text/css"), colorDark_css, colorDark_css_len); else - response = request->beginResponse_P(200, F("text/css"), colorBright_css, colorBright_css_len); + response = beginResponse(request, 200, F("text/css"), colorBright_css, colorBright_css_len); response->addHeader(F("Content-Encoding"), "gzip"); if(request->hasParam("v")) { response->addHeader(F("Cache-Control"), F("max-age=604800")); @@ -375,7 +378,7 @@ class Web { void onCss(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("onCss")); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/css"), style_css, style_css_len); response->addHeader(F("Content-Encoding"), "gzip"); if(request->hasParam("v")) { response->addHeader(F("Cache-Control"), F("max-age=604800")); @@ -386,7 +389,7 @@ class Web { void onApiJs(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("onApiJs")); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/javascript"), api_js, api_js_len); response->addHeader(F("Content-Encoding"), "gzip"); if(request->hasParam("v")) response->addHeader(F("Cache-Control"), F("max-age=604800")); @@ -396,7 +399,7 @@ class Web { void onGridInfoJson(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("onGridInfoJson")); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("application/json; charset=utf-8"), grid_info_json, grid_info_json_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("application/json; charset=utf-8"), grid_info_json, grid_info_json_len); response->addHeader(F("Content-Encoding"), "gzip"); if(request->hasParam("v")) response->addHeader(F("Cache-Control"), F("max-age=604800")); @@ -405,7 +408,7 @@ class Web { void onFavicon(AsyncWebServerRequest *request) { static const char favicon_type[] PROGMEM = "image/x-icon"; - AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); + AsyncWebServerResponse *response = beginResponse(request, 200, favicon_type, favicon_ico, favicon_ico_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } @@ -418,7 +421,7 @@ class Web { void onReboot(AsyncWebServerRequest *request) { mApp->setRebootFlag(); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), system_html, system_html_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } @@ -426,7 +429,7 @@ class Web { void showHtml(AsyncWebServerRequest *request) { checkProtection(request); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), system_html, system_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), system_html, system_html_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } @@ -443,7 +446,7 @@ class Web { } #endif - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), wizard_html, wizard_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), wizard_html, wizard_html_len); response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("content-type"), "text/html; charset=UTF-8"); request->send(response); @@ -635,7 +638,7 @@ class Web { mApp->saveSettings((request->arg("reboot") == "on")); - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), save_html, save_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), save_html, save_html_len); response->addHeader(F("Content-Encoding"), "gzip"); request->send(response); } @@ -649,7 +652,7 @@ class Web { } void onAbout(AsyncWebServerRequest *request) { - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len); + AsyncWebServerResponse *response = beginResponse(request, 200, F("text/html; charset=UTF-8"), about_html, about_html_len); response->addHeader(F("Content-Encoding"), "gzip"); response->addHeader(F("content-type"), "text/html; charset=UTF-8"); if(request->hasParam("v")) { @@ -673,6 +676,14 @@ class Web { getPage(request, PROT_MASK_SYSTEM, system_html, system_html_len); } + AsyncWebServerResponse* beginResponse(AsyncWebServerRequest *request, int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr) { + #if defined(ESP32) + return request->beginResponse(code, contentType, content, len); + #else + return request->beginResponse_P(code, contentType, content, len); + #endif + } + #ifdef ENABLE_PROMETHEUS_EP // Note