diff --git a/.vscode/settings.json b/.vscode/settings.json index cad7657..fc6091a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,6 @@ { - "cmake.configureOnOpen": false + "cmake.configureOnOpen": false, + "files.associations": { + "ranges": "cpp" + } } \ No newline at end of file diff --git a/dtuConst.h b/dtuConst.h new file mode 100644 index 0000000..e36c62f --- /dev/null +++ b/dtuConst.h @@ -0,0 +1,119 @@ +// constants converted from python constant file: https://raw.githubusercontent.com/suaveolent/hoymiles-wifi/main/hoymiles_wifi/const.py +// +// """Constants for the Hoymiles WiFi integration.""" +// +#define DTU_PORT 10081 +// +// # App -> DTU start with 0xa3, responses start 0xa2 +const byte CMD_HEADER[] = {}; +const byte CMD_APP_INFO_DATA_RES_DTO[] = {0xa3,0x01}; +const byte CMD_HB_RES_DTO[] = {0xa3,0x02}; +const byte CMD_REAL_DATA_RES_DTO[] = {0xa3,0x03}; +const byte CMD_W_INFO_RES_DTO[] = {0xa3,0x04}; +const byte CMD_COMMAND_RES_DTO[] = {0xa3,0x05}; +const byte CMD_COMMAND_STATUS_RES_DTO[] = {0xa3,0x06}; +const byte CMD_DEV_CONFIG_FETCH_RES_DTO[] = {0xa3,0x07}; +const byte CMD_DEV_CONFIG_PUT_RES_DTO[] = {0xa3,0x08}; +const byte CMD_GET_CONFIG[] = {0xa3,0x09}; +const byte CMD_SET_CONFIG[] = {0xa3,0x10}; +const byte CMD_REAL_RES_DTO[] = {0xa3,0x11}; +const byte CMD_GPST_RES_DTO[] = {0xa3,0x12}; +const byte CMD_AUTO_SEARCH[] = {0xa3,0x13}; +const byte CMD_NETWORK_INFO_RES[] = {0xa3,0x14}; +const byte CMD_APP_GET_HIST_POWER_RES[] = {0xa3,0x15}; +const byte CMD_APP_GET_HIST_ED_RES[] = {0xa3,0x16}; +const byte CMD_HB_RES_DTO_ALT[] = {0x83,0x01}; +const byte CMD_REGISTER_RES_DTO[] = {0x83,0x02}; +const byte CMD_STORAGE_DATA_RES[] = {0x83,0x03}; +const byte CMD_COMMAND_RES_DTO_2[] = {0x83,0x05}; +const byte CMD_COMMAND_STATUS_RES_DTO_2[] = {0x83,0x06}; +const byte CMD_DEV_CONFIG_FETCH_RES_DTO_2[] = {0x83,0x07}; +const byte CMD_DEV_CONFIG_PUT_RES_DTO_2[] = {0x83,0x08}; +const byte CMD_GET_CONFIG_RES[] = {0xdb,0x08}; +const byte CMD_SET_CONFIG_RES[] = {0xdb,0x07}; +// +const byte CMD_CLOUD_INFO_DATA_RES_DTO[] = {0x23,0x01}; +const byte CMD_CLOUD_COMMAND_RES_DTO[] = {0x23,0x05}; +// +#define CMD_ACTION_MICRO_DEFAULT 0 +#define CMD_ACTION_DTU_REBOOT 1 +#define CMD_ACTION_DTU_UPGRADE 2 +#define CMD_ACTION_MI_REBOOT 3 +#define CMD_ACTION_COLLECT_VERSION 4 +#define CMD_ACTION_ANTI_THEFT_SETTING 5 +#define CMD_ACTION_MI_START 6 +#define CMD_ACTION_MI_SHUTDOWN 7 +#define CMD_ACTION_LIMIT_POWER 8 +#define CMD_ACTION_REFLUX_CONTROL 9 +#define CMD_ACTION_CLEAN_GROUNDING_FAULT 10 +#define CMD_ACTION_CT_SET 11 +#define CMD_ACTION_MI_LOCK 12 +#define CMD_ACTION_MI_UNLOCK 13 +#define CMD_ACTION_SET_GRID_FILE 14 +#define CMD_ACTION_UPGRADE_MI 15 +#define CMD_ACTION_ID_NETWORKING 16 +#define CMD_ACTION_REFLUX_NETWORKING 17 +#define CMD_ACTION_STOP_CONTROLLER_CMD 18 +#define CMD_ACTION_SET_WIFI_PASS 19 +#define CMD_ACTION_SET_SVR_DNS_PORT 20 +#define CMD_ACTION_SET_GPRS_APN 21 +#define CMD_ACTION_ANTI_THEFT_CONTROL 22 +#define CMD_ACTION_REPEATER_NETWORKING 0 +#define CMD_ACTION_DTU_DEFAULT 0 +#define CMD_ACTION_GATEWAY_DEFAULT 0 +#define CMD_ACTION_METER_REVERSE 49 +#define CMD_ACTION_GW_REBOOT 4096 +#define CMD_ACTION_GW_RESET 4097 +#define CMD_ACTION_GW_STOP_RUN 4098 +#define CMD_ACTION_GW_COLLECT_REAL_DATA 4099 +#define CMD_ACTION_GW_COLLECT_VER 4100 +#define CMD_ACTION_GW_AUTO_NETWORKING 4101 +#define CMD_ACTION_GW_UPGRADE 4102 +#define CMD_ACTION_MICRO_MEMORY_SNAPSHOT 53 +#define CMD_ACTION_MICRO_DATA_WAVE 54 +#define CMD_ACTION_SET_485_PORT 36 +#define CMD_ACTION_THREE_BALANCE_SET 37 +#define CMD_ACTION_MI_GRID_PROTECT_SELF 38 +#define CMD_ACTION_SUN_SPEC_CONFIG 39 +#define CMD_ACTION_POWER_GENERATION_CORRECT 40 +#define CMD_ACTION_GRID_FILE_READ 41 +#define CMD_ACTION_CLEAN_WARN 42 +#define CMD_ACTION_DRM_SETTING 43 +#define CMD_ACTION_ES_CONFIG_MANAGER 0 +#define CMD_ACTION_ES_USER_SETTING 0 +#define CMD_ACTION_READ_MI_HU_WARN 46 +#define CMD_ACTION_LIMIT_POWER_PF 47 +#define CMD_ACTION_LIMIT_POWER_REACTIVE 48 +#define CMD_ACTION_INV_BOOT_UP 8193 +#define CMD_ACTION_INV_SHUTDOWN 8194 +#define CMD_ACTION_INV_REBOOT 8195 +#define CMD_ACTION_INV_RESET 8196 +#define CMD_ACTION_INV_CLEAN_WARN 8197 +#define CMD_ACTION_INV_CLEAN_HIS_DATA 8198 +#define CMD_ACTION_INV_UPLOAD_REAL_DATA 8199 +#define CMD_ACTION_INV_FIND_DEV 8200 +#define CMD_ACTION_INV_BATTERY_MODE_CONFIG 0 +#define CMD_ACTION_BMS_REBOOT 8224 +#define CMD_ACTION_BMS_URGENT_CHARGING 8225 +#define CMD_ACTION_BMS_BALANCE 8208 +#define CMD_ACTION_INV_UPGRADE 4112 +#define CMD_ACTION_BMS_UPGRADE 4112 +// +// +#define DEV_DTU 1 +#define DEV_REPEATER 2 +#define DEV_MICRO 3 +#define DEV_MODEL 4 +#define DEV_METER 5 +#define DEV_INV 6 +#define DEV_RSD 7 +#define DEV_OP 8 +#define DEV_GATEWAY 9 +#define DEV_BMS 10 +// +// DTU_FIRMWARE_URL_00_01_11 = ( +// "http://fwupdate.hoymiles.com/cfs/bin/2311/06/,1488725943932555264.bin" +// ) +// +#define MAX_POWER_LIMIT 100 +// \ No newline at end of file diff --git a/include/dtuConst.h b/include/dtuConst.h new file mode 100644 index 0000000..50e74e8 --- /dev/null +++ b/include/dtuConst.h @@ -0,0 +1,119 @@ +// constants converted from python constants file: https://raw.githubusercontent.com/suaveolent/hoymiles-wifi/main/hoymiles_wifi/const.py +// +// """Constants for the Hoymiles WiFi integration.""" +// +#define DTU_PORT 10081 +// +// # App -> DTU start with 0xa3, responses start 0xa2 +const byte CMD_HEADER[] = {}; +const byte CMD_APP_INFO_DATA_RES_DTO[] = {0xa3,0x01}; +const byte CMD_HB_RES_DTO[] = {0xa3,0x02}; +const byte CMD_REAL_DATA_RES_DTO[] = {0xa3,0x03}; +const byte CMD_W_INFO_RES_DTO[] = {0xa3,0x04}; +const byte CMD_COMMAND_RES_DTO[] = {0xa3,0x05}; +const byte CMD_COMMAND_STATUS_RES_DTO[] = {0xa3,0x06}; +const byte CMD_DEV_CONFIG_FETCH_RES_DTO[] = {0xa3,0x07}; +const byte CMD_DEV_CONFIG_PUT_RES_DTO[] = {0xa3,0x08}; +const byte CMD_GET_CONFIG[] = {0xa3,0x09}; +const byte CMD_SET_CONFIG[] = {0xa3,0x10}; +const byte CMD_REAL_RES_DTO[] = {0xa3,0x11}; +const byte CMD_GPST_RES_DTO[] = {0xa3,0x12}; +const byte CMD_AUTO_SEARCH[] = {0xa3,0x13}; +const byte CMD_NETWORK_INFO_RES[] = {0xa3,0x14}; +const byte CMD_APP_GET_HIST_POWER_RES[] = {0xa3,0x15}; +const byte CMD_APP_GET_HIST_ED_RES[] = {0xa3,0x16}; +const byte CMD_HB_RES_DTO_ALT[] = {0x83,0x01}; +const byte CMD_REGISTER_RES_DTO[] = {0x83,0x02}; +const byte CMD_STORAGE_DATA_RES[] = {0x83,0x03}; +const byte CMD_COMMAND_RES_DTO_2[] = {0x83,0x05}; +const byte CMD_COMMAND_STATUS_RES_DTO_2[] = {0x83,0x06}; +const byte CMD_DEV_CONFIG_FETCH_RES_DTO_2[] = {0x83,0x07}; +const byte CMD_DEV_CONFIG_PUT_RES_DTO_2[] = {0x83,0x08}; +const byte CMD_GET_CONFIG_RES[] = {0xdb,0x08}; +const byte CMD_SET_CONFIG_RES[] = {0xdb,0x07}; +// +const byte CMD_CLOUD_INFO_DATA_RES_DTO[] = {0x23,0x01}; +const byte CMD_CLOUD_COMMAND_RES_DTO[] = {0x23,0x05}; +// +#define CMD_ACTION_MICRO_DEFAULT 0 +#define CMD_ACTION_DTU_REBOOT 1 +#define CMD_ACTION_DTU_UPGRADE 2 +#define CMD_ACTION_MI_REBOOT 3 +#define CMD_ACTION_COLLECT_VERSION 4 +#define CMD_ACTION_ANTI_THEFT_SETTING 5 +#define CMD_ACTION_MI_START 6 +#define CMD_ACTION_MI_SHUTDOWN 7 +#define CMD_ACTION_LIMIT_POWER 8 +#define CMD_ACTION_REFLUX_CONTROL 9 +#define CMD_ACTION_CLEAN_GROUNDING_FAULT 10 +#define CMD_ACTION_CT_SET 11 +#define CMD_ACTION_MI_LOCK 12 +#define CMD_ACTION_MI_UNLOCK 13 +#define CMD_ACTION_SET_GRID_FILE 14 +#define CMD_ACTION_UPGRADE_MI 15 +#define CMD_ACTION_ID_NETWORKING 16 +#define CMD_ACTION_REFLUX_NETWORKING 17 +#define CMD_ACTION_STOP_CONTROLLER_CMD 18 +#define CMD_ACTION_SET_WIFI_PASS 19 +#define CMD_ACTION_SET_SVR_DNS_PORT 20 +#define CMD_ACTION_SET_GPRS_APN 21 +#define CMD_ACTION_ANTI_THEFT_CONTROL 22 +#define CMD_ACTION_REPEATER_NETWORKING 0 +#define CMD_ACTION_DTU_DEFAULT 0 +#define CMD_ACTION_GATEWAY_DEFAULT 0 +#define CMD_ACTION_METER_REVERSE 49 +#define CMD_ACTION_GW_REBOOT 4096 +#define CMD_ACTION_GW_RESET 4097 +#define CMD_ACTION_GW_STOP_RUN 4098 +#define CMD_ACTION_GW_COLLECT_REAL_DATA 4099 +#define CMD_ACTION_GW_COLLECT_VER 4100 +#define CMD_ACTION_GW_AUTO_NETWORKING 4101 +#define CMD_ACTION_GW_UPGRADE 4102 +#define CMD_ACTION_MICRO_MEMORY_SNAPSHOT 53 +#define CMD_ACTION_MICRO_DATA_WAVE 54 +#define CMD_ACTION_SET_485_PORT 36 +#define CMD_ACTION_THREE_BALANCE_SET 37 +#define CMD_ACTION_MI_GRID_PROTECT_SELF 38 +#define CMD_ACTION_SUN_SPEC_CONFIG 39 +#define CMD_ACTION_POWER_GENERATION_CORRECT 40 +#define CMD_ACTION_GRID_FILE_READ 41 +#define CMD_ACTION_CLEAN_WARN 42 +#define CMD_ACTION_DRM_SETTING 43 +#define CMD_ACTION_ES_CONFIG_MANAGER 0 +#define CMD_ACTION_ES_USER_SETTING 0 +#define CMD_ACTION_READ_MI_HU_WARN 46 +#define CMD_ACTION_LIMIT_POWER_PF 47 +#define CMD_ACTION_LIMIT_POWER_REACTIVE 48 +#define CMD_ACTION_INV_BOOT_UP 8193 +#define CMD_ACTION_INV_SHUTDOWN 8194 +#define CMD_ACTION_INV_REBOOT 8195 +#define CMD_ACTION_INV_RESET 8196 +#define CMD_ACTION_INV_CLEAN_WARN 8197 +#define CMD_ACTION_INV_CLEAN_HIS_DATA 8198 +#define CMD_ACTION_INV_UPLOAD_REAL_DATA 8199 +#define CMD_ACTION_INV_FIND_DEV 8200 +#define CMD_ACTION_INV_BATTERY_MODE_CONFIG 0 +#define CMD_ACTION_BMS_REBOOT 8224 +#define CMD_ACTION_BMS_URGENT_CHARGING 8225 +#define CMD_ACTION_BMS_BALANCE 8208 +#define CMD_ACTION_INV_UPGRADE 4112 +#define CMD_ACTION_BMS_UPGRADE 4112 +// +// +#define DEV_DTU 1 +#define DEV_REPEATER 2 +#define DEV_MICRO 3 +#define DEV_MODEL 4 +#define DEV_METER 5 +#define DEV_INV 6 +#define DEV_RSD 7 +#define DEV_OP 8 +#define DEV_GATEWAY 9 +#define DEV_BMS 10 +// +// DTU_FIRMWARE_URL_00_01_11 = ( +// "http://fwupdate.hoymiles.com/cfs/bin/2311/06/,1488725943932555264.bin" +// ) +// +#define MAX_POWER_LIMIT 100 +// \ No newline at end of file diff --git a/include/dtuInterface.h b/include/dtuInterface.h index 6dfabb5..a7af80e 100644 --- a/include/dtuInterface.h +++ b/include/dtuInterface.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "pb_encode.h" #include "pb_decode.h" @@ -24,6 +25,7 @@ const uint16_t dtuPort = 10081; #define DTU_STATE_CONNECTED 1 #define DTU_STATE_CLOUD_PAUSE 2 #define DTU_STATE_TRY_RECONNECT 3 +#define DTU_STATE_DTU_REBOOT 4 #define DTU_ERROR_NO_ERROR 0 #define DTU_ERROR_NO_TIME 1 @@ -65,21 +67,29 @@ struct inverterData uint32_t respTimestamp = 1704063600; // init with start time stamp > 0 uint32_t lastRespTimestamp = 1704063600; // init with start time stamp > 0 boolean uptodate = false; + int dtuResetRequested = 0; }; extern inverterData globalData; extern CRC16 crc; +void dtuConnectionEstablish(WiFiClient *localDtuClient, char localDtuHostIp[16], uint16_t localDtuPort = 10081); +void dtuConnectionStop(WiFiClient *localDtuClient, uint8_t tgtState); +void dtuConnectionHandleError(WiFiClient *localDtuClient, unsigned long locTimeSec, uint8_t errorState = DTU_ERROR_NO_ERROR); + +unsigned long getDtuRemoteTimeAndDataUpdate(WiFiClient *localDtuClient, unsigned long locTimeSec); +void printDataAsTextToSerial(); +void printDataAsJsonToSerial(); void initializeCRC(); float calcValue(int32_t value, int32_t divider = 10); String getTimeStringByTimestamp(unsigned long timestamp); -boolean preventCloudErrorTask(unsigned long locTimeSec); +boolean dtuCloudPauseActiveControl(unsigned long locTimeSec); void readRespAppGetHistPower(WiFiClient *localDtuClient); void writeReqAppGetHistPower(WiFiClient *localDtuClient, unsigned long locTimeSec); -void readRespRealDataNew(WiFiClient *localDtuClient); +void readRespRealDataNew(WiFiClient *localDtuClient, unsigned long locTimeSec); void writeReqRealDataNew(WiFiClient *localDtuClient, unsigned long locTimeSec); void readRespGetConfig(WiFiClient *localDtuClient); @@ -88,5 +98,7 @@ void writeReqGetConfig(WiFiClient *localDtuClient, unsigned long locTimeSec); boolean readRespCommand(WiFiClient *localDtuClient); boolean writeReqCommand(WiFiClient *localDtuClient, uint8_t setPercent, unsigned long locTimeSec); +boolean readRespCommandRestartDevice(WiFiClient *localDtuClient); +boolean writeCommandRestartDevice(WiFiClient *localDtuClient, unsigned long locTimeSec); #endif // DTUINTERFACE_H \ No newline at end of file diff --git a/include/index_html.h b/include/index_html.h index 79d016d..a3a9f1c 100644 --- a/include/index_html.h +++ b/include/index_html.h @@ -314,6 +314,10 @@ const char INDEX_HTML[] PROGMEM = R"=====( DTU state ok +
+ DTU reboots + 0 +
temperature --.- °C @@ -496,8 +500,11 @@ const char INDEX_HTML[] PROGMEM = R"=====( case 3: dtuConnect = "try reconnect"; break; + case 4: + dtuConnect = "dtu rebooting"; + break; default: - dtuConnect = "no_info"; + dtuConnect = "not known"; } checkValueUpdate('#dtu_connect_state', dtuConnect); var dtuState = ""; @@ -538,7 +545,9 @@ const char INDEX_HTML[] PROGMEM = R"=====( // setting timer value according to user setting waitTime = data.dtuConnection.dtuDataCycle * 1000; - + + checkValueUpdate('#dtu_reboots_no', data.dtuConnection.dtuResetRequested ); + return true; } @@ -577,7 +586,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( // get networkdata $('#dtuHostIp').val(dtuData.dtuHostIp); $('#dtuDataCycle').val(dtuData.dtuDataCycle); - if(dtuData.dtuCloudPause) { + if (dtuData.dtuCloudPause) { $('#dtuCloudPause').prop("checked", true); } else { $('#dtuCloudPause').prop("checked", false); @@ -660,7 +669,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( function changeDtuData() { var dtuHostIpSend = $('#dtuHostIp').val(); var dtuDataCycleSend = $('#dtuDataCycle').val(); - if($("#dtuCloudPause").is(':checked')) { + if ($("#dtuCloudPause").is(':checked')) { dtuCloudPauseSend = 1; } else { dtuCloudPauseSend = 0; @@ -668,7 +677,7 @@ const char INDEX_HTML[] PROGMEM = R"=====( var dtuSsidSend = $('#dtuSsid').val(); var dtuPasswordSend = $('#dtuPassword').val(); - + var data = {}; data["dtuHostIpSend"] = dtuHostIpSend; data["dtuDataCycleSend"] = dtuDataCycleSend; diff --git a/include/style_css.h b/include/style_css.h index 3de1a54..9f26b12 100644 --- a/include/style_css.h +++ b/include/style_css.h @@ -112,6 +112,7 @@ hr { .panelHead { padding-bottom: 2px; + font-size: x-small; } /* animated change of color if value chnaged */ diff --git a/include/version.h b/include/version.h index 1006de1..c505119 100644 --- a/include/version.h +++ b/include/version.h @@ -1,3 +1,3 @@ -#define VERSION "1.3.0" -#define BUILDTIME "10.03.2024 - 09:23:07" -#define BUILDTIMESTAMP "1710058987" \ No newline at end of file +#define VERSION "1.4.0" +#define BUILDTIME "24.03.2024 - 15:28:25" +#define BUILDTIMESTAMP "1711290505" \ No newline at end of file diff --git a/include/version.json b/include/version.json index 2856cc3..a57d02e 100644 --- a/include/version.json +++ b/include/version.json @@ -1,6 +1,6 @@ { - "version": "1.3.0", - "versiondate": "10.03.2024 - 09:23:07", - "linksnapshot": "https://github.com/ohAnd/dtuGateway/releases/download/snapshot/dtuGateway_snapshot_1.3.0.bin", - "link": "https://github.com/ohAnd/dtuGateway//releases/latest/download/dtuGateway_release_1.3.0.bin" + "version": "1.4.0", + "versiondate": "24.03.2024 - 15:28:25", + "linksnapshot": "https://github.com/ohAnd/dtuGateway/releases/download/snapshot/dtuGateway_snapshot_1.4.0.bin", + "link": "https://github.com/ohAnd/dtuGateway//releases/latest/download/dtuGateway_release_1.4.0.bin" } \ No newline at end of file diff --git a/readme.md b/readme.md index 2176675..20d1a4f 100644 --- a/readme.md +++ b/readme.md @@ -30,19 +30,19 @@ ## problem The new series of Hoymiles inverter with internal wireless access point and wireless client have no direct API to include this endpoint in smarthome installations/ IFTT environments. -Usually there should be no need for an extra device to "translate" the connection to common APIs or bindings. Unfortunately the interface on the dtu is unlikey unstable/ or not really stable. +Usually there should be no need for an extra device to "translate" the connection to common APIs or bindings. Unfortunately the interface on the dtu is unlikely unstable/ or not really stable. E.g. there is a treshhold of ~ 31 seconds before you can send new data to the dtu (e.g. new power limit), otherwise the connection hangs and an internal restart/ reboot (???) leads to an offline time of ~ 30 minutes. Data from dtu can be read in a very short time, but it has to be tested how often a request leads to the problem before. -On a manual way you can be back on track if you logging in to the local access point of the dtu and resend your local wifi login data to (it seems) initiate a reboot. With this way you can be back online in ~ 1:30 minutes. +On a manual way you can be back on track, if you are logging in to the local access point of the dtu and resend your local wifi login data to (it seems) initiate a reboot. With this way you can be back online in ~ 1:30 minutes. > *hint: the whole project could be also implemented on a small server and translated to e.g. python [see here for an example](https://github.com/henkwiedig/Hoymiles-DTU-Proto) and also the sources below* ## goal -1. Abstract the interface to the dtu (inverter connection endpoint) with different possibilities to connect other systems. (push/ pull) -2. Very stable interface with no dependencies to a environment system with a stand alone application based on a arduino board (ESP8266). +1. Abstract the interface to the dtu (inverter connection endpoint) with different possibilities to connect to other systems. (push/ pull) +2. Very stable interface with no dependencies to an environment/ a system with a stand alone application based on an arduino board (ESP8266). 3. TODO: Ability to change running wifi to connect to dtu over local network or direct access point. 4. Use this need to create a full enivronment for an ESP based project. (see features below) @@ -59,7 +59,8 @@ On a manual way you can be back on track if you logging in to the local access p - updating openHab instance with readed data and pulling set data from the instance - for testing purposes the time between each request is adjustable (default 31 seconds) - syncing time of gateway with the local time of the dtu to prevent wrong restart counters -- configurable 'cloud pause' - see [experiences](#-experiences-with-the-hoymiles-HMS-800W-2T) - to prevent missing updates by the dtu to the hoymiles cloud +- configurable 'cloud pause' - see [experiences](#-experiences-with-the-hoymiles-HMS-800W-2T) - to prevent missing updates by the dtu to the hoymiles cloud +- automatic reboot of DTU, if there is an error detected (e.g. inplausible not changed values) ### regarding environment @@ -137,6 +138,7 @@ On a manual way you can be back on track if you logging in to the local access p "dtuPassword": "dtubiPassword", "dtuRssi": 0, "dtuDataCycle": 32, + "dtuResetRequested": 0, "dtuCloudPause": 1, "dtuCloudPauseTime": 40 }, @@ -198,7 +200,7 @@ On a manual way you can be back on track if you logging in to the local access p - "_WifiRSSI" ## known bugs -- ... +- sometimes out-of-memory resets with instant reboots (rare after some hours or more often after some days) ## releases ### installation / update @@ -246,6 +248,13 @@ If there to much requests of setting the power limit minutes later the connectio With the manual login to dtu access point and forcing the storing of local wifi connect data again, then dtu is back online and accessable in your local network. (This is a possible feature that can be implemented in future - needed protocol sequence has to be investigated) +[2024-03-24] +- lot of single updates for power setting within few seconds (< 2-3) without any reading of values (e.g. realdata) -> it seems this creating no problems +- therefore current setup -> no time limit for power setting, but reading data only every 31 seconds is running fine +- sometimes hanging or full shutdown/ break of DTU will be prevented by sending an active reboot request to dtu (hanging detection at this time over grid voltage, should be changing at least within 10 consecutive incoming data) +- with this setup: now the device is running for days without any stops (overall system point of view: target settings will be performed everytime, readed data will be available, no manual steps needed to recover the dtu connection) + + ### hoymiles cloud update - everey 15 min (0,15,30,45) -> timestamp update - after 7 min 40 s update of graph data (if wifi not reachable, also reset of wifi AP) diff --git a/src/dtuGateway.ino b/src/dtuGateway.ino index e1a7cda..1241769 100644 --- a/src/dtuGateway.ino +++ b/src/dtuGateway.ino @@ -91,7 +91,7 @@ unsigned long previousMillisShort = 1704063600; unsigned long previousMillis5000ms = 1704063600; unsigned long previousMillisMid = 1704063600; unsigned long previousMillisLong = 1704063600; -unsigned long localTimeSecond = 1704063600; +unsigned long timeStampInSecondsDtuSynced = 1704063600; struct controls { @@ -224,7 +224,7 @@ void handleRoot() void handleDataJson() { String JSON = "{"; - JSON = JSON + "\"localtime\": " + String(localTimeSecond) + ","; + JSON = JSON + "\"localtime\": " + String(timeStampInSecondsDtuSynced) + ","; JSON = JSON + "\"ntpStamp\": " + String(timeClient.getEpochTime() - CLIENT_TIME_OFFSET) + ","; JSON = JSON + "\"lastResponse\": " + globalData.lastRespTimestamp + ","; @@ -297,6 +297,7 @@ void handleInfojson() JSON = JSON + "\"dtuPassword\": \"" + String(userConfig.dtuPassword) + "\","; JSON = JSON + "\"dtuRssi\": " + globalData.dtuRssi + ","; JSON = JSON + "\"dtuDataCycle\": " + userConfig.dtuUpdateTime + ","; + JSON = JSON + "\"dtuResetRequested\": " + globalData.dtuResetRequested + ","; JSON = JSON + "\"dtuCloudPause\": " + userConfig.dtuCloudPauseActive + ","; JSON = JSON + "\"dtuCloudPauseTime\": " + userConfig.dtuCloudPauseTime; JSON = JSON + "},"; @@ -382,9 +383,8 @@ void handleUpdateDtuSettings() delay(2000); // give time for the json response - // stopping connection to DTU and set right state - to force reconnect with new data - dtuClient.stop(); - dtuConnection.dtuConnectState = DTU_STATE_OFFLINE; + // stopping connection to DTU - to force reconnect with new data + dtuConnectionStop(&dtuClient, DTU_STATE_TRY_RECONNECT); } void handleUpdateOpenhabSettings() @@ -405,10 +405,6 @@ void handleUpdateOpenhabSettings() delay(2000); // give time for the json response - // stopping connection to DTU and set right state - to force reconnect with new data - dtuClient.stop(); - dtuConnection.dtuConnectState = DTU_STATE_OFFLINE; - Serial.println("handleUpdateOpenhabSettings - send JSON: " + String(JSON)); } @@ -708,7 +704,7 @@ boolean postMessageToOpenhab(String key, String value) if (httpCode == HTTPC_ERROR_CONNECTION_REFUSED || httpCode == HTTPC_ERROR_SEND_HEADER_FAILED || httpCode == HTTPC_ERROR_SEND_PAYLOAD_FAILED) { - Serial.print("\n[HTTP] postMessageToOpenhab Timeout error: " + String(httpCode) + "\n"); + Serial.print("\n[HTTP] postMessageToOpenhab (" + key + ") Timeout error: " + String(httpCode) + "\n"); http.end(); return false; // Return timeout error } @@ -1132,60 +1128,29 @@ void getSerialCommand(String cmd, String value) ESP.restart(); } } - else - { - Serial.print(F("Cmd not recognized\n")); - } - Serial.print(F("\n")); -} - -// main - -void getDtuDataUpdate(WiFiClient *localDtuClient) -{ - if (localDtuClient->connected()) + else if (cmd == "rebootDTU") { - writeReqRealDataNew(localDtuClient, localTimeSecond); - // writeReqAppGetHistPower(&dtuClient); // only needed for sum energy daily/ total - but can lead to overflow for history data/ prevent maybe cloud update - writeReqGetConfig(localDtuClient, localTimeSecond); - // dtuClient.stop(); - // dtuConnection.dtuConnectState = DTU_STATE_OFFLINE; - - // check for up-to-date - last response timestamp have to not equal the current response timestamp - if ((globalData.lastRespTimestamp != globalData.respTimestamp) && (globalData.respTimestamp != 0)) - { - globalData.uptodate = true; - dtuConnection.dtuErrorState = DTU_ERROR_NO_ERROR; - // sync localTimeSecond to DTU time, only if abbrevation about 3 seconds - if (abs((int(globalData.respTimestamp) - int(localTimeSecond))) > 3) - { - localTimeSecond = globalData.respTimestamp; - Serial.print(F("\n>--> synced local time with DTU time <--<\n")); - } - } - else + Serial.print(F(" rebootDTU ")); + if (val == 1) { - globalData.uptodate = false; - dtuConnection.dtuErrorState = DTU_ERROR_TIME_DIFF; - dtuConnection.dtuConnectState = DTU_STATE_TRY_RECONNECT; - localDtuClient->stop(); // stopping connection to DTU when response time error - try with reconnect + Serial.print(F(" send reboot request ")); + writeCommandRestartDevice(&dtuClient, timeStampInSecondsDtuSynced); } - globalData.lastRespTimestamp = globalData.respTimestamp; } else { - globalData.uptodate = false; - dtuConnection.dtuErrorState = DTU_ERROR_NO_ERROR; - dtuConnection.dtuConnectState = DTU_STATE_TRY_RECONNECT; - dtuClient.stop(); + Serial.print(F("Cmd not recognized\n")); } + Serial.print(F("\n")); } +// main + // get precise localtime - increment void IRAM_ATTR timer1000MilliSeconds() { // localtime counter - increase every second - localTimeSecond++; + timeStampInSecondsDtuSynced++; } void loop() @@ -1204,13 +1169,13 @@ void loop() } // CHANGE to precise 1 second timer increment - currentMillis = localTimeSecond; + currentMillis = timeStampInSecondsDtuSynced; // short task if (currentMillis - previousMillisShort >= intervalShort) { // Serial.printf("\n>>>>> %02is task - state --> ", int(intervalShort)); - // Serial.print("local: " + getTimeStringByTimestamp(localTimeSecond)); + // Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); // Serial.print(" --- NTP: " + timeClient.getFormattedTime() + " --- currentMillis " + String(currentMillis) + " --- "); previousMillisShort = currentMillis; // --------> @@ -1218,19 +1183,20 @@ void loop() if (dtuConnection.preventCloudErrors) { // task to check and change for cloud update pause - if (preventCloudErrorTask(localTimeSecond)) + if (dtuCloudPauseActiveControl(timeStampInSecondsDtuSynced)) + { + globalData.uptodate = false; blinkCode = BLINK_PAUSE_CLOUD_UPDATE; - // disconnet DTU server, if prevention on - if (dtuConnection.dtuActiveOffToCloudUpdate) - dtuClient.stop(); + dtuConnectionStop(&dtuClient, DTU_STATE_CLOUD_PAUSE); // disconnet DTU server, if prevention on + } } if (globalControls.wifiSwitch && !userConfig.wifiAPstart) checkWifiTask(); else { - dtuClient.stop(); // stopping connection to DTU before go wifi offline - dtuConnection.dtuConnectState = DTU_STATE_OFFLINE; + // stopping connection to DTU before go wifi offline + dtuConnectionStop(&dtuClient, DTU_STATE_OFFLINE); WiFi.disconnect(); } @@ -1238,12 +1204,15 @@ void loop() if (globalData.powerLimitSet != globalData.powerLimit && globalData.powerLimitSet != 101 && globalData.uptodate) { Serial.print("\n----- ----- set new power limit from " + String(globalData.powerLimit) + " to " + String(globalData.powerLimitSet)); - if (writeReqCommand(&dtuClient, globalData.powerLimitSet, localTimeSecond)) + if (writeReqCommand(&dtuClient, globalData.powerLimitSet, timeStampInSecondsDtuSynced)) { Serial.print(" --- done"); - // set next normal request in 5 seconds from now on - previousMillisMid = currentMillis - (intervalMid - 5); - } else { + // set next normal request in 5 seconds from now on, only if last data updated within last 2 times of user setted update rate + if (currentMillis - globalData.lastRespTimestamp < (intervalMid * 2)) + previousMillisMid = currentMillis - (intervalMid - 5); + } + else + { Serial.print(" --- error"); } } @@ -1253,7 +1222,7 @@ void loop() if (currentMillis - previousMillis5000ms >= interval5000ms) { Serial.printf("\n>>>>> %02is task - state --> ", int(interval5000ms)); - Serial.print("local: " + getTimeStringByTimestamp(localTimeSecond)); + Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); Serial.print(" --- NTP: " + timeClient.getFormattedTime()); // Serial.print(" --- currentMillis " + String(currentMillis) + " --- "); previousMillis5000ms = currentMillis; @@ -1276,121 +1245,51 @@ void loop() if (currentMillis - previousMillisMid >= intervalMid) { Serial.printf("\n>>>>> %02is task - state --> ", int(intervalMid)); - Serial.print("local: " + getTimeStringByTimestamp(localTimeSecond)); + Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); Serial.print(" --- NTP: " + timeClient.getFormattedTime() + "\n"); previousMillisMid = currentMillis; // --------> if (WiFi.status() == WL_CONNECTED) { - // check for server connection - dtuClient.setTimeout(5000); - if (!dtuClient.connected() && !dtuConnection.dtuActiveOffToCloudUpdate) - { - Serial.print("\n>>> Client not connected with DTU! - trying to connect to " + String(userConfig.dtuHostIp) + " ... "); - if (!dtuClient.connect(userConfig.dtuHostIp, dtuPort)) - { - Serial.print(F("Connection to DTU failed.\n")); - dtuConnection.dtuConnectState = DTU_STATE_OFFLINE; - } - else - { - Serial.print(F("DTU connected.\n")); - dtuConnection.dtuConnectState = DTU_STATE_CONNECTED; - } - } - - getDtuDataUpdate(&dtuClient); + dtuConnectionEstablish(&dtuClient, userConfig.dtuHostIp); + timeStampInSecondsDtuSynced = getDtuRemoteTimeAndDataUpdate(&dtuClient, timeStampInSecondsDtuSynced); - if ((globalControls.getDataAuto || globalControls.getDataOnce) && !dtuConnection.dtuActiveOffToCloudUpdate && globalData.uptodate) + if (!dtuConnection.dtuActiveOffToCloudUpdate) { - updateValueToOpenhab(); - if (globalControls.dataFormatJSON) + if ((globalControls.getDataAuto || globalControls.getDataOnce) && globalData.uptodate) { - Serial.print(F("\nJSONObject:")); - JsonDocument doc; - - doc["timestamp"] = globalData.respTimestamp; - doc["uptodate"] = globalData.uptodate; - doc["dtuRssi"] = globalData.dtuRssi; - doc["powerLimit"] = globalData.powerLimit; - doc["powerLimitSet"] = globalData.powerLimitSet; - doc["inverterTemp"] = globalData.inverterTemp; - - doc["grid"]["current"] = globalData.grid.current; - doc["grid"]["voltage"] = globalData.grid.voltage; - doc["grid"]["power"] = globalData.grid.power; - doc["grid"]["dailyEnergy"] = globalData.grid.dailyEnergy; - doc["grid"]["totalEnergy"] = globalData.grid.totalEnergy; - - doc["pv0"]["current"] = globalData.pv0.current; - doc["pv0"]["voltage"] = globalData.pv0.voltage; - doc["pv0"]["power"] = globalData.pv0.power; - doc["pv0"]["dailyEnergy"] = globalData.pv0.dailyEnergy; - doc["pv0"]["totalEnergy"] = globalData.pv0.totalEnergy; - - doc["pv1"]["current"] = globalData.pv1.current; - doc["pv1"]["voltage"] = globalData.pv1.voltage; - doc["pv1"]["power"] = globalData.pv1.power; - doc["pv1"]["dailyEnergy"] = globalData.pv1.dailyEnergy; - doc["pv1"]["totalEnergy"] = globalData.pv1.totalEnergy; - serializeJson(doc, Serial); - } - else - { - Serial.print("\n+++ update at remote: " + getTimeStringByTimestamp(globalData.respTimestamp) + " - uptodate: " + String(globalData.uptodate) + " --- "); - Serial.print("wifi rssi: " + String(globalData.dtuRssi) + " % (DTU->Cloud) - " + String(globalData.wifi_rssi_gateway) + " % (Client->AP) \n"); - Serial.print("power limit (set): " + String(globalData.powerLimit) + " % (" + String(globalData.powerLimitSet) + " %) --- "); - Serial.print("inverter temp: " + String(globalData.inverterTemp) + " °C \n"); - - Serial.print(F(" \t |\t current |\t voltage |\t power | daily | total |\n")); - // 12341234 |1234 current |1234 voltage |1234 power1234|12341234daily 1234|12341234total 1234| - // grid1234 |1234 123456 A |1234 123456 V |1234 123456 W |1234 12345678 kWh |1234 12345678 kWh | - // pvO 1234 |1234 123456 A |1234 123456 V |1234 123456 W |1234 12345678 kWh |1234 12345678 kWh | - // pvI 1234 |1234 123456 A |1234 123456 V |1234 123456 W |1234 12345678 kWh |1234 12345678 kWh | - Serial.print(F("grid\t")); - Serial.printf(" |\t %6.2f A", globalData.grid.current); - Serial.printf(" |\t %6.2f V", globalData.grid.voltage); - Serial.printf(" |\t %6.2f W", globalData.grid.power); - Serial.printf(" |\t %8.3f kWh", globalData.grid.dailyEnergy); - Serial.printf(" |\t %8.3f kWh |\n", globalData.grid.totalEnergy); - - Serial.print(F("pv0\t")); - Serial.printf(" |\t %6.2f A", globalData.pv0.current); - Serial.printf(" |\t %6.2f V", globalData.pv0.voltage); - Serial.printf(" |\t %6.2f W", globalData.pv0.power); - Serial.printf(" |\t %8.3f kWh", globalData.pv0.dailyEnergy); - Serial.printf(" |\t %8.3f kWh |\n", globalData.pv0.totalEnergy); - - Serial.print(F("pv1\t")); - Serial.printf(" |\t %6.2f A", globalData.pv1.current); - Serial.printf(" |\t %6.2f V", globalData.pv1.voltage); - Serial.printf(" |\t %6.2f W", globalData.pv1.power); - Serial.printf(" |\t %8.3f kWh", globalData.pv1.dailyEnergy); - Serial.printf(" |\t %8.3f kWh |\n", globalData.pv1.totalEnergy); + updateValueToOpenhab(); + if (globalControls.dataFormatJSON) + { + printDataAsJsonToSerial(); + } + else + { + Serial.print("\n+++ update at remote: " + getTimeStringByTimestamp(globalData.respTimestamp) + " - uptodate: " + String(globalData.uptodate) + " --- "); + Serial.print("wifi rssi: " + String(globalData.dtuRssi) + " % (DTU->Cloud) - " + String(globalData.wifi_rssi_gateway) + " % (Client->AP) \n"); + printDataAsTextToSerial(); + } + if (globalControls.getDataOnce) + globalControls.getDataOnce = false; } - // switch off after get once - if (globalControls.getDataOnce) + else if ((timeStampInSecondsDtuSynced - globalData.lastRespTimestamp) > (5 * 60) && globalData.grid.voltage > 0) // globalData.grid.voltage > 0 indicates dtu/ inverter working { - globalControls.getDataOnce = false; - } - } - else if ((localTimeSecond - globalData.lastRespTimestamp) > (5 * 60) && globalData.grid.voltage > 0) // globalData.grid.voltage > 0 indicates dtu/ inverter working - { - globalData.grid.power = 0; - globalData.grid.current = 0; - globalData.grid.voltage = 0; + globalData.grid.power = 0; + globalData.grid.current = 0; + globalData.grid.voltage = 0; - globalData.pv0.power = 0; - globalData.pv0.current = 0; - globalData.pv0.voltage = 0; + globalData.pv0.power = 0; + globalData.pv0.current = 0; + globalData.pv0.voltage = 0; - globalData.pv1.power = 0; - globalData.pv1.current = 0; - globalData.pv1.voltage = 0; + globalData.pv1.power = 0; + globalData.pv1.current = 0; + globalData.pv1.voltage = 0; - updateValueToOpenhab(); - dtuConnection.dtuErrorState = DTU_ERROR_LAST_SEND; - Serial.print(F("\n>>>>> TIMEOUT 5 min for DTU -> NIGHT - send zero values\n")); + updateValueToOpenhab(); + dtuConnection.dtuErrorState = DTU_ERROR_LAST_SEND; + Serial.print(F("\n>>>>> TIMEOUT 5 min for DTU -> NIGHT - send zero values\n")); + } } } } @@ -1399,7 +1298,7 @@ void loop() if (currentMillis - previousMillisLong >= intervalLong) { // Serial.printf("\n>>>>> %02is task - state --> ", int(interval5000ms)); - // Serial.print("local: " + getTimeStringByTimestamp(localTimeSecond)); + // Serial.print("local: " + getTimeStringByTimestamp(timeStampInSecondsDtuSynced)); // Serial.print(" --- NTP: " + timeClient.getFormattedTime() + " --- currentMillis " + String(currentMillis) + " --- "); previousMillisLong = currentMillis; diff --git a/src/dtuInterface.cpp b/src/dtuInterface.cpp index d37b426..31316b0 100644 --- a/src/dtuInterface.cpp +++ b/src/dtuInterface.cpp @@ -1,11 +1,192 @@ // // dtuInterface.cpp #include "dtuInterface.h" +#include "dtuConst.h" CRC16 crc; struct connectionControl dtuConnection; struct inverterData globalData; -// control functions +// connection handling + +// Function to check and establish the server connection +void dtuConnectionEstablish(WiFiClient *localDtuClient, char localDtuHostIp[16], uint16_t localDtuPort) +{ + if (!localDtuClient->connected() && !dtuConnection.dtuActiveOffToCloudUpdate) + { + localDtuClient->setTimeout(5000); + Serial.print("\n>>> Client not connected with DTU! - trying to connect to " + String(localDtuHostIp) + " ... "); + if (!localDtuClient->connect(localDtuHostIp, localDtuPort)) + { + Serial.print(F("Connection to DTU failed. Setting try to reconnect.\n")); + dtuConnection.dtuConnectState = DTU_STATE_TRY_RECONNECT; + } + else + { + Serial.print(F("DTU connected.\n")); + dtuConnection.dtuConnectState = DTU_STATE_CONNECTED; + } + } +} + +// Function to stop the server connection +void dtuConnectionStop(WiFiClient *localDtuClient, uint8_t tgtState) +{ + if (localDtuClient->connected()) + { + localDtuClient->stop(); + dtuConnection.dtuConnectState = tgtState; + Serial.print(F("+++ DTU Connection --- stopped\n")); + } +} + +// Function to handle connection errors +void dtuConnectionHandleError(WiFiClient *localDtuClient, unsigned long locTimeSec, uint8_t errorState) +{ + if (localDtuClient->connected()) + { + dtuConnection.dtuErrorState = errorState; + dtuConnection.dtuConnectState = DTU_STATE_DTU_REBOOT; + Serial.print(F("\n+++ DTU Connection --- ERROR - try with reboot of DTU - error state: ")); + Serial.println(errorState); + writeCommandRestartDevice(localDtuClient, locTimeSec); + globalData.dtuResetRequested = globalData.dtuResetRequested + 1; + localDtuClient->stop(); + } +} + +// data handling + +float gridVoltHist[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +uint8_t gridVoltCnt = 0; + +unsigned long getDtuRemoteTimeAndDataUpdate(WiFiClient *localDtuClient, unsigned long locTimeSec) +{ + unsigned long newLocTimeSec = locTimeSec; + if (localDtuClient->connected()) + { + writeReqRealDataNew(localDtuClient, locTimeSec); + // writeReqAppGetHistPower(localDtuClient, locTimeSec); // only needed for sum energy daily/ total - but can lead to overflow for history data/ prevent maybe cloud update + writeReqGetConfig(localDtuClient, locTimeSec); + + //getRealDataNew(localDtuClient, locTimeSec); + + // checking for hanging values on DTU side + gridVoltHist[gridVoltCnt++] = globalData.grid.voltage; + if (gridVoltCnt > 9) + gridVoltCnt = 0; + + Serial.println(""); + bool gridVoltValueHanging = true; + for (uint8_t i = 1; i < 10; i++) + { + Serial.println("GridV check - " + String(i) + " : " + String(gridVoltHist[i]) + " V - with: " + String(gridVoltHist[0])); + if (gridVoltHist[i] != gridVoltHist[0]) + { + gridVoltValueHanging = false; + break; + } + } + Serial.println("GridV check result: " + String(gridVoltValueHanging)); + if (gridVoltValueHanging) + { + dtuConnectionHandleError(localDtuClient, locTimeSec, DTU_ERROR_DATA_NO_CHANGE); + globalData.uptodate = false; + } + + // check for up-to-date - last response timestamp have to not equal the current response timestamp + if ((globalData.lastRespTimestamp != globalData.respTimestamp) && (globalData.respTimestamp != 0)) + { + globalData.uptodate = true; + dtuConnection.dtuErrorState = DTU_ERROR_NO_ERROR; + // sync local time (in seconds) to DTU time, only if abbrevation about 3 seconds + if (abs((int(globalData.respTimestamp) - int(locTimeSec))) > 3) + { + newLocTimeSec = globalData.respTimestamp; + Serial.print(F("\n>--> synced local time with DTU time <--<\n")); + } + } + else + { + globalData.uptodate = false; + // stopping connection to DTU when response time error - try with reconnect + dtuConnectionHandleError(localDtuClient, locTimeSec, DTU_ERROR_TIME_DIFF); + } + globalData.lastRespTimestamp = globalData.respTimestamp; + } + else + { + globalData.uptodate = false; + // dtuConnectionHandleError(localDtuClient, locTimeSec); + // dtuConnection.dtuConnectState = DTU_STATE_TRY_RECONNECT; + } + return newLocTimeSec; +} + +void printDataAsTextToSerial() +{ + Serial.print("power limit (set): " + String(globalData.powerLimit) + " % (" + String(globalData.powerLimitSet) + " %) --- "); + Serial.print("inverter temp: " + String(globalData.inverterTemp) + " °C \n"); + + Serial.print(F(" \t |\t current |\t voltage |\t power | daily | total |\n")); + // 12341234 |1234 current |1234 voltage |1234 power1234|12341234daily 1234|12341234total 1234| + // grid1234 |1234 123456 A |1234 123456 V |1234 123456 W |1234 12345678 kWh |1234 12345678 kWh | + // pvO 1234 |1234 123456 A |1234 123456 V |1234 123456 W |1234 12345678 kWh |1234 12345678 kWh | + // pvI 1234 |1234 123456 A |1234 123456 V |1234 123456 W |1234 12345678 kWh |1234 12345678 kWh | + Serial.print(F("grid\t")); + Serial.printf(" |\t %6.2f A", globalData.grid.current); + Serial.printf(" |\t %6.2f V", globalData.grid.voltage); + Serial.printf(" |\t %6.2f W", globalData.grid.power); + Serial.printf(" |\t %8.3f kWh", globalData.grid.dailyEnergy); + Serial.printf(" |\t %8.3f kWh |\n", globalData.grid.totalEnergy); + + Serial.print(F("pv0\t")); + Serial.printf(" |\t %6.2f A", globalData.pv0.current); + Serial.printf(" |\t %6.2f V", globalData.pv0.voltage); + Serial.printf(" |\t %6.2f W", globalData.pv0.power); + Serial.printf(" |\t %8.3f kWh", globalData.pv0.dailyEnergy); + Serial.printf(" |\t %8.3f kWh |\n", globalData.pv0.totalEnergy); + + Serial.print(F("pv1\t")); + Serial.printf(" |\t %6.2f A", globalData.pv1.current); + Serial.printf(" |\t %6.2f V", globalData.pv1.voltage); + Serial.printf(" |\t %6.2f W", globalData.pv1.power); + Serial.printf(" |\t %8.3f kWh", globalData.pv1.dailyEnergy); + Serial.printf(" |\t %8.3f kWh |\n", globalData.pv1.totalEnergy); +} + +void printDataAsJsonToSerial() +{ + Serial.print(F("\nJSONObject:")); + JsonDocument doc; + + doc["timestamp"] = globalData.respTimestamp; + doc["uptodate"] = globalData.uptodate; + doc["dtuRssi"] = globalData.dtuRssi; + doc["powerLimit"] = globalData.powerLimit; + doc["powerLimitSet"] = globalData.powerLimitSet; + doc["inverterTemp"] = globalData.inverterTemp; + + doc["grid"]["current"] = globalData.grid.current; + doc["grid"]["voltage"] = globalData.grid.voltage; + doc["grid"]["power"] = globalData.grid.power; + doc["grid"]["dailyEnergy"] = globalData.grid.dailyEnergy; + doc["grid"]["totalEnergy"] = globalData.grid.totalEnergy; + + doc["pv0"]["current"] = globalData.pv0.current; + doc["pv0"]["voltage"] = globalData.pv0.voltage; + doc["pv0"]["power"] = globalData.pv0.power; + doc["pv0"]["dailyEnergy"] = globalData.pv0.dailyEnergy; + doc["pv0"]["totalEnergy"] = globalData.pv0.totalEnergy; + + doc["pv1"]["current"] = globalData.pv1.current; + doc["pv1"]["voltage"] = globalData.pv1.voltage; + doc["pv1"]["power"] = globalData.pv1.power; + doc["pv1"]["dailyEnergy"] = globalData.pv1.dailyEnergy; + doc["pv1"]["totalEnergy"] = globalData.pv1.totalEnergy; + serializeJson(doc, Serial); +} + +// base functions void initializeCRC() { @@ -34,7 +215,7 @@ String getTimeStringByTimestamp(unsigned long timestamp) } unsigned long lastSwOff = 0; -boolean preventCloudErrorTask(unsigned long locTimeSec) +boolean dtuCloudPauseActiveControl(unsigned long locTimeSec) { // check current DTU time UnixTime stamp(1); @@ -45,16 +226,15 @@ boolean preventCloudErrorTask(unsigned long locTimeSec) if (sec >= 40 && (min == 59 || min == 14 || min == 29 || min == 44) && !dtuConnection.dtuActiveOffToCloudUpdate) { - Serial.printf("\n\n<<< preventCloudErrorTask >>> --- "); + Serial.printf("\n\n<<< dtuCloudPauseActiveControl >>> --- "); Serial.printf("local time: %02i.%02i. - %02i:%02i:%02i ", stamp.day, stamp.month, stamp.hour, stamp.minute, stamp.second); Serial.print(F("----> switch ''OFF'' DTU server connection to upload data from DTU to Cloud\n\n")); lastSwOff = locTimeSec; dtuConnection.dtuActiveOffToCloudUpdate = true; - dtuConnection.dtuConnectState = DTU_STATE_CLOUD_PAUSE; } else if (locTimeSec > lastSwOff + DTU_CLOUD_UPLOAD_SECONDS && dtuConnection.dtuActiveOffToCloudUpdate) { - Serial.printf("\n\n<<< preventCloudErrorTask >>> --- "); + Serial.printf("\n\n<<< dtuCloudPauseActiveControl >>> --- "); Serial.printf("local time: %02i.%02i. - %02i:%02i:%02i ", stamp.day, stamp.month, stamp.hour, stamp.minute, stamp.second); Serial.print(F("----> switch ''ON'' DTU server connection after upload data from DTU to Cloud\n\n")); // reset request timer - starting directly new request after prevent @@ -189,7 +369,7 @@ void writeReqAppGetHistPower(WiFiClient *localDtuClient, unsigned long locTimeSe readRespAppGetHistPower(localDtuClient); } -void readRespRealDataNew(WiFiClient *localDtuClient) +void readRespRealDataNew(WiFiClient *localDtuClient, unsigned long locTimeSec) { unsigned long timeout = millis(); @@ -300,7 +480,8 @@ void readRespRealDataNew(WiFiClient *localDtuClient) } else { - dtuConnection.dtuErrorState = DTU_ERROR_NO_TIME; + // dtuConnection.dtuErrorState = DTU_ERROR_NO_TIME; + dtuConnectionHandleError(localDtuClient, locTimeSec, DTU_ERROR_NO_TIME); } } @@ -358,7 +539,7 @@ void writeReqRealDataNew(WiFiClient *localDtuClient, unsigned long locTimeSec) // Serial.println(""); localDtuClient->write(message, 10 + stream.bytes_written); - readRespRealDataNew(localDtuClient); + readRespRealDataNew(localDtuClient, locTimeSec); } void readRespGetConfig(WiFiClient *localDtuClient) @@ -482,7 +663,7 @@ boolean readRespCommand(WiFiClient *localDtuClient) return false; } } - // if there is no timeout, tehn assume limit was successfully changed + // if there is no timeout, then assume limit was successfully changed globalData.powerLimit = globalData.powerLimitSet; // Read all the bytes of the reply from server and print them to Serial @@ -538,9 +719,9 @@ boolean writeReqCommand(WiFiClient *localDtuClient, uint8_t setPercent, unsigned CommandResDTO commandresdto = CommandResDTO_init_default; commandresdto.time = int32_t(locTimeSec); - commandresdto.tid = int32_t(locTimeSec); - commandresdto.action = 8; + commandresdto.action = CMD_ACTION_LIMIT_POWER; commandresdto.package_nub = 1; + commandresdto.tid = int32_t(locTimeSec); const int bufferSize = 61; char dataArray[bufferSize]; @@ -599,3 +780,115 @@ boolean writeReqCommand(WiFiClient *localDtuClient, uint8_t setPercent, unsigned return false; return true; } + +boolean readRespCommandRestartDevice(WiFiClient *localDtuClient) +{ + unsigned long timeout = millis(); + while (localDtuClient->available() == 0) + { + if (millis() - timeout > 2000) + { + Serial.println(F(">>> Client Timeout !")); + localDtuClient->stop(); + return false; + } + } + + // Read all the bytes of the reply from server and print them to Serial + uint8_t buffer[1024]; + size_t read = 0; + while (localDtuClient->available()) + { + buffer[read++] = localDtuClient->read(); + } + + // Serial.printf("\nResponse: "); + // for (int i = 0; i < read; i++) + // { + // Serial.printf("%02X", buffer[i]); + // } + + pb_istream_t istream; + istream = pb_istream_from_buffer(buffer + 10, read - 10); + + CommandReqDTO commandreqdto = CommandReqDTO_init_default; + + Serial.print(" --> respCommand Restart - got remote: " + getTimeStringByTimestamp(commandreqdto.time)); + + pb_decode(&istream, &GetConfigReqDTO_msg, &commandreqdto); + Serial.printf("\ncommand req action: %i", commandreqdto.action); + Serial.printf("\ncommand req: %s", commandreqdto.dtu_sn); + Serial.printf("\ncommand req: %i", commandreqdto.err_code); + Serial.printf("\ncommand req: %i", commandreqdto.package_now); + Serial.printf("\ncommand req: %i", int(commandreqdto.tid)); + Serial.printf("\ncommand req time: %i", commandreqdto.time); + return true; +} + +boolean writeCommandRestartDevice(WiFiClient *localDtuClient, unsigned long locTimeSec) +{ + if (!localDtuClient->connected()) + return false; + + // request message + uint8_t buffer[200]; + pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); + + CommandResDTO commandresdto = CommandResDTO_init_default; + // commandresdto.time = int32_t(locTimeSec); + + commandresdto.action = CMD_ACTION_DTU_REBOOT; + commandresdto.package_nub = 1; + commandresdto.tid = int32_t(locTimeSec); + + bool status = pb_encode(&stream, CommandResDTO_fields, &commandresdto); + + if (!status) + { + Serial.println(F("Failed to encode")); + return false; + } + + // Serial.print(F("\nencoded: ")); + for (unsigned int i = 0; i < stream.bytes_written; i++) + { + // Serial.printf("%02X", buffer[i]); + crc.add(buffer[i]); + } + + uint8_t header[10]; + header[0] = 0x48; + header[1] = 0x4d; + header[2] = 0x23; // Command = 0x03 - CMD_CLOUD_COMMAND_RES_DTO = b"\x23\x05" => 0x23 + header[3] = 0x05; // Command = 0x05 => 0x05 + header[4] = 0x00; + header[5] = 0x01; + header[6] = (crc.calc() >> 8) & 0xFF; + header[7] = (crc.calc()) & 0xFF; + header[8] = ((stream.bytes_written + 10) >> 8) & 0xFF; // suggest parentheses around '+' inside '>>' [-Wparentheses] + header[9] = (stream.bytes_written + 10) & 0xFF; // warning: suggest parentheses around '+' in operand of '&' [-Wparentheses] + crc.restart(); + + uint8_t message[10 + stream.bytes_written]; + for (int i = 0; i < 10; i++) + { + message[i] = header[i]; + } + for (unsigned int i = 0; i < stream.bytes_written; i++) + { + message[i + 10] = buffer[i]; + } + + // Serial.print(F("\nRequest: ")); + // for (int i = 0; i < 10 + stream.bytes_written; i++) + // { + // Serial.print(message[i]); + // } + // Serial.println(""); + + localDtuClient->write(message, 10 + stream.bytes_written); + if (!readRespCommandRestartDevice(localDtuClient)) + return false; + return true; +} + diff --git a/srcConverter.py b/srcConverter.py new file mode 100644 index 0000000..fc39a09 --- /dev/null +++ b/srcConverter.py @@ -0,0 +1,47 @@ +import requests + +input_url = "https://raw.githubusercontent.com/suaveolent/hoymiles-wifi/main/hoymiles_wifi/const.py" +output_file = "include/dtuConst.h" + +# Download the input file +response = requests.get(input_url) +if response.status_code == 200: + input_lines = response.text.split('\n') +else: + print(f"Failed to download input file from {input_url}") + exit() + +output_lines = [] +output_lines.append("// constants converted from python constants file: " + input_url + "\n//") + +for line in input_lines: + if '=' in line: + # If the line contains an '=', it might define either a byte sequence or a numeric constant + parts = line.split('=') + name = parts[0].strip() + value = parts[1].strip() + if value.startswith('b"') and value.endswith('"'): + # This is a byte sequence + bytes_str = value.lstrip('b').strip().strip('"') + hex_values = bytes_str.split('\\x')[1:] + # Convert hex values to integers + int_values = [int(value, 16) for value in hex_values] + # Construct Arduino C++ line + output_line = f"const byte {name}[] = {{{','.join([f'0x{value:02x}' for value in int_values])}}};" + output_lines.append(output_line) + elif value.isdigit(): + # This is a numeric constant + output_line = f"#define {name} {value}" + output_lines.append(output_line) + else: + # Treat as a comment + output_lines.append(f"// {line}") + else: + # Treat as a comment + output_lines.append(f"// {line}") + +# Write output to file +with open(output_file, 'w') as file: + file.write('\n'.join(output_lines)) + +print(f"Output written to {output_file}") diff --git a/version_inc.py b/version_inc.py index 6e07809..c6d3381 100644 --- a/version_inc.py +++ b/version_inc.py @@ -33,7 +33,7 @@ build_string = build_string.replace("\n","") print(f"got buildnumber to use: ->{build_string}<-") except FileNotFoundError: - print('buildnumber file not found') + print('ERROR: buildnumber file not found. For automatic versioning there is a file called ../include/buildnumber.txt expected. With the content "localDev" or versionnumber e.g. "1.0.0" in first line. (File is blocked by .gitignore for GitHub actions to run.)') major, minor, patch = map(int, version_string.split('.'))