From 7d54eaa29f97d878141715aefc349f457f9eae1a Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:52:50 +0200 Subject: [PATCH 01/48] Add files via upload --- src/CHANGES.md | 30 +++---- src/app.cpp | 159 +++++++++++++++++++++++++++++-------- src/app.h | 31 +++++--- src/appInterface.h | 5 +- src/defines.h | 7 ++ src/platformio.ini | 192 +++++++++------------------------------------ 6 files changed, 214 insertions(+), 210 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 281dc784c..b187c3c03 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,15 +1,17 @@ -Changelog v0.6.9 +Changelog for ahoy-all-in-one compared to 0.6.9 of the main project -* improved MqTT -* fix WiFi hostname during boot up -* improved login: only one session at the same time is possible -* fix UI: login screen for small displays; burger menu height; small modifications for import button (in setup) -* improved WiFi reconnect -* optimized performance: browser caching was improved to reduce requests from ESP -* improved NRF24 communication for more stable data transmission -* added / fixed MqTT subscription `ctrl/power/[IV-ID]` -* improved save settings -* improved UI in setup: now `.` and `,` are allowed as floating point seperator -* fix zero yield day functionality -* LEDs are now configurable to show if 1st inverter is available and if MqTT is connected -* LED are configurable to active high or low +- read SML/OBIS from UART (stream parser with min resources needed); Connections 9600,8,n,1, GND-GND, VCC-3V3, TX-TX, RX-RX +- prepared to show chart of grid power and total solar ac power for current days daylight period (6 a.m. to 8 p.m.) +- show current grid power +- show max solar ac/dc power +- improved radio retransmit (complete retransmit if nothing was received, but only when inverter ought to be active) +- shortcut radio traces a little bit + +DRAWBACKS: +- MQTT Source is commented out (except 1 var which is used for other purpose as well) +- only up to 2 Inverters (was: 10) +- RX/TX of UART0 is used for serial interface to IR sensor. +But: Currently there is enough heap available for stable operation on a ESP8266 plattform (WEMOS D1 R1). So adjust to your needs and see if the AHOY-DTU is still stable in operation with your hw plattform. +To update firmware via USB, unplug serial connection to IR sensor first. Surprisingly during normal operation it seems that one can use a full connected USB cable (for power supply). But I'm not sure, if this allways will be true. +Of course you cannot operate a display that uses RX/TX pins of UART0, simultanously. +- Due to not matching licence of the chart lib certain parts of visualization.html are commented out. See comments there. diff --git a/src/app.cpp b/src/app.cpp index d2d87f508..e1f055ac6 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -6,21 +6,32 @@ #include "app.h" #include #include "utils/sun.h" +#include "plugins/SML_OBIS_Parser.h" + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif //----------------------------------------------------------------------------- app::app() : ah::Scheduler() {} - //----------------------------------------------------------------------------- void app::setup() { +#ifdef AHOY_SML_OBIS_SUPPORT + /* Assumptions made: + Electricity meter sends SML telegrams via IR interface (9600,8,n,1) without being asked (typical behaviour). + An IR sensor is connected to the UART0 of AHOY DTU. Connected pins: GND-GND, 3V3-VCC, RX-RX, TX-TX. + */ + Serial.begin(9600, SERIAL_8N1, SERIAL_RX_ONLY); +#else Serial.begin(115200); +#endif while (!Serial) yield(); ah::Scheduler::setup(); resetSystem(); - mSettings.setup(); mSettings.getPtr(mConfig); DPRINT(DBG_INFO, F("Settings valid: ")); @@ -30,7 +41,7 @@ void app::setup() { DBGPRINTLN(F("false")); mSys.enableDebug(); - mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso); + mSys.setup(&mTimestamp, mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso); #if defined(AP_ONLY) mInnerLoopCb = std::bind(&app::loopStandard, this); @@ -62,7 +73,7 @@ void app::setup() { DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring")); // when WiFi is in client mode, then enable mqtt broker - #if !defined(AP_ONLY) + #if !defined(AP_ONLY) && defined (AHOY_MQTT_SUPPORT) mMqttEnabled = (mConfig->mqtt.broker[0] > 0); if (mMqttEnabled) { mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp); @@ -84,8 +95,11 @@ void app::setup() { mPubSerial.setup(mConfig, &mSys, &mTimestamp); - regularTickers(); +#ifdef AHOY_SML_OBIS_SUPPORT + sml_setup (this, &mTimestamp); +#endif + regularTickers(); // DBGPRINTLN("--- end setup"); // DBGPRINTLN(String(ESP.getFreeHeap())); @@ -109,11 +123,16 @@ void app::loopStandard(void) { if (mConfig->serial.debug) { DPRINT(DBG_INFO, F("RX ")); DBGPRINT(String(p->len)); +#ifdef undef DBGPRINT(F("B Ch")); DBGPRINT(String(p->ch)); DBGPRINT(F(" | ")); mSys.Radio.dumpBuf(p->packet, p->len); +#else + DBGPRINTLN(" Bytes"); +#endif } + mStat.frmCnt++; Inverter<> *iv = mSys.findInverter(&p->packet[1]); @@ -132,8 +151,15 @@ void app::loopStandard(void) { mPayload.loop(); mMiPayload.loop(); - if (mMqttEnabled) +#ifdef AHOY_MQTT_SUPPORT + if (mMqttEnabled) { mMqtt.loop(); + } +#endif + +#ifdef AHOY_SML_OBIS_SUPPORT + sml_loop (); +#endif } //----------------------------------------------------------------------------- @@ -150,11 +176,15 @@ void app::onWifi(bool gotIp) { if (gotIp) { mInnerLoopCb = std::bind(&app::loopStandard, this); every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend"); + mMqttReconnect = true; + mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers! once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2"); if (WIFI_AP == WiFi.getMode()) { +#ifdef AHOY_MQTT_SUPPORT mMqttEnabled = false; +#endif everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); } } else { @@ -178,21 +208,27 @@ void app::tickNtpUpdate(void) { uint32_t nxtTrig = 5; // default: check again in 5 sec bool isOK = mWifi.getNtpTime(); if (isOK || mTimestamp != 0) { +#ifdef AHOY_MQTT_SUPPORT if (mMqttReconnect && mMqttEnabled) { mMqtt.tickerSecond(); everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS"); everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM"); } +#endif // only install schedulers once even if NTP wasn't successful in first loop if (mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed if (mConfig->inst.rstValsNotAvail) everyMin(std::bind(&app::tickMinute, this), "tMin"); - if (mConfig->inst.rstYieldMidNight) { - 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"); - } + + 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"); + + mSys.cleanup_history(); +#ifdef AHOY_SML_OBIS_SUPPORT + sml_cleanup_history (); +#endif } nxtTrig = isOK ? 43200 : 60; // depending on NTP update success check again in 12 h or in 1 min @@ -226,8 +262,10 @@ void app::tickCalcSunrise(void) { uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig, "Sunri"); +#ifdef AHOY_MQTT_SUPPORT if (mMqttEnabled) tickSun(); +#endif } //----------------------------------------------------------------------------- @@ -251,22 +289,26 @@ void app::tickIVCommunication(void) { tickComm(); } +#ifdef AHOY_MQTT_SUPPORT //----------------------------------------------------------------------------- void app::tickSun(void) { // only used and enabled by MQTT (see setup()) if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec, mConfig->sun.disNightCom)) once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry } +#endif //----------------------------------------------------------------------------- void app::tickComm(void) { if ((!mIVCommunicationOn) && (mConfig->inst.rstValsCommStop)) once(std::bind(&app::tickZeroValues, this), mConfig->nrf.sendInterval, "tZero"); +#ifdef AHOY_MQTT_SUPPORT if (mMqttEnabled) { if (!mMqtt.tickerComm(!mIVCommunicationOn)) once(std::bind(&app::tickComm, this), 5, "mqCom"); // MQTT not connected, retry after 5s } +#endif } //----------------------------------------------------------------------------- @@ -300,24 +342,31 @@ void app::tickMinute(void) { //----------------------------------------------------------------------------- void app::tickMidnight(void) { - // only triggered if 'reset values at midnight is enabled' - 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"); - - Inverter<> *iv; - // set values to zero, except yield total - for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { - iv = mSys.getInverterByPos(id); - if (NULL == iv) - continue; // skip to next inverter + if (mConfig->inst.rstYieldMidNight) { + // only if 'reset values at midnight is enabled' + 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"); + + Inverter<> *iv; + // set values to zero, except yield total + for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { + iv = mSys.getInverterByPos(id); + if (NULL == iv) + continue; // skip to next inverter - mPayload.zeroInverterValues(iv); - mPayload.zeroYieldDay(iv); + mPayload.zeroInverterValues(iv); + mPayload.zeroYieldDay(iv); + } +#ifdef AHOY_MQTT_SUPPORT + if (mMqttEnabled) + mMqtt.tickerMidnight(); +#endif } - - if (mMqttEnabled) - mMqtt.tickerMidnight(); + mSys.cleanup_history (); +#ifdef AHOY_SML_OBIS_SUPPORT + sml_cleanup_history(); +#endif } //----------------------------------------------------------------------------- @@ -329,7 +378,7 @@ void app::tickSend(void) { if (mIVCommunicationOn) { if (!mSys.Radio.mBufCtrl.empty()) { if (mConfig->serial.debug) { - DPRINT(DBG_DEBUG, F("recbuf not empty! #")); + DPRINT(DBG_INFO, F("recbuf not empty! #")); DBGPRINTLN(String(mSys.Radio.mBufCtrl.size())); } } @@ -343,11 +392,15 @@ void app::tickSend(void) { if (NULL != iv) { if (iv->config->enabled) { - if (iv->ivGen == IV_HM) - mPayload.ivSend(iv); - else - mMiPayload.ivSend(iv); + if (iv->ivGen == IV_HM) + mPayload.ivSend(iv); + else + mMiPayload.ivSend(iv); + } else { + DPRINTLN (DBG_INFO, "iv not enabled"); } + } else { + DPRINTLN (DBG_INFO, "no inverter"); } } else { if (mConfig->serial.debug) @@ -371,7 +424,9 @@ void app::resetSystem(void) { mSunrise = 0; mSunset = 0; +#ifdef AHOY_MQTT_SUPPORT mMqttEnabled = false; +#endif mSendLastIvId = 0; mShowRebootRequest = false; @@ -382,10 +437,12 @@ void app::resetSystem(void) { memset(&mStat, 0, sizeof(statistics_t)); } +#ifdef AHOY_MQTT_SUPPORT //----------------------------------------------------------------------------- void app::mqttSubRxCb(JsonObject obj) { mApi.ctrlRequest(obj); } +#endif //----------------------------------------------------------------------------- void app::setupLed(void) { @@ -416,6 +473,7 @@ void app::updateLed(void) { } } +#ifdef AHOY_MQTT_SUPPORT if (mConfig->led.led1 != 0xff) { if (getMqttIsConnected()) { digitalWrite(mConfig->led.led1, led_on); @@ -423,4 +481,41 @@ void app::updateLed(void) { digitalWrite(mConfig->led.led1, led_off); } } +#endif +} + +//----------------------------------------------------------------------------- +void app::check_hist_file (File file) +{ + if (file) { + uint16_t exp_index = AHOY_MIN_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL, index; + unsigned char data[4]; + + while (file.read (data, sizeof (data)) == sizeof (data)) { + index = data[0] + (data[1] << 8); + if (index != exp_index) { + DPRINTLN (DBG_WARN, "Unexpected " + String (index) + " <-> " + String (exp_index)); + } + exp_index = index + 1; + } + file.close(); + } +} + +//----------------------------------------------------------------------------- +void app::show_history (String path) +{ + Dir dir = LittleFS.openDir (path); + + DPRINTLN (DBG_INFO, "Enter Dir: " + path); + while (dir.next()) { + if (dir.isDirectory ()) { + show_history (path + "/" + dir.fileName()); + } else { + DPRINTLN (DBG_INFO, "file " + dir.fileName() + + ", Size: " + String (dir.fileSize())); + check_hist_file (dir.openFile ("r")); + } + } + DPRINTLN (DBG_INFO, "Leave Dir: " + path); } diff --git a/src/app.h b/src/app.h index b5f30346b..aa555072d 100644 --- a/src/app.h +++ b/src/app.h @@ -37,12 +37,15 @@ typedef HmPayload PayloadType; typedef MiPayload MiPayloadType; typedef Web WebType; typedef RestApi RestApiType; +#ifdef AHOY_MQTT_SUPPORT typedef PubMqtt PubMqttType; +#endif typedef PubSerial PubSerialType; // PLUGINS #include "plugins/Display/Display.h" typedef Display DisplayType; +// #include class app : public IApp, public ah::Scheduler { public: @@ -54,6 +57,7 @@ class app : public IApp, public ah::Scheduler { void loopStandard(void); void loopWifi(void); void onWifi(bool gotIp); + void cleanup_history(void); void regularTickers(void); void handleIntr(void) { @@ -135,7 +139,7 @@ class app : public IApp, public ah::Scheduler { bool getRebootRequestState() { return mShowRebootRequest; } - +#ifdef AHOY_MQTT_SUPPORT void setMqttDiscoveryFlag() { once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); } @@ -143,7 +147,7 @@ class app : public IApp, public ah::Scheduler { void setMqttPowerLimitAck(Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); } - +#endif void ivSendHighPrio(Inverter<> *iv) { if(mIVCommunicationOn) { // only send commands if communcation is enabled if (iv->ivGen == IV_HM) @@ -152,7 +156,7 @@ class app : public IApp, public ah::Scheduler { mMiPayload.ivSendHighPrio(iv); } } - +#ifdef AHOY_MQTT_SUPPORT bool getMqttIsConnected() { return mMqtt.isConnected(); } @@ -164,7 +168,7 @@ class app : public IApp, public ah::Scheduler { uint32_t getMqttRxCnt() { return mMqtt.getRxCnt(); } - +#endif bool getProtection(AsyncWebServerRequest *request) { return mWeb.isProtected(request); } @@ -205,21 +209,23 @@ class app : public IApp, public ah::Scheduler { HmSystemType mSys; + private: typedef std::function innerLoopCb; void resetSystem(void); void payloadEventListener(uint8_t cmd) { - #if !defined(AP_ONLY) + #if !defined(AP_ONLY) && defined(AHOY_MQTT_SUPPORT) if (mMqttEnabled) mMqtt.payloadEventListener(cmd); #endif if(mConfig->plugin.display.type != 0) mDisplay.payloadEventListener(cmd); } - +#ifdef AHOY_MQTT_SUPPORT void mqttSubRxCb(JsonObject obj); +#endif void setupLed(); void updateLed(); @@ -251,7 +257,11 @@ class app : public IApp, public ah::Scheduler { void tickMinute(void); void tickZeroValues(void); void tickMidnight(void); - /*void tickSerial(void) { + void check_hist_file (File file); + void show_history (String path); + + + /* void tickSerial(void) { if(Serial.available() == 0) return; @@ -264,7 +274,7 @@ class app : public IApp, public ah::Scheduler { DBGPRINT(String(buf[i], HEX) + " "); } DBGPRINTLN(""); - }*/ + } */ innerLoopCb mInnerLoopCb; @@ -283,16 +293,19 @@ class app : public IApp, public ah::Scheduler { settings_t *mConfig; bool mSavePending; bool mSaveReboot; + bool mCritical; uint8_t mSendLastIvId; bool mSendFirst; statistics_t mStat; +#ifdef AHOY_MQTT_SUPPORT // mqtt PubMqttType mMqtt; - bool mMqttReconnect; bool mMqttEnabled; +#endif + bool mMqttReconnect; // sun int32_t mCalculatedTimezoneOffset; diff --git a/src/appInterface.h b/src/appInterface.h index 0c67415ea..d62d57f26 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -40,14 +40,17 @@ class IApp { virtual bool getRebootRequestState() = 0; virtual bool getSettingsValid() = 0; +#ifdef AHOY_MQTT_SUPPORT virtual void setMqttDiscoveryFlag() = 0; virtual void setMqttPowerLimitAck(Inverter<> *iv) = 0; +#endif virtual void ivSendHighPrio(Inverter<> *iv) = 0; - +#ifdef AHOY_MQTT_SUPPORT virtual bool getMqttIsConnected() = 0; virtual uint32_t getMqttRxCnt() = 0; virtual uint32_t getMqttTxCnt() = 0; +#endif virtual bool getProtection(AsyncWebServerRequest *request) = 0; }; diff --git a/src/defines.h b/src/defines.h index 93722b6c1..22affda5a 100644 --- a/src/defines.h +++ b/src/defines.h @@ -69,10 +69,17 @@ union serial_u { uint8_t b[8]; }; + #define MIN_SERIAL_INTERVAL 2 // 5 #define MIN_SEND_INTERVAL 15 #define MIN_MQTT_INTERVAL 60 +#define AHOY_HIST_PATH "/hist" +// reduce resources and increase clarity for statistic output +#define AHOY_MIN_PAC_SUN_HOUR 6 +#define AHOY_MAX_PAC_SUN_HOUR 20 +// average power interval in minutes +#define AHOY_PAC_INTERVAL 10 #define MQTT_STATUS_NOT_AVAIL_NOT_PROD 0 #define MQTT_STATUS_AVAIL_NOT_PROD 1 diff --git a/src/platformio.ini b/src/platformio.ini index a8fa0d03b..4bb92fbcc 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -1,154 +1,38 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html - -[platformio] -src_dir = . -include_dir = . - -[env] -framework = arduino -board_build.filesystem = littlefs -upload_speed = 921600 -monitor_speed = 115200 - -extra_scripts = - pre:../scripts/auto_firmware_version.py - pre:web/html/convert.py - -lib_deps = - https://github.com/yubox-node-org/ESPAsyncWebServer - nrf24/RF24 @ ^1.4.5 - paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.4.2 - bblanchon/ArduinoJson @ ^6.21.2 - https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.34.17 - zinggjm/GxEPD2 @ ^1.5.0 - - -[env:esp8266-release] -platform = espressif8266 -board = esp12e -board_build.f_cpu = 80000000L -build_flags = -D RELEASE - ;-Wl,-Map,output.map -monitor_filters = - ;default ; Remove typical terminal control codes from input - ;time ; Add timestamp with milliseconds for each new line - ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - esp8266_exception_decoder - - -[env:esp8266-release-prometheus] -platform = espressif8266 -board = esp12e -board_build.f_cpu = 80000000L -build_flags = -D RELEASE -DENABLE_PROMETHEUS_EP -monitor_filters = - ;default ; Remove typical terminal control codes from input - ;time ; Add timestamp with milliseconds for each new line - ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - esp8266_exception_decoder - -[env:esp8266-debug] -platform = espressif8266 -board = esp12e -board_build.f_cpu = 80000000L -build_flags = -DDEBUG_LEVEL=DBG_DEBUG -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_OOM -DDEBUG_ESP_PORT=Serial -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 -build_type = debug -monitor_filters = - ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line - log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - -[env:esp8285-release] -platform = espressif8266 -board = esp8285 -board_build.ldscript = eagle.flash.1m64.ld -board_build.f_cpu = 80000000L -build_flags = -D RELEASE -monitor_filters = - ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line - ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - -[env:esp8285-debug] -platform = espressif8266 -board = esp8285 -board_build.ldscript = eagle.flash.1m64.ld -board_build.f_cpu = 80000000L -build_flags = -DDEBUG_LEVEL=DBG_DEBUG - -DDEBUG_ESP_CORE - -DDEBUG_ESP_WIFI - -DDEBUG_ESP_HTTP_CLIENT - -DDEBUG_ESP_HTTP_SERVER - -DDEBUG_ESP_OOM - -DDEBUG_ESP_PORT=Serial -build_type = debug -monitor_filters = - ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line - log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - -[env:esp32-wroom32-release] -platform = espressif32@>=6.1.0 -board = lolin_d32 -build_flags = -D RELEASE -std=gnu++14 -build_unflags = -std=gnu++11 -monitor_filters = - ;default ; Remove typical terminal control codes from input - ;time ; Add timestamp with milliseconds for each new line - ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - esp32_exception_decoder - -[env:esp32-wroom32-release-prometheus] -platform = espressif32@>=6.1.0 -board = lolin_d32 -build_flags = -D RELEASE - -std=gnu++14 - -DENABLE_PROMETHEUS_EP -build_unflags = -std=gnu++11 -monitor_filters = - ;default ; Remove typical terminal control codes from input - ;time ; Add timestamp with milliseconds for each new line - ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - esp32_exception_decoder - -[env:esp32-wroom32-debug] -platform = espressif32@>=6.1.0 -board = lolin_d32 -build_flags = -DDEBUG_LEVEL=DBG_DEBUG - -DDEBUG_ESP_CORE - -DDEBUG_ESP_WIFI - -DDEBUG_ESP_HTTP_CLIENT - -DDEBUG_ESP_HTTP_SERVER - -DDEBUG_ESP_OOM - -DDEBUG_ESP_PORT=Serial - -std=gnu++14 -build_unflags = -std=gnu++11 -build_type = debug -monitor_filters = - ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line - log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory - -[env:opendtufusionv1-release] -platform = espressif32@>=6.1.0 -board = esp32-s3-devkitc-1 -upload_protocol = esp-builtin -upload_speed = 115200 -debug_tool = esp-builtin -debug_speed = 12000 -build_flags = -D RELEASE -std=gnu++14 -build_unflags = -std=gnu++11 -monitor_filters = - ;default ; Remove typical terminal control codes from input - time ; Add timestamp with milliseconds for each new line - ;log2file ; Log data to a file “platformio-device-monitor-*.log” located in the current working directory +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[platformio] +src_dir = c:\AhoiDTU\src +include_dir = c:\AhoiDTU\src + +[env] +board = d1_mini +board_build.f_cpu = 80000000L +platform=https://github.com/platformio/platform-espressif8266.git@^4.2.0 +framework = arduino +board_build.filesystem = littlefs +upload_speed = 921600 +monitor_speed = 115200 +extra_scripts = + pre:../scripts/auto_firmware_version.py + pre:web/html/convert.py +lib_deps = + https://github.com/yubox-node-org/ESPAsyncWebServer + nrf24/RF24@1.4.5 + paulstoffregen/Time @ ^1.6.1 + https://github.com/bertmelis/espMqttClient#v1.4.2 + bblanchon/ArduinoJson @ ^6.21.2 + https://github.com/JChristensen/Timezone @ ^1.2.4 + olikraus/U8g2 @ ^2.34.17 + zinggjm/GxEPD2 @ ^1.5.0 + +[env:esp8266-release] +build_flags = -D RELEASE +monitor_filters = esp8266_exception_decoder From bc579e3d41b4557fe20056361d043ba7206c45c2 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:53:39 +0200 Subject: [PATCH 02/48] Add files via upload --- src/config/config.h | 16 ++++++++++++++-- src/config/settings.h | 1 - 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/config/config.h b/src/config/config.h index ac28c1a26..a3c0024b2 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -71,8 +71,8 @@ // number of packets hold in buffer #define PACKET_BUFFER_SIZE 30 -// number of configurable inverters -#define MAX_NUM_INVERTERS 10 +// number of configurable inverters (increase if you need, and if there is enough heap available for stable operation) +#define MAX_NUM_INVERTERS 2 // default serial interval #define SERIAL_INTERVAL 5 @@ -110,6 +110,15 @@ // NTP refresh interval in ms (default 12h) #define NTP_REFRESH_INTERVAL 12 * 3600 * 1000 +#define AHOY_SML_OBIS_SUPPORT +#ifdef AHOY_SML_OBIS_SUPPORT +#define AHOY_CHARTDATA_HDR "Time, AC Power, Net Power" +#else +#define AHOY_CHARTDATA_HDR "Time, AC Power" +#endif + +// #define AHOY_MQTT_SUPPORT + // default mqtt interval #define MQTT_INTERVAL 90 @@ -128,6 +137,9 @@ // default MQTT topic #define DEF_MQTT_TOPIC "inverter" + + + // discovery prefix #define MQTT_DISCOVERY_PREFIX "homeassistant" diff --git a/src/config/settings.h b/src/config/settings.h index d04fdce52..c90019e20 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -50,7 +50,6 @@ #define DEF_PROT_API 0x0000 #define DEF_PROT_MQTT 0x0000 - typedef struct { uint8_t ip[4]; // ip address uint8_t mask[4]; // sub mask From 44e1cae4ea3ea740be2069d33a1fcb235206f61f Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:55:00 +0200 Subject: [PATCH 03/48] Add files via upload --- src/hm/hmDefines.h | 29 ++++++-- src/hm/hmInverter.h | 55 +++++++++++++- src/hm/hmPayload.h | 55 +++++++++++--- src/hm/hmRadio.h | 6 ++ src/hm/hmSystem.h | 177 +++++++++++++++++++++++++++++++++++++++++++- src/hm/miPayload.h | 4 +- 6 files changed, 303 insertions(+), 23 deletions(-) diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index bd12f72ba..c7f853f57 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -9,6 +9,7 @@ #include "../utils/dbg.h" #include + // inverter generations enum {IV_HM = 0, IV_MI}; @@ -21,17 +22,17 @@ enum {FLD_UDC = 0, FLD_IDC, FLD_PDC, FLD_YD, FLD_YW, FLD_YT, FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_T, FLD_PF, FLD_EFF, FLD_IRR, FLD_Q, FLD_EVT, FLD_FW_VERSION, FLD_FW_BUILD_YEAR, FLD_FW_BUILD_MONTH_DAY, FLD_FW_BUILD_HOUR_MINUTE, FLD_HW_ID, - FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE}; - + FLD_ACT_ACTIVE_PWR_LIMIT, /*FLD_ACT_REACTIVE_PWR_LIMIT, FLD_ACT_PF,*/ FLD_LAST_ALARM_CODE, FLD_MP}; + const char* const fields[] = {"U_DC", "I_DC", "P_DC", "YieldDay", "YieldWeek", "YieldTotal", "U_AC", "I_AC", "P_AC", "F_AC", "Temp", "PF_AC", "Efficiency", "Irradiation","Q_AC", "ALARM_MES_ID","FWVersion","FWBuildYear","FWBuildMonthDay","FWBuildHourMinute","HWPartId", - "active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode"}; + "active_PowerLimit", /*"reactivePowerLimit","Powerfactor",*/ "LastAlarmCode", "MaxPower"}; const char* const notAvail = "n/a"; const uint8_t fieldUnits[] = {UNIT_V, UNIT_A, UNIT_W, UNIT_WH, UNIT_KWH, UNIT_KWH, UNIT_V, UNIT_A, UNIT_W, UNIT_HZ, UNIT_C, UNIT_NONE, UNIT_PCT, UNIT_PCT, UNIT_VAR, - UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE}; + UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_NONE, UNIT_PCT, UNIT_NONE, UNIT_W}; // mqtt discovery device classes enum {DEVICE_CLS_NONE = 0, DEVICE_CLS_CURRENT, DEVICE_CLS_ENERGY, DEVICE_CLS_PWR, DEVICE_CLS_VOLTAGE, DEVICE_CLS_FREQ, DEVICE_CLS_TEMP}; @@ -62,7 +63,7 @@ const byteAssign_fieldDeviceClass deviceFieldAssignment[] = { #define DEVICE_CLS_ASSIGN_LIST_LEN (sizeof(deviceFieldAssignment) / sizeof(byteAssign_fieldDeviceClass)) // indices to calculation functions, defined in hmInverter.h -enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH, CALC_PDC_CH0, CALC_EFF_CH0, CALC_IRR_CH}; +enum {CALC_YT_CH0 = 0, CALC_YD_CH0, CALC_UDC_CH, CALC_PDC_CH0, CALC_EFF_CH0, CALC_IRR_CH, CALC_MPAC_CH0, CALC_MPDC_CH}; enum {CMD_CALC = 0xffff}; @@ -126,6 +127,7 @@ const byteAssign_t hm1chAssignment[] = { { FLD_YD, UNIT_WH, CH1, 12, 2, 1 }, { FLD_YT, UNIT_KWH, CH1, 8, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, + { FLD_MP, UNIT_W, CH1, CALC_MPDC_CH, CH1, CMD_CALC }, { FLD_UAC, UNIT_V, CH0, 14, 2, 10 }, { FLD_IAC, UNIT_A, CH0, 22, 2, 100 }, @@ -138,7 +140,9 @@ const byteAssign_t hm1chAssignment[] = { { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } + { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }, + { FLD_MP, UNIT_W, CH0, CALC_MPAC_CH0, 0, CMD_CALC } + }; #define HM1CH_LIST_LEN (sizeof(hm1chAssignment) / sizeof(byteAssign_t)) #define HM1CH_PAYLOAD_LEN 30 @@ -154,6 +158,7 @@ const byteAssign_t hm2chAssignment[] = { { FLD_YD, UNIT_WH, CH1, 22, 2, 1 }, { FLD_YT, UNIT_KWH, CH1, 14, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, + { FLD_MP, UNIT_W, CH1, CALC_MPDC_CH, CH1, CMD_CALC }, { FLD_UDC, UNIT_V, CH2, 8, 2, 10 }, { FLD_IDC, UNIT_A, CH2, 10, 2, 100 }, @@ -161,6 +166,7 @@ const byteAssign_t hm2chAssignment[] = { { FLD_YD, UNIT_WH, CH2, 24, 2, 1 }, { FLD_YT, UNIT_KWH, CH2, 18, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, + { FLD_MP, UNIT_W, CH2, CALC_MPDC_CH, CH2, CMD_CALC }, { FLD_UAC, UNIT_V, CH0, 26, 2, 10 }, { FLD_IAC, UNIT_A, CH0, 34, 2, 100 }, @@ -173,7 +179,8 @@ const byteAssign_t hm2chAssignment[] = { { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } + { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }, + { FLD_MP, UNIT_W, CH0, CALC_MPAC_CH0, 0, CMD_CALC } }; #define HM2CH_LIST_LEN (sizeof(hm2chAssignment) / sizeof(byteAssign_t)) @@ -190,6 +197,7 @@ const byteAssign_t hm4chAssignment[] = { { FLD_YD, UNIT_WH, CH1, 20, 2, 1 }, { FLD_YT, UNIT_KWH, CH1, 12, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH1, CALC_IRR_CH, CH1, CMD_CALC }, + { FLD_MP, UNIT_W, CH1, CALC_MPDC_CH, CH1, CMD_CALC }, { FLD_UDC, UNIT_V, CH2, CALC_UDC_CH, CH1, CMD_CALC }, { FLD_IDC, UNIT_A, CH2, 6, 2, 100 }, @@ -197,6 +205,7 @@ const byteAssign_t hm4chAssignment[] = { { FLD_YD, UNIT_WH, CH2, 22, 2, 1 }, { FLD_YT, UNIT_KWH, CH2, 16, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH2, CALC_IRR_CH, CH2, CMD_CALC }, + { FLD_MP, UNIT_W, CH2, CALC_MPDC_CH, CH2, CMD_CALC }, { FLD_UDC, UNIT_V, CH3, 24, 2, 10 }, { FLD_IDC, UNIT_A, CH3, 26, 2, 100 }, @@ -204,6 +213,7 @@ const byteAssign_t hm4chAssignment[] = { { FLD_YD, UNIT_WH, CH3, 42, 2, 1 }, { FLD_YT, UNIT_KWH, CH3, 34, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH3, CALC_IRR_CH, CH3, CMD_CALC }, + { FLD_MP, UNIT_W, CH3, CALC_MPDC_CH, CH3, CMD_CALC }, { FLD_UDC, UNIT_V, CH4, CALC_UDC_CH, CH3, CMD_CALC }, { FLD_IDC, UNIT_A, CH4, 28, 2, 100 }, @@ -211,6 +221,7 @@ const byteAssign_t hm4chAssignment[] = { { FLD_YD, UNIT_WH, CH4, 44, 2, 1 }, { FLD_YT, UNIT_KWH, CH4, 38, 4, 1000 }, { FLD_IRR, UNIT_PCT, CH4, CALC_IRR_CH, CH4, CMD_CALC }, + { FLD_MP, UNIT_W, CH4, CALC_MPDC_CH, CH4, CMD_CALC }, { FLD_UAC, UNIT_V, CH0, 46, 2, 10 }, { FLD_IAC, UNIT_A, CH0, 54, 2, 100 }, @@ -223,7 +234,9 @@ const byteAssign_t hm4chAssignment[] = { { FLD_YD, UNIT_WH, CH0, CALC_YD_CH0, 0, CMD_CALC }, { FLD_YT, UNIT_KWH, CH0, CALC_YT_CH0, 0, CMD_CALC }, { FLD_PDC, UNIT_W, CH0, CALC_PDC_CH0, 0, CMD_CALC }, - { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC } + { FLD_EFF, UNIT_PCT, CH0, CALC_EFF_CH0, 0, CMD_CALC }, + { FLD_MP, UNIT_W, CH0, CALC_MPAC_CH0, 0, CMD_CALC } + }; #define HM4CH_LIST_LEN (sizeof(hm4chAssignment) / sizeof(byteAssign_t)) #define HM4CH_PAYLOAD_LEN 62 diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index b1390b29f..c4a8af33a 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -47,6 +47,12 @@ static T calcEffiencyCh0(Inverter<> *iv, uint8_t arg0); template static T calcIrradiation(Inverter<> *iv, uint8_t arg0); +template +static T calcMaxPowerAcCh0(Inverter<> *iv, uint8_t arg0); + +template +static T calcMaxPowerDc(Inverter<> *iv, uint8_t arg0); + template using func_t = T (Inverter<> *, uint8_t); @@ -98,7 +104,9 @@ const calcFunc_t calcFunctions[] = { { CALC_UDC_CH, &calcUdcCh }, { CALC_PDC_CH0, &calcPowerDcCh0 }, { CALC_EFF_CH0, &calcEffiencyCh0 }, - { CALC_IRR_CH, &calcIrradiation } + { CALC_IRR_CH, &calcIrradiation }, + { CALC_MPAC_CH0, &calcMaxPowerAcCh0 }, + { CALC_MPDC_CH, &calcMaxPowerDc } }; @@ -122,6 +130,8 @@ class Inverter { //String lastAlarmMsg; bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) + uint32_t pac_sum; // average calc for Highcharts: sum of ac power values for cur interval + uint16_t pac_cnt; // average calc for Highcharts: number of ac power values for cur interval Inverter() { ivGen = IV_HM; @@ -143,9 +153,11 @@ class Inverter { template void enqueCommand(uint8_t cmd) { _commandQueue.push(std::make_shared(cmd)); +#ifdef undef DPRINT_IVID(DBG_INFO, id); DBGPRINT(F("enqueCommand: 0x")); DBGHEXLN(cmd); +#endif } void setQueuedCmdFinished() { @@ -677,4 +689,45 @@ static T calcIrradiation(Inverter<> *iv, uint8_t arg0) { return 0.0; } +template +static T calcMaxPowerAcCh0(Inverter<> *iv, uint8_t arg0) { + DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcMaxPowerAcCh0")); + T acMaxPower = 0.0; + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC, rec); + T acPower = iv->getValue(pos, rec); + + for(uint8_t i = 0; i < rec->length; i++) { + if((FLD_MP == rec->assign[i].fieldId) && (0 == rec->assign[i].ch)) { + acMaxPower = iv->getValue(i, rec); + } + } + if(acPower > acMaxPower) + return acPower; + } + return acMaxPower; +} + +template +static T calcMaxPowerDc(Inverter<> *iv, uint8_t arg0) { + DPRINTLN(DBG_VERBOSE, F("hmInverter.h:calcMaxPowerDc")); + // arg0 = channel + T dcMaxPower = 0.0; + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + uint8_t pos = iv->getPosByChFld(arg0, FLD_PDC, rec); + T dcPower = iv->getValue(pos, rec); + + for(uint8_t i = 0; i < rec->length; i++) { + if((FLD_MP == rec->assign[i].fieldId) && (arg0 == rec->assign[i].ch)) { + dcMaxPower = iv->getValue(i, rec); + } + } + if(dcPower > dcMaxPower) + return dcPower; + } + return dcMaxPower; +} + #endif /*__HM_INVERTER_H__*/ diff --git a/src/hm/hmPayload.h b/src/hm/hmPayload.h index 29f380fa3..e77263970 100644 --- a/src/hm/hmPayload.h +++ b/src/hm/hmPayload.h @@ -24,6 +24,7 @@ typedef struct { uint8_t retransmits; bool requested; bool gotFragment; + bool rxTmo; } invPayload_t; @@ -77,6 +78,8 @@ class HmPayload { for(uint8_t ch = 0; ch <= iv->channels; ch++) { pos = iv->getPosByChFld(ch, FLD_YD, rec); iv->setValue(pos, rec, 0.0f); + pos = iv->getPosByChFld(ch, FLD_MP, rec); + iv->setValue(pos, rec, 0.0f); } } @@ -89,6 +92,7 @@ class HmPayload { switch(fld) { case FLD_YD: case FLD_YT: + case FLD_MP: continue; } pos = iv->getPosByChFld(ch, fld, rec); @@ -104,6 +108,8 @@ class HmPayload { } void ivSend(Inverter<> *iv, bool highPrio = false) { + bool save_rxTmo; + if(!highPrio) { if (mPayload[iv->id].requested) { if (!mPayload[iv->id].complete) @@ -129,15 +135,19 @@ class HmPayload { } } + save_rxTmo = mPayload[iv->id].rxTmo; reset(iv->id); + mPayload[iv->id].rxTmo = save_rxTmo; mPayload[iv->id].requested = true; yield(); +#ifdef undef if (mSerialDebug) { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINT(F("Requesting Inv SN ")); DBGPRINTLN(String(iv->config->serial.u64, HEX)); } +#endif if (iv->getDevControlRequest()) { if (mSerialDebug) { @@ -194,11 +204,14 @@ class HmPayload { if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { bool ok = true; - if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) + + if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) { +#ifdef AHOY_MQTT_SUPPORT mApp->setMqttPowerLimitAck(iv); - else +#endif + } else { ok = false; - + } DPRINT_IVID(DBG_INFO, iv->id); DBGPRINT(F("has ")); if(!ok) DBGPRINT(F("not ")); @@ -248,15 +261,16 @@ class HmPayload { mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true); } else { if(false == mPayload[iv->id].gotFragment) { - /* - DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit")); - mPayload[iv->id].txCmd = iv->getQueuedCmd(); - DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX)); - mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); - */ - DPRINT_IVID(DBG_INFO, iv->id); - DBGPRINTLN(F("nothing received")); - mPayload[iv->id].retransmits = mMaxRetrans; + DPRINT_IVID(DBG_WARN, iv->id); + if (mPayload[iv->id].rxTmo) { + DBGPRINTLN(F("nothing received")); + mPayload[iv->id].retransmits = mMaxRetrans; + } else { + DBGPRINTLN(F("nothing received: complete retransmit")); + mPayload[iv->id].txCmd = iv->getQueuedCmd(); + DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX)); + mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); + } } else { for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) { if (mPayload[iv->id].len[i] == 0) { @@ -271,6 +285,8 @@ class HmPayload { } } } + } else { + mPayload[iv->id].rxTmo = true; // inv might be down, no complete retransmit anymore } } } else if(!crcPass && pyldComplete) { // crc error on complete Payload @@ -284,14 +300,18 @@ class HmPayload { mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true); } } else { // payload complete +#ifdef undef DPRINT(DBG_INFO, F("procPyld: cmd: 0x")); DBGHEXLN(mPayload[iv->id].txCmd); DPRINT(DBG_INFO, F("procPyld: txid: 0x")); DBGHEXLN(mPayload[iv->id].txId); +#endif DPRINT(DBG_DEBUG, F("procPyld: max: ")); DPRINTLN(DBG_DEBUG, String(mPayload[iv->id].maxPackId)); + record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser mPayload[iv->id].complete = true; + mPayload[iv->id].rxTmo = false; uint8_t payload[128]; uint8_t payloadLen = 0; @@ -305,12 +325,14 @@ class HmPayload { } payloadLen -= 2; +#ifdef undef if (mSerialDebug) { DPRINT(DBG_INFO, F("Payload (")); DBGPRINT(String(payloadLen)); DBGPRINT(F("): ")); mSys->Radio.dumpBuf(payload, payloadLen); } +#endif if (NULL == rec) { DPRINTLN(DBG_ERROR, F("record is NULL!")); @@ -324,6 +346,12 @@ class HmPayload { yield(); } iv->doCalculations(); + uint8_t pos = iv->getPosByChFld(CH0, FLD_PAC, rec); + if (pos != 0xff) { + float ac_power = iv->getValue(pos, rec); + mSys->handle_pac (iv, (uint16_t)(ac_power+0.5f)); + } + notify(mPayload[iv->id].txCmd); if(AlarmData == mPayload[iv->id].txCmd) { @@ -394,8 +422,10 @@ class HmPayload { } void reset(uint8_t id) { +#ifdef undef DPRINT_IVID(DBG_INFO, id); DBGPRINTLN(F("resetPayload")); +#endif memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES); mPayload[id].txCmd = 0; mPayload[id].gotFragment = false; @@ -405,6 +435,7 @@ class HmPayload { mPayload[id].complete = false; mPayload[id].requested = false; mPayload[id].ts = *mTimestamp; + mPayload[id].rxTmo = true; // design: dont start with complete retransmit } IApp *mApp; diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 35993829e..29d8ae0d6 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -306,12 +306,14 @@ class HmRadio { } void initPacket(uint64_t invId, uint8_t mid, uint8_t pid) { +#ifdef undef if(mSerialDebug) { DPRINT(DBG_VERBOSE, F("initPacket, mid: ")); DHEX(mid); DBGPRINT(F(" pid: ")); DBGHEXLN(pid); } +#endif memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE); mTxBuf[0] = mid; // message id CP_U32_BigEndian(&mTxBuf[1], (invId >> 8)); @@ -341,10 +343,14 @@ class HmRadio { if(mSerialDebug) { DPRINT(DBG_INFO, F("TX ")); DBGPRINT(String(len)); +#ifdef undef DBGPRINT("B Ch"); DBGPRINT(String(mRfChLst[mTxChIdx])); DBGPRINT(F(" | ")); dumpBuf(mTxBuf, len); +#else + DBGPRINTLN (" Bytes"); +#endif } mNrf24.stopListening(); diff --git a/src/hm/hmSystem.h b/src/hm/hmSystem.h index a95d4d24f..f6f42b7e4 100644 --- a/src/hm/hmSystem.h +++ b/src/hm/hmSystem.h @@ -8,6 +8,10 @@ #include "hmInverter.h" #include "hmRadio.h" +#include "../config/settings.h" + +#define AC_POWER_PATH AHOY_HIST_PATH "/ac_power" +#define AC_FORMAT_FILE_NAME "%02u_%02u_%04u.bin" template > class HmSystem { @@ -21,7 +25,8 @@ class HmSystem { Radio.setup(); } - void setup(uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin, uint8_t sclkPin, uint8_t mosiPin, uint8_t misoPin) { + void setup(uint32 *timestamp, uint8_t ampPwr, uint8_t irqPin, uint8_t cePin, uint8_t csPin, uint8_t sclkPin, uint8_t mosiPin, uint8_t misoPin) { + mTimestamp = timestamp; mNumInv = 0; Radio.setup(ampPwr, irqPin, cePin, csPin, sclkPin, mosiPin, misoPin); } @@ -128,9 +133,179 @@ class HmSystem { Radio.enableDebug(); } + //----------------------------------------------------------------------------- + void cleanup_history () + { + time_t time_today; + INVERTERTYPE *p; + uint16_t i; + + time_today = *mTimestamp; + + cur_pac_index = 0; + for (i = 0, p = mInverter; i < MAX_NUM_INVERTERS; i++, p++) { + p->pac_cnt = 0; + p->pac_sum = 0; + } + + if (time_today) { + Dir ac_power_dir; + struct tm tm_today; + char cur_file_name[sizeof (AC_FORMAT_FILE_NAME)]; + + localtime_r (&time_today, &tm_today); + snprintf (cur_file_name, sizeof (cur_file_name), AC_FORMAT_FILE_NAME, + tm_today.tm_mday, tm_today.tm_mon+1, tm_today.tm_year + 1900); + ac_power_dir = LittleFS.openDir (AC_POWER_PATH); + /* design: no dataserver, cleanup old history */ + + while (ac_power_dir.next()) { + if (ac_power_dir.fileName() != cur_file_name) { + DPRINTLN (DBG_INFO, "Remove file " + ac_power_dir.fileName() + + ", Size: " + String (ac_power_dir.fileSize())); + LittleFS.remove (AC_POWER_PATH "/" + ac_power_dir.fileName()); + } + } + } else { + DPRINTLN (DBG_WARN, "cleanup_history, no time yet"); + } + } + + //----------------------------------------------------------------------------- + File open_hist () + { + File file = (File) NULL; + char file_name[sizeof (AC_POWER_PATH) + sizeof (AC_FORMAT_FILE_NAME)]; + time_t time_today = *mTimestamp; + struct tm tm_today; + + localtime_r (&time_today, &tm_today); + snprintf (file_name, sizeof (file_name), AC_POWER_PATH "/" AC_FORMAT_FILE_NAME, + tm_today.tm_mday, tm_today.tm_mon+1, tm_today.tm_year + 1900); + file = LittleFS.open (file_name, "r"); + if (!file) { + DPRINT (DBG_WARN, "open_hist, failed to open "); + DBGPRINTLN (file_name); + } + return file; + } + + //----------------------------------------------------------------------------- + bool has_pac_value () + { + if (*mTimestamp) { + INVERTERTYPE *p; + uint16_t i; + + for (i = 0, p = mInverter; i < MAX_NUM_INVERTERS; i++, p++) { + if (p->config->serial.u64 && p->isConnected && p->pac_cnt) { + return true; + } + } + } + return false; + } + + //----------------------------------------------------------------------------- + uint16_t get_pac_average (bool cleanup) + { + uint32_t pac_average = 0; + INVERTERTYPE *p; + uint16_t i; + + for (i = 0, p = mInverter; i < MAX_NUM_INVERTERS; i++, p++) { + if (p->config->serial.u64 && p->isConnected && p->pac_cnt) { + pac_average += (p->pac_sum + (p->pac_cnt >> 1)) / p->pac_cnt; + } + if (cleanup) { + p->pac_sum = 0; + p->pac_cnt = 0; + } + } + if (pac_average > UINT16_MAX) { + pac_average = UINT16_MAX; + } + return (uint16_t)pac_average; + } + + //----------------------------------------------------------------------------- + bool get_cur_value (uint16_t *interval, uint16_t *pac) + { + if (has_pac_value()) { + *interval = cur_pac_index; + *pac = get_pac_average (false); + return true; + } + DPRINTLN (DBG_INFO, "get_cur_value: none"); + return false; + } + + //----------------------------------------------------------------------------- + void close_hist (File file) + { + if (file) { + file.close (); + } + } + + //----------------------------------------------------------------------------- + void handle_pac (INVERTERTYPE *p, uint16_t pac) + { + if (*mTimestamp) { + uint32_t pac_index = gTimezone.toLocal (*mTimestamp); + + pac_index = hour(pac_index) * 60 + minute(pac_index); + pac_index /= AHOY_PAC_INTERVAL; + if (pac_index != cur_pac_index) { + if (has_pac_value ()) { + /* calc sum of all inverter averages for last interval */ + /* and cleanup all counts and sums */ + uint16_t pac_average = get_pac_average(true); + File file; + char file_name[sizeof (AC_POWER_PATH) + sizeof (AC_FORMAT_FILE_NAME)]; + time_t time_today = *mTimestamp; + struct tm tm_today; + + localtime_r (&time_today, &tm_today); + snprintf (file_name, sizeof (file_name), AC_POWER_PATH "/" AC_FORMAT_FILE_NAME, + tm_today.tm_mday, tm_today.tm_mon+1, tm_today.tm_year + 1900); + // append last average + if ((file = LittleFS.open (file_name, "a"))) { + unsigned char buf[4]; + buf[0] = cur_pac_index & 0xff; + buf[1] = cur_pac_index >> 8; + buf[2] = pac_average & 0xff; + buf[3] = pac_average >> 8; + if (file.write (buf, sizeof (buf)) != sizeof (buf)) { + DPRINTLN (DBG_WARN, "handle_pac, failed_to_write"); + } else { + DPRINTLN (DBG_DEBUG, "handle_pac, write to " + String(file_name)); + } + file.close (); + } else { + DPRINTLN (DBG_WARN, "handle_pac, failed to open"); + } + } + cur_pac_index = pac_index; + } + if ((pac_index >= AHOY_MIN_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL) && + (pac_index < AHOY_MAX_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL)) { + p->pac_sum += pac; + p->pac_cnt++; + } else { + DPRINTLN (DBG_INFO, "handle_pac, outside daylight, minutes: " + String (pac_index * AHOY_PAC_INTERVAL)); + } + } else { + DPRINTLN (DBG_INFO, "handle_pac, no time2"); + } + } + private: INVERTERTYPE mInverter[MAX_INVERTER]; uint8_t mNumInv; + uint32_t *mTimestamp; + uint16_t cur_pac_index; + }; #endif /*__HM_SYSTEM_H__*/ diff --git a/src/hm/miPayload.h b/src/hm/miPayload.h index fd922c8bf..f1d82aff3 100644 --- a/src/hm/miPayload.h +++ b/src/hm/miPayload.h @@ -11,6 +11,7 @@ #include "../utils/crc.h" #include "../config/config.h" #include +#include typedef struct { uint32_t ts; @@ -311,7 +312,9 @@ const byteAssign_t InfoAssignment[] = { iv->clearDevControlRequest(); if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) { +#ifdef AHOY_MQTT_SUPPORT mApp->setMqttPowerLimitAck(iv); +#endif DPRINT_IVID(DBG_INFO, iv->id); DBGPRINT(F("has accepted power limit set point ")); DBGPRINT(String(iv->powerLimit[0])); @@ -747,7 +750,6 @@ const byteAssign_t InfoAssignment[] = { } ac_pow = (int) (ac_pow*9.5); iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) ac_pow/10); - iv->doCalculations(); iv->setQueuedCmdFinished(); mStat->rxSuccess++; From 3ad302b66f913020f7c3d8c49c86ce8a66a14039 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:55:56 +0200 Subject: [PATCH 04/48] Add files via upload --- src/publisher/pubMqtt.h | 3 +++ src/publisher/pubMqttDefs.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index f9bf6f4d5..40ea17531 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -8,6 +8,8 @@ #ifndef __PUB_MQTT_H__ #define __PUB_MQTT_H__ +#ifdef AHOY_MQTT_SUPPORT + #ifdef ESP8266 #include #elif defined(ESP32) @@ -675,5 +677,6 @@ class PubMqtt { char mVal[40]; discovery_t mDiscovery; }; +#endif #endif /*__PUB_MQTT_H__*/ diff --git a/src/publisher/pubMqttDefs.h b/src/publisher/pubMqttDefs.h index 088023b7a..869243ea9 100644 --- a/src/publisher/pubMqttDefs.h +++ b/src/publisher/pubMqttDefs.h @@ -6,6 +6,8 @@ #ifndef __PUB_MQTT_DEFS_H__ #define __PUB_MQTT_DEFS_H__ +#ifdef AHOY_MQTT_SUPPORT + #include enum { @@ -92,5 +94,6 @@ enum { const char* const subscr[] PROGMEM = { "setup/set_time" }; +#endif #endif /*__PUB_MQTT_DEFS_H__*/ From c60e94509f65a01af4b71252e6f78325f86a3dc4 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:56:44 +0200 Subject: [PATCH 05/48] Add files via upload --- src/plugins/SML_OBIS_Parser.cpp | 1022 +++++++++++++++++++++++++++++++ src/plugins/SML_OBIS_Parser.h | 22 + 2 files changed, 1044 insertions(+) create mode 100644 src/plugins/SML_OBIS_Parser.cpp create mode 100644 src/plugins/SML_OBIS_Parser.h diff --git a/src/plugins/SML_OBIS_Parser.cpp b/src/plugins/SML_OBIS_Parser.cpp new file mode 100644 index 000000000..1ba865a9f --- /dev/null +++ b/src/plugins/SML_OBIS_Parser.cpp @@ -0,0 +1,1022 @@ +#include +#include +#include +#include +#include "../utils/dbg.h" +#include "../utils/scheduler.h" +#include "../config/settings.h" +#include "SML_OBIS_Parser.h" + +#ifdef AHOY_SML_OBIS_SUPPORT + +// you might use this testwise if you dont have an IR sensor connected to your AHOY-DTU +// #define SML_OBIS_TEST + +// at least the size of the largest entry that is of any interest +#define SML_MAX_SERIAL_BUF 32 + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#define SML_ESCAPE_CHAR 0x1b +#define SML_VERSION1_CHAR 0x01 +#define SML_MAX_LIST_LAYER 8 + +#define SML_EXT_LENGTH 0x80 +#define SML_OBIS_GRID_POWER_PATH AHOY_HIST_PATH "/grid_power" +#define SML_OBIS_FORMAT_FILE_NAME "%02u_%02u_%04u.bin" + +#define SML_MSG_GET_LIST_RSP 0x701 + +#define OBIS_SIG_YIELD_IN_ALL "\x01\x08\x00" +#define OBIS_SIG_YIELD_OUT_ALL "\x02\x08\x00" +#define OBIS_SIG_POWER_ALL "\x10\x07\x00" +#define OBIS_SIG_POWER_L1 "\x24\x07\x00" +#define OBIS_SIG_POWER_L2 "\x38\x07\x00" +#define OBIS_SIG_POWER_L3 "\x4c\x07\x00" +/* the folloing OBIS objects may not be transmitted by your electricity meter */ +#define OBIS_SIG_VOLTAGE_L1 "\x20\x07\x00" +#define OBIS_SIG_VOLTAGE_L2 "\x34\x07\x00" +#define OBIS_SIG_VOLTAGE_L3 "\x48\x07\x00" +#define OBIS_SIG_CURRENT_L1 "\x1f\x07\x00" +#define OBIS_SIG_CURRENT_L2 "\x33\x07\x00" +#define OBIS_SIG_CURRENT_L3 "\x47\x07\x00" + +typedef enum _sml_state { + SML_ST_FIND_START_TAG = 0, + SML_ST_FIND_VERSION, + SML_ST_FIND_MSG, + SML_ST_FIND_LIST_ENTRIES, + SML_ST_SKIP_LIST_ENTRY, + SML_ST_FIND_END_TAG, + SML_ST_CHECK_CRC +} sml_state_t; + +typedef enum _sml_list_entry_type { + SML_TYPE_OCTET_STRING = 0x00, + SML_TYPE_BOOL = 0x40, + SML_TYPE_INT = 0x50, + SML_TYPE_UINT = 0x60, + SML_TYPE_LIST = 0x70 +} sml_list_entry_type_t; + +typedef enum _obis_state { + OBIS_ST_NONE = 0, + OBIS_ST_SERIAL_NR, + OBIS_ST_YIELD_IN_ALL, + OBIS_ST_YIELD_OUT_ALL, + OBIS_ST_POWER_ALL, + OBIS_ST_POWER_L1, + OBIS_ST_POWER_L2, + OBIS_ST_POWER_L3, + OBIS_ST_VOLTAGE_L1, + OBIS_ST_VOLTAGE_L2, + OBIS_ST_VOLTAGE_L3, + OBIS_ST_CURRENT_L1, + OBIS_ST_CURRENT_L2, + OBIS_ST_CURRENT_L3, + OBIS_ST_UNKNOWN +} obis_state_t; + +static sml_state_t sml_state = SML_ST_FIND_START_TAG; +static uint16_t cur_sml_list_layer; +static unsigned char sml_list_layer_entries [SML_MAX_LIST_LAYER]; +static unsigned char sml_serial_buf[SML_MAX_SERIAL_BUF]; +static unsigned char *cur_serial_buf = sml_serial_buf; +static uint16 sml_serial_len = 0; +static uint16 sml_skip_len = 0; +static obis_state_t obis_state = OBIS_ST_NONE; +static int obis_power_all_scale, obis_power_all_value; +/* design: max 16 bit fuer aktuelle Powerwerte */ +static int16_t obis_cur_pac; +static uint16_t obis_crc; +static uint16_t obis_cur_pac_cnt; +static uint16_t obis_cur_pac_index; +static int32_t obis_pac_sum; +static uint32_t *obis_timestamp; +static int obis_yield_in_all_scale, obis_yield_out_all_scale; +static uint64_t obis_yield_in_all_value, obis_yield_out_all_value; +static bool sml_trace_obis = false; +static IApp *mApp; + +const unsigned char version_seq[] = { SML_VERSION1_CHAR, SML_VERSION1_CHAR, SML_VERSION1_CHAR, SML_VERSION1_CHAR }; +const unsigned char esc_seq[] = {SML_ESCAPE_CHAR, SML_ESCAPE_CHAR, SML_ESCAPE_CHAR, SML_ESCAPE_CHAR}; + +#ifdef SML_OBIS_TEST +static size_t sml_test_telegram_offset; +const unsigned char sml_test_telegram[] = { + 0x1b, 0x1b, 0x1b, 0x1b, // Escapesequenz + 0x01, 0x01, 0x01, 0x01, // Version 1 + 0x76, // Liste mit 6 Eintraegen (1. SML Nachricht dieses Telegramms) + 0x05, 0x03, 0x2b, 0x18, 0x20, + 0x62, 0x00, + 0x62, 0x00, + 0x72, + 0x63, 0x01, 0x01, // Messagetyp: OpenResponse + 0x76, + 0x01, + 0x01, + 0x05, 0x01, 0x0e, 0x5d, 0x5b, + 0x0b, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, + 0x01, + 0x63, 0x53, 0x34, + 0x00, + 0x76, // Liste mit 6 Eintraegen (2. SML Nachricht dieses Telegramms) + 0x05, 0x03, 0x2b, 0x18, 0x21, + 0x62, 0x00, + 0x62, 0x00, + 0x72, + 0x63, 0x07, 0x01, // Messagetyp: GetListResponse + 0x77, + 0x01, + 0x0b, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x07, 0x01, 0x00, 0x62, 0x0a, 0xff, 0xff, + 0x72, + 0x62, 0x01, + 0x65, 0x02, 0x1a, 0x58, 0x7f, + 0x7a, + 0x77, + 0x07, 0x01, 0x00, 0x01, 0x08, 0x00, 0xff, // OBIS Kennzahl fuer Wirkenergie Bezug gesamt tariflos + 0x65, 0x00, 0x01, 0x01, 0x80, + 0x01, + 0x62, 0x1e, // Einheit "Wh" + 0x52, 0xff, // Skalierung 0.1 + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xc3, 0x05, // Wert fuer Wirkenergie Bezug gesamt tariflos + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x01, 0x08, 0x01, 0xff, // OBIS-Kennzahl fuer Wirkenergie Bezug Tarif 1 + 0x01, + 0x01, + 0x62, 0x1e, // Einheit "Wh" + 0x52, 0xff, // Skalierung 0.1 + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xc3, 0x05, // Wert fuer Wirkenergie Bezug Tarif 1 + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x01, 0x08, 0x02, 0xff, // OBIS-Kennzahl fuer Wirkenergie Bezug Tarif 2 + 0x01, + 0x01, + 0x62, 0x1e, // Einheit "Wh" + 0x52, 0xff, // Skalierung 0.1 + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Wert fuer Wirkenergie Bezug Tarif 2 + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x02, 0x08, 0x00, 0xff, // OBIS-Kennzahl für Wirkenergie Einspeisung gesamt tariflos + 0x01, + 0x01, + 0x62, 0x1e, // Einheit "Wh" + 0x52, 0xff, // Skalierung 0.1 + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Wert für Wirkenergie Einspeisung gesamt tariflos + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x02, 0x08, 0x01, 0xff, // OBIS-Kennzahl für Wirkenergie Einspeisung Tarif1 + 0x01, + 0x01, + 0x62, 0x1e, // Einheit "Wh" + 0x52, 0xff, // Skalierung 0.1 + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Wert fuer Wirkenergie Einspeisung Tarif 1 + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x02, 0x08, 0x02, 0xff, // OBIS-Kennzahl für Wirkenergie Einspeisung Tarif 2 + 0x01, + 0x01, + 0x62, 0x1e, // Einheit "Wh" + 0x52, 0xff, // Skalierung 0.1 + 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Wert für Wirkenergie Einspeisung Tarif 2 + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x10, 0x07, 0x00, 0xff, // OBIS-Kennzahl fuer momentane Gesamtwirkleistung + 0x01, + 0x01, + 0x62, 0x1b, // Einheit "W" + 0x52, 0x00, // Skalierung 1 + 0x55, 0x00, 0x00, 0x01, 0x29, // Wert fuer momentane Gesamtwirkleistung + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x24, 0x07, 0x00, 0xff, // OBIS-Kennzahl fuer momentane Wirkleistung L1 + 0x01, + 0x01, + 0x62, 0x1b, // Einheit "W" + 0x52, 0x00, // Skalierung 1 + 0x55, 0x00, 0x00, 0x01, 0x29, // Wert fuer momentane Wirkleistung L1 + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x38, 0x07, 0x00, 0xff, // OBIS-Kennzahl fuer momentane Wirkleistung L2 + 0x01, + 0x01, + 0x62, 0x1b, // Einheit "W" + 0x52, 0x00, // Skalierung 1 + 0x55, 0x00, 0x00, 0x00, 0x00, // Wert für momentane Wirkleistung L2 + 0x01, + 0x77, + 0x07, 0x01, 0x00, 0x4c, 0x07, 0x00, 0xff, // OBIS-Kennzahl fuer momentane Wirkleistung L3 + 0x01, + 0x01, + 0x62, 0x1b, // Einheit "W" + 0x52, 0x00, // Skalierung 1 + 0x55, 0x00, 0x00, 0x00, 0x00, // Wert fuer momentane Wirkleistung L3 + 0x01, + 0x01, + 0x01, + 0x63, 0x23, 0x59, + 0x00, + 0x76, // Liste mit 6 Eintraegen (3. SML Nachricht dieses Telegramms) + 0x05, 0x03, 0x2b, 0x18, 0x22, + 0x62, 0x00, + 0x62, 0x00, + 0x72, + 0x63, 0x02, 0x01, // Messagetyp: CloseResponse + 0x71, + 0x01, + 0x63, 0x91, 0x26, + 0x00, + 0x1b, 0x1b, 0x1b, 0x1b, // Escapesequenz + 0x1a, 0x00, 0x7d, 0x9d // 1a + Fuellbyte + CRC16 des gesamten Telegrammes (ggf. passend eintragen) +}; +#endif + +//----------------------------------------------------------------------------- +// DIN EN 62056-46, Polynom 0x1021 +static const uint16_t obis_crctab[256] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, + 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, 0x2102, 0x308b, 0x0210, 0x1399, + 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, + 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, + 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, + 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a, 0xb693, + 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, + 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, 0xb58b, 0xa402, 0x9699, 0x8710, + 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, + 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, + 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, + 0x3de3, 0x2c6a, 0x1ef1, 0x0f78}; + +//----------------------------------------------------------------------------- +void sml_init_obis_crc () +{ + obis_crc = 0xffff; +} + +//----------------------------------------------------------------------------- +void sml_calc_obis_crc (unsigned int len, unsigned char *data) +{ + while (len--) { + obis_crc = (obis_crc >> 8) ^ obis_crctab[(obis_crc ^ *data++) & 0xff]; + } +} + +//----------------------------------------------------------------------------- +uint16_t sml_finit_obis_crc () +{ + obis_crc ^= 0xffff; + obis_crc = (obis_crc << 8) | (obis_crc >> 8); + return obis_crc; +} + +//----------------------------------------------------------------------------- +void sml_set_trace_obis (bool trace_flag) +{ + sml_trace_obis = trace_flag; +} + +//----------------------------------------------------------------------------- +void sml_cleanup_history () +{ + time_t time_today; + + time_today = *obis_timestamp; + + obis_cur_pac = 0; + obis_cur_pac_cnt = 0; + obis_cur_pac_index = 0; + obis_pac_sum = 0; + if (time_today) { + Dir grid_power_dir; + struct tm tm_today; + char cur_file_name[sizeof (SML_OBIS_FORMAT_FILE_NAME)]; + + localtime_r (&time_today, &tm_today); + snprintf (cur_file_name, sizeof (cur_file_name), SML_OBIS_FORMAT_FILE_NAME, + tm_today.tm_mday, tm_today.tm_mon+1, tm_today.tm_year + 1900); + grid_power_dir = LittleFS.openDir (SML_OBIS_GRID_POWER_PATH); + /* design: no dataserver, cleanup old history */ + + while (grid_power_dir.next()) { + if (grid_power_dir.fileName() != cur_file_name) { + DPRINTLN (DBG_INFO, "Remove file " + grid_power_dir.fileName() + + ", Size: " + String (grid_power_dir.fileSize())); + LittleFS.remove (SML_OBIS_GRID_POWER_PATH "/" + grid_power_dir.fileName()); + } + } + } else { + DPRINTLN (DBG_WARN, "sml_cleanup_history, no time yet"); + } +} + +//----------------------------------------------------------------------------- +File sml_open_hist () +{ + File file = (File) NULL; + + if (*obis_timestamp) { + char file_name[sizeof (SML_OBIS_GRID_POWER_PATH) + sizeof (SML_OBIS_FORMAT_FILE_NAME)]; + time_t time_today = *obis_timestamp; + struct tm tm_today; + + localtime_r (&time_today, &tm_today); + snprintf (file_name, sizeof (file_name), SML_OBIS_GRID_POWER_PATH "/" SML_OBIS_FORMAT_FILE_NAME, + tm_today.tm_mday, tm_today.tm_mon+1, tm_today.tm_year + 1900); + file = LittleFS.open (file_name, "r"); + if (!file) { + DPRINT (DBG_WARN, "sml_open_hist, failed to open "); + DBGPRINTLN (file_name); + } + } else { + DPRINTLN (DBG_WARN, "sml_open_history, no time yet"); + } + return file; +} + +//----------------------------------------------------------------------------- +void sml_close_hist (File file) +{ + if (file) { + file.close (); + } +} + +//----------------------------------------------------------------------------- +int sml_find_hist_power (File file, uint16_t index) +{ + if (file) { + size_t len; + uint16_t cmp_index = 0; /* init wegen Compilerwarnung */ + unsigned char data[4]; + + while ((len = file.read (data, sizeof (data))) == sizeof (data)) { + cmp_index = data[0] + (data[1] << 8); + if (cmp_index >= index) { + break; + } +// yield(); /* do not do this here: seems to cause hanger */ + } + if (len < sizeof (data)) { + if (index == obis_cur_pac_index) { + return sml_get_obis_pac_average (); + } + DPRINTLN (DBG_DEBUG, "sml_find_hist_power(1), cant find " + String (index)); + return -1; + } + if (cmp_index == index) { + return (int16_t)(data[2] + (data[3] << 8)); + } + DPRINTLN (DBG_DEBUG, "sml_find_hist_power(2), cant find " + String (index) + ", found " + String (cmp_index)); + file.seek (file.position() - sizeof (data)); + } else if ((index == obis_cur_pac_index) && obis_cur_pac_cnt) { + return sml_get_obis_pac_average (); + } + return -1; +} + +//----------------------------------------------------------------------------- +void sml_setup (IApp *app, uint32_t *timestamp) +{ + obis_timestamp = timestamp; + mApp = app; +} + +//----------------------------------------------------------------------------- +int16_t sml_get_obis_pac () +{ + return obis_cur_pac; +} + +//----------------------------------------------------------------------------- +void sml_handle_obis_state (unsigned char *buf) +{ +#ifdef undef + if (sml_trace_obis) { + DPRINTLN(DBG_INFO, "OBIS " + String(buf[0], HEX) + "-" + String(buf[1], HEX) + ":" + String(buf[2], HEX) + + "." + String (buf[3], HEX) + "." + String(buf[4], HEX) + "*" + String(buf[5], HEX)); + } +#endif + if (obis_state != OBIS_ST_NONE) { + if (buf[0] == 1) { + if (!memcmp (&buf[2], OBIS_SIG_YIELD_IN_ALL, 3)) { + obis_state = OBIS_ST_YIELD_IN_ALL; + } else if (!memcmp (&buf[2], OBIS_SIG_YIELD_OUT_ALL, 3)) { + obis_state = OBIS_ST_YIELD_OUT_ALL; + } else if (!memcmp (&buf[2], OBIS_SIG_POWER_ALL, 3)) { + obis_state = OBIS_ST_POWER_ALL; + } else if (!memcmp (&buf[2], OBIS_SIG_POWER_L1, 3)) { + obis_state = OBIS_ST_POWER_L1; + } else if (!memcmp (&buf[2], OBIS_SIG_POWER_L2, 3)) { + obis_state = OBIS_ST_POWER_L2; + } else if (!memcmp (&buf[2], OBIS_SIG_POWER_L3, 3)) { + obis_state = OBIS_ST_POWER_L3; + } else if (!memcmp (&buf[2], OBIS_SIG_CURRENT_L1, 3)) { + obis_state = OBIS_ST_CURRENT_L1; + } else if (!memcmp (&buf[2], OBIS_SIG_CURRENT_L2, 3)) { + obis_state = OBIS_ST_CURRENT_L2; + } else if (!memcmp (&buf[2], OBIS_SIG_CURRENT_L3, 3)) { + obis_state = OBIS_ST_CURRENT_L3; + } else if (!memcmp (&buf[2], OBIS_SIG_VOLTAGE_L1, 3)) { + obis_state = OBIS_ST_VOLTAGE_L1; + } else if (!memcmp (&buf[2], OBIS_SIG_VOLTAGE_L2, 3)) { + obis_state = OBIS_ST_VOLTAGE_L2; + } else if (!memcmp (&buf[2], OBIS_SIG_VOLTAGE_L3, 3)) { + obis_state = OBIS_ST_VOLTAGE_L3; + } else { + obis_state = OBIS_ST_UNKNOWN; + } + } else { + obis_state = OBIS_ST_UNKNOWN; + } + } +} + +//----------------------------------------------------------------------------- +int64_t sml_obis_get_uint (unsigned char *data, unsigned int len) +{ + int64_t value = 0; + + if (len > 8) { + DPRINTLN(DBG_WARN, "Int too big"); + } else { + unsigned int i; + + for (i=0; i 0) { + value = value * (10 * scale); + } else if (scale < 0) { + value = value / (10 * -scale); + } + return (int)value; +} +//----------------------------------------------------------------------------- +int64_t sml_obis_get_int (unsigned char *data, unsigned int len) +{ + int64_t value = 0; + + if (len > 8) { + DPRINTLN(DBG_WARN, "Int too big"); + } else { + unsigned int i; + + if ((len > 0) && (*data & 0x80)) { + value = -1LL; + } + for (i=0; i 0) { + value = value * (10 * scale); + } else if (scale < 0) { + value = value / (10 * -scale); + } + return (int)value; +} + +//----------------------------------------------------------------------------- +int16_t sml_get_obis_pac_average () +{ + int32_t average; + int16_t pac_average = 0; + + if (obis_cur_pac_cnt) { + if (obis_pac_sum >= 0) { + average = (obis_pac_sum + (obis_cur_pac_cnt >> 1)) / obis_cur_pac_cnt; + if (average > INT16_MAX) { + pac_average = INT16_MAX; + } else { + pac_average = average; + } + } else { + average = (obis_pac_sum - (obis_cur_pac_cnt >> 1)) / obis_cur_pac_cnt; + if (average < INT16_MIN) { + pac_average = INT16_MIN; + } else { + pac_average = average; + } + } + } + return pac_average; +} + +//----------------------------------------------------------------------------- +void sml_handle_obis_pac (int16_t pac) +{ + obis_cur_pac = pac; + + if (*obis_timestamp) { + uint32_t pac_index = gTimezone.toLocal (*obis_timestamp); + + pac_index = hour(pac_index) * 60 + minute(pac_index); + pac_index /= AHOY_PAC_INTERVAL; + + if (pac_index != obis_cur_pac_index) { + /* calc average for last interval */ + if (obis_cur_pac_cnt) { + int16_t pac_average = sml_get_obis_pac_average(); + File file; + char file_name[sizeof (SML_OBIS_GRID_POWER_PATH) + sizeof (SML_OBIS_FORMAT_FILE_NAME)]; + time_t time_today = *obis_timestamp; + struct tm tm_today; + + localtime_r (&time_today, &tm_today); + snprintf (file_name, sizeof (file_name), SML_OBIS_GRID_POWER_PATH "/" SML_OBIS_FORMAT_FILE_NAME, + tm_today.tm_mday, tm_today.tm_mon+1, tm_today.tm_year + 1900); + // append last average + if ((file = LittleFS.open (file_name, "a"))) { + unsigned char buf[4]; + buf[0] = obis_cur_pac_index & 0xff; + buf[1] = obis_cur_pac_index >> 8; + buf[2] = pac_average & 0xff; + buf[3] = pac_average >> 8; + if (file.write (buf, sizeof (buf)) != sizeof (buf)) { + DPRINTLN (DBG_WARN, "sml_handle_obis_pac, failed_to_write"); + } else { + DPRINTLN (DBG_DEBUG, "sml_handle_obis_pac, write to " + String(file_name)); + } + file.close (); + } else { + DPRINTLN (DBG_WARN, "sml_handle_obis_pac, failed to open"); + } + obis_cur_pac_cnt = 0; + obis_pac_sum = 0; + } + obis_cur_pac_index = pac_index; + } + if ((pac_index >= AHOY_MIN_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL) && + (pac_index < AHOY_MAX_PAC_SUN_HOUR * 60 / AHOY_PAC_INTERVAL)) { + obis_pac_sum += pac; + obis_cur_pac_cnt++; + } else { + DPRINTLN (DBG_INFO, "sml_handle_obis_pac, outside daylight, minutes: " + String (pac_index * AHOY_PAC_INTERVAL)); + } + } else { + DPRINTLN (DBG_INFO, "sml_handle_obis_pac, no time2"); + } +} + +//----------------------------------------------------------------------------- +uint16_t sml_fill_buf (uint16_t len) +{ + len = min (sizeof (sml_serial_buf) - (cur_serial_buf - sml_serial_buf) - sml_serial_len, len); + +#ifdef SML_OBIS_TEST + size_t partlen; + + partlen = min (len, sizeof (sml_test_telegram) - sml_test_telegram_offset); + memcpy (cur_serial_buf + sml_serial_len, &sml_test_telegram[sml_test_telegram_offset], partlen); + sml_serial_len += partlen; + sml_test_telegram_offset += partlen; + if (sml_test_telegram_offset >= sizeof (sml_test_telegram)) { + sml_test_telegram_offset = 0; + } + if (partlen < len) { + memcpy (cur_serial_buf + sml_serial_len, &sml_test_telegram[sml_test_telegram_offset], len - partlen); + sml_serial_len += len - partlen; + sml_test_telegram_offset += len - partlen; + } +#else + if (len) { + len = Serial.readBytes(cur_serial_buf + sml_serial_len, len); + sml_serial_len += len; + } +#endif + return len; +} + +//----------------------------------------------------------------------------- +bool sml_get_list_entries (uint16_t layer) +{ + bool error = false; + + while (!error && sml_serial_len) { + sml_list_entry_type_t type = (sml_list_entry_type_t)(*cur_serial_buf & 0x70); + unsigned char entry_len; + // Acc. to Spec there might be len_info > 2. But does this happen in real life? + uint16 len_info = (*cur_serial_buf & SML_EXT_LENGTH) ? 2 : 1; + +#ifdef undef + DPRINT (DBG_INFO, "get_list_entries"); + DBGPRINT (", layer " + String (layer)); + DBGPRINT (", entries " + String (sml_list_layer_entries[layer])); + DBGPRINT (", type 0x" + String (type, HEX)); + DBGPRINT (", len_info " + String (len_info)); + DBGPRINTLN (", sml_len " + String (sml_serial_len)); +#endif + + if (sml_serial_len < len_info) { + if (cur_serial_buf > sml_serial_buf) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + cur_serial_buf = sml_serial_buf; + } + error = true; + } else { + if (len_info == 2) { + entry_len = (*cur_serial_buf << 4) | (*(cur_serial_buf+1) & 0xf); + } else { + entry_len = *cur_serial_buf & 0x0f; /* bei Listen andere Bedeutung */ + } + if ((type == SML_TYPE_LIST) || (sml_serial_len >= entry_len)) { + sml_calc_obis_crc (len_info, cur_serial_buf); + sml_serial_len -= len_info; + if (entry_len && (type != SML_TYPE_LIST)) { + entry_len -= len_info; + } + if (sml_serial_len) { + cur_serial_buf += len_info; + } else { + cur_serial_buf = sml_serial_buf; + } + if (sml_list_layer_entries[layer]) { + switch (type) { + case SML_TYPE_OCTET_STRING: + if ((layer == 4) && (entry_len == 6)) { + sml_handle_obis_state (cur_serial_buf); + } + break; + case SML_TYPE_BOOL: + break; + case SML_TYPE_INT: + if (layer == 1) { + if ((obis_state == OBIS_ST_NONE) && + (sml_list_layer_entries[layer] == 2) && + (sml_obis_get_int (cur_serial_buf, entry_len) == SML_MSG_GET_LIST_RSP)) { + obis_state = OBIS_ST_UNKNOWN; + } + } else if (layer == 4) { + if (obis_state == OBIS_ST_POWER_ALL) { + if (sml_list_layer_entries[layer] == 3) { + obis_power_all_scale = (int)sml_obis_get_int (cur_serial_buf, entry_len); + } else if (sml_list_layer_entries[layer] == 2) { + obis_power_all_value = (int)sml_obis_get_int (cur_serial_buf, entry_len); + } + } else if (obis_state == OBIS_ST_YIELD_IN_ALL) { + if (sml_list_layer_entries[layer] == 3) { + obis_yield_in_all_scale = (int)sml_obis_get_int (cur_serial_buf, entry_len); + } else if (sml_list_layer_entries[layer] == 2) { + obis_yield_in_all_value = sml_obis_get_int (cur_serial_buf, entry_len); + } + } else if (obis_state == OBIS_ST_YIELD_OUT_ALL) { + if (sml_list_layer_entries[layer] == 3) { + obis_yield_out_all_scale = (int)sml_obis_get_int (cur_serial_buf, entry_len); + } else if (sml_list_layer_entries[layer] == 2) { + obis_yield_out_all_value = sml_obis_get_int (cur_serial_buf, entry_len); + } + } + } + break; + case SML_TYPE_UINT: + if (layer == 1) { + if ((obis_state == OBIS_ST_NONE) && + (sml_list_layer_entries[layer] == 2) && + (sml_obis_get_uint (cur_serial_buf, entry_len) == SML_MSG_GET_LIST_RSP)) { + obis_state = OBIS_ST_UNKNOWN; + } + } else if (layer == 4) { + if (obis_state == OBIS_ST_YIELD_IN_ALL) { + if (sml_list_layer_entries[layer] == 2) { + obis_yield_in_all_value = sml_obis_get_uint (cur_serial_buf, entry_len); + } + } else if (obis_state == OBIS_ST_YIELD_OUT_ALL) { + if (sml_list_layer_entries[layer] == 2) { + obis_yield_out_all_value = sml_obis_get_uint (cur_serial_buf, entry_len); + } + } + } + break; + case SML_TYPE_LIST: + if (layer + 1 < SML_MAX_LIST_LAYER) { + sml_list_layer_entries[layer]--; + layer++; + cur_sml_list_layer = layer; + sml_list_layer_entries[layer] = entry_len; +#ifdef undef + DPRINTLN(DBG_INFO, "Open layer " + String(layer) + ", entries " + String(entry_len)); +#endif + if (!sml_serial_len) { + error = true; + } + } else { + sml_state = SML_ST_FIND_START_TAG; + return sml_serial_len ? false : true; + } + break; + default: + DPRINT(DBG_WARN, "Ill Element 0x" + String(type, HEX)); + DBGPRINTLN(", len " + String (entry_len + len_info)); + /* design: aussteigen */ + sml_state = SML_ST_FIND_START_TAG; + return sml_serial_len ? false : true; + } + if (type != SML_TYPE_LIST) { + sml_calc_obis_crc (entry_len, cur_serial_buf); + sml_serial_len -= entry_len; + if (sml_serial_len) { + cur_serial_buf += entry_len; + } else { + cur_serial_buf = sml_serial_buf; + error = true; + } + sml_list_layer_entries[layer]--; + } + } + while (!sml_list_layer_entries[layer]) { +#ifdef undef + DPRINTLN(DBG_INFO, "COMPLETE, layer " + String (layer)); +#endif + if (layer) { + layer--; + cur_sml_list_layer = layer; + } else { + sml_state = SML_ST_FIND_MSG; + return sml_serial_len ? false : true; + } + } + } else if (entry_len > sizeof (sml_serial_buf)) { + DPRINTLN (DBG_INFO, "skip " + String (entry_len)); + sml_skip_len = entry_len; + sml_state = SML_ST_SKIP_LIST_ENTRY; + return false; + } else { + if (cur_serial_buf > sml_serial_buf) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + cur_serial_buf = sml_serial_buf; + } + error = true; + } + } + } + return error; +} + +//----------------------------------------------------------------------------- +uint16_t sml_parse_stream (uint16 len) +{ + bool parse_continue; + uint16_t serial_read; + + serial_read = sml_fill_buf (len); + + do { + parse_continue = false; + switch (sml_state) { + case SML_ST_FIND_START_TAG: + if (sml_serial_len >= sizeof (esc_seq)) { + unsigned char *last_serial_buf = cur_serial_buf; + + if ((cur_serial_buf = (unsigned char *)memmem (cur_serial_buf, sml_serial_len, esc_seq, sizeof (esc_seq)))) { + sml_init_obis_crc (); + sml_calc_obis_crc (sizeof (esc_seq), cur_serial_buf); + sml_serial_len -= cur_serial_buf - last_serial_buf; + sml_serial_len -= sizeof (esc_seq); + if (sml_serial_len) { + cur_serial_buf += sizeof (esc_seq); + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + sml_state = SML_ST_FIND_VERSION; +#ifdef undef + DPRINTLN(DBG_INFO, "START_TAG, rest " + String(sml_serial_len)); +#endif + } else { + cur_serial_buf = last_serial_buf + sml_serial_len; + last_serial_buf = cur_serial_buf; + + /* handle up to last 3 esc chars */ + while ((*(cur_serial_buf - 1) == SML_ESCAPE_CHAR)) { + cur_serial_buf--; + } + if ((sml_serial_len = last_serial_buf - cur_serial_buf)) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + } + cur_serial_buf = sml_serial_buf; + } + } else if (cur_serial_buf > sml_serial_buf) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + cur_serial_buf = sml_serial_buf; + } + break; + case SML_ST_FIND_VERSION: + if (sml_serial_len >=sizeof (version_seq)) { + if (!memcmp (cur_serial_buf, version_seq, sizeof (version_seq))) { + sml_calc_obis_crc (sizeof (version_seq), cur_serial_buf); + sml_state = SML_ST_FIND_MSG; +#ifdef undef + DPRINTLN(DBG_INFO, "VERSION, rest " + String (sml_serial_len - sizeof (version_seq))); +#endif + } else { +#ifdef undef + DPRINTLN(DBG_INFO, "no VERSION, rest " + String (sml_serial_len - sizeof (version_seq))); +#endif + sml_state = SML_ST_FIND_START_TAG; + } + sml_serial_len -= sizeof (version_seq); + if (sml_serial_len) { + cur_serial_buf += sizeof (version_seq); + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + } else if (cur_serial_buf > sml_serial_buf) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + cur_serial_buf = sml_serial_buf; + } + break; + case SML_ST_FIND_MSG: + if (sml_serial_len) { + if (*cur_serial_buf == 0x1b) { + sml_state = SML_ST_FIND_END_TAG; + parse_continue = true; + } else if ((*cur_serial_buf & 0x70) == 0x70) { + /* todo: extended list on 1st level */ + sml_calc_obis_crc (1, cur_serial_buf); +#ifdef undef + DPRINTLN (DBG_INFO, "TOPLIST 0x" + String(*cur_serial_buf, HEX) + ", rest " + String (sml_serial_len - 1)); +#endif + sml_state = SML_ST_FIND_LIST_ENTRIES; + cur_sml_list_layer = 0; + obis_state = OBIS_ST_NONE; + sml_list_layer_entries[0] = *cur_serial_buf & 0xf; + sml_serial_len--; + if (sml_serial_len) { + cur_serial_buf++; + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + } else if (*cur_serial_buf == 0x00) { + /* fill byte (depends on the size of the telegram) */ + sml_calc_obis_crc (1, cur_serial_buf); + sml_serial_len--; + if (sml_serial_len) { + cur_serial_buf++; + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + } else { + DPRINTLN(DBG_WARN, "Unexpected 0x" + String(*cur_serial_buf, HEX) + ", rest: " + String (sml_serial_len)); + sml_state = SML_ST_FIND_START_TAG; + parse_continue = true; + } + } + break; + case SML_ST_FIND_LIST_ENTRIES: + parse_continue = !sml_get_list_entries (cur_sml_list_layer); + break; + case SML_ST_SKIP_LIST_ENTRY: + if (sml_serial_len) { /* design: keep rcv buf small and skip irrelevant long list entries */ + size_t len = min (sml_serial_len, sml_skip_len); + + sml_calc_obis_crc (len, cur_serial_buf); + sml_serial_len -= len; + if (sml_serial_len) { + cur_serial_buf += len; + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + sml_skip_len -= len; + if (!sml_skip_len) { + sml_state = SML_ST_FIND_LIST_ENTRIES; + sml_list_layer_entries[cur_sml_list_layer]--; + while (!sml_list_layer_entries[cur_sml_list_layer]) { +#ifdef undef + DPRINTLN(DBG_INFO, "COMPLETE, layer " + String (cur_sml_list_layer)); +#endif + if (cur_sml_list_layer) { + cur_sml_list_layer--; + } else { + sml_state = SML_ST_FIND_MSG; + break; + } + } + } + } + break; + case SML_ST_FIND_END_TAG: + if (sml_serial_len >= sizeof (esc_seq)) { + if (!memcmp (cur_serial_buf, esc_seq, sizeof (esc_seq))) { + sml_calc_obis_crc (sizeof (esc_seq), cur_serial_buf); + sml_state = SML_ST_CHECK_CRC; + } else { + DPRINTLN(DBG_WARN, "Missing END_TAG, found 0x" + String (*cur_serial_buf) + + " 0x" + String (*(cur_serial_buf+1)) + + " 0x" + String (*(cur_serial_buf+2)) + + " 0x" + String (*(cur_serial_buf+3))); + sml_state = SML_ST_FIND_START_TAG; + } + sml_serial_len -= sizeof (esc_seq); + if (sml_serial_len) { + cur_serial_buf += sizeof (esc_seq); + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + } else if (cur_serial_buf > sml_serial_buf) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + cur_serial_buf = sml_serial_buf; + } + break; + case SML_ST_CHECK_CRC: + if (sml_serial_len >= 4) { + if (*cur_serial_buf == 0x1a) { + uint16_t calc_crc16, rcv_crc16; + + sml_calc_obis_crc (2, cur_serial_buf); + calc_crc16 = sml_finit_obis_crc (); + rcv_crc16 = (*(cur_serial_buf+2) << 8) + *(cur_serial_buf+3); + if (calc_crc16 == rcv_crc16) { + obis_power_all_value = sml_obis_scale_int (obis_power_all_value, obis_power_all_scale); +#ifdef undef + obis_yield_in_all_value = sml_obis_scale_uint (obis_yield_in_all_value, obis_yield_in_all_scale); + obis_yield_out_all_value = sml_obis_scale_uint (obis_yield_out_all_value, obis_yield_out_all_scale); + DPRINTLN(DBG_INFO, "Power " + String (obis_power_all_value) + + ", Yield in " + String (obis_yield_in_all_value) + + ", Yield out " + String (obis_yield_out_all_value)); +#else + DPRINTLN(DBG_INFO, "Power " + String (obis_power_all_value)); +#endif + sml_handle_obis_pac (obis_power_all_value); + } else { + DPRINTLN(DBG_WARN, "CRC ERROR 0x" + String (calc_crc16, HEX) + " <-> 0x" + String (rcv_crc16, HEX)); + } + } + sml_state = SML_ST_FIND_START_TAG; + sml_serial_len -= 4; + if (sml_serial_len) { + cur_serial_buf += 4; + parse_continue = true; + } else { + cur_serial_buf = sml_serial_buf; + } + } else if (cur_serial_buf > sml_serial_buf) { + memmove (sml_serial_buf, cur_serial_buf, sml_serial_len); + cur_serial_buf = sml_serial_buf; + } + break; + } + } while (parse_continue); + return serial_read; +} + +//----------------------------------------------------------------------------- +void sml_loop () +{ + uint16_t serial_avail; + uint16_t serial_read = 0; + +#ifdef SML_OBIS_TEST + uint32_t cur_uptime; + static uint32_t last_uptime; + if (((cur_uptime = mApp->getUptime()) > 30) && (cur_uptime != last_uptime)) { + last_uptime = cur_uptime; + serial_avail = sizeof (sml_test_telegram) >> 2; + } else { + serial_avail = 0; + } +#else + serial_avail = Serial.available(); +#endif + if (serial_avail > 0) { + do { + serial_read = sml_parse_stream (serial_avail); + serial_avail -= serial_read; + yield(); + } while (serial_read && serial_avail); + } +} + +#endif diff --git a/src/plugins/SML_OBIS_Parser.h b/src/plugins/SML_OBIS_Parser.h new file mode 100644 index 000000000..cb255f75f --- /dev/null +++ b/src/plugins/SML_OBIS_Parser.h @@ -0,0 +1,22 @@ +#ifndef _SML_OBIS_PARSER_H_ +#define _SML_OBIS_PARSER_H_ + +#ifdef AHOY_SML_OBIS_SUPPORT + +#include "appInterface.h" + + +void sml_setup (IApp *app, uint32_t *timestamp); +void sml_cleanup_history (); +File sml_open_hist (); +void sml_close_hist (File file); +int sml_find_hist_power (File file, uint16_t index); +int16_t sml_get_obis_pac (); +int16_t sml_get_obis_pac_average (); +uint16_t sml_parse_stream (uint16 len); +void sml_set_trace_obis (bool trace_flag); +void sml_loop(); + +#endif + +#endif \ No newline at end of file From 3c486633d9ce52c1f946052dfa81e99ef5db6fee Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:57:55 +0200 Subject: [PATCH 06/48] Add files via upload From d58bb8ce528d67720f4a5e2d8b5de9aa4d4b338e Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:58:44 +0200 Subject: [PATCH 07/48] Add files via upload --- src/web/RestApi.h | 231 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 194 insertions(+), 37 deletions(-) diff --git a/src/web/RestApi.h b/src/web/RestApi.h index fde8c7a4b..d06be1fa7 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -7,6 +7,7 @@ #define __WEB_API_H__ #include "../utils/dbg.h" +#include "../config/config.h" #ifdef ESP32 #include "AsyncTCP.h" #else @@ -17,14 +18,15 @@ #include "../utils/helper.h" #include "AsyncJson.h" #include "ESPAsyncWebServer.h" +#include "../plugins/SML_OBIS_Parser.h" #if defined(F) && defined(ESP32) #undef F #define F(sl) (sl) #endif -const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; -const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR}; +const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; +const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP}; template class RestApi { @@ -45,8 +47,8 @@ class RestApi { mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); + mSrv->on("/get_chartdata", HTTP_GET, std::bind(&RestApi::onGetChartData, this, std::placeholders::_1)); } uint32_t getTimezoneOffset(void) { @@ -73,39 +75,38 @@ class RestApi { mHeapFrag = ESP.getHeapFragmentation(); #endif - AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); - JsonObject root = response->getRoot(); String path = request->url().substring(5); - if(path == "html/system") getHtmlSystem(request, root); - else if(path == "html/logout") getHtmlLogout(request, root); - else if(path == "html/reboot") getHtmlReboot(request, root); - else if(path == "html/save") getHtmlSave(request, root); - else if(path == "system") getSysInfo(request, root); - else if(path == "generic") getGeneric(request, root); - else if(path == "reboot") getReboot(request, root); - else if(path == "statistics") getStatistics(root); - else if(path == "inverter/list") getInverterList(root); - else if(path == "index") getIndex(request, root); - else if(path == "setup") getSetup(request, root); - else if(path == "setup/networks") getNetworks(root); - else if(path == "live") getLive(request, root); - else if(path == "record/info") getRecord(root, InverterDevInform_All); - else if(path == "record/alarm") getRecord(root, AlarmData); - else if(path == "record/config") getRecord(root, SystemConfigPara); - else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); - else { - if(path.substring(0, 12) == "inverter/id/") - getInverter(root, request->url().substring(17).toInt()); - else - getNotFound(root, F("http://") + request->host() + F("/api/")); - } - - //DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); - response->addHeader("Access-Control-Allow-Origin", "*"); - response->addHeader("Access-Control-Allow-Headers", "content-type"); - response->setLength(); - request->send(response); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); + JsonObject root = response->getRoot(); + if(path == "html/system") getHtmlSystem(request, root); + else if(path == "html/logout") getHtmlLogout(request, root); + else if(path == "html/reboot") getHtmlReboot(request, root); + else if(path == "html/save") getHtmlSave(request, root); + else if(path == "system") getSysInfo(request, root); + else if(path == "generic") getGeneric(request, root); + else if(path == "reboot") getReboot(request, root); + else if(path == "statistics") getStatistics(root); + else if(path == "inverter/list") getInverterList(root); + else if(path == "index") getIndex(request, root); + else if(path == "setup") getSetup(request, root); + else if(path == "setup/networks") getNetworks(root); + else if(path == "live") getLive(request, root); + else if(path == "record/info") getRecord(root, InverterDevInform_All); + else if(path == "record/alarm") getRecord(root, AlarmData); + else if(path == "record/config") getRecord(root, SystemConfigPara); + else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); + else { + if(path.substring(0, 12) == "inverter/id/") + getInverter(root, request->url().substring(17).toInt()); + else + getNotFound(root, F("http://") + request->host() + F("/api/")); + } + //DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); + response->addHeader("Access-Control-Allow-Origin", "*"); + response->addHeader("Access-Control-Allow-Headers", "content-type"); + response->setLength(); + request->send(response); } void onApiPost(AsyncWebServerRequest *request) { @@ -159,6 +160,25 @@ class RestApi { ep[F("record/live")] = url + F("record/live"); } + unsigned int get_int_length (int value) + { + unsigned int length = 1; + unsigned int base10 = 10, last_base10; + + if (value < 0) { + length++; + value = -value; + } + while ((unsigned int)value >= base10) { + length++; + last_base10 = base10; + base10 *= 10; + if (base10 <= last_base10) { + break; + } + } + return length; + } void onDwnldSetup(AsyncWebServerRequest *request) { AsyncWebServerResponse *response; @@ -189,6 +209,132 @@ class RestApi { fp.close(); } + void onGetChartData(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response; + File ac_hist; + unsigned char *ac_hist_buf, *cur_ac_hist_buf, *end_ac_hist_buf; + uint16_t ac_power, cur_interval, length = 0; + size_t ac_hist_size; + + // phase 1: count mem needed for CSV String + + if ((ac_hist = mSys->open_hist()) && + (ac_hist_size = ac_hist.size()) && + (ac_hist_size <= (AHOY_MAX_PAC_SUN_HOUR - AHOY_MIN_PAC_SUN_HOUR) * 60 / AHOY_PAC_INTERVAL * 4) && + (ac_hist_buf = (unsigned char *)malloc (ac_hist_size))) { + ac_hist.read (ac_hist_buf, ac_hist_size); + mSys->close_hist (ac_hist); + ac_hist.close(); + cur_ac_hist_buf = ac_hist_buf; + end_ac_hist_buf = cur_ac_hist_buf + ac_hist_size; + while (cur_ac_hist_buf < end_ac_hist_buf) { + cur_interval = *cur_ac_hist_buf++; + cur_interval += (*cur_ac_hist_buf++) << 8; + ac_power = *cur_ac_hist_buf++; + ac_power += (*cur_ac_hist_buf++) << 8; + if (cur_interval < 600 / AHOY_PAC_INTERVAL) { + length += 1 + 4 + 1; // +1: comma, +1: lf + } else { + length += 1 + 5 + 1; + } + length += get_int_length (ac_power); +#ifdef AHOY_SML_OBIS_SUPPORT + length += 1 + 6; // reserve longest power value ,-abcde +#endif + } + + if (mSys->get_cur_value (&cur_interval, &ac_power)) { + if (cur_interval < 600 / AHOY_PAC_INTERVAL) { + length += 1 + 4 + 1; + } else { + length += 1 + 5 + 1; + } + length += get_int_length (ac_power); +#ifdef AHOY_SML_OBIS_SUPPORT + length += 1 + 6; // reserve longest power value ,-abcde +#endif + } + length += sizeof (AHOY_CHARTDATA_HDR); + } + + // phase 2: concatenate CSV string + + if (length) { + char *content = NULL; + unsigned int index; + + if ((content = (char *)malloc (length))) { + uint16_t minutes; +#ifdef AHOY_SML_OBIS_SUPPORT + int sml_power; + File sml_hist = sml_open_hist (); +#endif + + strcpy (content, AHOY_CHARTDATA_HDR); + index = strlen (content); + + cur_ac_hist_buf = ac_hist_buf; + end_ac_hist_buf = cur_ac_hist_buf + ac_hist_size; + while (cur_ac_hist_buf < end_ac_hist_buf) { + cur_interval = *cur_ac_hist_buf++; + cur_interval += (*cur_ac_hist_buf++) << 8; + ac_power = *cur_ac_hist_buf++; + ac_power += (*cur_ac_hist_buf++) << 8; + + minutes = cur_interval * AHOY_PAC_INTERVAL; + +#ifdef AHOY_SML_OBIS_SUPPORT + if ((sml_power = sml_find_hist_power(sml_hist, cur_interval)) == -1) { + snprintf (&content[index], length - index, "\n%u:%02u,%u,", + minutes / 60, minutes % 60, ac_power); + } else { + snprintf (&content[index], length - index, "\n%u:%02u,%u,%d", + minutes / 60, minutes % 60, ac_power, sml_power); + } +#else + snprintf (&content[index], length - index, "\n%u:%02u,%u", + minutes / 60, minutes % 60, ac_power); +#endif + index += strlen (&content[index]); + } + free (ac_hist_buf); + if (mSys->get_cur_value (&cur_interval, &ac_power)) { + uint16_t minutes = cur_interval * AHOY_PAC_INTERVAL; + +#ifdef AHOY_SML_OBIS_SUPPORT + if ((sml_power = sml_find_hist_power(sml_hist, cur_interval)) == -1) { + snprintf (&content[index], length - index, "\n%u:%02u,%u,", + minutes / 60, minutes % 60, ac_power); + } else { + snprintf (&content[index], length - index, "\n%u:%02u,%u,%d", + minutes / 60, minutes % 60, ac_power, sml_power); + } +#else + snprintf (&content[index], length - index, "\n%u:%02u,%u", + minutes / 60, minutes % 60, ac_power); +#endif + index += strlen (&content[index]); + } +#ifdef AHOY_SML_OBIS_SUPPORT + sml_close_hist (sml_hist); +#endif + response = request->beginResponse(200, F("text/plain"), content); + free (content); + } else { + response = request->beginResponse(200, F("text/plain"), AHOY_CHARTDATA_HDR "\nno memory"); + } + } else { + response = request->beginResponse(200, F("text/plain"), AHOY_CHARTDATA_HDR "\nno value found"); + } + if (response) { + response->addHeader("Content-Description", "File Transfer"); + response->addHeader("Content-Disposition", "attachment; filename=chartdata.csv"); + request->send(response); + } else { + request->send(404); + } + } + void getGeneric(AsyncWebServerRequest *request, JsonObject obj) { obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); obj[F("ts_uptime")] = mApp->getUptime(); @@ -337,6 +483,10 @@ class RestApi { obj[F("version")] = String(iv->getFwVersion()); obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); obj[F("ts_last_success")] = rec->ts; +#ifdef AHOY_SML_OBIS_SUPPORT + // design: no value og inverter but I want this value to be displayed prominently + obj[F("grid_power")] = sml_get_obis_pac (); +#endif JsonArray ch = obj.createNestedArray("ch"); @@ -469,15 +619,15 @@ class RestApi { warn.add(F("time not set. No communication to inverter possible")); /*if(0 == mSys->getNumInverters()) warn.add(F("no inverter configured"));*/ - +#ifdef AHOY_MQTT_SUPPORT if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) warn.add(F("MQTT is not connected")); - JsonArray info = obj.createNestedArray(F("infos")); if(mApp->getMqttIsConnected()) info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); if(mConfig->mqtt.interval > 0) info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); +#endif } void getSetup(AsyncWebServerRequest *request, JsonObject obj) { @@ -501,6 +651,10 @@ class RestApi { void getLive(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = mConfig->nrf.sendInterval; +#ifdef AHOY_SML_OBIS_SUPPORT + // additionally here for correct chart titles + obj[F("grid_power")] = sml_get_obis_pac (); +#endif for (uint8_t fld = 0; fld < sizeof(acList); fld++) { obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]); @@ -512,6 +666,7 @@ class RestApi { } Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { iv = mSys->getInverterByPos(i); bool parse = false; @@ -593,10 +748,12 @@ class RestApi { mApp->setTimestamp(jsonIn[F("val")]); else if(F("sync_ntp") == jsonIn[F("cmd")]) mApp->setTimestamp(0); // 0: update ntp flag - else if(F("serial_utc_offset") == jsonIn[F("cmd")]) + else if(F("serial_utc_offset") == jsonIn[F("cmd")]) { mTimezoneOffset = jsonIn[F("val")]; +#ifdef AHOY_MQTT_SUPPORT else if(F("discovery_cfg") == jsonIn[F("cmd")]) { mApp->setMqttDiscoveryFlag(); // for homeassistant +#endif } else { jsonOut[F("error")] = F("unknown cmd"); return false; From 3a52f91ea9a0f9a98a646717173d49aca2722d84 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:01:02 +0200 Subject: [PATCH 08/48] Add files via upload --- src/web/html/api.js | 43 +++++++++++-- src/web/html/style.css | 6 ++ src/web/html/visualization.html | 111 ++++++++++++++++++++++++++++++-- 3 files changed, 150 insertions(+), 10 deletions(-) diff --git a/src/web/html/api.js b/src/web/html/api.js index 13ca50b58..de273e413 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -30,7 +30,7 @@ iconSuccess = [ "M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" ]; - /** +/** * GENERIC FUNCTIONS */ function ml(tagName, ...args) { @@ -135,6 +135,32 @@ function toggle(id, cl="hide") { e.classList.remove(cl); } +function getCSV(url, ptr) { + var xhr = new XMLHttpRequest(); + if(xhr != null) { + xhr.open("GET", url, true); + xhr.onreadystatechange = q; + try { + xhr.send(); + } catch (error) { + console.log(error.message); + } + } + function q() { + if(xhr.readyState == 4) { + if(null != xhr.responseText) { + if(null != ptr) { + try { + ptr(xhr.responseText); + } catch (error) { + console.log(error.message); + } + } + } + } + } +} + function getAjax(url, ptr, method="GET", json=null) { var xhr = new XMLHttpRequest(); if(xhr != null) { @@ -142,13 +168,22 @@ function getAjax(url, ptr, method="GET", json=null) { xhr.onreadystatechange = p; if("POST" == method) xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - xhr.send(json); + try { + xhr.send(json); + } catch (error) { + console.log(error.message); + } } function p() { if(xhr.readyState == 4) { if(null != xhr.responseText) { - if(null != ptr) - ptr(JSON.parse(xhr.responseText)); + if(null != ptr) { + try { + ptr(JSON.parse(xhr.responseText)); + } catch (error) { + console.log(error.message); + } + } } } } diff --git a/src/web/html/style.css b/src/web/html/style.css index bb2318817..daf12d380 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -636,3 +636,9 @@ div.hr { margin-left: -5px; transform: translate(0,0px); } +#powerchart { + min-width: 310px; + max-width: 800px; + height: 300px; + margin: 0 auto; +} diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index de5b0069b..acb0cfe0b 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -6,13 +6,29 @@ + + + {#HTML_NAV}
+

Every seconds the values are updated

+ + + + + {#HTML_FOOTER} From 5c377e2330ad2ca12311c66ffe0dbd367587ba8f Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:24:40 +0200 Subject: [PATCH 09/48] Add files via upload --- src/app.cpp | 14 +++++--------- src/app.h | 11 ++--------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index e1f055ac6..ed9193a3d 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -378,7 +378,7 @@ void app::tickSend(void) { if (mIVCommunicationOn) { if (!mSys.Radio.mBufCtrl.empty()) { if (mConfig->serial.debug) { - DPRINT(DBG_INFO, F("recbuf not empty! #")); + DPRINT(DBG_DEBUG, F("recbuf not empty! #")); DBGPRINTLN(String(mSys.Radio.mBufCtrl.size())); } } @@ -392,15 +392,11 @@ void app::tickSend(void) { if (NULL != iv) { if (iv->config->enabled) { - if (iv->ivGen == IV_HM) - mPayload.ivSend(iv); - else - mMiPayload.ivSend(iv); - } else { - DPRINTLN (DBG_INFO, "iv not enabled"); + if (iv->ivGen == IV_HM) + mPayload.ivSend(iv); + else + mMiPayload.ivSend(iv); } - } else { - DPRINTLN (DBG_INFO, "no inverter"); } } else { if (mConfig->serial.debug) diff --git a/src/app.h b/src/app.h index aa555072d..4a361bf32 100644 --- a/src/app.h +++ b/src/app.h @@ -45,7 +45,6 @@ typedef PubSerial PubSerialType; // PLUGINS #include "plugins/Display/Display.h" typedef Display DisplayType; -// #include class app : public IApp, public ah::Scheduler { public: @@ -57,7 +56,6 @@ class app : public IApp, public ah::Scheduler { void loopStandard(void); void loopWifi(void); void onWifi(bool gotIp); - void cleanup_history(void); void regularTickers(void); void handleIntr(void) { @@ -209,7 +207,6 @@ class app : public IApp, public ah::Scheduler { HmSystemType mSys; - private: typedef std::function innerLoopCb; @@ -257,10 +254,6 @@ class app : public IApp, public ah::Scheduler { void tickMinute(void); void tickZeroValues(void); void tickMidnight(void); - void check_hist_file (File file); - void show_history (String path); - - /* void tickSerial(void) { if(Serial.available() == 0) return; @@ -274,7 +267,8 @@ class app : public IApp, public ah::Scheduler { DBGPRINT(String(buf[i], HEX) + " "); } DBGPRINTLN(""); - } */ + }*/ + void show_history (String path); innerLoopCb mInnerLoopCb; @@ -293,7 +287,6 @@ class app : public IApp, public ah::Scheduler { settings_t *mConfig; bool mSavePending; bool mSaveReboot; - bool mCritical; uint8_t mSendLastIvId; bool mSendFirst; From 6ae9a14120da1606325525c1452f27cdb7ada219 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:25:18 +0200 Subject: [PATCH 10/48] Add files via upload --- src/config/config.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/config/config.h b/src/config/config.h index a3c0024b2..bca9708e5 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -112,7 +112,7 @@ #define AHOY_SML_OBIS_SUPPORT #ifdef AHOY_SML_OBIS_SUPPORT -#define AHOY_CHARTDATA_HDR "Time, AC Power, Net Power" +#define AHOY_CHARTDATA_HDR "Time, AC Power, Grid Power" #else #define AHOY_CHARTDATA_HDR "Time, AC Power" #endif @@ -137,9 +137,6 @@ // default MQTT topic #define DEF_MQTT_TOPIC "inverter" - - - // discovery prefix #define MQTT_DISCOVERY_PREFIX "homeassistant" From 144d4ed86d1ac3652fe4ef13b2d98e4ca7e326e7 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:50:42 +0200 Subject: [PATCH 11/48] Add files via upload --- src/app.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app.h b/src/app.h index 4a361bf32..900e435bd 100644 --- a/src/app.h +++ b/src/app.h @@ -254,7 +254,7 @@ class app : public IApp, public ah::Scheduler { void tickMinute(void); void tickZeroValues(void); void tickMidnight(void); - /* void tickSerial(void) { + /*void tickSerial(void) { if(Serial.available() == 0) return; @@ -268,6 +268,7 @@ class app : public IApp, public ah::Scheduler { } DBGPRINTLN(""); }*/ + void check_hist_file (File file); void show_history (String path); innerLoopCb mInnerLoopCb; From 0c1ddd8c7ac6ba20e7329490ffd52c623e81286f Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:51:33 +0200 Subject: [PATCH 12/48] Add files via upload --- src/web/RestApi.h | 66 +++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/web/RestApi.h b/src/web/RestApi.h index d06be1fa7..1af90d700 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -77,36 +77,36 @@ class RestApi { String path = request->url().substring(5); - AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); - JsonObject root = response->getRoot(); - if(path == "html/system") getHtmlSystem(request, root); - else if(path == "html/logout") getHtmlLogout(request, root); - else if(path == "html/reboot") getHtmlReboot(request, root); - else if(path == "html/save") getHtmlSave(request, root); - else if(path == "system") getSysInfo(request, root); - else if(path == "generic") getGeneric(request, root); - else if(path == "reboot") getReboot(request, root); - else if(path == "statistics") getStatistics(root); - else if(path == "inverter/list") getInverterList(root); - else if(path == "index") getIndex(request, root); - else if(path == "setup") getSetup(request, root); - else if(path == "setup/networks") getNetworks(root); - else if(path == "live") getLive(request, root); - else if(path == "record/info") getRecord(root, InverterDevInform_All); - else if(path == "record/alarm") getRecord(root, AlarmData); - else if(path == "record/config") getRecord(root, SystemConfigPara); - else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); - else { - if(path.substring(0, 12) == "inverter/id/") - getInverter(root, request->url().substring(17).toInt()); - else - getNotFound(root, F("http://") + request->host() + F("/api/")); - } - //DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); - response->addHeader("Access-Control-Allow-Origin", "*"); - response->addHeader("Access-Control-Allow-Headers", "content-type"); - response->setLength(); - request->send(response); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); + JsonObject root = response->getRoot(); + if(path == "html/system") getHtmlSystem(request, root); + else if(path == "html/logout") getHtmlLogout(request, root); + else if(path == "html/reboot") getHtmlReboot(request, root); + else if(path == "html/save") getHtmlSave(request, root); + else if(path == "system") getSysInfo(request, root); + else if(path == "generic") getGeneric(request, root); + else if(path == "reboot") getReboot(request, root); + else if(path == "statistics") getStatistics(root); + else if(path == "inverter/list") getInverterList(root); + else if(path == "index") getIndex(request, root); + else if(path == "setup") getSetup(request, root); + else if(path == "setup/networks") getNetworks(root); + else if(path == "live") getLive(request, root); + else if(path == "record/info") getRecord(root, InverterDevInform_All); + else if(path == "record/alarm") getRecord(root, AlarmData); + else if(path == "record/config") getRecord(root, SystemConfigPara); + else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); + else { + if(path.substring(0, 12) == "inverter/id/") + getInverter(root, request->url().substring(17).toInt()); + else + getNotFound(root, F("http://") + request->host() + F("/api/")); + } + //DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); + response->addHeader("Access-Control-Allow-Origin", "*"); + response->addHeader("Access-Control-Allow-Headers", "content-type"); + response->setLength(); + request->send(response); } void onApiPost(AsyncWebServerRequest *request) { @@ -748,13 +748,13 @@ class RestApi { mApp->setTimestamp(jsonIn[F("val")]); else if(F("sync_ntp") == jsonIn[F("cmd")]) mApp->setTimestamp(0); // 0: update ntp flag - else if(F("serial_utc_offset") == jsonIn[F("cmd")]) { + else if(F("serial_utc_offset") == jsonIn[F("cmd")]) mTimezoneOffset = jsonIn[F("val")]; #ifdef AHOY_MQTT_SUPPORT - else if(F("discovery_cfg") == jsonIn[F("cmd")]) { + else if(F("discovery_cfg") == jsonIn[F("cmd")]) mApp->setMqttDiscoveryFlag(); // for homeassistant #endif - } else { + else { jsonOut[F("error")] = F("unknown cmd"); return false; } From 737526a0b10dd8c592188c7cdcf0ec53107f1502 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:52:23 +0200 Subject: [PATCH 13/48] Add files via upload --- src/hm/hmInverter.h | 4 ++-- src/hm/miPayload.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index c4a8af33a..23bdd1a5b 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -130,8 +130,8 @@ class Inverter { //String lastAlarmMsg; bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) - uint32_t pac_sum; // average calc for Highcharts: sum of ac power values for cur interval - uint16_t pac_cnt; // average calc for Highcharts: number of ac power values for cur interval + uint32_t pac_sum; // average calc for chart: sum of ac power values for cur interval + uint16_t pac_cnt; // average calc for chart: number of ac power values for cur interval Inverter() { ivGen = IV_HM; diff --git a/src/hm/miPayload.h b/src/hm/miPayload.h index f1d82aff3..b6e3ecca6 100644 --- a/src/hm/miPayload.h +++ b/src/hm/miPayload.h @@ -11,7 +11,6 @@ #include "../utils/crc.h" #include "../config/config.h" #include -#include typedef struct { uint32_t ts; @@ -750,6 +749,7 @@ const byteAssign_t InfoAssignment[] = { } ac_pow = (int) (ac_pow*9.5); iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) ac_pow/10); + iv->doCalculations(); iv->setQueuedCmdFinished(); mStat->rxSuccess++; From f7978db6beda03b71b3460f775d226e08cdb3199 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:53:11 +0200 Subject: [PATCH 14/48] Add files via upload --- src/config/settings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config/settings.h b/src/config/settings.h index c90019e20..d04fdce52 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -50,6 +50,7 @@ #define DEF_PROT_API 0x0000 #define DEF_PROT_MQTT 0x0000 + typedef struct { uint8_t ip[4]; // ip address uint8_t mask[4]; // sub mask From e315142bdc0b9b5713d2c461989f70abfda7971b Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:53:45 +0200 Subject: [PATCH 15/48] Add files via upload --- src/defines.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/defines.h b/src/defines.h index 22affda5a..1ac683459 100644 --- a/src/defines.h +++ b/src/defines.h @@ -69,7 +69,6 @@ union serial_u { uint8_t b[8]; }; - #define MIN_SERIAL_INTERVAL 2 // 5 #define MIN_SEND_INTERVAL 15 #define MIN_MQTT_INTERVAL 60 From c90d2fe579c6f65bf7e217d2dd5e0a4e5bc4a0af Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 23:06:50 +0200 Subject: [PATCH 16/48] Add files via upload --- src/web/html/visualization.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index acb0cfe0b..6a36e50b9 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -208,7 +208,6 @@ mNum++; var chn = []; - for(var i = 1; i < obj.ch.length; i++) { var name = obj.ch_name[i]; if(name.length == 0) @@ -216,7 +215,6 @@ if(obj.ch_max_pwr[i] > 0) // show channel only if max mod pwr chn.push(ch(name, obj.ch[i])); } - mIvHtml.push( ml("div", {}, [ ivHead(obj), From e65d117735f4814ce120237a2d082eb658ec48b0 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 23:07:23 +0200 Subject: [PATCH 17/48] Add files via upload --- src/web/html/api.js | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/src/web/html/api.js b/src/web/html/api.js index de273e413..62ac1bf21 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -30,7 +30,7 @@ iconSuccess = [ "M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z" ]; -/** + /** * GENERIC FUNCTIONS */ function ml(tagName, ...args) { @@ -140,21 +140,13 @@ function getCSV(url, ptr) { if(xhr != null) { xhr.open("GET", url, true); xhr.onreadystatechange = q; - try { - xhr.send(); - } catch (error) { - console.log(error.message); - } + xhr.send(); } function q() { if(xhr.readyState == 4) { if(null != xhr.responseText) { if(null != ptr) { - try { - ptr(xhr.responseText); - } catch (error) { - console.log(error.message); - } + ptr(xhr.responseText); } } } @@ -168,22 +160,13 @@ function getAjax(url, ptr, method="GET", json=null) { xhr.onreadystatechange = p; if("POST" == method) xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - try { - xhr.send(json); - } catch (error) { - console.log(error.message); - } + xhr.send(json); } function p() { if(xhr.readyState == 4) { if(null != xhr.responseText) { - if(null != ptr) { - try { - ptr(JSON.parse(xhr.responseText)); - } catch (error) { - console.log(error.message); - } - } + if(null != ptr) + ptr(JSON.parse(xhr.responseText)); } } } From 9b2305e6024fb530d24f17733716f309d0f06935 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Fri, 4 Aug 2023 23:48:50 +0200 Subject: [PATCH 18/48] Add files via upload --- src/web/html/visualization.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 6a36e50b9..9afdd59fd 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -241,7 +241,7 @@ function parseCSV(data) { document.getElementById("csv").innerHTML = data; - // the following line may not be distributed functionally with this project. See comment above + // the following line may not be distributed functionally with this project. See comment at the top area of this file about the commercial license of this chart lib // mychart.update({data: {csv: document.getElementById("csv").innerHTML}}); } @@ -275,7 +275,7 @@ window.setInterval("getAjax('/api/live', parse)", obj["refresh"] * 1000); exeOnce = false; - // This object may not be distributed functionally with this project. See comment above + // This object may not be distributed functionally with this project. See the comment at the top area of this file about the commercial license of this chart lib /* mychart = Highcharts.chart('powerchart', { accessibility: { From 1bf430048045b8c18848806fa83a04ebac5c8379 Mon Sep 17 00:00:00 2001 From: oberfritze <139758614+oberfritze@users.noreply.github.com> Date: Sat, 5 Aug 2023 05:32:54 +0200 Subject: [PATCH 19/48] Add files via upload --- src/web/html/visualization.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 9afdd59fd..49b37d2fc 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -6,11 +6,11 @@ - +