Browse Source

Merge branch 'development03'

main
Lukas Pusch 2 weeks ago
parent
commit
f36d2ed380
  1. 26
      patches/AsyncWeb_Prometheus.patch
  2. 123
      patches/espMqttClientSemaphore.patch
  3. 4
      scripts/applyPatches.py
  4. 10
      scripts/auto_firmware_version.py
  5. 4
      src/CHANGES.md
  6. 153
      src/app.cpp
  7. 35
      src/app.h
  8. 3
      src/appInterface.h
  9. 2
      src/defines.h
  10. 242
      src/hm/CommQueue.h
  11. 126
      src/hm/Communication.h
  12. 1
      src/hm/Radio.h
  13. 17
      src/hm/hmInverter.h
  14. 40
      src/hms/CmtRadio.h
  15. 33
      src/hms/cmt2300a.h
  16. 97
      src/network/AhoyNetwork.h
  17. 1
      src/network/AhoyWifiEsp32.h
  18. 1
      src/network/AhoyWifiEsp8266.h
  19. 49
      src/platformio.ini
  20. 2
      src/plugins/Display/Display.h
  21. 16
      src/plugins/Display/Display_ePaper.cpp
  22. 3
      src/plugins/Display/Display_ePaper.h
  23. 6
      src/plugins/plugin_lang.h
  24. 61
      src/publisher/pubMqtt.h
  25. 6
      src/publisher/pubMqttDefs.h
  26. 22
      src/utils/helper.cpp
  27. 2
      src/utils/helper.h
  28. 33
      src/utils/scheduler.h
  29. 18
      src/web/RestApi.h
  30. 2
      src/web/html/about.html
  31. 2
      src/web/html/index.html
  32. 17
      src/web/html/setup.html
  33. 1
      src/web/html/style.css
  34. 13
      src/web/html/system.html
  35. 20
      src/web/html/update.html
  36. 5
      src/web/html/visualization.html
  37. 31
      src/web/lang.json
  38. 85
      src/web/web.h

26
patches/AsyncWeb_Prometheus.patch

@ -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';

123
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 <https://opensource.org/licenses/MIT> 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<bool>
#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 <https://opensource.org/licenses/MIT> 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;

4
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")

10
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

4
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)
full version log: [Development Log](https://github.com/lumapu/ahoy/blob/development03/src/CHANGES.md)

153
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<AhoyNetwork*>(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;
}
//-----------------------------------------------------------------------------

35
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

3
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;

2
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;

242
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 <uint8_t N=200>
#else
template <uint8_t N=100>
#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<void(bool valid, const queue_s *q)> 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<void(bool valid, QueueElement *q)> 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<queue_s, N> mQueue;
uint8_t mWrPtr = 0;
uint8_t mRdPtr = 0;
std::array<QueueElement, N> mQueue;
private:
uint8_t wrPtr;
uint8_t rdPtr;
#if defined(ESP32)
SemaphoreHandle_t mutex;
StaticSemaphore_t mutex_buffer;
#else
bool mutex;
#endif
};

126
src/hm/Communication.h

@ -21,6 +21,12 @@ typedef std::function<void(Inverter<> *)> 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<frame_t, MAX_PAYLOAD_ENTRIES> mLocalBuf;

1
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; }

17
src/hm/hmInverter.h

@ -116,6 +116,13 @@ const calcFunc_t<T> calcFunctions[] = {
template <class REC_TYP>
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<REC_TYP> recordConfig; // structure for system config values
record_t<REC_TYP> recordAlarm; // structure for alarm values
InverterStatus status = InverterStatus::OFF; // indicates the current inverter status
std::array<alarm_t, 10> lastAlarm; // holds last 10 alarms
std::array<alarm_t, MaxAlarmNum> 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;
}

40
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__*/

33
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__*/

97
src/network/AhoyNetwork.h

@ -11,21 +11,22 @@
#include "../utils/helper.h"
#include "AhoyWifiAp.h"
#include "AsyncJson.h"
#include <lwip/dns.h>
#define NTP_PACKET_SIZE 48
class AhoyNetwork {
public:
typedef std::function<void(bool)> OnNetworkCB;
typedef std::function<void(bool)> OnTimeCB;
typedef std::function<void(uint32_t utcTimestamp)> 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<AhoyNetwork*>(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;

1
src/network/AhoyWifiEsp32.h

@ -37,6 +37,7 @@ class AhoyWifi : public AhoyNetwork {
}
void tickNetworkLoop() override {
AhoyNetwork::tickNetworkLoop();
if(mAp.isEnabled())
mAp.tickLoop();
}

1
src/network/AhoyWifiEsp8266.h

@ -23,6 +23,7 @@ class AhoyWifi : public AhoyNetwork {
}
void tickNetworkLoop() override {
AhoyNetwork::tickNetworkLoop();
if(mAp.isEnabled())
mAp.tickLoop();

49
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 =

2
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

16
src/plugins/Display/Display_ePaper.cpp

@ -1,7 +1,6 @@
#include "Display_ePaper.h"
#if defined(ESP32)
#include <WiFi.h>
#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();
}

3
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

6
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"

61
src/publisher/pubMqtt.h

@ -11,8 +11,11 @@
#if defined(ENABLE_MQTT)
#ifdef ESP8266
#include <ESP8266WiFi.h>
#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 <WiFi.h>
#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<JsonObject>(); // 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 }

6
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 {

22
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 <Arduino.h>
#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
}
}

2
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__*/

33
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) {

18
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<int>());
} 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;

2
src/web/html/about.html

@ -14,7 +14,7 @@
<div class="p-2">Used Libraries</div>
</div>
<div class="row"><a href="https://github.com/bertmelis/espMqttClient" target="_blank">bertmelis/espMqttClient</a></div>
<div class="row"><a href="https://github.com/esphome/ESPAsyncWebServer" target="_blank">esphome/ESPAsyncWebServer</a></div>
<div class="row"><a href="https://github.com/mathieucarbou/ESPAsyncWebServer" target="_blank">mathieucarbou/ESPAsyncWebServer</a></div>
<div class="row"><a href="https://github.com/bblanchon/ArduinoJson" target="_blank">bblanchon/ArduinoJson</a></div>
<div class="row"><a href="https://github.com/nrf24/RF24" target="_blank">nrf24/RF24</a></div>
<div class="row"><a href="https://github.com/paulstoffregen/Time" target="_blank">paulstoffregen/Time</a></div>

2
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()
);

17
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";

1
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; }
}

13
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 + "&deg;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')),

20
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() {

5
src/web/html/visualization.html

@ -208,8 +208,11 @@
if(obj.rssi > -127) {
if(obj.generation < 2)
ageInfo += " (RSSI: " + ((obj.rssi == -64) ? "&gt;=" : "&lt;") + " -64&nbsp;dBm)";
else
else {
if(obj.rssi == 0)
obj.rssi = "--";
ageInfo += " (RSSI: " + obj.rssi + "&nbsp;dBm)";
}
}
return ml("div", {class: "mb-5"}, [

31
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&auml;hrend der Nacht pausieren (Breiten- und L&auml;ngengrad m&uuml;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&auml;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&ouml;glich (Partition Layout ge&auml;ndert), bitte per Websinstaller neu installieren"
},
{
"token": "CONTIUE",
"en": "continue",

85
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

Loading…
Cancel
Save