From f7bd4a40d89f1391dbce472260ac2127fbf99114 Mon Sep 17 00:00:00 2001 From: helgeerbe Date: Thu, 14 Sep 2023 13:45:23 +0200 Subject: [PATCH 1/4] revert Revert "Merge remote-tracking branch 'tbnobody/OpenDTU/master' into development" merge of v23.9.11 broke the system. As a workaround upgrade espressif32 from 6.3.2 to 6.4.0 is skipped. See #440 --- include/BatteryStats.h | 2 - include/Configuration.h | 22 +- include/Datastore.h | 16 +- include/JkBmsDataPoints.h | 4 - include/MqttHandleInverter.h | 3 + include/NetworkSettings.h | 8 +- include/SunPosition.h | 4 +- include/WebApi.h | 2 + include/WebApi_gridprofile.h | 15 + include/WebApi_ws_live.h | 2 + include/defaults.h | 3 + lib/Hoymiles/src/Hoymiles.cpp | 119 ++++--- lib/Hoymiles/src/Hoymiles.h | 2 +- lib/Hoymiles/src/Utils.cpp | 15 + lib/Hoymiles/src/Utils.h | 9 + .../src/commands/GridOnProFilePara.cpp | 40 +++ lib/Hoymiles/src/commands/GridOnProFilePara.h | 13 + lib/Hoymiles/src/commands/README.md | 1 + .../src/commands/RealTimeRunDataCommand.cpp | 5 + .../src/commands/SystemConfigParaCommand.cpp | 13 + lib/Hoymiles/src/inverters/HM_Abstract.cpp | 25 +- lib/Hoymiles/src/inverters/HM_Abstract.h | 1 + .../src/inverters/InverterAbstract.cpp | 38 +- lib/Hoymiles/src/inverters/InverterAbstract.h | 19 +- lib/Hoymiles/src/parser/AlarmLogParser.cpp | 17 - lib/Hoymiles/src/parser/AlarmLogParser.h | 5 - lib/Hoymiles/src/parser/DevInfoParser.cpp | 19 +- lib/Hoymiles/src/parser/DevInfoParser.h | 6 - lib/Hoymiles/src/parser/GridProfileParser.cpp | 40 +++ lib/Hoymiles/src/parser/GridProfileParser.h | 18 + lib/Hoymiles/src/parser/Parser.cpp | 18 +- lib/Hoymiles/src/parser/Parser.h | 13 + .../src/parser/PowerCommandParser.cpp | 2 +- lib/Hoymiles/src/parser/PowerCommandParser.h | 1 - lib/Hoymiles/src/parser/StatisticsParser.cpp | 110 +++++- lib/Hoymiles/src/parser/StatisticsParser.h | 12 +- .../src/parser/SystemConfigParaParser.cpp | 26 +- .../src/parser/SystemConfigParaParser.h | 8 +- platformio.ini | 2 +- src/BatteryStats.cpp | 31 -- src/Configuration.cpp | 8 + src/Datastore.cpp | 8 +- src/InverterSettings.cpp | 3 + src/JkBmsController.cpp | 4 +- src/JkBmsDataPoints.cpp | 9 +- src/MqttHandleHass.cpp | 2 +- src/MqttHandleInverter.cpp | 8 +- src/MqttSettings.cpp | 2 + src/NtpSettings.cpp | 3 +- src/SunPosition.cpp | 3 +- src/Utils.cpp | 2 +- src/WebApi.cpp | 2 + src/WebApi_gridprofile.cpp | 49 +++ src/WebApi_inverter.cpp | 10 + src/WebApi_mqtt.cpp | 4 + src/WebApi_ws_live.cpp | 17 +- webapp/package.json | 16 +- webapp/src/components/GridProfile.vue | 50 +++ webapp/src/locales/de.json | 21 +- webapp/src/locales/en.json | 21 +- webapp/src/locales/fr.json | 21 +- webapp/src/types/GridProfileStatus.ts | 3 + webapp/src/types/LiveDataStatus.ts | 1 + webapp/src/types/MqttConfig.ts | 1 + webapp/src/types/MqttStatus.ts | 1 + webapp/src/views/HomeView.vue | 76 +++- webapp/src/views/InverterAdminView.vue | 176 +++++---- webapp/src/views/MqttAdminView.vue | 4 + webapp/src/views/MqttInfoView.vue | 6 + webapp/yarn.lock | 333 ++++++++---------- webapp_dist/js/app.js.gz | Bin 189998 -> 192901 bytes 71 files changed, 1062 insertions(+), 511 deletions(-) create mode 100644 include/WebApi_gridprofile.h create mode 100644 lib/Hoymiles/src/Utils.cpp create mode 100644 lib/Hoymiles/src/Utils.h create mode 100644 lib/Hoymiles/src/commands/GridOnProFilePara.cpp create mode 100644 lib/Hoymiles/src/commands/GridOnProFilePara.h create mode 100644 lib/Hoymiles/src/parser/GridProfileParser.cpp create mode 100644 lib/Hoymiles/src/parser/GridProfileParser.h create mode 100644 src/WebApi_gridprofile.cpp create mode 100644 webapp/src/components/GridProfile.vue create mode 100644 webapp/src/types/GridProfileStatus.ts diff --git a/include/BatteryStats.h b/include/BatteryStats.h index 6dc816a8c..ebdca11fd 100644 --- a/include/BatteryStats.h +++ b/include/BatteryStats.h @@ -95,6 +95,4 @@ class JkBmsBatteryStats : public BatteryStats { private: JkBms::DataPointContainer _dataPoints; - mutable uint32_t _lastMqttPublish = 0; - mutable uint32_t _lastFullMqttPublish = 0; }; diff --git a/include/Configuration.h b/include/Configuration.h index 5c10e15bb..8cb44caf8 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once -#include +#include #define CONFIG_FILENAME "/config.json" #define CONFIG_VERSION 0x00011900 // 0.1.24 // make sure to clean all after change @@ -54,6 +54,9 @@ struct INVERTER_CONFIG_T { bool Poll_Enable_Night; bool Command_Enable; bool Command_Enable_Night; + uint8_t ReachableThreshold; + bool ZeroRuntimeDataIfUnrechable; + bool ZeroYieldDayOnMidnight; CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT]; }; @@ -72,18 +75,18 @@ struct POWERMETER_HTTP_PHASE_CONFIG_T { struct CONFIG_T { uint32_t Cfg_Version; - uint Cfg_SaveCount; + uint32_t Cfg_SaveCount; char WiFi_Ssid[WIFI_MAX_SSID_STRLEN + 1]; char WiFi_Password[WIFI_MAX_PASSWORD_STRLEN + 1]; - byte WiFi_Ip[4]; - byte WiFi_Netmask[4]; - byte WiFi_Gateway[4]; - byte WiFi_Dns1[4]; - byte WiFi_Dns2[4]; + uint8_t WiFi_Ip[4]; + uint8_t WiFi_Netmask[4]; + uint8_t WiFi_Gateway[4]; + uint8_t WiFi_Dns1[4]; + uint8_t WiFi_Dns2[4]; bool WiFi_Dhcp; char WiFi_Hostname[WIFI_MAX_HOSTNAME_STRLEN + 1]; - uint WiFi_ApTimeout; + uint32_t WiFi_ApTimeout; char Ntp_Server[NTP_MAX_SERVER_STRLEN + 1]; char Ntp_Timezone[NTP_MAX_TIMEZONE_STRLEN + 1]; @@ -95,7 +98,7 @@ struct CONFIG_T { bool Mqtt_Enabled; char Mqtt_Hostname[MQTT_MAX_HOSTNAME_STRLEN + 1]; bool Mqtt_VerboseLogging; - uint Mqtt_Port; + uint32_t Mqtt_Port; char Mqtt_Username[MQTT_MAX_USERNAME_STRLEN + 1]; char Mqtt_Password[MQTT_MAX_PASSWORD_STRLEN + 1]; char Mqtt_Topic[MQTT_MAX_TOPIC_STRLEN + 1]; @@ -104,6 +107,7 @@ struct CONFIG_T { char Mqtt_LwtValue_Online[MQTT_MAX_LWTVALUE_STRLEN + 1]; char Mqtt_LwtValue_Offline[MQTT_MAX_LWTVALUE_STRLEN + 1]; uint32_t Mqtt_PublishInterval; + bool Mqtt_CleanSession; INVERTER_CONFIG_T Inverter[INV_MAX_COUNT]; diff --git a/include/Datastore.h b/include/Datastore.h index fd42174d8..6e4c03964 100644 --- a/include/Datastore.h +++ b/include/Datastore.h @@ -31,16 +31,16 @@ class DatastoreClass { float getTotalDcIrradiation(); // Amount of relevant digits for yield total - unsigned int getTotalAcYieldTotalDigits(); + uint32_t getTotalAcYieldTotalDigits(); // Amount of relevant digits for yield total - unsigned int getTotalAcYieldDayDigits(); + uint32_t getTotalAcYieldDayDigits(); // Amount of relevant digits for AC power - unsigned int getTotalAcPowerDigits(); + uint32_t getTotalAcPowerDigits(); // Amount of relevant digits for DC power - unsigned int getTotalDcPowerDigits(); + uint32_t getTotalDcPowerDigits(); // True, if at least one inverter is reachable bool getIsAtLeastOneReachable(); @@ -68,10 +68,10 @@ class DatastoreClass { float _totalDcPowerIrradiation = 0; float _totalDcIrradiationInstalled = 0; float _totalDcIrradiation = 0; - unsigned int _totalAcYieldTotalDigits = 0; - unsigned int _totalAcYieldDayDigits = 0; - unsigned int _totalAcPowerDigits = 0; - unsigned int _totalDcPowerDigits = 0; + uint32_t _totalAcYieldTotalDigits = 0; + uint32_t _totalAcYieldDayDigits = 0; + uint32_t _totalAcPowerDigits = 0; + uint32_t _totalDcPowerDigits = 0; bool _isAtLeastOneReachable = false; bool _isAtLeastOneProducing = false; bool _isAllEnabledProducing = false; diff --git a/include/JkBmsDataPoints.h b/include/JkBmsDataPoints.h index bf3a0f9bd..db7cd37ab 100644 --- a/include/JkBmsDataPoints.h +++ b/include/JkBmsDataPoints.h @@ -185,10 +185,6 @@ class DataPoint { std::string const& getUnitText() const { return _strUnit; } uint32_t getTimestamp() const { return _timestamp; } - bool operator==(DataPoint const& other) const { - return _value == other._value; - } - private: std::string _strLabel; std::string _strValue; diff --git a/include/MqttHandleInverter.h b/include/MqttHandleInverter.h index ee4275037..e1b292e4c 100644 --- a/include/MqttHandleInverter.h +++ b/include/MqttHandleInverter.h @@ -3,6 +3,7 @@ #include "Configuration.h" #include +#include #include class MqttHandleInverterClass { @@ -19,6 +20,8 @@ class MqttHandleInverterClass { uint32_t _lastPublishStats[INV_MAX_COUNT]; uint32_t _lastPublish; + TimeoutHelper _statsTimeout; + FieldId_t _publishFields[14] = { FLD_UDC, FLD_IDC, diff --git a/include/NetworkSettings.h b/include/NetworkSettings.h index 87c8dce9e..f2cbdad3f 100644 --- a/include/NetworkSettings.h +++ b/include/NetworkSettings.h @@ -63,10 +63,10 @@ class NetworkSettingsClass { void NetworkEvent(WiFiEvent_t event); bool adminEnabled = true; bool forceDisconnection = false; - int adminTimeoutCounter = 0; - int adminTimeoutCounterMax = 0; - int connectTimeoutTimer = 0; - int connectRedoTimer = 0; + uint32_t adminTimeoutCounter = 0; + uint32_t adminTimeoutCounterMax = 0; + uint32_t connectTimeoutTimer = 0; + uint32_t connectRedoTimer = 0; uint32_t lastTimerCall = 0; const byte DNS_PORT = 53; IPAddress apIp; diff --git a/include/SunPosition.h b/include/SunPosition.h index 691d42c08..c268813c9 100644 --- a/include/SunPosition.h +++ b/include/SunPosition.h @@ -22,8 +22,8 @@ class SunPositionClass { SunSet _sun; bool _isDayPeriod = true; bool _isSunsetAvailable = true; - uint _sunriseMinutes = 0; - uint _sunsetMinutes = 0; + uint32_t _sunriseMinutes = 0; + uint32_t _sunsetMinutes = 0; uint32_t _lastUpdate = 0; bool _isValidInfo = false; diff --git a/include/WebApi.h b/include/WebApi.h index d7a8ab85c..40e66e339 100644 --- a/include/WebApi.h +++ b/include/WebApi.h @@ -8,6 +8,7 @@ #include "WebApi_dtu.h" #include "WebApi_eventlog.h" #include "WebApi_firmware.h" +#include "WebApi_gridprofile.h" #include "WebApi_inverter.h" #include "WebApi_limit.h" #include "WebApi_maintenance.h" @@ -52,6 +53,7 @@ class WebApiClass { WebApiDtuClass _webApiDtu; WebApiEventlogClass _webApiEventlog; WebApiFirmwareClass _webApiFirmware; + WebApiGridProfileClass _webApiGridprofile; WebApiInverterClass _webApiInverter; WebApiLimitClass _webApiLimit; WebApiMaintenanceClass _webApiMaintenance; diff --git a/include/WebApi_gridprofile.h b/include/WebApi_gridprofile.h new file mode 100644 index 000000000..cf78cf647 --- /dev/null +++ b/include/WebApi_gridprofile.h @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +class WebApiGridProfileClass { +public: + void init(AsyncWebServer* server); + void loop(); + +private: + void onGridProfileStatus(AsyncWebServerRequest* request); + + AsyncWebServer* _server; +}; \ No newline at end of file diff --git a/include/WebApi_ws_live.h b/include/WebApi_ws_live.h index 0cf1449b5..1e6649200 100644 --- a/include/WebApi_ws_live.h +++ b/include/WebApi_ws_live.h @@ -25,4 +25,6 @@ class WebApiWsLiveClass { uint32_t _lastInvUpdateCheck = 0; uint32_t _lastWsCleanup = 0; uint32_t _newestInverterTimestamp = 0; + + std::mutex _mutex; }; \ No newline at end of file diff --git a/include/defaults.h b/include/defaults.h index 051b0ba9c..c02067411 100644 --- a/include/defaults.h +++ b/include/defaults.h @@ -74,6 +74,7 @@ #define MQTT_LWT_ONLINE "online" #define MQTT_LWT_OFFLINE "offline" #define MQTT_PUBLISH_INTERVAL 5U +#define MQTT_CLEAN_SESSION true #define DTU_SERIAL 0x99978563412U #define DTU_POLL_INTERVAL 5U @@ -95,6 +96,8 @@ #define DISPLAY_CONTRAST 60U #define DISPLAY_LANGUAGE 0U +#define REACHABLE_THRESHOLD 2U + #define VEDIRECT_ENABLED false #define VEDIRECT_VERBOSE_LOGGING false #define VEDIRECT_UPDATESONLY true diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index a5f468b56..8504d87ff 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "Hoymiles.h" +#include "Utils.h" #include "inverters/HMS_1CH.h" #include "inverters/HMS_2CH.h" #include "inverters/HMS_4CH.h" @@ -12,18 +13,10 @@ #include "inverters/HM_4CH.h" #include -#define HOY_SEMAPHORE_TAKE() \ - do { \ - } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) -#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) - HoymilesClass Hoymiles; void HoymilesClass::init() { - _xSemaphore = xSemaphoreCreateMutex(); - HOY_SEMAPHORE_GIVE(); // release before first use - _pollInterval = 0; _radioNrf.reset(new HoymilesRadio_NRF()); _radioCmt.reset(new HoymilesRadio_CMT()); @@ -41,7 +34,7 @@ void HoymilesClass::initCMT(int8_t pin_sdio, int8_t pin_clk, int8_t pin_cs, int8 void HoymilesClass::loop() { - HOY_SEMAPHORE_TAKE(); + std::lock_guard lock(_mutex); _radioNrf->loop(); _radioCmt->loop(); @@ -57,67 +50,90 @@ void HoymilesClass::loop() } if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) { - _messageOutput->print("Fetch inverter: "); - _messageOutput->println(iv->serial(), HEX); - if (!iv->isReachable()) { - iv->sendChangeChannelRequest(); - } + if (iv->getEnablePolling() || iv->getEnableCommands()) { + _messageOutput->print("Fetch inverter: "); + _messageOutput->println(iv->serial(), HEX); - iv->sendStatsRequest(); + if (!iv->isReachable()) { + iv->sendChangeChannelRequest(); + } - // Fetch event log - bool force = iv->EventLog()->getLastAlarmRequestSuccess() == CMD_NOK; - iv->sendAlarmLogRequest(force); + iv->sendStatsRequest(); - // Fetch limit - if ((iv->SystemConfigPara()->getLastLimitRequestSuccess() == CMD_NOK) - || ((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL) - && (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) { - _messageOutput->println("Request SystemConfigPara"); - iv->sendSystemConfigParaRequest(); - } + // Fetch event log + bool force = iv->EventLog()->getLastAlarmRequestSuccess() == CMD_NOK; + iv->sendAlarmLogRequest(force); - // Set limit if required - if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) { - _messageOutput->println("Resend ActivePowerControl"); - iv->resendActivePowerControlRequest(); - } + // Fetch limit + if (((millis() - iv->SystemConfigPara()->getLastUpdateRequest() > HOY_SYSTEM_CONFIG_PARA_POLL_INTERVAL) + && (millis() - iv->SystemConfigPara()->getLastUpdateCommand() > HOY_SYSTEM_CONFIG_PARA_POLL_MIN_DURATION))) { + _messageOutput->println("Request SystemConfigPara"); + iv->sendSystemConfigParaRequest(); + } - // Set power status if required - if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) { - _messageOutput->println("Resend PowerCommand"); - iv->resendPowerControlRequest(); - } + // Set limit if required + if (iv->SystemConfigPara()->getLastLimitCommandSuccess() == CMD_NOK) { + _messageOutput->println("Resend ActivePowerControl"); + iv->resendActivePowerControlRequest(); + } - // Fetch dev info (but first fetch stats) - if (iv->Statistics()->getLastUpdate() > 0) { - bool invalidDevInfo = !iv->DevInfo()->containsValidData() - && iv->DevInfo()->getLastUpdateAll() > 0 - && iv->DevInfo()->getLastUpdateSimple() > 0; + // Set power status if required + if (iv->PowerCommand()->getLastPowerCommandSuccess() == CMD_NOK) { + _messageOutput->println("Resend PowerCommand"); + iv->resendPowerControlRequest(); + } - if (invalidDevInfo) { - _messageOutput->println("DevInfo: No Valid Data"); + // Fetch dev info (but first fetch stats) + if (iv->Statistics()->getLastUpdate() > 0) { + bool invalidDevInfo = !iv->DevInfo()->containsValidData() + && iv->DevInfo()->getLastUpdateAll() > 0 + && iv->DevInfo()->getLastUpdateSimple() > 0; + + if (invalidDevInfo) { + _messageOutput->println("DevInfo: No Valid Data"); + } + + if ((iv->DevInfo()->getLastUpdateAll() == 0) + || (iv->DevInfo()->getLastUpdateSimple() == 0) + || invalidDevInfo) { + _messageOutput->println("Request device info"); + iv->sendDevInfoRequest(); + } } - if ((iv->DevInfo()->getLastUpdateAll() == 0) - || (iv->DevInfo()->getLastUpdateSimple() == 0) - || invalidDevInfo) { - _messageOutput->println("Request device info"); - iv->sendDevInfoRequest(); + // Fetch grid profile + if (iv->Statistics()->getLastUpdate() > 0 && iv->GridProfile()->getLastUpdate() == 0) { + iv->sendGridOnProFileParaRequest(); } + + _lastPoll = millis(); } if (++inverterPos >= getNumInverters()) { inverterPos = 0; } + } - _lastPoll = millis(); + // Perform housekeeping of all inverters on day change + int8_t currentWeekDay = Utils::getWeekDay(); + static int8_t lastWeekDay = -1; + if (lastWeekDay == -1) { + lastWeekDay = currentWeekDay; + } else { + if (currentWeekDay != lastWeekDay) { + + for (auto& inv : _inverters) { + if (inv->getZeroYieldDayOnMidnight()) { + inv->Statistics()->zeroDailyData(); + } + } + + lastWeekDay = currentWeekDay; + } } } } - - HOY_SEMAPHORE_GIVE(); } std::shared_ptr HoymilesClass::addInverter(const char* name, uint64_t serial) @@ -195,9 +211,8 @@ void HoymilesClass::removeInverterBySerial(uint64_t serial) { for (uint8_t i = 0; i < _inverters.size(); i++) { if (_inverters[i]->serial() == serial) { - HOY_SEMAPHORE_TAKE(); + std::lock_guard lock(_mutex); _inverters.erase(_inverters.begin() + i); - HOY_SEMAPHORE_GIVE(); return; } } diff --git a/lib/Hoymiles/src/Hoymiles.h b/lib/Hoymiles/src/Hoymiles.h index c6300e4f5..7975f1ba1 100644 --- a/lib/Hoymiles/src/Hoymiles.h +++ b/lib/Hoymiles/src/Hoymiles.h @@ -45,7 +45,7 @@ class HoymilesClass { std::unique_ptr _radioNrf; std::unique_ptr _radioCmt; - SemaphoreHandle_t _xSemaphore; + std::mutex _mutex; uint32_t _pollInterval = 0; bool _verboseLogging = true; diff --git a/lib/Hoymiles/src/Utils.cpp b/lib/Hoymiles/src/Utils.cpp new file mode 100644 index 000000000..138e32a16 --- /dev/null +++ b/lib/Hoymiles/src/Utils.cpp @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Thomas Basler and others + */ +#include "Utils.h" +#include + +uint8_t Utils::getWeekDay() +{ + time_t raw; + struct tm info; + time(&raw); + localtime_r(&raw, &info); + return info.tm_mday; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/Utils.h b/lib/Hoymiles/src/Utils.h new file mode 100644 index 000000000..157dd75ce --- /dev/null +++ b/lib/Hoymiles/src/Utils.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include + +class Utils { +public: + static uint8_t getWeekDay(); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/GridOnProFilePara.cpp b/lib/Hoymiles/src/commands/GridOnProFilePara.cpp new file mode 100644 index 000000000..e9171672d --- /dev/null +++ b/lib/Hoymiles/src/commands/GridOnProFilePara.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2022 Thomas Basler and others + */ +#include "GridOnProFilePara.h" +#include "Hoymiles.h" +#include "inverters/InverterAbstract.h" + +GridOnProFilePara::GridOnProFilePara(uint64_t target_address, uint64_t router_address, time_t time) + : MultiDataCommand(target_address, router_address) +{ + setTime(time); + setDataType(0x02); + setTimeout(500); +} + +String GridOnProFilePara::getCommandName() +{ + return "GridOnProFilePara"; +} + +bool GridOnProFilePara::handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id) +{ + // Check CRC of whole payload + if (!MultiDataCommand::handleResponse(inverter, fragment, max_fragment_id)) { + return false; + } + + // Move all fragments into target buffer + uint8_t offs = 0; + inverter->GridProfile()->beginAppendFragment(); + inverter->GridProfile()->clearBuffer(); + for (uint8_t i = 0; i < max_fragment_id; i++) { + inverter->GridProfile()->appendFragment(offs, fragment[i].fragment, fragment[i].len); + offs += (fragment[i].len); + } + inverter->GridProfile()->endAppendFragment(); + inverter->GridProfile()->setLastUpdate(millis()); + return true; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/GridOnProFilePara.h b/lib/Hoymiles/src/commands/GridOnProFilePara.h new file mode 100644 index 000000000..41ee57ece --- /dev/null +++ b/lib/Hoymiles/src/commands/GridOnProFilePara.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "MultiDataCommand.h" + +class GridOnProFilePara : public MultiDataCommand { +public: + explicit GridOnProFilePara(uint64_t target_address = 0, uint64_t router_address = 0, time_t time = 0); + + virtual String getCommandName(); + + virtual bool handleResponse(InverterAbstract* inverter, fragment_t fragment[], uint8_t max_fragment_id); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/README.md b/lib/Hoymiles/src/commands/README.md index 90ca62d39..8d5f3b36f 100644 --- a/lib/Hoymiles/src/commands/README.md +++ b/lib/Hoymiles/src/commands/README.md @@ -8,6 +8,7 @@ * AlarmDataCommand * DevInfoAllCommand * DevInfoSimpleCommand + * GridOnProFilePara * RealTimeRunDataCommand * SystemConfigParaCommand * ParaSetCommand diff --git a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp index 3f0aed36b..e5ece4092 100644 --- a/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp +++ b/lib/Hoymiles/src/commands/RealTimeRunDataCommand.cpp @@ -55,4 +55,9 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment void RealTimeRunDataCommand::gotTimeout(InverterAbstract* inverter) { inverter->Statistics()->incrementRxFailureCount(); + + if (inverter->getZeroValuesIfUnreachable() && !inverter->isReachable()) { + Hoymiles.getMessageOutput()->println("Set runtime data to zero"); + inverter->Statistics()->zeroRuntimeData(); + } } \ No newline at end of file diff --git a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp index ef2854690..5e238a59b 100644 --- a/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp +++ b/lib/Hoymiles/src/commands/SystemConfigParaCommand.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2022 Thomas Basler and others */ #include "SystemConfigParaCommand.h" +#include "Hoymiles.h" #include "inverters/InverterAbstract.h" SystemConfigParaCommand::SystemConfigParaCommand(uint64_t target_address, uint64_t router_address, time_t time) @@ -25,6 +26,18 @@ bool SystemConfigParaCommand::handleResponse(InverterAbstract* inverter, fragmen return false; } + // Check if at least all required bytes are received + // In case of low power in the inverter it occours that some incomplete fragments + // with a valid CRC are received. + uint8_t fragmentsSize = getTotalFragmentSize(fragment, max_fragment_id); + uint8_t expectedSize = inverter->SystemConfigPara()->getExpectedByteCount(); + if (fragmentsSize < expectedSize) { + Hoymiles.getMessageOutput()->printf("ERROR in %s: Received fragment size: %d, min expected size: %d\r\n", + getCommandName().c_str(), fragmentsSize, expectedSize); + + return false; + } + // Move all fragments into target buffer uint8_t offs = 0; inverter->SystemConfigPara()->beginAppendFragment(); diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index da6437c56..10d936791 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -8,6 +8,7 @@ #include "commands/AlarmDataCommand.h" #include "commands/DevInfoAllCommand.h" #include "commands/DevInfoSimpleCommand.h" +#include "commands/GridOnProFilePara.h" #include "commands/PowerControlCommand.h" #include "commands/RealTimeRunDataCommand.h" #include "commands/SystemConfigParaCommand.h" @@ -210,4 +211,26 @@ bool HM_Abstract::resendPowerControlRequest() return false; break; } -} \ No newline at end of file +} + +bool HM_Abstract::sendGridOnProFileParaRequest() +{ + if (!getEnablePolling()) { + return false; + } + + struct tm timeinfo; + if (!getLocalTime(&timeinfo, 5)) { + return false; + } + + time_t now; + time(&now); + + auto cmd = _radio->prepareCommand(); + cmd->setTime(now); + cmd->setTargetAddress(serial()); + _radio->enqueCommand(cmd); + + return true; +} diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.h b/lib/Hoymiles/src/inverters/HM_Abstract.h index ea44c2427..3a5cc637b 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.h +++ b/lib/Hoymiles/src/inverters/HM_Abstract.h @@ -15,6 +15,7 @@ class HM_Abstract : public InverterAbstract { bool sendPowerControlRequest(bool turnOn); bool sendRestartControlRequest(); bool resendPowerControlRequest(); + bool sendGridOnProFileParaRequest(); private: uint8_t _lastAlarmLogCnt = 0; diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 2383f5fcd..4c5aa422d 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -20,6 +20,7 @@ InverterAbstract::InverterAbstract(HoymilesRadio* radio, uint64_t serial) _alarmLogParser.reset(new AlarmLogParser()); _devInfoParser.reset(new DevInfoParser()); + _gridProfileParser.reset(new GridProfileParser()); _powerCommandParser.reset(new PowerCommandParser()); _statisticsParser.reset(new StatisticsParser()); _systemConfigParaParser.reset(new SystemConfigParaParser()); @@ -73,7 +74,7 @@ bool InverterAbstract::isProducing() bool InverterAbstract::isReachable() { - return _enablePolling && Statistics()->getRxFailureCount() <= MAX_ONLINE_FAILURE_COUNT; + return _enablePolling && Statistics()->getRxFailureCount() <= _reachableThreshold; } void InverterAbstract::setEnablePolling(bool enabled) @@ -96,6 +97,36 @@ bool InverterAbstract::getEnableCommands() return _enableCommands; } +void InverterAbstract::setReachableThreshold(uint8_t threshold) +{ + _reachableThreshold = threshold; +} + +uint8_t InverterAbstract::getReachableThreshold() +{ + return _reachableThreshold; +} + +void InverterAbstract::setZeroValuesIfUnreachable(bool enabled) +{ + _zeroValuesIfUnreachable = enabled; +} + +bool InverterAbstract::getZeroValuesIfUnreachable() +{ + return _zeroValuesIfUnreachable; +} + +void InverterAbstract::setZeroYieldDayOnMidnight(bool enabled) +{ + _zeroYieldDayOnMidnight = enabled; +} + +bool InverterAbstract::getZeroYieldDayOnMidnight() +{ + return _zeroYieldDayOnMidnight; +} + bool InverterAbstract::sendChangeChannelRequest() { return false; @@ -116,6 +147,11 @@ DevInfoParser* InverterAbstract::DevInfo() return _devInfoParser.get(); } +GridProfileParser* InverterAbstract::GridProfile() +{ + return _gridProfileParser.get(); +} + PowerCommandParser* InverterAbstract::PowerCommand() { return _powerCommandParser.get(); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index e12211d38..e6f70f070 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -4,6 +4,7 @@ #include "../commands/ActivePowerControlCommand.h" #include "../parser/AlarmLogParser.h" #include "../parser/DevInfoParser.h" +#include "../parser/GridProfileParser.h" #include "../parser/PowerCommandParser.h" #include "../parser/StatisticsParser.h" #include "../parser/SystemConfigParaParser.h" @@ -24,7 +25,6 @@ enum { }; #define MAX_RF_FRAGMENT_COUNT 13 -#define MAX_ONLINE_FAILURE_COUNT 2 class CommandAbstract; @@ -49,6 +49,15 @@ class InverterAbstract { void setEnableCommands(bool enabled); bool getEnableCommands(); + void setReachableThreshold(uint8_t threshold); + uint8_t getReachableThreshold(); + + void setZeroValuesIfUnreachable(bool enabled); + bool getZeroValuesIfUnreachable(); + + void setZeroYieldDayOnMidnight(bool enabled); + bool getZeroYieldDayOnMidnight(); + void clearRxFragmentBuffer(); void addRxFragment(uint8_t fragment[], uint8_t len); uint8_t verifyAllFragments(CommandAbstract* cmd); @@ -63,11 +72,13 @@ class InverterAbstract { virtual bool sendRestartControlRequest() = 0; virtual bool resendPowerControlRequest() = 0; virtual bool sendChangeChannelRequest(); + virtual bool sendGridOnProFileParaRequest() = 0; HoymilesRadio* getRadio(); AlarmLogParser* EventLog(); DevInfoParser* DevInfo(); + GridProfileParser* GridProfile(); PowerCommandParser* PowerCommand(); StatisticsParser* Statistics(); SystemConfigParaParser* SystemConfigPara(); @@ -87,8 +98,14 @@ class InverterAbstract { bool _enablePolling = true; bool _enableCommands = true; + uint8_t _reachableThreshold = 3; + + bool _zeroValuesIfUnreachable = false; + bool _zeroYieldDayOnMidnight = false; + std::unique_ptr _alarmLogParser; std::unique_ptr _devInfoParser; + std::unique_ptr _gridProfileParser; std::unique_ptr _powerCommandParser; std::unique_ptr _statisticsParser; std::unique_ptr _systemConfigParaParser; diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.cpp b/lib/Hoymiles/src/parser/AlarmLogParser.cpp index 10ed30528..2a6527ac3 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.cpp +++ b/lib/Hoymiles/src/parser/AlarmLogParser.cpp @@ -86,16 +86,9 @@ const std::array AlarmLogParser::_alarmMe { AlarmMessageType_t::ALL, 9000, "Microinverter is suspected of being stolen" }, } }; -#define HOY_SEMAPHORE_TAKE() \ - do { \ - } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) -#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) - AlarmLogParser::AlarmLogParser() : Parser() { - _xSemaphore = xSemaphoreCreateMutex(); - HOY_SEMAPHORE_GIVE(); // release before first use clearBuffer(); } @@ -115,16 +108,6 @@ void AlarmLogParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t le _alarmLogLength += len; } -void AlarmLogParser::beginAppendFragment() -{ - HOY_SEMAPHORE_TAKE(); -} - -void AlarmLogParser::endAppendFragment() -{ - HOY_SEMAPHORE_GIVE(); -} - uint8_t AlarmLogParser::getEntryCount() { if (_alarmLogLength < 2) { diff --git a/lib/Hoymiles/src/parser/AlarmLogParser.h b/lib/Hoymiles/src/parser/AlarmLogParser.h index 5c37589df..a2c6b348f 100644 --- a/lib/Hoymiles/src/parser/AlarmLogParser.h +++ b/lib/Hoymiles/src/parser/AlarmLogParser.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" -#include #include #include @@ -34,8 +33,6 @@ class AlarmLogParser : public Parser { AlarmLogParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); - void beginAppendFragment(); - void endAppendFragment(); uint8_t getEntryCount(); void getLogEntry(uint8_t entryId, AlarmLogEntry_t* entry); @@ -56,6 +53,4 @@ class AlarmLogParser : public Parser { AlarmMessageType_t _messageType = AlarmMessageType_t::ALL; static const std::array _alarmMessages; - - SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index eab3a303e..978af6001 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022 - 2023 Thomas Basler and others */ #include "DevInfoParser.h" #include "../Hoymiles.h" @@ -46,16 +46,9 @@ const devInfo_t devInfo[] = { { { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250" } // 01 }; -#define HOY_SEMAPHORE_TAKE() \ - do { \ - } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) -#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) - DevInfoParser::DevInfoParser() : Parser() { - _xSemaphore = xSemaphoreCreateMutex(); - HOY_SEMAPHORE_GIVE(); // release before first use clearBufferSimple(); clearBufferAll(); } @@ -92,16 +85,6 @@ void DevInfoParser::appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8 _devInfoSimpleLength += len; } -void DevInfoParser::beginAppendFragment() -{ - HOY_SEMAPHORE_TAKE(); -} - -void DevInfoParser::endAppendFragment() -{ - HOY_SEMAPHORE_GIVE(); -} - uint32_t DevInfoParser::getLastUpdateAll() { return _lastUpdateAll; diff --git a/lib/Hoymiles/src/parser/DevInfoParser.h b/lib/Hoymiles/src/parser/DevInfoParser.h index b18bc8a11..838ba1102 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.h +++ b/lib/Hoymiles/src/parser/DevInfoParser.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" -#include #define DEV_INFO_SIZE 20 @@ -14,9 +13,6 @@ class DevInfoParser : public Parser { void clearBufferSimple(); void appendFragmentSimple(uint8_t offset, uint8_t* payload, uint8_t len); - void beginAppendFragment(); - void endAppendFragment(); - uint32_t getLastUpdateAll(); void setLastUpdateAll(uint32_t lastUpdate); @@ -47,6 +43,4 @@ class DevInfoParser : public Parser { uint8_t _payloadDevInfoSimple[DEV_INFO_SIZE] = {}; uint8_t _devInfoSimpleLength = 0; - - SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/GridProfileParser.cpp b/lib/Hoymiles/src/parser/GridProfileParser.cpp new file mode 100644 index 000000000..35f7689d5 --- /dev/null +++ b/lib/Hoymiles/src/parser/GridProfileParser.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023 Thomas Basler and others + */ +#include "GridProfileParser.h" +#include "../Hoymiles.h" +#include + +GridProfileParser::GridProfileParser() + : Parser() +{ + clearBuffer(); +} + +void GridProfileParser::clearBuffer() +{ + memset(_payloadGridProfile, 0, GRID_PROFILE_SIZE); + _gridProfileLength = 0; +} + +void GridProfileParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t len) +{ + if (offset + len > GRID_PROFILE_SIZE) { + Hoymiles.getMessageOutput()->printf("FATAL: (%s, %d) grid profile packet too large for buffer\r\n", __FILE__, __LINE__); + return; + } + memcpy(&_payloadGridProfile[offset], payload, len); + _gridProfileLength += len; +} + +std::vector GridProfileParser::getRawData() +{ + std::vector ret; + HOY_SEMAPHORE_TAKE(); + for (uint8_t i = 0; i < GRID_PROFILE_SIZE; i++) { + ret.push_back(_payloadGridProfile[i]); + } + HOY_SEMAPHORE_GIVE(); + return ret; +} diff --git a/lib/Hoymiles/src/parser/GridProfileParser.h b/lib/Hoymiles/src/parser/GridProfileParser.h new file mode 100644 index 000000000..c2af52f87 --- /dev/null +++ b/lib/Hoymiles/src/parser/GridProfileParser.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once +#include "Parser.h" + +#define GRID_PROFILE_SIZE 141 + +class GridProfileParser : public Parser { +public: + GridProfileParser(); + void clearBuffer(); + void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); + + std::vector getRawData(); + +private: + uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {}; + uint8_t _gridProfileLength = 0; +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/Parser.cpp b/lib/Hoymiles/src/parser/Parser.cpp index 94c694c0b..b8e5e0e56 100644 --- a/lib/Hoymiles/src/parser/Parser.cpp +++ b/lib/Hoymiles/src/parser/Parser.cpp @@ -1,9 +1,15 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022 - 2023 Thomas Basler and others */ #include "Parser.h" +Parser::Parser() +{ + _xSemaphore = xSemaphoreCreateMutex(); + HOY_SEMAPHORE_GIVE(); // release before first use +} + uint32_t Parser::getLastUpdate() { return _lastUpdate; @@ -12,4 +18,14 @@ uint32_t Parser::getLastUpdate() void Parser::setLastUpdate(uint32_t lastUpdate) { _lastUpdate = lastUpdate; +} + +void Parser::beginAppendFragment() +{ + HOY_SEMAPHORE_TAKE(); +} + +void Parser::endAppendFragment() +{ + HOY_SEMAPHORE_GIVE(); } \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/Parser.h b/lib/Hoymiles/src/parser/Parser.h index ccb486bda..5d6df75df 100644 --- a/lib/Hoymiles/src/parser/Parser.h +++ b/lib/Hoymiles/src/parser/Parser.h @@ -1,7 +1,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include +#define HOY_SEMAPHORE_TAKE() \ + do { \ + } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) +#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) + typedef enum { CMD_OK, CMD_NOK, @@ -10,9 +16,16 @@ typedef enum { class Parser { public: + Parser(); uint32_t getLastUpdate(); void setLastUpdate(uint32_t lastUpdate); + void beginAppendFragment(); + void endAppendFragment(); + +protected: + SemaphoreHandle_t _xSemaphore; + private: uint32_t _lastUpdate = 0; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/PowerCommandParser.cpp b/lib/Hoymiles/src/parser/PowerCommandParser.cpp index 0ba3a8ae6..d698dad8d 100644 --- a/lib/Hoymiles/src/parser/PowerCommandParser.cpp +++ b/lib/Hoymiles/src/parser/PowerCommandParser.cpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022 - 2023 Thomas Basler and others */ #include "PowerCommandParser.h" diff --git a/lib/Hoymiles/src/parser/PowerCommandParser.h b/lib/Hoymiles/src/parser/PowerCommandParser.h index 6aa042f30..e005812e6 100644 --- a/lib/Hoymiles/src/parser/PowerCommandParser.h +++ b/lib/Hoymiles/src/parser/PowerCommandParser.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" -#include class PowerCommandParser : public Parser { public: diff --git a/lib/Hoymiles/src/parser/StatisticsParser.cpp b/lib/Hoymiles/src/parser/StatisticsParser.cpp index ac61be81b..d415fc3f8 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.cpp +++ b/lib/Hoymiles/src/parser/StatisticsParser.cpp @@ -1,15 +1,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022 - 2023 Thomas Basler and others */ #include "StatisticsParser.h" #include "../Hoymiles.h" -#define HOY_SEMAPHORE_TAKE() \ - do { \ - } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) -#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) - static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0); static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0); static float calcUdcCh(StatisticsParser* iv, uint8_t arg0); @@ -33,11 +28,35 @@ const calcFunc_t calcFunctions[] = { { CALC_IRR_CH, &calcIrradiation } }; +const FieldId_t runtimeFields[] = { + FLD_UDC, + FLD_IDC, + FLD_PDC, + FLD_UAC, + FLD_IAC, + FLD_PAC, + FLD_F, + FLD_T, + FLD_PF, + FLD_Q, + FLD_UAC_1N, + FLD_UAC_2N, + FLD_UAC_3N, + FLD_UAC_12, + FLD_UAC_23, + FLD_UAC_31, + FLD_IAC_1, + FLD_IAC_2, + FLD_IAC_3, +}; + +const FieldId_t dailyProductionFields[] = { + FLD_YD, +}; + StatisticsParser::StatisticsParser() : Parser() { - _xSemaphore = xSemaphoreCreateMutex(); - HOY_SEMAPHORE_GIVE(); // release before first use clearBuffer(); } @@ -75,16 +94,6 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t _statisticLength += len; } -void StatisticsParser::beginAppendFragment() -{ - HOY_SEMAPHORE_TAKE(); -} - -void StatisticsParser::endAppendFragment() -{ - HOY_SEMAPHORE_GIVE(); -} - const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { for (uint8_t i = 0; i < _byteAssignmentSize; i++) { @@ -150,6 +159,47 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch return 0; } +bool StatisticsParser::setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value) +{ + const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId); + fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId); + + if (pos == NULL) { + return false; + } + + uint8_t ptr = pos->start + pos->num - 1; + uint8_t end = pos->start; + uint16_t div = pos->div; + + if (CMD_CALC == div) { + return false; + } + + if (setting != NULL) { + value -= setting->offset; + } + value *= static_cast(div); + + uint32_t val = 0; + if (pos->isSigned && pos->num == 2) { + val = static_cast(static_cast(value)); + } else if (pos->isSigned && pos->num == 4) { + val = static_cast(static_cast(value)); + } else { + val = static_cast(value); + } + + HOY_SEMAPHORE_TAKE(); + do { + _payloadStatistic[ptr] = val; + val >>= 8; + } while (--ptr >= end); + HOY_SEMAPHORE_GIVE(); + + return true; +} + String StatisticsParser::getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId) { return String( @@ -253,6 +303,30 @@ uint32_t StatisticsParser::getRxFailureCount() return _rxFailureCount; } +void StatisticsParser::zeroRuntimeData() +{ + zeroFields(runtimeFields); +} + +void StatisticsParser::zeroDailyData() +{ + zeroFields(dailyProductionFields); +} + +void StatisticsParser::zeroFields(const FieldId_t* fields) +{ + // Loop all channels + for (auto& t : getChannelTypes()) { + for (auto& c : getChannelsByType(t)) { + for (uint8_t i = 0; i < (sizeof(runtimeFields) / sizeof(runtimeFields[0])); i++) { + if (hasChannelFieldValue(t, c, fields[i])) { + setChannelFieldValue(t, c, fields[i], 0); + } + } + } + } +} + static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0) { float yield = 0; diff --git a/lib/Hoymiles/src/parser/StatisticsParser.h b/lib/Hoymiles/src/parser/StatisticsParser.h index 13d7d4f47..4423f1f3b 100644 --- a/lib/Hoymiles/src/parser/StatisticsParser.h +++ b/lib/Hoymiles/src/parser/StatisticsParser.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" -#include #include #include @@ -107,8 +106,6 @@ class StatisticsParser : public Parser { StatisticsParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); - void beginAppendFragment(); - void endAppendFragment(); void setByteAssignment(const byteAssign_t* byteAssignment, uint8_t size); @@ -125,6 +122,8 @@ class StatisticsParser : public Parser { const char* getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); uint8_t getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); + bool setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value); + float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId); void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset); @@ -139,7 +138,12 @@ class StatisticsParser : public Parser { void incrementRxFailureCount(); uint32_t getRxFailureCount(); + void zeroRuntimeData(); + void zeroDailyData(); + private: + void zeroFields(const FieldId_t* fields); + uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {}; uint8_t _statisticLength = 0; uint16_t _stringMaxPower[CH_CNT]; @@ -150,6 +154,4 @@ class StatisticsParser : public Parser { std::list _fieldSettings; uint32_t _rxFailureCount = 0; - - SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp index 2756e1ec8..d1ed30b63 100644 --- a/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.cpp @@ -1,21 +1,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * Copyright (C) 2022 Thomas Basler and others + * Copyright (C) 2022 - 2023 Thomas Basler and others */ #include "SystemConfigParaParser.h" #include "../Hoymiles.h" #include -#define HOY_SEMAPHORE_TAKE() \ - do { \ - } while (xSemaphoreTake(_xSemaphore, portMAX_DELAY) != pdPASS) -#define HOY_SEMAPHORE_GIVE() xSemaphoreGive(_xSemaphore) - SystemConfigParaParser::SystemConfigParaParser() : Parser() { - _xSemaphore = xSemaphoreCreateMutex(); - HOY_SEMAPHORE_GIVE(); // release before first use clearBuffer(); } @@ -35,16 +28,6 @@ void SystemConfigParaParser::appendFragment(uint8_t offset, uint8_t* payload, ui _payloadLength += len; } -void SystemConfigParaParser::beginAppendFragment() -{ - HOY_SEMAPHORE_TAKE(); -} - -void SystemConfigParaParser::endAppendFragment() -{ - HOY_SEMAPHORE_GIVE(); -} - float SystemConfigParaParser::getLimitPercent() { HOY_SEMAPHORE_TAKE(); @@ -101,4 +84,9 @@ void SystemConfigParaParser::setLastUpdateRequest(uint32_t lastUpdate) { _lastUpdateRequest = lastUpdate; setLastUpdate(lastUpdate); -} \ No newline at end of file +} + +uint8_t SystemConfigParaParser::getExpectedByteCount() +{ + return SYSTEM_CONFIG_PARA_SIZE; +} diff --git a/lib/Hoymiles/src/parser/SystemConfigParaParser.h b/lib/Hoymiles/src/parser/SystemConfigParaParser.h index 4ec73817c..300a81822 100644 --- a/lib/Hoymiles/src/parser/SystemConfigParaParser.h +++ b/lib/Hoymiles/src/parser/SystemConfigParaParser.h @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" -#include #define SYSTEM_CONFIG_PARA_SIZE 16 @@ -10,8 +9,6 @@ class SystemConfigParaParser : public Parser { SystemConfigParaParser(); void clearBuffer(); void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); - void beginAppendFragment(); - void endAppendFragment(); float getLimitPercent(); void setLimitPercent(float value); @@ -26,6 +23,9 @@ class SystemConfigParaParser : public Parser { uint32_t getLastUpdateRequest(); void setLastUpdateRequest(uint32_t lastUpdate); + // Returns 1 based amount of expected bytes of data + uint8_t getExpectedByteCount(); + private: uint8_t _payload[SYSTEM_CONFIG_PARA_SIZE]; uint8_t _payloadLength; @@ -35,6 +35,4 @@ class SystemConfigParaParser : public Parser { uint32_t _lastUpdateCommand = 0; uint32_t _lastUpdateRequest = 0; - - SemaphoreHandle_t _xSemaphore; }; \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 0c75747e8..5ba51da5e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,7 +33,7 @@ build_unflags = lib_deps = https://github.com/yubox-node-org/ESPAsyncWebServer bblanchon/ArduinoJson @ ^6.21.3 - https://github.com/bertmelis/espMqttClient.git#v1.4.4 + https://github.com/bertmelis/espMqttClient.git#v1.4.5 nrf24/RF24 @ ^1.4.7 olikraus/U8g2 @ ^2.35.4 buelowp/sunset @ ^1.1.7 diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index fb1d2f2de..2c4997e97 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -1,8 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include #include "BatteryStats.h" -#include "Configuration.h" #include "MqttSettings.h" #include "JkBmsDataPoints.h" @@ -148,34 +145,6 @@ void PylontechBatteryStats::mqttPublish() const void JkBmsBatteryStats::mqttPublish() const { BatteryStats::mqttPublish(); - - using Label = JkBms::DataPointLabel; - - static std::vector