From f7cabf54f098c4a1451715077157f800476e91a5 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Tue, 24 Dec 2024 06:57:24 +0100 Subject: [PATCH 001/106] change to 3.3.1, update ElegantOTA, fix custom firmware upload --- ChangeLog.md | 10 ++++++++++ include/_Release.h | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 307ffa89..c3476ce8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,6 @@ +Release 3.3.1: + - ToDo: support for OpenWB 2.0 Api + Release 3.3.0: - new feature: WebSerial as remote serial output (#74) - new feature: configurable serial output pins @@ -12,6 +15,13 @@ Release 3.3.0: - move config files from root to subfolder /config - new feature: Backup/Restore of configfiles for OTA + **Breaking changes** + From release 0.7 onwards, an access point will no longer be opened during an initial installation. The WiFi access data must be entered via the web installer. This is used for both initial installation and entry of WiFi access data. + When updating version 0.6 to 0.7, a new installation must also be carried out because the WiFi handling has been switched to the ImprovWiFi Library. + If version 0.7 is already installed on the ESP device, an OTA update is sufficient. See “Update” button at ESP-webinterface + + Detailed instructions can be found in the [WiKi](https://github.com/tobiasfaust/SolaxModbusGateway/wiki) + Release 3.2.2: - new feature: GoodWe Support, by @TigerGrey (#58) - new feature: support for id and livedate on same functioncode, by @TigerGrey (#58) diff --git a/include/_Release.h b/include/_Release.h index a4e5b321..d6f5edc4 100644 --- a/include/_Release.h +++ b/include/_Release.h @@ -1 +1 @@ -#define Release "3.3.0" +#define Release "3.3.1" From ed5d97d100f3ffa89599fbb89063fe535a6fd91b Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:12:57 +0100 Subject: [PATCH 002/106] Update modbus.cpp ID Daten per MQTT --- src/modbus.cpp | 87 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 206ca91b..c0a433f3 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -748,7 +748,14 @@ void modbus::ParseData() { IsActiveItem = true; } } - + //Lazgar + // check if item is active to send data out via mqtt + for (uint16_t i=0; i < this->InverterIdData->size(); i++) { + if (this->InverterIdData->at(i).Name == d.Name && this->InverterIdData->at(i).active) { + IsActiveItem = true; + } + } + //Lazgar // ************* processing data ****************** if (datatype == "float") { //********** handle Datatype FLOAT ***********// @@ -974,7 +981,29 @@ void modbus::GetLiveDataAsJson(AsyncResponseStream *response, String subaction) response->print(s); count++; } - + //Lazgar + for (uint16_t i=0; i < this->InverterIdData->size(); i++) { + if (subaction == "onlyactive" && !this->InverterIdData->at(i).active) continue; + JsonDocument doc; + String s = ""; + doc["name"] = this->InverterIdData->at(i).Name.c_str(); + doc["realname"] = this->InverterIdData->at(i).RealName.c_str(); + doc["value"] = std::move(this->InverterIdData->at(i).value + " " + this->InverterIdData->at(i).unit); + doc["active"]["checked"] = (this->InverterIdData->at(i).active?1:0); + doc["active"]["name"] = this->InverterIdData->at(i).Name.c_str(); + doc["mqtttopic"] = std::move(this->mqtt->getTopic(this->InverterIdData->at(i).Name, false)); + //Wird für die ID Daten nicht benötigt gibt keine Infos für die OpenWallbox + //if (this->InverterIdData->at(i).openwb.length() > 0) { + // JsonArray wb = doc["openwb"].to(); + // wb[0]["openwbtopic"] = this->InverterIdData->at(i).openwb.c_str(); + //} + + serializeJson(doc, s); + if(count>0) response->print(", "); + response->print(s); + count++; + } + //Lazgar response->printf(" ]}, \"object_id\": \"%s/%s\"}", Config->GetMqttBasePath().c_str(), Config->GetMqttRoot().c_str()); } @@ -1022,7 +1051,38 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { count++; } while (regfile.findUntil(",","]")); + //Lazgar + streamString = ""; + streamString = "\""+ this->InverterType.name +"\": {"; + regfile.find(streamString.c_str()); + + streamString = "\"id\": ["; + regfile.find(streamString.c_str()); + + do { + JsonDocument elem; + DeserializationError error = deserializeJson(elem, regfile); + + if (!error) { + // Print the result + if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } + if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + } else { + if (Config->GetDebugLevel() >=1) { + dbg.print("(Function GetRegisterAsJson) Failed to parse JSON Register Data: "); + dbg.print(error.c_str()); + dbg.println(); + } + } + + String s = ""; + serializeJson(elem, s); + if(count>0) response->print(", "); + response->print(s); + count++; + } while (regfile.findUntil(",","]")); + //Lazgar if (regfile) { regfile.close(); } response->print("]}"); } @@ -1040,6 +1100,16 @@ void modbus::SetItemActiveStatus(String item, bool newstate) { this->InverterLiveData->at(j).active = newstate; } } + //Lazgar + for (uint16_t j=0; j < this->InverterIdData->size(); j++) { + if (this->InverterIdData->at(j).Name == item) { + if (Config->GetDebugLevel() >=3) { + dbg.printf("Set Item <%s> ActiveState to %s\n", item.c_str(), (newstate?"true":"false")); + } + this->InverterIdData->at(j).active = newstate; + } + } + //Lazgar } /******************************************************* @@ -1304,6 +1374,19 @@ void modbus::LoadJsonItemConfig() { break; } } + //Lazgar + for(uint16_t i=0; iInverterIdData->size(); i++) { + if (this->InverterIdData->at(i).Name == ItemName ) { + this->InverterIdData->at(i).active = kv.value().as(); + + if (Config->GetDebugLevel() >=3) { + dbg.printf("item %s -> %s\n", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); + } + + break; + } + } + //Lazgar } } while (stream.findUntil(",","]")); From 16566f94107d600c136c8966ae271423c24448fa Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:26:00 +0100 Subject: [PATCH 003/106] Create Solax-X3-PRO.json --- data/regs/Solax-X3-PRO.json | 575 ++++++++++++++++++++++++++++++++++++ 1 file changed, 575 insertions(+) create mode 100644 data/regs/Solax-X3-PRO.json diff --git a/data/regs/Solax-X3-PRO.json b/data/regs/Solax-X3-PRO.json new file mode 100644 index 00000000..69df205c --- /dev/null +++ b/data/regs/Solax-X3-PRO.json @@ -0,0 +1,575 @@ +{ + "Solax-X3-PRO": { + "config": { + "author": "MagicSven81", + "RequestLiveData": [ + [ + "#ClientID", + "0x04", + "0x04", + "0x00", + "0x00", + "0x40" + ], + [ + "#ClientID", + "0x04", + "0x07", + "0x00", + "0x00", + "0x0A" + ] + ], + "RequestIdData": [ + [ + "#ClientID", + "0x03", + "0x03", + "0x00", + "0x00", + "0x54" + ] + ], + "ClientIdPos": 0, + "LiveDataFunctionCodePos": 1, + "LiveDataFunctionCode": "0x04", + "IdDataFunctionCodePos": 1, + "IdDataFunctionCode": "0x03", + "LiveDataStartsAtPos": 3, + "IdDataStartsAtPos": 3, + "LiveDataErrorPos": 1, + "LiveDataErrorCode": "0x84", + "IdDataErrorPos": 1, + "IdDataErrorCode": "0x83", + "LiveDataSuccessPos": 1, + "LiveDataSuccessCode": "0x04", + "IdDataSuccessPos": 1, + "IdDataSuccessCode": "0x03" + }, + "data": { + "livedata": [ + { + "position": [ + 3, + 4 + ], + "name": "PvVoltage1", + "realname": "Pv Voltage 1", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 5, + 6 + ], + "name": "PvVoltage2", + "realname": "PV Voltage 2", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 85, + 86 + ], + "name": "PvVoltage3", + "realname": "PV Voltage 3", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 7, + 8 + ], + "name": "PvCurrent1", + "realname": "PV Current 1", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 9, + 10 + ], + "name": "PvCurrent2", + "realname": "PV Current 2", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 87, + 88 + ], + "name": "PvCurrent3", + "realname": "PV Current 3", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 31, + 32 + ], + "name": "PAC", + "realname": "P-AC", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 33, + 34 + ], + "name": "RunMode", + "realname": "Inverter Status", + "datatype": "integer", + "mapping": [ + [ + 0, + "WaitMode" + ], + [ + 1, + "CheckMode" + ], + [ + 2, + "NormalMode" + ], + [ + 3, + "FaultMode" + ], + [ + 4, + "PermanentFaultMode" + ] + ] + }, + { + "position": [ + 41, + 42 + ], + "name": "Total Power DC", + "realname": "P-DC", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 43, + 44 + ], + "name": "PDC1", + "realname": "P-DC1", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 45, + 46 + ], + "name": "PDC2", + "realname": "P-DC2", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 89, + 90 + ], + "name": "PDC3", + "realname": "P-DC3", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 11, + 12 + ], + "name": "GridVoltage_R", + "realname": "Grid Voltage L1", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 13, + 14 + ], + "name": "GridVoltage_S", + "realname": "Grid Voltage L2", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 15, + 16 + ], + "name": "GridVoltage_T", + "realname": "Grid Voltage L3", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 17, + 18 + ], + "name": "GridFrequency_R", + "realname": "Grid Frequency L1", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [ + 19, + 20 + ], + "name": "GridFrequency_S", + "realname": "Grid Frequency L2", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [ + 21, + 22 + ], + "name": "GridFrequency_T", + "realname": "Grid Frequency L3", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [ + 23, + 24 + ], + "name": "GridOutputCurrent_R", + "realname": "Grid Current L1", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 25, + 26 + ], + "name": "GridOutputCurrent_S", + "realname": "Grid Current L2", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 27, + 28 + ], + "name": "GridOutputCurrent_T", + "realname": "Grid Current L3", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 29, + 30 + ], + "name": "InverterTemperature", + "realname": "Inverter Temperature", + "datatype": "integer", + "unit": "°C" + }, + { + "position": [ + 91, + 92 + ], + "name": "MainboardTemperature", + "realname": "Mainboard Temperature", + "datatype": "integer", + "unit": "°C" + }, + { + "position": [ + 75, + 76, + 73, + 74 + ], + "name": "YieldTotal", + "realname": "Gesamtertrag", + "datatype": "integer", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 79, + 80, + 77, + 78 + ], + "name": "YieldToday", + "realname": "Tagesertrag", + "datatype": "integer", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 123, + 124, + 121, + 122 + ], + "name": "GridPower", + "realname": "GridPower", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 127, + 128, + 125, + 126 + ], + "name": "FeedInEnergy", + "realname": "Feed In Energy", + "datatype": "integer", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 131, + 132, + 129, + 130 + ], + "name": "ConsumeEnergy", + "realname": "Consume Energy", + "datatype": "integer", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 129, + 130, + 131, + 132 + ], + "name": "ConsumeEnergy2", + "realname": "Consume Energy2", + "datatype": "integer", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 133, + 134 + ], + "name": "PowerRef", + "realname": "PowerRef", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 137, + 138, + 135, + 136 + ], + "name": "PowerToEV", + "realname": "PowerToEV", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 139, + 140 + ], + "name": "PVRef", + "realname": "PV Ref", + "datatype": "integer", + "unit": " " + }, + { + "position": [ + 143, + 144, + 141, + 142 + ], + "name": "FeedInPowerR", + "realname": "FeedInPowerL1", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 147, + 148, + 145, + 146 + ], + "name": "FeedInPowerS", + "realname": "FeedInPowerL2", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 151, + 152, + 149, + 150 + ], + "name": "FeedInPowerT", + "realname": "FeedInPowerL3", + "datatype": "integer", + "unit": "W" + } + ], + "id": [ + { + "position": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "name": "InverterSN", + "realname": "Inverter SerialNumber", + "datatype": "string" + }, + { + "position": [ + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30 + ], + "name": "FactoryName", + "realname": "Factory Name", + "datatype": "string" + }, + { + "position": [ + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42 + ], + "name": "ModuleName", + "realname": "Module Name", + "datatype": "string" + }, + { + "position": [ + 43, + 44, + 45, + 46, + 47, + 48 + ], + "name": "FirmwareVersion", + "realname": "Firmware Version", + "datatype": "string" + }, + { + "position": [ + 159 + ], + "name": "Safty", + "realname": "Safty", + "datatype": "integer" + }, + { + "position": [ + 160 + ], + "name": "MachineType", + "realname": "Machine Type", + "datatype": "integer" + }, + { + "position": [ + 161 + ], + "name": "Power Ratio", + "realname": "Percent of Powerlimits", + "datatype": "integer", + "factor": 0.01, + "unit": "%" + }, + { + "position": [ + 162 + ], + "name": "MpptScanMode", + "realname": "MpptScanMode", + "datatype": "integer" + } + ] + } + } +} From 2c7f868ae9c5d67e2ac0e68e67a0307805e1e426 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Thu, 26 Dec 2024 11:27:39 +0100 Subject: [PATCH 004/106] Update Solax-X3.json --- data/regs/Solax-X3.json | 573 ---------------------------------------- 1 file changed, 573 deletions(-) diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index 460b29be..31156883 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -872,578 +872,5 @@ } ] } - }, - "Solax-X3-PRO": { - "config": { - "author": "MagicSven81", - "RequestLiveData": [ - [ - "#ClientID", - "0x04", - "0x04", - "0x00", - "0x00", - "0x40" - ], - [ - "#ClientID", - "0x04", - "0x07", - "0x00", - "0x00", - "0x0A" - ] - ], - "RequestIdData": [ - [ - "#ClientID", - "0x03", - "0x03", - "0x00", - "0x00", - "0x54" - ] - ], - "ClientIdPos": 0, - "LiveDataFunctionCodePos": 1, - "LiveDataFunctionCode": "0x04", - "IdDataFunctionCodePos": 1, - "IdDataFunctionCode": "0x03", - "LiveDataStartsAtPos": 3, - "IdDataStartsAtPos": 3, - "LiveDataErrorPos": 1, - "LiveDataErrorCode": "0x84", - "IdDataErrorPos": 1, - "IdDataErrorCode": "0x83", - "LiveDataSuccessPos": 1, - "LiveDataSuccessCode": "0x04", - "IdDataSuccessPos": 1, - "IdDataSuccessCode": "0x03" - }, - "data": { - "livedata": [ - { - "position": [ - 3, - 4 - ], - "name": "PvVoltage1", - "realname": "Pv Voltage 1", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 5, - 6 - ], - "name": "PvVoltage2", - "realname": "PV Voltage 2", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 85, - 86 - ], - "name": "PvVoltage3", - "realname": "PV Voltage 3", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 7, - 8 - ], - "name": "PvCurrent1", - "realname": "PV Current 1", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 9, - 10 - ], - "name": "PvCurrent2", - "realname": "PV Current 2", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 87, - 88 - ], - "name": "PvCurrent3", - "realname": "PV Current 3", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 31, - 32 - ], - "name": "PAC", - "realname": "P-AC", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 33, - 34 - ], - "name": "RunMode", - "realname": "Inverter Status", - "datatype": "integer", - "mapping": [ - [ - 0, - "WaitMode" - ], - [ - 1, - "CheckMode" - ], - [ - 2, - "NormalMode" - ], - [ - 3, - "FaultMode" - ], - [ - 4, - "PermanentFaultMode" - ] - ] - }, - { - "position": [ - 41, - 42 - ], - "name": "Total Power DC", - "realname": "P-DC", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 43, - 44 - ], - "name": "PDC1", - "realname": "P-DC1", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 45, - 46 - ], - "name": "PDC2", - "realname": "P-DC2", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 89, - 90 - ], - "name": "PDC3", - "realname": "P-DC3", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 11, - 12 - ], - "name": "GridVoltage_R", - "realname": "Grid Voltage L1", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 13, - 14 - ], - "name": "GridVoltage_S", - "realname": "Grid Voltage L2", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 15, - 16 - ], - "name": "GridVoltage_T", - "realname": "Grid Voltage L3", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 17, - 18 - ], - "name": "GridFrequency_R", - "realname": "Grid Frequency L1", - "datatype": "float", - "factor": 0.01, - "unit": "Hz" - }, - { - "position": [ - 19, - 20 - ], - "name": "GridFrequency_S", - "realname": "Grid Frequency L2", - "datatype": "float", - "factor": 0.01, - "unit": "Hz" - }, - { - "position": [ - 21, - 22 - ], - "name": "GridFrequency_T", - "realname": "Grid Frequency L3", - "datatype": "float", - "factor": 0.01, - "unit": "Hz" - }, - { - "position": [ - 23, - 24 - ], - "name": "GridOutputCurrent_R", - "realname": "Grid Current L1", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 25, - 26 - ], - "name": "GridOutputCurrent_S", - "realname": "Grid Current L2", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 27, - 28 - ], - "name": "GridOutputCurrent_T", - "realname": "Grid Current L3", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 29, - 30 - ], - "name": "InverterTemperature", - "realname": "Inverter Temperature", - "datatype": "integer", - "unit": "°C" - }, - { - "position": [ - 91, - 92 - ], - "name": "MainboardTemperature", - "realname": "Mainboard Temperature", - "datatype": "integer", - "unit": "°C" - }, - { - "position": [ - 75, - 76, - 73, - 74 - ], - "name": "YieldTotal", - "realname": "Gesamtertrag", - "datatype": "integer", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 79, - 80, - 77, - 78 - ], - "name": "YieldToday", - "realname": "Tagesertrag", - "datatype": "integer", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 123, - 124, - 121, - 122 - ], - "name": "GridPower", - "realname": "GridPower", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 127, - 128, - 125, - 126 - ], - "name": "FeedInEnergy", - "realname": "Feed In Energy", - "datatype": "integer", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 131, - 132, - 129, - 130 - ], - "name": "ConsumeEnergy", - "realname": "Consume Energy", - "datatype": "integer", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 129, - 130, - 131, - 132 - ], - "name": "ConsumeEnergy2", - "realname": "Consume Energy2", - "datatype": "integer", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 133, - 134 - ], - "name": "PowerRef", - "realname": "PowerRef", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 137, - 138, - 135, - 136 - ], - "name": "PowerToEV", - "realname": "PowerToEV", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 139, - 140 - ], - "name": "PVRef", - "realname": "PV Ref", - "datatype": "integer", - "unit": " " - }, - { - "position": [ - 143, - 144, - 141, - 142 - ], - "name": "FeedInPowerR", - "realname": "FeedInPowerL1", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 147, - 148, - 145, - 146 - ], - "name": "FeedInPowerS", - "realname": "FeedInPowerL2", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 151, - 152, - 149, - 150 - ], - "name": "FeedInPowerT", - "realname": "FeedInPowerL3", - "datatype": "integer", - "unit": "W" - } - ], - "id": [ - { - "position": [ - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16 - ], - "name": "InverterSN", - "realname": "Inverter SerialNumber", - "datatype": "string" - }, - { - "position": [ - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30 - ], - "name": "FactoryName", - "realname": "Factory Name", - "datatype": "string" - }, - { - "position": [ - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42 - ], - "name": "ModuleName", - "realname": "Module Name", - "datatype": "string" - }, - { - "position": [ - 43, - 44, - 45, - 46, - 47, - 48 - ], - "name": "FirmwareVersion", - "realname": "Firmware Version", - "datatype": "string" - }, - { - "position": [ - 159 - ], - "name": "Safty", - "realname": "Safty", - "datatype": "integer" - }, - { - "position": [ - 160 - ], - "name": "MachineType", - "realname": "Machine Type", - "datatype": "integer" - }, - { - "position": [ - 161 - ], - "name": "Power Ratio", - "realname": "Percent of Powerlimits", - "datatype": "integer", - "factor": 0.01, - "unit": "%" - }, - { - "position": [ - 162 - ], - "name": "MpptScanMode", - "realname": "MpptScanMode", - "datatype": "integer" - } - ] - } } } From 0988cd9f6f4436ca53b7239818c9d82b6004e279 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Fri, 27 Dec 2024 09:57:57 +0100 Subject: [PATCH 005/106] fix null-termination of string handling (#96) --- ChangeLog.md | 1 + src/modbus.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index c3476ce8..4859d93c 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,5 @@ Release 3.3.1: + - BugFix: fix null-terminationof string handling (#96) - ToDo: support for OpenWB 2.0 Api Release 3.3.0: diff --git a/src/modbus.cpp b/src/modbus.cpp index c0a433f3..a7ed8270 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -772,12 +772,13 @@ void modbus::ParseData() { } else if (datatype == "string") { //********** handle Datatype String ***********// if (!posArray.isNull()) { - char buffer[posArray.size()]; + char buffer[posArray.size()+1]; uint8_t i=0; for(int v : posArray) { buffer[i] = static_cast(DataFrame->at(v)); i++; } + buffer[i] = '\0'; d.value = String(buffer); } } else { From e5b47d3cc798a6f7bef0186973bc78f8f1aeaa80 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sat, 28 Dec 2024 09:08:40 +0100 Subject: [PATCH 006/106] some additional infos per mqtt with debuglevel > 4 some additional infos per mqtt with debuglevel > 4 - memory in kB - uptime in sec - wifi ssid --- src/mqtt.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 6f84156c..48308971 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -389,12 +389,21 @@ void MQTT::loop() { if (Config->GetDebugLevel() >=4) { char buffer[100] = {0}; memset(buffer, 0, sizeof(buffer)); - - snprintf(buffer, sizeof(buffer), "%d", ESP.getFreeHeap()); + + snprintf(buffer, sizeof(buffer), "%d", ESP.getFreeHeap() / 1024); this->Publish_String("memory", buffer, false); snprintf(buffer, sizeof(buffer), "%d", WiFi.RSSI()); this->Publish_String("rssi", buffer, false); + + snprintf(buffer, sizeof(buffer), "%s", WiFi.SSID()); + this->Publish_String("ssid", buffer, false); + + uint64_t uptimeMicroSeconds = esp_timer_get_time(); + uint64_t uptimeSeconds = uptimeMicroSeconds / 1000000; + this->Publish_Int("uptime",uptimeSeconds,false); + + } } } From 2c13cb8724ba33790998775f0b1d7a511b01a3d0 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sat, 28 Dec 2024 09:09:38 +0100 Subject: [PATCH 007/106] Update mqtt.cpp --- src/mqtt.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 48308971..37d10798 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -402,8 +402,6 @@ void MQTT::loop() { uint64_t uptimeMicroSeconds = esp_timer_get_time(); uint64_t uptimeSeconds = uptimeMicroSeconds / 1000000; this->Publish_Int("uptime",uptimeSeconds,false); - - } } } From b0fd3c6b97c5c2559d7781a6af14da5aeb4fc377 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sat, 28 Dec 2024 12:39:44 +0100 Subject: [PATCH 008/106] Update mqtt.cpp --- src/mqtt.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 37d10798..86f3363a 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -214,8 +214,10 @@ void MQTT::WaitForConnect() { void MQTT::reconnect() { char topic[50]; char LWT[50]; + char buffer[100]; memset(&LWT[0], 0, sizeof(LWT)); memset(&topic[0], 0, sizeof(topic)); + memset(buffer, 0, sizeof(buffer)); if (Config->UseRandomMQTTClientID()) { snprintf (topic, sizeof(topic), "%s-%s", this->mqtt_root.c_str(), String(random(0xffff)).c_str()); @@ -233,6 +235,9 @@ void MQTT::reconnect() { this->Publish_String("version", Config->GetReleaseName(), false); this->Publish_String("state", "Online", false); //LWT reset + snprintf(buffer, sizeof(buffer), "%s", WiFi.SSID()); + this->Publish_String("ssid", buffer, false); + // ... and resubscribe if needed for (uint8_t i=0; i< this->subscriptions->size(); i++) { PubSubClient::subscribe(this->subscriptions->at(i).c_str()); @@ -395,9 +400,6 @@ void MQTT::loop() { snprintf(buffer, sizeof(buffer), "%d", WiFi.RSSI()); this->Publish_String("rssi", buffer, false); - - snprintf(buffer, sizeof(buffer), "%s", WiFi.SSID()); - this->Publish_String("ssid", buffer, false); uint64_t uptimeMicroSeconds = esp_timer_get_time(); uint64_t uptimeSeconds = uptimeMicroSeconds / 1000000; From 94020a38f8b021139672f451f8c464bb87f34fb1 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sat, 28 Dec 2024 14:53:33 +0100 Subject: [PATCH 009/106] Update modbus.cpp --- src/modbus.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index a7ed8270..fb0907f0 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -422,6 +422,7 @@ void modbus::QueryQueueToInverter() { if (rwtype == WRITE) { this->ReceiveSetData(&m); + this->QueryIdData(); } else if (rwtype == READ) { this->ReceiveReadData(); @@ -461,7 +462,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { //ret = true; //} } - + return ret; } From 977c08d182b79ce711e0a5d1c88d33e141651172 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 28 Dec 2024 17:27:46 +0100 Subject: [PATCH 010/106] support for OpenWB 2.0 Api (#100) --- ChangeLog.md | 2 +- data/misc/openwb.json | 32 +++++ data/regs/Growatt-MOD.json | 22 ++-- data/regs/Growatt-SPH.json | 20 ++-- data/regs/QVolt.json | 14 +-- data/regs/Sofar.json | 6 +- data/regs/Solax-MIC-Pro.json | 8 +- data/regs/Solax-MIC.json | 8 +- data/regs/Solax-X1.json | 16 +-- data/regs/Solax-X3.json | 14 +-- data/web/modbusconfig.html | 48 ++++++-- src/MyWebServer.cpp | 2 +- src/baseconfig.cpp | 19 ++- src/baseconfig.h | 7 ++ src/commonlibs.h | 2 +- src/main.cpp | 3 +- src/modbus.cpp | 222 ++++++++++++++++------------------- src/modbus.h | 13 +- src/mqtt.cpp | 13 +- src/mqtt.h | 8 +- src/openwb.cpp | 108 +++++++++++++++++ src/openwb.h | 73 ++++++++++++ 22 files changed, 454 insertions(+), 206 deletions(-) create mode 100644 data/misc/openwb.json create mode 100644 src/openwb.cpp create mode 100644 src/openwb.h diff --git a/ChangeLog.md b/ChangeLog.md index 4859d93c..66f65740 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,6 @@ Release 3.3.1: - BugFix: fix null-terminationof string handling (#96) - - ToDo: support for OpenWB 2.0 Api + - new feature: support for OpenWB 2.0 Api (#100) Release 3.3.0: - new feature: WebSerial as remote serial output (#74) diff --git a/data/misc/openwb.json b/data/misc/openwb.json new file mode 100644 index 00000000..1aafdf56 --- /dev/null +++ b/data/misc/openwb.json @@ -0,0 +1,32 @@ +[ + { + "version": "1.9", + "topics": [ + { "setpvw": "openWB/set/pv/W" }, + { "setpv1w": "openWB/set/pv/1/W"}, + { "setpv2w": "openWB/set/pv/2/W" }, + { "setbatw": "openWB/set/houseBattery/W"}, + { "setbatsoc": "openWB/set/houseBattery/%Soc"}, + { "setbatexpwh": "openWB/set/houseBattery/WhExported"}, + { "setbatimpwh": "openWB/set/houseBattery/WhImported"}, + { "setcounterwh": "openWB/set/pv/WhCounter"}, + { "setpv1counterwh": "openWB/set/pv/1/WhCounter"}, + { "setpv2counterwh": "openWB/set/pv/2/WhCounter"} + + ] + }, + { + "version": "2.0", + "topics": [ + { "setpvw": "openWB/set/pv/#InverterID#/get/power" }, + { "setpv1w": "openWB/set/pv/1/W"}, + { "setpv2w": "openWB/set/pv/2/W" }, + { "setbatw": "openWB/set/bat/#BatteryID#/get/power"}, + { "setbatsoc": "openWB/set/bat/#BatteryID#/get/soc"}, + { "setbatexpwh": "openWB/set/bat/#BatteryID#/get/exported"}, + { "setbatimpwh": "openWB/set/bat/#BatteryID#/get/imported"}, + { "setcounterwh": " openWB/set/pv/#InverterID#/get/exported"} + + ] + } +] \ No newline at end of file diff --git a/data/regs/Growatt-MOD.json b/data/regs/Growatt-MOD.json index 3ca6bf98..2ea75677 100644 --- a/data/regs/Growatt-MOD.json +++ b/data/regs/Growatt-MOD.json @@ -80,7 +80,7 @@ ], "name": "InputPower", "realname": "Erzeugungsleistung Gesamt", - "openwbtopic": "openWB/set/pv/W", + "openwbtopic": "setpvw", "datatype": "float", "factor": 0.1, "unit": "W" @@ -94,7 +94,7 @@ ], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -130,7 +130,7 @@ ], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -345,7 +345,6 @@ ], "name": "TodayEnergyKWhPv1", "realname": "Erzeugte Energie Pv1 heute", - "openwbtopic": "openWB/set/pv/1/WhCounter", "datatype": "integer", "factor": 0.1, "unit": "KWh" @@ -359,7 +358,7 @@ ], "name": "TotalEnergyWhPv1", "realname": "Erzeugte Energie Pv1 in Wh", - "openwbtopic": "openWB/set/pv/1/WhCounter", + "openwbtopic": "setpv1counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -373,7 +372,6 @@ ], "name": "TodayEnergyKWhPv2", "realname": "Erzeugte Energie Pv2 heute", - "openwbtopic": "openWB/set/pv/2/WhCounter", "datatype": "integer", "factor": 0.1, "unit": "KWh" @@ -387,7 +385,7 @@ ], "name": "TotalEnergyWhPv2", "realname": "Erzeugte Energie Pv2 in Wh", - "openwbtopic": "openWB/set/pv/2/WhCounter", + "openwbtopic": "setpv2counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -401,7 +399,7 @@ ], "name": "TotalEnergyKwhPv2", "realname": "Erzeugte Energie Pv2 in Kwh", - "openwbtopic": "openWB/set/pv/2/WhCounter", + "openwbtopic": "setpv2counterwh", "datatype": "float", "factor": 0.1, "unit": "KWh" @@ -435,7 +433,7 @@ ], "name": "BatChargingPower", "realname": "Battery Charging Power", - "openwbtopic": "openWB/set/houseBattery/W", + "openwbtopic": "setbatimpwh", "datatype": "float", "factor": 0.1, "unit": "W" @@ -609,7 +607,7 @@ ], "name": "BatCapacity", "realname": "Battery Capacity (SOC)", - "openwbtopic": "openWB/set/houseBattery/%Soc", + "openwbtopic": "setbatsoc", "datatype": "integer", "unit": "%" }, @@ -622,7 +620,7 @@ ], "name": "InputEnergyChargeWh", "realname": "Geladene Energie Speicher (Wh)", - "openwbtopic": "openWB/set/houseBattery/WhImported", + "openwbtopic": "setbatimpwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -649,7 +647,7 @@ ], "name": "OutputEnergyChargeWh", "realname": "Entladene Energie Speicher (Wh)", - "openwbtopic": "openWB/set/houseBattery/WhExported", + "openwbtopic": "setbatexpwh", "datatype": "integer", "factor": 100, "unit": "Wh" diff --git a/data/regs/Growatt-SPH.json b/data/regs/Growatt-SPH.json index 791060ce..bbef1292 100644 --- a/data/regs/Growatt-SPH.json +++ b/data/regs/Growatt-SPH.json @@ -38,7 +38,7 @@ "position": [5, 6, 7, 8], "name": "InputPower", "realname": "Erzeugungsleistung Gesamt", - "openwbtopic": "openWB/set/pv/W", + "openwbtopic": "setpvw", "datatype": "float", "factor": 0.1, "unit": "W" @@ -47,7 +47,7 @@ "position": [13, 14, 15, 16], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -56,7 +56,7 @@ "position": [21, 22, 23, 24], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -73,7 +73,7 @@ "position": [125, 126, 127, 128], "name": "TotalEnergyWhPv1", "realname": "Erzeugte Energie Pv1 in Wh", - "openwbtopic": "openWB/set/pv/1/WhCounter", + "openwbtopic": "setpv1counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -82,7 +82,6 @@ "position": [125, 126, 127, 128], "name": "TotalEnergyKwhPv1", "realname": "Erzeugte Energie Pv1 in Kwh", - "openwbtopic": "openWB/set/pv/1/WhCounter", "datatype": "float", "factor": 0.1, "unit": "KWh" @@ -91,7 +90,7 @@ "position": [133, 134, 135, 136], "name": "TotalEnergyWhPv2", "realname": "Erzeugte Energie Pv2 in Wh", - "openwbtopic": "openWB/set/pv/2/WhCounter", + "openwbtopic": "setpv2counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -100,7 +99,6 @@ "position": [133, 134, 135, 136], "name": "TotalEnergyKwhPv2", "realname": "Erzeugte Energie Pv2 in Kwh", - "openwbtopic": "openWB/set/pv/2/WhCounter", "datatype": "float", "factor": 0.1, "unit": "KWh" @@ -118,7 +116,7 @@ "position2": [264, 265, 266, 267], "name": "BatChargingPower", "realname": "Battery Charging Power", - "openwbtopic": "openWB/set/houseBattery/W", + "openwbtopic": "setbatimpwh", "datatype": "float", "factor": 0.1, "unit": "W" @@ -127,7 +125,7 @@ "position": [274, 275], "name": "BatCapacity", "realname": "Battery Capacity (SOC)", - "openwbtopic": "openWB/set/houseBattery/%Soc", + "openwbtopic": "setbatsoc", "datatype": "integer", "unit": "%" }, @@ -135,7 +133,7 @@ "position": [362, 363, 364, 365], "name": "InputEnergyChargeWh", "realname": "Geladene Energie Speicher (Wh)", - "openwbtopic": "openWB/set/houseBattery/WhImported", + "openwbtopic": "setbatimpwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -152,7 +150,7 @@ "position": [354, 355, 356, 357], "name": "OutputEnergyChargeWh", "realname": "Entladene Energie Speicher (Wh)", - "openwbtopic": "openWB/set/houseBattery/WhExported", + "openwbtopic": "setbatexpwh", "datatype": "integer", "factor": 100, "unit": "Wh" diff --git a/data/regs/QVolt.json b/data/regs/QVolt.json index 15a4d3ef..74ff20d7 100644 --- a/data/regs/QVolt.json +++ b/data/regs/QVolt.json @@ -169,7 +169,7 @@ "name": "PowerPv1", "realname": "Power PV 1", "datatype": "integer", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "unit": "W" }, { @@ -180,7 +180,7 @@ "name": "PowerPv2", "realname": "Power PV 2", "datatype": "integer", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "unit": "W" }, { @@ -213,7 +213,7 @@ "name": "BatPower", "realname": "Battery Power", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/W", + "openwbtopic": "setbatimpwh", "unit": "W" }, { @@ -285,7 +285,7 @@ "name": "BatCapacity", "realname": "Battery Capacity", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/%Soc", + "openwbtopic": "setbatsoc", "unit": "%" }, { @@ -298,7 +298,7 @@ "name": "OutputEnergyChargeWh", "realname": "Output Energy Charge (Wh)", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/WhExported", + "openwbtopic": "setbatexpwh", "factor": 100, "unit": "Wh" }, @@ -336,7 +336,7 @@ "name": "InputEnergyChargeWh", "realname": "Input Energy Charge (Wh)", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/WhImported", + "openwbtopic": "setbatimpwhhImported", "factor": 100, "unit": "Wh" }, @@ -449,7 +449,7 @@ "name": "EnergyTotalToGridWh", "realname": "Total Energy to Grid in Wh", "datatype": "integer", - "openwbtopic": "openWB/set/pv/WhCounter", + "openwbtopic": "setcounterwh", "factor": 100, "unit": "Wh" }, diff --git a/data/regs/Sofar.json b/data/regs/Sofar.json index 172b588f..303d0876 100644 --- a/data/regs/Sofar.json +++ b/data/regs/Sofar.json @@ -69,7 +69,7 @@ "position": [23, 24], "name": "PowerPv1", "realname": "Power PV 1", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "datatype": "integer", "factor": 10, "unit": "W" @@ -78,7 +78,7 @@ "position": [25, 26], "name": "PowerPv2", "realname": "Power PV 2", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "datatype": "integer", "factor": 10, "unit": "W" @@ -87,7 +87,7 @@ "position": [27,28], "name": "GridPower", "realname": "Grid Power", - "openwbtopic": "openWB/set/pv/W", + "openwbtopic": "setpvw", "datatype": "float", "factor": 10, "unit": "W" diff --git a/data/regs/Solax-MIC-Pro.json b/data/regs/Solax-MIC-Pro.json index 158aca54..d7b5f228 100644 --- a/data/regs/Solax-MIC-Pro.json +++ b/data/regs/Solax-MIC-Pro.json @@ -281,7 +281,7 @@ ], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "datatype": "float", "unit": "W" }, @@ -292,7 +292,7 @@ ], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "datatype": "float", "unit": "W" }, @@ -305,7 +305,7 @@ ], "name": "EnergyTotalToGridWh", "realname": "Total Energy to Grid in Wh", - "openwbtopic": "openWB/set/pv/WhCounter", + "openwbtopic": "setcounterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -345,7 +345,7 @@ ], "name": "GridPower", "realname": "Grid Power", - "openwbtopic": "openWB/set/pv/W", + "openwbtopic": "setpvw", "datatype": "integer", "unit": "W" }, diff --git a/data/regs/Solax-MIC.json b/data/regs/Solax-MIC.json index 41c3ad9e..0e91f294 100644 --- a/data/regs/Solax-MIC.json +++ b/data/regs/Solax-MIC.json @@ -183,7 +183,7 @@ "position": [43, 44], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "datatype": "float", "unit": "W" }, @@ -191,7 +191,7 @@ "position": [45, 46], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "datatype": "float", "unit": "W" }, @@ -199,7 +199,7 @@ "position": [75, 76, 73, 74], "name": "EnergyTotalToGridWh", "realname": "Total Energy to Grid in Wh", - "openwbtopic": "openWB/set/pv/WhCounter", + "openwbtopic": "setcounterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -224,7 +224,7 @@ "position": [121, 122, 119, 120], "name": "GridPower", "realname": "Grid Power", - "openwbtopic": "openWB/set/pv/W", + "openwbtopic": "setpvw", "datatype": "integer", "unit": "W" }, diff --git a/data/regs/Solax-X1.json b/data/regs/Solax-X1.json index 35489a24..07c69796 100644 --- a/data/regs/Solax-X1.json +++ b/data/regs/Solax-X1.json @@ -77,7 +77,7 @@ ], "name": "GridPower", "realname": "Grid Power", - "openwbtopic": "openWB/set/pv/W", + "openwbtopic": "setpvw", "datatype": "integer", "unit": "W" }, @@ -204,7 +204,7 @@ ], "name": "PowerPv1", "realname": "Power PV 1", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "datatype": "integer", "unit": "W" }, @@ -215,7 +215,7 @@ ], "name": "PowerPv2", "realname": "Power PV 2", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "datatype": "integer", "unit": "W" }, @@ -249,7 +249,7 @@ "name": "BatPower", "realname": "Battery Power", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/W", + "openwbtopic": "setbatw", "unit": "W" }, { @@ -270,7 +270,7 @@ "name": "BatCapacity", "realname": "Battery Capacity", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/%Soc", + "openwbtopic": "setbatsoc", "unit": "%" }, { @@ -282,7 +282,7 @@ ], "name": "OutputEnergyChargeWh", "realname": "Output Energy Charge (Wh)", - "openwbtopic": "openWB/set/houseBattery/WhExported", + "openwbtopic": "setbatexpwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -320,7 +320,7 @@ ], "name": "InputEnergyChargeWh", "realname": "Input Energy Charge (Wh)", - "openwbtopic": "openWB/set/houseBattery/WhImported", + "openwbtopic": "setbatimpwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -407,7 +407,7 @@ ], "name": "EnergyTotalToGridWh", "realname": "Total Energy to Grid in Wh", - "openwbtopic": "openWB/set/pv/WhCounter", + "openwbtopic": "setcounterwh", "datatype": "integer", "factor": 100, "unit": "Wh" diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index 31156883..d9da86cf 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -290,7 +290,7 @@ "name": "PowerPv1", "realname": "Power PV 1", "datatype": "integer", - "openwbtopic": "openWB/set/pv/1/W", + "openwbtopic": "setpv1w", "unit": "W" }, { @@ -301,7 +301,7 @@ "name": "PowerPv2", "realname": "Power PV 2", "datatype": "integer", - "openwbtopic": "openWB/set/pv/2/W", + "openwbtopic": "setpv2w", "unit": "W" }, { @@ -334,7 +334,7 @@ "name": "BatPower", "realname": "Battery Power", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/W", + "openwbtopic": "setbatimpwh", "unit": "W" }, { @@ -374,7 +374,7 @@ "name": "BatCapacity", "realname": "Battery Capacity", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/%Soc", + "openwbtopic": "setbatsoc", "unit": "%" }, { @@ -387,7 +387,7 @@ "name": "OutputEnergyChargeWh", "realname": "Output Energy Charge (Wh)", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/WhExported", + "openwbtopic": "setbatexpwh", "factor": 100, "unit": "Wh" }, @@ -425,7 +425,7 @@ "name": "InputEnergyChargeWh", "realname": "Input Energy Charge (Wh)", "datatype": "integer", - "openwbtopic": "openWB/set/houseBattery/WhImported", + "openwbtopic": "setbatimpwhhImported", "factor": 100, "unit": "Wh" }, @@ -525,7 +525,7 @@ "name": "EnergyTotalToGridWh", "realname": "Total Energy to Grid in Wh", "datatype": "integer", - "openwbtopic": "openWB/set/pv/WhCounter", + "openwbtopic": "setcounterwh", "factor": 100, "unit": "Wh" }, diff --git a/data/web/modbusconfig.html b/data/web/modbusconfig.html index a78c52b4..d3ba4a5a 100644 --- a/data/web/modbusconfig.html +++ b/data/web/modbusconfig.html @@ -82,26 +82,48 @@ Select connected Pin to read Relay 1 - + - + Select connected Pin to read Relay 2 - + - Enable OpenWB Compatibility - -
- - -
+ +
+ + +
+ +
+ + +
- + + + Select OpenWB Mqtt API Version + + + + + + + Inverter module ID + + + + + Battery module ID + + + Enable Dataframe CRC Check diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index c3151294..cfa6a746 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -109,7 +109,7 @@ void MyWebServer::handleReset(AsyncWebServerRequest *request) { void MyWebServer::handleWiFiReset(AsyncWebServerRequest *request) { #ifdef ESP32 WiFi.disconnect(true,true); - #elif ESP8266 + #elif defined(ESP8266) ESP.eraseConfig(); #endif diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index b683652a..21503438 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -3,7 +3,7 @@ BaseConfig::BaseConfig() : debuglevel(0), serial_rx(3), serial_tx(1), useAuth(false) { #ifdef ESP8266 LittleFS.begin(); - #elif ESP32 + #elif defined(ESP32) if (LittleFS.begin(true)) { // true: format LittleFS/NVS if mount fails if (!LittleFS.exists("/config")) { LittleFS.mkdir("/config"); @@ -121,4 +121,21 @@ void BaseConfig::GetInitData(AsyncResponseStream *response) { json["response"]["text"] = "successful"; serializeJson(json, ret); response->print(ret); +} + +void BaseConfig::log(const int loglevel, const char* format, ...) { + if (this->GetDebugLevel() < loglevel) return; + + va_list args; + va_start(args, format); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), format, args); + #ifdef USE_WEBSERIAL + WebSerial.printf("[Log %d] ", loglevel); + WebSerial.println(buffer); + #else + Serial.printf("[Log %d] ", loglevel); + Serial.println(buffer); + #endif + va_end(args); } \ No newline at end of file diff --git a/src/baseconfig.h b/src/baseconfig.h index ab77ce42..a4d07043 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -13,6 +13,13 @@ class BaseConfig { void LoadJsonConfig(); void GetInitData(AsyncResponseStream *response); + /** + * @brief Wrapper function for logging like Serial.printf + * @param format the format string + * @param ... the arguments + */ + void log(const int loglevel, const char* format, ...); + const String& GetMqttServer() const {return mqtt_server;} const uint16_t& GetMqttPort() const {return mqtt_port;} const String& GetMqttUsername() const {return mqtt_username;} diff --git a/src/commonlibs.h b/src/commonlibs.h index a1785820..ecd3a2b8 100644 --- a/src/commonlibs.h +++ b/src/commonlibs.h @@ -27,7 +27,7 @@ #include #include -#elif ESP32 +#elif defined(ESP32) #include #include #endif diff --git a/src/main.cpp b/src/main.cpp index 2952204b..0c4824ef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,7 +24,6 @@ BaseConfig* Config = NULL; MQTT* mqtt = NULL; MyWebServer* mywebserver = NULL; - void myMQTTCallBack(char* topic, byte* payload, unsigned int length) { String msg; if (Config->GetDebugLevel() >=3) { @@ -45,7 +44,7 @@ void myMQTTCallBack(char* topic, byte* payload, unsigned int length) { void setup() { Serial.begin(115200); - dbg.println("Start of Solar Inverter MQTT Gateway"); + dbg.println("Start of Modbus-RTU MQTT Gateway"); dbg.println("Starting BaseConfig"); Config = new BaseConfig(); diff --git a/src/modbus.cpp b/src/modbus.cpp index a7ed8270..3189f3ce 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -12,6 +12,7 @@ modbus::modbus() : enableRelays(false), Baudrate(19200), enableCrcCheck(true), e InverterIdData = new std::vector{}; AvailableInverters = new std::vector{}; Setters = new std::vector{}; + OpenWB = new openwb(); Conf_RequestLiveData= new std::vector>{}; Conf_RequestIdData = new std::vector>{}; @@ -37,6 +38,7 @@ modbus::modbus() : enableRelays(false), Baudrate(19200), enableCrcCheck(true), e this->LoadInvertersFromJson(); //needed for selecting default inverter this->LoadJsonConfig(true); + this->OpenWB->begin(this->Conf_OpenWBVersion); this->init(true); } @@ -44,11 +46,8 @@ modbus::modbus() : enableRelays(false), Baudrate(19200), enableCrcCheck(true), e * initialize transmission *******************************************************/ void modbus::init(bool firstrun) { - if (Config->GetDebugLevel() >=3) { - dbg.printf("Start Hardwareserial 1 on RX (%d), TX(%d), RTS(%d)\n", this->pin_RX, this->pin_TX, this->pin_RTS); - dbg.printf("Init Modbus to Client 0x%02X with %d Baud\n", this->ClientID, this->Baudrate); - - } + Config->log(3, "Start Hardwareserial 1 on RX (%d), TX(%d), RTS(%d)\n", this->pin_RX, this->pin_TX, this->pin_RTS); + Config->log(3, "Init Modbus to Client 0x%02X with %d Baud\n", this->ClientID, this->Baudrate); // Configure Direction Control pin pinMode(this->pin_RTS, OUTPUT); @@ -113,7 +112,7 @@ void modbus::GenerateMqttSubscriptions() { DeserializationError error = deserializeJson(elem, regfile); if (!error) { // Print the result - if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON for data ok"); } + Config->log(4, "parsing JSON for data ok"); if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} if(!elem["name"].isNull() && elem["request"].is()) { @@ -129,8 +128,7 @@ void modbus::GenerateMqttSubscriptions() { s.request = t; this->mqtt->Subscribe(this->GetMqttSetTopic(s.command)); - if (Config->GetDebugLevel() >=4) { - dbg.printf("Set command successfully parsed from JSON: %s with %s\n", s.command.c_str(), (this->PrintDataFrame(&(s.request))).c_str()); } + Config->log(4, "Set command successfully parsed from JSON: %s with %s\n", s.command.c_str(), (this->PrintDataFrame(&(s.request))).c_str()); this->Setters->push_back(s); } else { @@ -138,9 +136,7 @@ void modbus::GenerateMqttSubscriptions() { } } else { - if (Config->GetDebugLevel() >=1) { - dbg.printf("Failed to parse JSON Register Data: %s\n", error.c_str()); - } + Config->log(1, "Failed to parse JSON Register Data: %s\n", error.c_str()); } } while (regfile.findUntil(",","]")); @@ -153,9 +149,7 @@ void modbus::GenerateMqttSubscriptions() { *******************************************************/ void modbus::ReceiveMQTT(String topic, int msg) { if (!this->Conf_EnableSetters) { - if (Config->GetDebugLevel() >=2) { - dbg.printf("Set command <%s> received, but setters over mqtt are currently disabled\n", topic.c_str()); - } + Config->log(2, "Set command <%s> received, but setters over mqtt are currently disabled\n", topic.c_str()); return; } @@ -173,10 +167,8 @@ void modbus::ReceiveMQTT(String topic, int msg) { request.push_back(bytes[2]); request.push_back(bytes[3]); - if (Config->GetDebugLevel() >=3) { - dbg.printf("MQTT Setter found: %s\n" ,this->Setters->at(i).command.c_str()); - dbg.printf("Initiate Set Request to queue: %s\n" ,(this->PrintDataFrame(&request)).c_str()); - } + Config->log(3, "MQTT Setter found: %s\n" ,this->Setters->at(i).command.c_str()); + Config->log(3, "Initiate Set Request to queue: %s\n" ,(this->PrintDataFrame(&request)).c_str()); this->SetQueue->enqueue(request); } @@ -196,16 +188,14 @@ void modbus::LoadInvertersFromJson() { File root = LittleFS.open("/regs/"); File file = root.openNextFile(); while(file){ - if (Config->GetDebugLevel() >=3) { dbg.printf("open register file from Filesystem: %s\n", file.name()); } + Config->log(3, "open register file from Filesystem: %s\n", file.name()); DeserializationError error = deserializeJson(regjson, file, DeserializationOption::Filter(filter)); if (!error && regjson.size() > 0) { // https://arduinojson.org/v6/api/jsonobject/begin_end/ JsonObject root = regjson.as(); for (JsonPair kv : root) { - if (Config->GetDebugLevel() >=3) { - dbg.printf("Inverter found: %s\n", kv.key().c_str()); - } + Config->log(3, "Inverter found: %s\n", kv.key().c_str()); regfiles_t wr = {}; wr.filename = file.name(); @@ -213,7 +203,7 @@ void modbus::LoadInvertersFromJson() { AvailableInverters->push_back(wr); } } else{ - dbg.printf("Error: unable to load inverters from File %s: %s\n", file.name(), error.c_str()); + Config->log(1, "Error: unable to load inverters from File %s: %s\n", file.name(), error.c_str()); } file.close(); file = root.openNextFile(); @@ -221,8 +211,8 @@ void modbus::LoadInvertersFromJson() { root.close(); if (this->AvailableInverters->size() == 0) { - dbg.println("ALERT: No register definitions found. ESP cannot work properly"); - dbg.println("Please flash filesystem Image!"); + Config->log(1, "ALERT: No register definitions found. ESP cannot work properly"); + Config->log(1, "Please flash filesystem Image!"); } } @@ -235,7 +225,7 @@ void modbus::LoadInverterConfigFromJson() { File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - dbg.printf("failed to open %s file\n", this->InverterType.filename.c_str()); + Config->log(1, "failed to open %s file\n", this->InverterType.filename.c_str()); } filter[this->InverterType.name]["config"] = true; @@ -321,7 +311,7 @@ void modbus::enableMqtt(MQTT* object) { * Query ID Data to Inverter *******************************************************/ void modbus::QueryIdData() { - if (Config->GetDebugLevel() >=4) {dbg.println("Query ID Data into Queue:");} + Config->log(4, "Query ID Data into Queue:"); /* byte message[] = {this->ClientID, 0x03, // FunctionCode @@ -336,7 +326,7 @@ void modbus::QueryIdData() { if (this->ReadQueue->isEmpty()) { for (uint8_t i = 0; i < this->Conf_RequestIdData->size(); i++) { - if (Config->GetDebugLevel() >=4) { dbg.println(this->PrintDataFrame(&this->Conf_RequestIdData->at(i)).c_str()); } + Config->log(4, this->PrintDataFrame(&this->Conf_RequestIdData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestIdData->at(i)); } } @@ -347,7 +337,7 @@ void modbus::QueryIdData() { * Query Live Data to Inverter *******************************************************/ void modbus::QueryLiveData() { - if (Config->GetDebugLevel() >=4) {dbg.println("Query Live Data into Queue:"); } + Config->log(4, "Query Live Data into Queue:"); /* byte message[] = {this->ClientID, 0x04, // FunctionCode @@ -362,7 +352,7 @@ void modbus::QueryLiveData() { if (this->ReadQueue->isEmpty()) { for (uint8_t i = 0; i < this->Conf_RequestLiveData->size(); i++) { - if (Config->GetDebugLevel() >=4) { dbg.println(this->PrintDataFrame(&this->Conf_RequestLiveData->at(i)).c_str()); } + Config->log(4, this->PrintDataFrame(&this->Conf_RequestLiveData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestLiveData->at(i)); } } @@ -390,7 +380,7 @@ void modbus::QueryQueueToInverter() { else { rwtype = NUL; } if (rwtype != NUL) { - if (Config->GetDebugLevel() >=3) { dbg.print("Request queue data to inverter: "); } + Config->log(3, "Request queue data to inverter: "); digitalWrite(this->pin_RTS, RS485Receive); // init Receive while (RS485Serial->available() > 0) { // read serial if any old data is available @@ -409,7 +399,7 @@ void modbus::QueryQueueToInverter() { m.push_back(lowByte(crc)); m.push_back(highByte(crc)); - if (Config->GetDebugLevel() >=3) { dbg.println(this->PrintDataFrame(message, sizeof(message))); } + Config->log(3, this->PrintDataFrame(message, sizeof(message)).c_str()); digitalWrite(this->pin_RTS, RS485Transmit); // init Transmit RS485Serial->write(message, sizeof(message)); @@ -441,7 +431,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { std::vector RecvHexframe = {}; bool ret = false; - if (Config->GetDebugLevel() >=3) {dbg.println("Read Data from Queue: ");} + Config->log(3, "Read Data from Queue: "); digitalWrite(this->pin_RTS, RS485Receive); // init Receive if (RS485Serial->available()) { @@ -471,7 +461,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { void modbus::ReceiveReadData() { size_t dataFrameStartPos = this->DataFrame->size(); - if (Config->GetDebugLevel() >=3) {dbg.println("Read Data from Queue: ");} + Config->log(3, "Read Data from Queue: "); digitalWrite(this->pin_RTS, RS485Receive); // init Receive if (RS485Serial->available()) { @@ -491,45 +481,39 @@ void modbus::ReceiveReadData() { this->DataFrame->at(dataFrameStartPos+this->Conf_IdDataErrorPos) != this->Conf_IdDataErrorCode && this->DataFrame->at(dataFrameStartPos+this->Conf_LiveDataErrorPos) != this->Conf_LiveDataErrorCode) { - if (Config->GetDebugLevel() >=4) dbg.println("ErrorCode passed, OK"); + Config->log(4, "ErrorCode passed, OK"); if (this->enableCrcCheck) { //CRC Check uint16_t crc = this->Calc_CRC(this->DataFrame, dataFrameStartPos, this->DataFrame->size()-2); - if (Config->GetDebugLevel() >=4) { - dbg.printf("Received CRC: 0x%02X 0x%02X\n", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1)); - dbg.printf("Calculated CRC: 0x%02X 0x%02X\n", lowByte(crc), highByte(crc)); - } + Config->log(4, "Received CRC: 0x%02X 0x%02X\n", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1)); + Config->log(4, "Calculated CRC: 0x%02X 0x%02X\n", lowByte(crc), highByte(crc)); if (this->DataFrame->at(this->DataFrame->size()-2) != lowByte(crc) || this->DataFrame->at(this->DataFrame->size()-1) != highByte(crc)) { valid = false; - if (Config->GetDebugLevel() >=2) dbg.println("CRC check failed!"); + Config->log(2, "CRC check failed!"); } } if (this->enableLengthCheck) { // Check datalength - if (Config->GetDebugLevel() >=4) { - dbg.printf("Dataframe length should be: %d, is: %d bytes\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); - } + Config->log(4, "Dataframe length should be: %d, is: %d bytes\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); if (this->DataFrame->at(dataFrameStartPos+2) != this->DataFrame->size()-dataFrameStartPos-5) { valid = false; - if (Config->GetDebugLevel() >=2) dbg.printf("data length check failed, should be %d but is %d bytes\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); + Config->log(2, "data length check failed, should be %d but is %d bytes\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); } } } else { valid = false; } if (valid) { // Dataframe valid - if (Config->GetDebugLevel() >=3) { - dbg.printf("Dataframe valid, Dateframe size: %d bytes\n", this->DataFrame->size()); - } + Config->log(3, "Dataframe valid, Dateframe size: %d bytes\n", this->DataFrame->size()); } else { - if (Config->GetDebugLevel() >=2) {dbg.println("Dataframe invalid");} + Config->log(2, "Dataframe invalid"); // clear dataframe, clear ReadQueue to start from fresh this->DataFrame->clear(); for (unsigned int n = 0; n < this->ReadQueue->itemCount(); n++) { @@ -538,7 +522,7 @@ void modbus::ReceiveReadData() { } } else { - if (Config->GetDebugLevel() >=2) {dbg.println("no response from client");} + Config->log(2, "no response from client"); } } @@ -605,32 +589,33 @@ void modbus::ParseData() { // *********************************************** #ifdef DEBUGMODE this->DataFrame->clear(); - if (Config->GetDebugLevel() >=3) {dbg.println("Start parsing in testmode, use some testdata instead real live data :)");} + Config->log(3, "Start parsing in testmode, use some testdata instead real live data :)"); // Solar-KTL - //byte ReadBuffer[] = {0x01, 0x03, 0x60, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x34, 0x01, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x13, 0x86, 0x09, 0x11, 0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x4C, 0x00, 0x00, 0x02, 0x5F, 0x00, 0x8A, 0x01, 0x7D, 0x00, 0x28, 0x00, 0x33, 0x0E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x60, 0x01, 0x42, 0x0A, 0x3B, 0x00, 0x0E, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x25}; + //byte ReadBuffer[] = {0x01, 0x03, 0x60, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x34, 0x01, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x13, 0x86, 0x09, 0x11, 0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x4C, 0x00, 0x00, 0x02, 0x5F, 0x00, 0x8A, 0x01, 0x7D, 0x00, 0x28, 0x00, 0x33, 0x0E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x60, 0x01, 0x42, 0x0A, 0x3B, 0x00, 0x0E, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x25,0x01}; // Solax MIC - //byte ReadBuffer[] = {0x01, 0x04, 0x80, 0x12, 0x34, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x09, 0x64, 0x09, 0x67, 0x09, 0x6B, 0x13, 0x8C, 0x13, 0x8D, 0x13, 0x8B, 0x00, 0x27, 0x00, 0x28, 0x00, 0x28, 0x00, 0x2C, 0x0B, 0x54, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xC3}; + //byte ReadBuffer[] = {0x01, 0x04, 0x80, 0x12, 0x34, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x09, 0x64, 0x09, 0x67, 0x09, 0x6B, 0x13, 0x8C, 0x13, 0x8D, 0x13, 0x8B, 0x00, 0x27, 0x00, 0x28, 0x00, 0x28, 0x00, 0x2C, 0x0B, 0x54, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xC3,0x02s}; //Solax X1 - //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02}; + byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02}; //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x02}; - byte ReadBuffer[] = {0x01,0x03,0x28,0x48,0x34,0x35,0x30,0x32,0x41,0x49,0x34,0x34,0x35,0x39,0x30,0x30,0x35,0x73,0x6F,0x6C,0x61,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x4A,0xA0, 0x01}; + //byte ReadBuffer[] = {0x01,0x03,0x28,0x48,0x34,0x35,0x30,0x32,0x41,0x49,0x34,0x34,0x35,0x39,0x30,0x30,0x35,0x73,0x6F,0x6C,0x61,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x4A,0xA0, 0x01}; //Growatt IDData - //byte ReadBuffer[] = {0x01,0x03,0xEE,0x00,0x01,0x00,0xBD,0xFF,0xFF,0x00,0x64,0x00,0x00,0x27,0x10,0x00,0x00,0x9C,0x40,0x06,0x40,0x44,0x4E,0x31,0x2E,0x30,0x00,0x5A,0x42,0x44,0x42,0x00,0x02,0x00,0x02,0x00,0x00,0x06,0x40,0x00,0x3C,0x00,0x3C,0x00,0x5A,0x00,0x5A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x50,0x56,0x20,0x49,0x6E,0x76,0x65,0x72,0x74,0x65,0x72,0x20,0x20,0x00,0x00,0x15,0x18,0x02,0x03,0x07,0xE8,0x00,0x07,0x00,0x13,0x00,0x08,0x00,0x10,0x00,0x05,0x00,0x05,0x0C,0x73,0x13,0x73,0x12,0x8E,0x14,0x1E,0x07,0x00,0x13,0x73,0x12,0x8E,0x14,0x1E,0x07,0x00,0x13,0x73,0x12,0x8E,0x14,0x1E,0x0D,0x61,0x10,0xF6,0x12,0x9D,0x13,0x8D,0x00,0x98,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x11,0x1E,0x00,0x00,0x44,0x4E,0x41,0x41,0x30,0x31,0x35,0x31,0x30,0x30,0x30,0x32,0x01,0x31,0x00,0x00,0x00,0x00,0x13,0x9C,0x00,0x32,0x10,0x07,0x10,0xA6,0x0F,0x18,0x0E,0x79,0x00,0x14,0x00,0x05,0x10,0x57,0x0F,0x90,0x26,0xFD,0x26,0xFD,0x26,0xA5,0x27,0xC1,0x27,0xC1,0x28,0x21,0x00,0x0A,0x00,0x00,0x01,0xE4,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x07,0x00,0xFC,0xE5}; - - //Growatt LiveData - //byte ReadBuffer[] = {0x01,0x04,0xEE,0x00,0x01,0x00,0x00,0x32,0x8F,0x07,0xF5,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0xC2,0x00,0x2B,0x00,0x00,0x32,0x8F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xED,0x13,0x86,0x09,0x5B,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x5C,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x4D,0x00,0x06,0x00,0x00,0x05,0x94,0x10,0x54,0x10,0x1B,0x10,0x1B,0x00,0x00,0x00,0x03,0x00,0x00,0x5F,0x9F,0x01,0xA8,0xF8,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x67,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x99,0x01,0xBD,0x01,0x63,0x01,0x54,0x00,0x00,0x01,0x79,0x0C,0xD4,0x0C,0xCA,0x4E,0x20,0x00,0x00,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xFA,0x01,0x04,0xEE,0x4E,0x20,0x00,0x03,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x25,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x02,0x00,0x00,0x20,0x19,0x00,0x00,0x00,0x08,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x51,0x00,0x00,0x66,0xD9,0x00,0x00,0x00,0x05,0x00,0x00,0x50,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x10,0x00,0x00,0x19,0xA0,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x53,0x00,0x00,0x30,0x00,0x12,0x19,0x9E,0x0D,0x4A,0x00,0x15,0x00,0x15,0x01,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x14,0x00,0x00,0x20,0x19,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x03,0x0C,0x54,0x00,0x39,0x00,0x10,0x00,0x00,0x01,0x01,0x00,0xE1,0x00,0x02,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x31,0x00,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x12,0x52,0x94,0x01,0xCC,0x01,0xA5,0xD1,0x6F,0xEE,0x00,0x01,0x00,0x00,0x32,0x8F,0x07,0xF5,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0xC2,0x00,0x2B,0x00,0x00,0x32,0x8F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xED,0x13,0x86,0x09,0x5B,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x5C,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x4D,0x00,0x06,0x00,0x00,0x05,0x94,0x10,0x54,0x10,0x1B,0x10,0x1B,0x00,0x00,0x00,0x03,0x00,0x00,0x5F,0x9F,0x01,0xA8,0xF8,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x67,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x99,0x01,0xBD,0x01,0x63,0x01,0x54,0x00,0x00,0x01,0x79,0x0C,0xD4,0x0C,0xCA,0x4E,0x20,0x00,0x00,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xFA,0x01,0x04,0xEE,0x4E,0x20,0x00,0x03,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x25,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x02,0x00,0x00,0x20,0x19,0x00,0x00,0x00,0x08,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x51,0x00,0x00,0x66,0xD9,0x00,0x00,0x00,0x05,0x00,0x00,0x50,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x10,0x00,0x00,0x19,0xA0,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x53,0x00,0x00,0x30,0x00,0x12,0x19,0x9E,0x0D,0x4A,0x00,0x15,0x00,0x15,0x01,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x14,0x00,0x00,0x20,0x19,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x03,0x0C,0x54,0x00,0x39,0x00,0x10,0x00,0x00,0x01,0x01,0x00,0xE1,0x00,0x02,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x31,0x00,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x12,0x52,0x94,0x01,0xCC,0x01,0xA5,0xD1,0x6F}; + //byte ReadBuffer[] = {0x01,0x03,0xEE,0x00,0x01,0x00,0xBD,0xFF,0xFF,0x00,0x64,0x00,0x00,0x27,0x10,0x00,0x00,0x9C,0x40,0x06,0x40,0x44,0x4E,0x31,0x2E,0x30,0x00,0x5A,0x42,0x44,0x42,0x00,0x02,0x00,0x02,0x00,0x00,0x06,0x40,0x00,0x3C,0x00,0x3C,0x00,0x5A,0x00,0x5A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x20,0x20,0x20,0x50,0x56,0x20,0x49,0x6E,0x76,0x65,0x72,0x74,0x65,0x72,0x20,0x20,0x00,0x00,0x15,0x18,0x02,0x03,0x07,0xE8,0x00,0x07,0x00,0x13,0x00,0x08,0x00,0x10,0x00,0x05,0x00,0x05,0x0C,0x73,0x13,0x73,0x12,0x8E,0x14,0x1E,0x07,0x00,0x13,0x73,0x12,0x8E,0x14,0x1E,0x07,0x00,0x13,0x73,0x12,0x8E,0x14,0x1E,0x0D,0x61,0x10,0xF6,0x12,0x9D,0x13,0x8D,0x00,0x98,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x00,0x32,0x11,0x1E,0x00,0x00,0x44,0x4E,0x41,0x41,0x30,0x31,0x35,0x31,0x30,0x30,0x30,0x32,0x01,0x31,0x00,0x00,0x00,0x00,0x13,0x9C,0x00,0x32,0x10,0x07,0x10,0xA6,0x0F,0x18,0x0E,0x79,0x00,0x14,0x00,0x05,0x10,0x57,0x0F,0x90,0x26,0xFD,0x26,0xFD,0x26,0xA5,0x27,0xC1,0x27,0xC1,0x28,0x21,0x00,0x0A,0x00,0x00,0x01,0xE4,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x00,0xFF,0x4E,0x20,0x07,0x00,0xFC,0xE5,0x01}; + //byte ReadBuffer[] = {0x01,0x03,0x40,0x00,0x01,0x08,0x40,0x00,0x00,0x00,0x64,0x00,0x64,0x27,0x10,0x00,0x00,0x0B,0xB8,0x0E,0x10,0x52,0x41,0x31,0x2E,0x30,0x20,0x5A,0x43,0x42,0x43,0x00,0x05,0x00,0x02,0x00,0x00,0x03,0x52,0x00,0x1E,0x00,0x3C,0x00,0xC8,0x00,0x64,0x00,0x00,0x44,0x54,0x4D,0x34,0x45,0x35,0x4A,0x30,0x30,0x4E,0x01,0x00,0xF2,0x27,0x00,0x01,0x00,0x00,0xCC,0xF4,0x01}; + //Growatt SPH LiveData + //byte ReadBuffer[] = {0x01,0x04,0xEE,0x00,0x01,0x00,0x00,0x32,0x8F,0x07,0xF5,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0xC2,0x00,0x2B,0x00,0x00,0x32,0x8F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xED,0x13,0x86,0x09,0x5B,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x5C,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x4D,0x00,0x06,0x00,0x00,0x05,0x94,0x10,0x54,0x10,0x1B,0x10,0x1B,0x00,0x00,0x00,0x03,0x00,0x00,0x5F,0x9F,0x01,0xA8,0xF8,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x67,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x99,0x01,0xBD,0x01,0x63,0x01,0x54,0x00,0x00,0x01,0x79,0x0C,0xD4,0x0C,0xCA,0x4E,0x20,0x00,0x00,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xFA,0x01,0x04,0xEE,0x4E,0x20,0x00,0x03,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x25,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x02,0x00,0x00,0x20,0x19,0x00,0x00,0x00,0x08,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x51,0x00,0x00,0x66,0xD9,0x00,0x00,0x00,0x05,0x00,0x00,0x50,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x10,0x00,0x00,0x19,0xA0,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x53,0x00,0x00,0x30,0x00,0x12,0x19,0x9E,0x0D,0x4A,0x00,0x15,0x00,0x15,0x01,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x14,0x00,0x00,0x20,0x19,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x03,0x0C,0x54,0x00,0x39,0x00,0x10,0x00,0x00,0x01,0x01,0x00,0xE1,0x00,0x02,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x31,0x00,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x12,0x52,0x94,0x01,0xCC,0x01,0xA5,0xD1,0x6F,0xEE,0x00,0x01,0x00,0x00,0x32,0x8F,0x07,0xF5,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0xC2,0x00,0x2B,0x00,0x00,0x32,0x8F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xED,0x13,0x86,0x09,0x5B,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x5C,0x00,0x06,0x00,0x00,0x05,0x9D,0x09,0x4D,0x00,0x06,0x00,0x00,0x05,0x94,0x10,0x54,0x10,0x1B,0x10,0x1B,0x00,0x00,0x00,0x03,0x00,0x00,0x5F,0x9F,0x01,0xA8,0xF8,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x67,0x99,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x67,0x99,0x01,0xBD,0x01,0x63,0x01,0x54,0x00,0x00,0x01,0x79,0x0C,0xD4,0x0C,0xCA,0x4E,0x20,0x00,0x00,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xFA,0x01,0x04,0xEE,0x4E,0x20,0x00,0x03,0x00,0x00,0x9C,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x25,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x02,0x00,0x00,0x20,0x19,0x00,0x00,0x00,0x08,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x51,0x00,0x00,0x66,0xD9,0x00,0x00,0x00,0x05,0x00,0x00,0x50,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x10,0x00,0x00,0x19,0xA0,0x00,0x00,0x00,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x53,0x00,0x00,0x30,0x00,0x12,0x19,0x9E,0x0D,0x4A,0x00,0x15,0x00,0x15,0x01,0x61,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x14,0x00,0x00,0x20,0x19,0x00,0x00,0x21,0xD5,0x00,0x00,0x00,0x03,0x0C,0x54,0x00,0x39,0x00,0x10,0x00,0x00,0x01,0x01,0x00,0xE1,0x00,0x02,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x31,0x00,0x31,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x12,0x52,0x94,0x01,0xCC,0x01,0xA5,0xD1,0x6F,0x02}; + // K8H //byte ReadBuffer[] = {0xF7,0x02,0x1E,0x0B,0x3E,0x00,0x39,0x05,0xE1,0x0A,0x68,0x00,0x39,0x05,0xFA,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xFC,0x00,0x7C,0x00,0x00,0x13,0x87,0x00,0x00,0x24,0x27, 0x02}; for (uint16_t i = 0; iDataFrame->push_back(ReadBuffer[i]); } - if (Config->GetDebugLevel() >=4) { dbg.printf("%s\n", this->PrintDataFrame(this->DataFrame).c_str()); } + Config->log(4, "%s\n", this->PrintDataFrame(this->DataFrame).c_str()); #endif // *********************************************** } @@ -647,12 +632,12 @@ void modbus::ParseData() { RequestType = "livedata"; } - if (Config->GetDebugLevel() >=3) dbg.printf("parse %d bytes of data\n", this->DataFrame->size()); - if (Config->GetDebugLevel() >=4) dbg.printf("identified datatype: %s\n", RequestType.c_str()); + Config->log(3, "parse %d bytes of data\n", this->DataFrame->size()); + Config->log(4, "identified datatype: %s\n", RequestType.c_str()); File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - dbg.printf("failed to open %s file\n", this->InverterType.filename.c_str()); + Config->log(1, "failed to open %s file\n", this->InverterType.filename.c_str()); } String streamString = ""; streamString = "\""+ this->InverterType.name +"\": {"; @@ -666,14 +651,10 @@ void modbus::ParseData() { if (!error) { // Print the result - if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } + Config->log(4, "parsing JSON ok"); if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} } else { - if (Config->GetDebugLevel() >=1) { - dbg.print("(Function ParseData) Failed to parse JSON Register Data: "); - dbg.print(error.c_str()); - dbg.println(); - } + Config->log(1, "(Function ParseData) Failed to parse JSON Register Data: %s", error.c_str()); } // setUp local variables @@ -703,9 +684,7 @@ void modbus::ParseData() { if (elem["position"].is()) { posArray = elem["position"].as(); } else { - if (Config->GetDebugLevel() >=1) { - dbg.printf("Error: for Name '%s' no position array found", d.Name.c_str()); - } + Config->log(1, "Error: for Name '%s' no position array found", d.Name.c_str()); continue; } @@ -716,7 +695,7 @@ void modbus::ParseData() { } // optional field - if(this->Conf_EnableOpenWBTopic && !elem["openwbtopic"].isNull()) { + if(this->Conf_EnableOpenWB && !elem["openwbtopic"].isNull()) { openwbtopic = elem["openwbtopic"].as(); } @@ -784,23 +763,28 @@ void modbus::ParseData() { } else { //****************** sonst, leer *******************// d.value = ""; - if (Config->GetDebugLevel() >=2) dbg.printf("Error: for Name '%s\n' no valid datatype found", d.Name.c_str()); + Config->log(2, "Error: for Name '%s\n' no valid datatype found", d.Name.c_str()); } // map values if a mapping is specified if(!elem["mapping"].isNull() && elem["mapping"].is() && d.value != "") { - if (Config->GetDebugLevel() >=4) dbg.printf("Map values for item %s\n", d.Name.c_str()); + Config->log(4, "Map values for item %s\n", d.Name.c_str()); JsonArray map = elem["mapping"].as(); d.value = this->MapItem(map, d.value); } - if (Config->GetDebugLevel() >=4) dbg.printf("Data: %s -> %s %s\n", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); + Config->log(4, "Data: %s -> %s %s\n", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); if (this->mqtt && IsActiveItem && d.Name != "") { this->mqtt->Publish_String(d.Name.c_str(), d.value, false); - if (openwbtopic.length() > 0) { this->mqtt->Publish_String(openwbtopic.c_str(), d.value, true);} + if (openwbtopic.length() > 0) { + const String newTopic(OpenWB->getOpenWbTopic(openwbtopic)); + if (newTopic.length() > 0) { + this->mqtt->Publish_String(newTopic.c_str(), d.value, true); + } + } } if(RequestType == "livedata") { @@ -808,9 +792,7 @@ void modbus::ParseData() { } else if(RequestType == "id") { this->ChangeRegItem(this->InverterIdData, d); - if (Config->GetDebugLevel() >=3) { - dbg.printf("Inverter ID Data found -> %s: %s \n", d.Name.c_str(), d.value.c_str()); - } + Config->log(3, "Inverter ID Data found -> %s: %s \n", d.Name.c_str(), d.value.c_str()); } @@ -844,11 +826,11 @@ String modbus::MapItem(JsonArray map, String value) { String v2 = mapItem[1].as(); - if (Config->GetDebugLevel() >=5) dbg.printf("Check Map value: %s -> %s\n", v1.c_str(), v2.c_str()); + Config->log(5, "Check Map value: %s -> %s\n", v1.c_str(), v2.c_str()); if (value == v1) { ret = v2; - if (Config->GetDebugLevel() >=4) dbg.printf("Mapped value: %s -> %s", v1.c_str(), v2.c_str()); + Config->log(4, "Mapped value: %s -> %s", v1.c_str(), v2.c_str()); } } return ret; @@ -888,7 +870,6 @@ uint16_t modbus::Calc_CRC(uint8_t* message, uint8_t len) { uint16_t modbus::Calc_CRC(std::vector* message, uint16_t startpos, uint16_t endpos) { uint16_t crc = 0xFFFF; -// dbg.print("Calc_CRC of: "); for (uint16_t pos = startpos; pos < endpos; pos++) { crc ^= (uint16_t)message->at(pos); // XOR byte into least sig. byte of crc for (int i = 8; i != 0; i--) { // Loop over each bit @@ -899,10 +880,7 @@ uint16_t modbus::Calc_CRC(std::vector* message, uint16_t startpos, uint16_ else // Else LSB is not set crc >>= 1; // Just shift right } -// dbg.printf("%s ",this->PrintHex(message->at(pos)).c_str()); } -// dbg.println(); -// dbg.printf("Calculated CRC (%d values): 0x%02X 0x%02X\n", endpos-startpos, highByte(crc), lowByte(crc)); return crc; } @@ -974,7 +952,7 @@ void modbus::GetLiveDataAsJson(AsyncResponseStream *response, String subaction) if (this->InverterLiveData->at(i).openwb.length() > 0) { JsonArray wb = doc["openwb"].to(); - wb[0]["openwbtopic"] = this->InverterLiveData->at(i).openwb.c_str(); + wb[0]["openwbtopic"] = std::move(OpenWB->getOpenWbTopic(this->InverterLiveData->at(i).openwb)); } serializeJson(doc, s); @@ -1225,10 +1203,10 @@ void modbus::LoadJsonConfig(bool firstrun) { if (LittleFS.exists("/config/modbusconfig.json")) { //file exists, reading and loading - if (Config->GetDebugLevel() >=3) dbg.println("reading config file...."); + Config->log(3, "reading config file...."); File configFile = LittleFS.open("/config/modbusconfig.json", "r"); if (configFile) { - if (Config->GetDebugLevel() >=3) dbg.println("config file is open:"); + Config->log(3, "config file is open:"); //size_t size = configFile.size(); JsonDocument doc; @@ -1236,6 +1214,7 @@ void modbus::LoadJsonConfig(bool firstrun) { if (!error && doc["data"]) { if (Config->GetDebugLevel() >=3) { serializeJsonPretty(doc, dbg); dbg.println(); } + OpenWB->clearMappings(); if (doc["data"]["pin_rx"]) { this->pin_RX = (int)(doc["data"]["pin_rx"]);} else {this->pin_RX = this->default_pin_RX;} if (doc["data"]["pin_tx"]) { this->pin_TX = (int)(doc["data"]["pin_tx"]);} else {this->pin_TX = this->default_pin_TX;} @@ -1246,21 +1225,22 @@ void modbus::LoadJsonConfig(bool firstrun) { if (doc["data"]["txintervalid"]) { this->TxIntervalIdData = (int)(doc["data"]["txintervalid"]);} else {this->TxIntervalIdData = 3600;} if (doc["data"]["pin_RELAY1"]) { this->pin_Relay1= doc["data"]["pin_RELAY1"].as();} else {this->pin_Relay1 = this->default_pin_Relay1;} if (doc["data"]["pin_RELAY2"]) { this->pin_Relay2 = doc["data"]["pin_RELAY2"].as();} else {this->pin_Relay2 = this->default_pin_Relay2;} - - this->Conf_EnableOpenWBTopic = doc["data"]["enable_openwbtopic"].as(); + if (doc["data"]["openwbversion"]) { this->Conf_OpenWBVersion = doc["data"]["openwbversion"].as(); this->OpenWB->setVersion(this->Conf_OpenWBVersion); } + if (doc["data"]["openwbmodulid"]) { this->Conf_OpenWBModulID = doc["data"]["openwbmodulid"].as(); this->OpenWB->addMapping("InverterID", String(this->Conf_OpenWBModulID)); } + if (doc["data"]["openwbbatteryid"]) { this->Conf_OpenWBBatteryID = doc["data"]["openwbbatteryid"].as(); this->OpenWB->addMapping("BatteryID", String(this->Conf_OpenWBBatteryID)); } + + this->Conf_EnableOpenWB = (bool)doc["data"]["EnableOpenWb"].as(); this->Conf_EnableSetters = doc["data"]["enable_setters"].as(); this->enableCrcCheck = doc["data"]["enableCrcCheck"].as(); this->enableLengthCheck = doc["data"]["enableLengthCheck"].as(); this->enableRelays = (bool)(doc["data"]["EnableRelays"]).as(); - + if (doc["data"]["invertertype"]) { bool found = false; for (uint8_t i=0; iAvailableInverters->size(); i++) { if (this->AvailableInverters->at(i).name == (doc["data"]["invertertype"]).as()) { this->InverterType = this->AvailableInverters->at(i); - if (Config->GetDebugLevel() >=3) { - dbg.printf("Invertertyp '%s' was found in register file '%s', set it as selected active Inverter\n", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); - } + Config->log(3, "Invertertyp '%s' was found in register file '%s', set it as selected active Inverter\n", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); found = true; } } @@ -1268,20 +1248,18 @@ void modbus::LoadJsonConfig(bool firstrun) { if (this->AvailableInverters->size()>0) { this->InverterType = this->AvailableInverters->at(0); } - if (Config->GetDebugLevel() >=3) { - dbg.printf("Invertertyp '%s' was not found, use default '%s' instead\n", (doc["data"]["invertertype"]).as().c_str(), this->InverterType.name.c_str()); - } + Config->log(3, "Invertertyp '%s' was not found, use default '%s' instead\n", (doc["data"]["invertertype"]).as().c_str(), this->InverterType.name.c_str()); } } } else { - if (Config->GetDebugLevel() >=1) {dbg.println("failed to load modbus json config, load default config");} + Config->log(1, "failed to load modbus json config, load default config"); loadDefaultConfig = true; } configFile.close(); } } else { - if (Config->GetDebugLevel() >=3) {dbg.println("modbusconfig.json config File not exists, load default config");} + Config->log(3, "modbusconfig.json config File not exists, load default config"); loadDefaultConfig = true; } @@ -1300,7 +1278,8 @@ void modbus::LoadJsonConfig(bool firstrun) { this->pin_Relay1 = this->default_pin_Relay1; this->pin_Relay2 = this->default_pin_Relay2; this->enableRelays = false; - this->Conf_EnableOpenWBTopic = false; + this->Conf_EnableOpenWB = false; + this->Conf_OpenWBVersion = "1.9"; this->Conf_EnableSetters = false; loadDefaultConfig = false; //set back @@ -1335,10 +1314,10 @@ void modbus::LoadJsonItemConfig() { if (LittleFS.exists("/config/modbusitemconfig.json")) { //file exists, reading and loading - if (Config->GetDebugLevel() >=3) dbg.println("reading modbus item config file...."); + Config->log(3, "reading modbus item config file...."); File configFile = LittleFS.open("/config/modbusitemconfig.json", "r"); if (configFile) { - if (Config->GetDebugLevel() >=3) dbg.println("modbus item config file is open:"); + Config->log(3, "modbus item config file is open:"); ReadBufferingStream stream{configFile, 64}; stream.find("\"data\":["); @@ -1348,29 +1327,20 @@ void modbus::LoadJsonItemConfig() { if (!error) { // Print the result - if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } + Config->log(4, "parsing JSON ok"); if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} } else { - if (Config->GetDebugLevel() >=1) { - dbg.print("(Function LoadJsonItemConfig) Failed to parse JSON Register Data: "); - dbg.print(error.c_str()); - dbg.println(); - } + Config->log(1, "(Function LoadJsonItemConfig) Failed to parse JSON Register Data: %s", error.c_str()); } for (JsonPair kv : elem.as()) { const char* ItemName = kv.key().c_str(); - //dbg.println(kv.key().c_str()); - //dbg.println(kv.value().as()); - for(uint16_t i=0; iInverterLiveData->size(); i++) { if (this->InverterLiveData->at(i).Name == ItemName ) { this->InverterLiveData->at(i).active = kv.value().as(); - if (Config->GetDebugLevel() >=3) { - dbg.printf("item %s -> %s\n", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); - } + Config->log(3, "item %s -> %s\n", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); break; } @@ -1380,10 +1350,7 @@ void modbus::LoadJsonItemConfig() { if (this->InverterIdData->at(i).Name == ItemName ) { this->InverterIdData->at(i).active = kv.value().as(); - if (Config->GetDebugLevel() >=3) { - dbg.printf("item %s -> %s\n", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); - } - + Config->log(3, "item %s -> %s\n", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); break; } } @@ -1393,10 +1360,10 @@ void modbus::LoadJsonItemConfig() { } while (stream.findUntil(",","]")); configFile.close(); } else { - if (Config->GetDebugLevel() >=1) {dbg.println("failed to load modbusitemconfig.json, load default item config");} + Config->log(1, "failed to load modbusitemconfig.json, load default item config"); } } else { - if (Config->GetDebugLevel() >=3) {dbg.println("modbusitemconfig.json config File not exists, all items are inactive as default");} + Config->log(3, "modbusitemconfig.json config File not exists, all items are inactive as default"); } } @@ -1419,7 +1386,11 @@ void modbus::GetInitData(AsyncResponseStream *response) { json["data"]["EnableRelays_On"] = ((this->enableRelays)?1:0); json["data"]["EnableRelays_Off"] = ((this->enableRelays)?0:1); - json["data"]["enable_openwbtopic"] = ((this->Conf_EnableOpenWBTopic)?1:0); + json["data"]["EnableOpenWb_On"] = ((this->Conf_EnableOpenWB)?1:0); + json["data"]["EnableOpenWb_Off"] = ((this->Conf_EnableOpenWB)?0:1); + json["data"]["openwbmodulid"] = this->Conf_OpenWBModulID; + json["data"]["openwbbatteryid"] = this->Conf_OpenWBBatteryID; + json["data"]["enableCrcCheck"] = ((this->enableCrcCheck)?1:0); json["data"]["enableLengthCheck"] = ((this->enableLengthCheck)?1:0); json["data"]["enable_setters"] = ((this->Conf_EnableSetters)?1:0); @@ -1431,6 +1402,15 @@ void modbus::GetInitData(AsyncResponseStream *response) { json["data"]["inverters"][i]["inverter"]["text"] = AvailableInverters->at(i).name; } + const std::vector *OpenWBVersions = OpenWB->getOpenWbVersions(); + + for (uint8_t i=0; i< OpenWBVersions->size(); i++) { + json["data"]["openwbversions"][i]["openwbversion"].to(); + json["data"]["openwbversions"][i]["openwbversion"]["value"] = OpenWBVersions->at(i); + json["data"]["openwbversions"][i]["openwbversion"]["selected"] = (OpenWBVersions->at(i) == this->Conf_OpenWBVersion?1:0); + json["data"]["openwbversions"][i]["openwbversion"]["text"] = OpenWBVersions->at(i); + } + json["response"].to(); json["response"]["status"] = 1; json["response"]["text"] = "successful"; diff --git a/src/modbus.h b/src/modbus.h index 7819195c..25169cae 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -11,8 +11,9 @@ #include #include #include +#include -//#define DEBUGMODE +#define DEBUGMODE class modbus { @@ -50,6 +51,7 @@ class modbus { void loop(); const String& GetInverterType() const {return InverterType.name;} + const String GetOpenWbVersion() const {return Conf_OpenWBVersion;} void enableMqtt(MQTT* object); void GetInitData(AsyncResponseStream *response); @@ -94,9 +96,9 @@ class modbus { std::vector*AvailableInverters; // available inverters from JSON std::vector* Setters; // available set Options from JSON register - MQTT* mqtt = NULL; - + openwb* OpenWB = NULL; + String PrintHex(byte num); String PrintDataFrame(std::vector* frame); String PrintDataFrame(byte* frame, uint8_t len); @@ -140,7 +142,10 @@ class modbus { //uint8_t Conf_LiveDataFunctionCodePos; //uint8_t Conf_IdDataFunctionCodePos; - bool Conf_EnableOpenWBTopic; + bool Conf_EnableOpenWB; + String Conf_OpenWBVersion; + uint8_t Conf_OpenWBModulID; + uint8_t Conf_OpenWBBatteryID; bool Conf_EnableSetters; byte String2Byte(String s); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 6f84156c..a9c00657 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -47,15 +47,22 @@ MQTT::MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint1 if (Config->GetUseETH()) { #ifdef ESP32 - //ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); eth_shield_t* shield = this->GetEthShield(Config->GetLANBoard()); - ETH.begin(shield->PHY_ADDR, + + ETH.begin(shield->PHY_TYPE, + shield->PHY_ADDR, + shield->PHY_MDC, + shield->PHY_MDIO, + shield->PHY_POWER, + shield->CLK_MODE); + //ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); +/* ETH.begin(shield->PHY_ADDR, shield->PHY_POWER, shield->PHY_MDC, shield->PHY_MDIO, shield->PHY_TYPE, shield->CLK_MODE); - +*/ this->WaitForConnect(); #endif diff --git a/src/mqtt.h b/src/mqtt.h index a028bebc..50e72e26 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -10,7 +10,9 @@ #ifdef ESP8266 //#define SetHostName(x) wifi_station_set_hostname(x); #define ESP_getChipId() ESP.getChipId() -#elif ESP32 +#endif + +#ifdef ESP32 #include //#define SetHostName(x) WiFi.getHostname(x); --> MQTT.cpp TODO #define ESP_getChipId() (uint32_t)ESP.getEfuseMac() // Unterschied zu ESP.getFlashChipId() ??? @@ -27,7 +29,7 @@ eth_phy_type_t PHY_TYPE; eth_clock_mode_t CLK_MODE; } eth_shield_t; -#elif ESP8266 +#elif defined(ESP8266) typedef struct { String name; } eth_shield_t; @@ -38,7 +40,7 @@ class MQTT: PubSubClient { #ifdef ESP32 std::vector lan_shields = {{"WT32-ETH01", 1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN}, {"test", 1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN}}; - #elif ESP8266 + #elif defined(ESP8266) std::vector lan_shields = {{"test1"}, {"test2"}}; #endif diff --git a/src/openwb.cpp b/src/openwb.cpp new file mode 100644 index 00000000..894a54bc --- /dev/null +++ b/src/openwb.cpp @@ -0,0 +1,108 @@ +#include "openwb.h" + +openwb::openwb(): _version("") { + OpenWBTopics = new std::vector(); + OpenWBVersions = new std::vector(); + OpenWBMappings = new std::vector(); +} + +void openwb::begin(String version) { + this->_version = version; + this->LoadAvailableOpenWbVersions(); + this->LoadOpenWBTopicsFromJson(); +} + +void openwb::setVersion(String version) { + this->_version = version; + this->LoadOpenWBTopicsFromJson(); +} + +void openwb::LoadAvailableOpenWbVersions() { + File file = LittleFS.open("/misc/openwb.json", "r"); + if (!file) { + Config->log(1, "Failed to open /misc/openwb.json"); + return; + } + + OpenWBVersions->clear(); + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + if (error) { + Config->log(1, "Failed to parse /misc/openwb.json: %s\n", error.c_str()); + file.close(); + return; + } + + for (JsonObject v : doc.as()) { + OpenWBVersions->push_back(v["version"].as()); + Config->log(3, "OpenWB Version found: %s\n", v["version"].as().c_str()); + } + + file.close(); +} + +void openwb::LoadOpenWBTopicsFromJson() { + File file = LittleFS.open("/misc/openwb.json", "r"); + if (!file) { + Config->log(1, "Failed to open /misc/openwb.json"); + return; + } + + JsonDocument doc; + DeserializationError error = deserializeJson(doc, file); + + if (error) { + Config->log(1, "Failed to parse /misc/openwb.json: %s\n", error.c_str()); + file.close(); + return; + } + + this->OpenWBTopics->clear(); + + for (JsonObject v : doc.as()) { + if (v["version"].as() == this->_version) { + for (JsonObject topic : v["topics"].as()) { + for (JsonPair kv : topic) { + openwb_t t = {}; + t.key = kv.key().c_str(); + t.value = kv.value().as(); + this->OpenWBTopics->push_back(t); + + Config->log(3, "openWB topic loaded: %s\n", kv.value().as().c_str()); + + } + } + break; + } + } + + file.close(); +} + +String openwb::getOpenWbTopic(String& key) { + for (uint8_t i = 0; i < this->OpenWBTopics->size(); i++) { + if (this->OpenWBTopics->at(i).key == key) { + String topic = this->OpenWBTopics->at(i).value; + for (uint8_t j = 0; j < this->OpenWBMappings->size(); j++) { + topic.replace("#" + this->OpenWBMappings->at(j).key + "#", this->OpenWBMappings->at(j).value); + } + return topic; + } + } + return ""; +} + +void openwb::addMapping(String key, String value) { + for (uint8_t i = 0; i < this->OpenWBMappings->size(); i++) { + if (this->OpenWBMappings->at(i).key == key) { + this->OpenWBMappings->at(i).value = value; + return; + } + } + + openwb_t t = {}; + t.key = key; + t.value = value; + this->OpenWBMappings->push_back(t); +} \ No newline at end of file diff --git a/src/openwb.h b/src/openwb.h new file mode 100644 index 00000000..42bcbce4 --- /dev/null +++ b/src/openwb.h @@ -0,0 +1,73 @@ +#ifndef OPENWB_H +#define SOLAXMODBUS_H + +#include "commonlibs.h" +#include "baseconfig.h" +#include "ArduinoJson.h" + +class openwb { + //openwb mqtt topics + typedef struct { + String key; + String value; + } openwb_t; + + public: + openwb(); + + /******************************************************* + * @brief initialize openWB + *******************************************************/ + void begin(String version); + + /******************************************************* + * @brief set the needed OpenWB API Version, used from getOpenWbVersions + *******************************************************/ + void setVersion(String version); + + /******************************************************* + * get openWB topic from key + * + * @param key: key from openWB JSON + * @return string: topic + *******************************************************/ + String getOpenWbTopic(String& key); + + /******************************************************* + * @brief Get all available openWB API Versions + * + * @return std::vector* + ******************************************************/ + const std::vector* getOpenWbVersions() { return OpenWBVersions; } + + /******************************************************* + * @brief add a new Mapping for Topic Keys like #key#, + * if key still exists, it will be replaced + * + * @param key: key from openWB JSON, without delemite # + * @param value: value to replace + * + * example: addMapping("key", "battery1"); + * topic definition: /openWB/#key#/value + * result topic: /openWB/battery1/value + ******************************************************/ + void addMapping(String key, String value); + + /******************************************************* + * @brief clear all mappings + ******************************************************/ + void clearMappings() { OpenWBMappings->clear(); } + + private: + + std::vector* OpenWBTopics; // openWB mqtt topics from JSON + std::vector* OpenWBVersions; // openWB available versions from JSON + std::vector* OpenWBMappings; // openWB mappings from JSON + + String _version; + + void LoadOpenWBTopicsFromJson(); + void LoadAvailableOpenWbVersions(); +}; + +#endif \ No newline at end of file From 03b5a23b51eb8254519f22e6e9740b5f11ec01d7 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 28 Dec 2024 17:28:12 +0100 Subject: [PATCH 011/106] update --- data/misc/openwb.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/misc/openwb.json b/data/misc/openwb.json index 1aafdf56..5f5bd788 100644 --- a/data/misc/openwb.json +++ b/data/misc/openwb.json @@ -19,8 +19,8 @@ "version": "2.0", "topics": [ { "setpvw": "openWB/set/pv/#InverterID#/get/power" }, - { "setpv1w": "openWB/set/pv/1/W"}, - { "setpv2w": "openWB/set/pv/2/W" }, + { "setpv1w": ""}, + { "setpv2w": "" }, { "setbatw": "openWB/set/bat/#BatteryID#/get/power"}, { "setbatsoc": "openWB/set/bat/#BatteryID#/get/soc"}, { "setbatexpwh": "openWB/set/bat/#BatteryID#/get/exported"}, From aa3cac537b7f01c9cdf8b424fd56b205fa15d26f Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 28 Dec 2024 17:57:40 +0100 Subject: [PATCH 012/106] remove unused buffer and improve clarity --- src/mqtt.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index dae1cf88..13db8679 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -49,20 +49,21 @@ MQTT::MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint1 #ifdef ESP32 eth_shield_t* shield = this->GetEthShield(Config->GetLANBoard()); - ETH.begin(shield->PHY_TYPE, +/* ETH.begin(shield->PHY_TYPE, shield->PHY_ADDR, shield->PHY_MDC, shield->PHY_MDIO, shield->PHY_POWER, shield->CLK_MODE); +*/ //ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); -/* ETH.begin(shield->PHY_ADDR, + ETH.begin(shield->PHY_ADDR, shield->PHY_POWER, shield->PHY_MDC, shield->PHY_MDIO, shield->PHY_TYPE, shield->CLK_MODE); -*/ + this->WaitForConnect(); #endif @@ -221,10 +222,8 @@ void MQTT::WaitForConnect() { void MQTT::reconnect() { char topic[50]; char LWT[50]; - char buffer[100]; memset(&LWT[0], 0, sizeof(LWT)); memset(&topic[0], 0, sizeof(topic)); - memset(buffer, 0, sizeof(buffer)); if (Config->UseRandomMQTTClientID()) { snprintf (topic, sizeof(topic), "%s-%s", this->mqtt_root.c_str(), String(random(0xffff)).c_str()); @@ -239,12 +238,10 @@ void MQTT::reconnect() { dbg.println("connected... "); // Once connected, publish basics ... this->Publish_IP(); + this->Publish_String("ssid", WiFi.SSID(), false); this->Publish_String("version", Config->GetReleaseName(), false); this->Publish_String("state", "Online", false); //LWT reset - snprintf(buffer, sizeof(buffer), "%s", WiFi.SSID()); - this->Publish_String("ssid", buffer, false); - // ... and resubscribe if needed for (uint8_t i=0; i< this->subscriptions->size(); i++) { PubSubClient::subscribe(this->subscriptions->at(i).c_str()); @@ -396,6 +393,7 @@ void MQTT::loop() { } if (Config->GetDebugLevel() >=4 && millis() - this->last_keepalive > (30 * 1000)) { + // send messages for debugging every 30 seconds this->last_keepalive = millis(); if (Config->GetDebugLevel() >=4) { From 456d791bf6537d71938ce8ec6721e601906e11aa Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sun, 29 Dec 2024 11:23:00 +0100 Subject: [PATCH 013/106] Update modbus.cpp --- src/modbus.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 2e4cce65..dd2dff7a 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -412,7 +412,6 @@ void modbus::QueryQueueToInverter() { if (rwtype == WRITE) { this->ReceiveSetData(&m); - this->QueryIdData(); } else if (rwtype == READ) { this->ReceiveReadData(); @@ -452,7 +451,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { //ret = true; //} } - + this->QueryIdData(); return ret; } From b5c7808919abc7ba6c8d79af988965acc5dc291a Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 11:27:02 +0100 Subject: [PATCH 014/106] minor fixes --- platformio.ini | 25 ++++++++++++++----------- src/mqtt.cpp | 9 +-------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/platformio.ini b/platformio.ini index 9e69e578..4cd30004 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,8 +11,10 @@ [env] monitor_speed = 115200 upload_speed = 921600 +platform = espressif32 +framework = arduino ; https://registry.platformio.org/tools/espressif/toolchain-riscv32-esp/versions -platform_packages = toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 +;platform_packages = espressif/toolchain-riscv32-esp board_build.partitions = partitions.csv build_flags = -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 @@ -20,6 +22,8 @@ build_flags = !python scripts/build_flags.py git_repo !python scripts/build_flags.py git_owner -D GITHUB_RUN=\"${sysenv.GITHUB_RUN}\" + -D WIFISSID "gast" + -D WIFIPASSWORD "12345678" custom_build_flags_webserial = -D USE_WEBSERIAL=1 -Wall -Wextra @@ -46,9 +50,9 @@ custom_lib_webserial = [env:firmware_ESP32-WebSerial] -platform = espressif32 +;platform = espressif32 board = esp32dev -framework = arduino +;framework = arduino monitor_speed = ${env.monitor_speed} upload_speed = ${env.upload_speed} monitor_filters = esp32_exception_decoder @@ -59,9 +63,8 @@ lib_deps = ${env.lib_deps} ${env.custom_lib_webserial} [env:firmware_ESP32] -platform = espressif32 +;platform = espressif32 board = esp32dev -framework = arduino monitor_speed = ${env.monitor_speed} upload_speed = ${env.upload_speed} monitor_filters = esp32_exception_decoder @@ -71,9 +74,9 @@ lib_deps = ${env.lib_deps} ${env.custom_lib_std} [env:firmware_ESP32-S2] -platform = espressif32 +;platform = espressif32 board = esp32dev -framework = arduino +;framework = arduino board_build.mcu = esp32s2 board_build.f_cpu = 240000000L monitor_speed = ${env.monitor_speed} @@ -85,8 +88,8 @@ lib_deps = ${env.lib_deps} ${env.custom_lib_std} [env:firmware_ESP32-S3] -platform = espressif32 -framework = arduino +;platform = espressif32 +;framework = arduino board = esp32-s3-devkitc-1 board_build.mcu = esp32s3 board_build.f_cpu = 240000000L @@ -99,8 +102,8 @@ lib_deps = ${env.lib_deps} ${env.custom_lib_std} [env:firmware_ESP32-C3] -platform = espressif32 -framework = arduino +;platform = espressif32 +;framework = arduino board = esp32-c3-devkitm-1 board_build.mcu = esp32c3 board_build.f_cpu = 160000000L diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 13db8679..1b8164ac 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -49,14 +49,7 @@ MQTT::MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint1 #ifdef ESP32 eth_shield_t* shield = this->GetEthShield(Config->GetLANBoard()); -/* ETH.begin(shield->PHY_TYPE, - shield->PHY_ADDR, - shield->PHY_MDC, - shield->PHY_MDIO, - shield->PHY_POWER, - shield->CLK_MODE); -*/ - //ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); + //ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); ETH.begin(shield->PHY_ADDR, shield->PHY_POWER, shield->PHY_MDC, From 9eec85d0c18ffa1ea45c903d49525f2df4776053 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 12:02:43 +0100 Subject: [PATCH 015/106] add optional WIFISSID/WIFIPASSWORD --- platformio.ini | 57 +++++--------------------------------------------- 1 file changed, 5 insertions(+), 52 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4cd30004..28dc0a41 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,14 +16,15 @@ framework = arduino ; https://registry.platformio.org/tools/espressif/toolchain-riscv32-esp/versions ;platform_packages = espressif/toolchain-riscv32-esp board_build.partitions = partitions.csv +board_build.filesystem = littlefs +monitor_filters = esp32_exception_decoder build_flags = - -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 !python scripts/build_flags.py git_branch !python scripts/build_flags.py git_repo !python scripts/build_flags.py git_owner -D GITHUB_RUN=\"${sysenv.GITHUB_RUN}\" - -D WIFISSID "gast" - -D WIFIPASSWORD "12345678" +; -D WIFISSID "gast" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board +; -D WIFIPASSWORD "12345678" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board custom_build_flags_webserial = -D USE_WEBSERIAL=1 -Wall -Wextra @@ -43,74 +44,26 @@ lib_deps = https://github.com/tobiasfaust/ElegantOTA.git https://github.com/mathieucarbou/AsyncTCP https://github.com/mathieucarbou/ESPAsyncWebServer -custom_lib_std = custom_lib_webserial = https://github.com/ayushsharma82/WebSerial.git [env:firmware_ESP32-WebSerial] -;platform = espressif32 board = esp32dev -;framework = arduino -monitor_speed = ${env.monitor_speed} -upload_speed = ${env.upload_speed} -monitor_filters = esp32_exception_decoder -board_build.filesystem = littlefs build_flags = ${env.build_flags} ${env.custom_build_flags_webserial} lib_deps = ${env.lib_deps} ${env.custom_lib_webserial} [env:firmware_ESP32] -;platform = espressif32 board = esp32dev -monitor_speed = ${env.monitor_speed} -upload_speed = ${env.upload_speed} -monitor_filters = esp32_exception_decoder -board_build.filesystem = littlefs -build_flags = ${env.build_flags} -lib_deps = ${env.lib_deps} - ${env.custom_lib_std} [env:firmware_ESP32-S2] -;platform = espressif32 -board = esp32dev -;framework = arduino -board_build.mcu = esp32s2 -board_build.f_cpu = 240000000L -monitor_speed = ${env.monitor_speed} -upload_speed = ${env.upload_speed} -monitor_filters = esp32_exception_decoder -board_build.filesystem = littlefs -build_flags = ${env.build_flags} -lib_deps = ${env.lib_deps} - ${env.custom_lib_std} +board = featheresp32-s2 [env:firmware_ESP32-S3] -;platform = espressif32 -;framework = arduino board = esp32-s3-devkitc-1 -board_build.mcu = esp32s3 -board_build.f_cpu = 240000000L -monitor_speed = ${env.monitor_speed} -upload_speed = ${env.upload_speed} -monitor_filters = esp32_exception_decoder -board_build.filesystem = littlefs -build_flags = ${env.build_flags} -lib_deps = ${env.lib_deps} - ${env.custom_lib_std} [env:firmware_ESP32-C3] -;platform = espressif32 -;framework = arduino board = esp32-c3-devkitm-1 -board_build.mcu = esp32c3 -board_build.f_cpu = 160000000L -monitor_speed = ${env.monitor_speed} -upload_speed = ${env.upload_speed} -monitor_filters = esp32_exception_decoder -board_build.filesystem = littlefs -build_flags = ${env.build_flags} -lib_deps = ${env.lib_deps} - ${env.custom_lib_std} \ No newline at end of file From d1a7910ec2aa2db32ec83887867ec63c6fddbc07 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 12:58:14 +0100 Subject: [PATCH 016/106] change ESP32-S2 board --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 28dc0a41..96d5cb2e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -60,7 +60,7 @@ lib_deps = ${env.lib_deps} board = esp32dev [env:firmware_ESP32-S2] -board = featheresp32-s2 +board = esp32-s2-saola-1 [env:firmware_ESP32-S3] board = esp32-s3-devkitc-1 From a33c8cc94f7d76e22ea1301f1c75ed1a7997a316 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 13:16:04 +0100 Subject: [PATCH 017/106] adjust openWB topics --- data/misc/openwb.json | 11 ++--------- data/regs/Deye_SUN_SG04LP3.json | 14 ++++++++++++++ data/regs/Growatt-MOD.json | 19 ++++++++++++++----- data/regs/Growatt-SPH.json | 4 ---- data/regs/QVolt.json | 2 -- data/regs/Sofar.json | 2 -- data/regs/Solax-MIC-Pro.json | 2 -- data/regs/Solax-MIC.json | 2 -- data/regs/Solax-X1.json | 2 -- data/regs/Solax-X3-PRO.json | 1 + data/regs/Solax-X3.json | 2 -- 11 files changed, 31 insertions(+), 30 deletions(-) diff --git a/data/misc/openwb.json b/data/misc/openwb.json index 5f5bd788..a1ed92c7 100644 --- a/data/misc/openwb.json +++ b/data/misc/openwb.json @@ -2,25 +2,18 @@ { "version": "1.9", "topics": [ - { "setpvw": "openWB/set/pv/W" }, - { "setpv1w": "openWB/set/pv/1/W"}, - { "setpv2w": "openWB/set/pv/2/W" }, + { "setpvw": "openWB/set/pv/#InverterID#/W" }, { "setbatw": "openWB/set/houseBattery/W"}, { "setbatsoc": "openWB/set/houseBattery/%Soc"}, { "setbatexpwh": "openWB/set/houseBattery/WhExported"}, { "setbatimpwh": "openWB/set/houseBattery/WhImported"}, - { "setcounterwh": "openWB/set/pv/WhCounter"}, - { "setpv1counterwh": "openWB/set/pv/1/WhCounter"}, - { "setpv2counterwh": "openWB/set/pv/2/WhCounter"} - + { "setcounterwh": "openWB/set/pv/#InverterID#/WhCounter"}, ] }, { "version": "2.0", "topics": [ { "setpvw": "openWB/set/pv/#InverterID#/get/power" }, - { "setpv1w": ""}, - { "setpv2w": "" }, { "setbatw": "openWB/set/bat/#BatteryID#/get/power"}, { "setbatsoc": "openWB/set/bat/#BatteryID#/get/soc"}, { "setbatexpwh": "openWB/set/bat/#BatteryID#/get/exported"}, diff --git a/data/regs/Deye_SUN_SG04LP3.json b/data/regs/Deye_SUN_SG04LP3.json index 079f13be..7bb3cab9 100644 --- a/data/regs/Deye_SUN_SG04LP3.json +++ b/data/regs/Deye_SUN_SG04LP3.json @@ -325,6 +325,20 @@ "factor": 0.1, "unit": "KWh" }, + { + "position": [ + 73, + 74, + 71, + 72 + ], + "name": "TotalPVPowerWh", + "realname": "Total PV Power in Wh", + "datatype": "float", + "factor": 100, + "openwbtopic": "setcounterwh", + "unit": "KWh" + }, { "position": [ 81, diff --git a/data/regs/Growatt-MOD.json b/data/regs/Growatt-MOD.json index 2ea75677..e4e858a9 100644 --- a/data/regs/Growatt-MOD.json +++ b/data/regs/Growatt-MOD.json @@ -94,7 +94,6 @@ ], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "setpv1w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -130,7 +129,6 @@ ], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "setpv2w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -323,6 +321,20 @@ "factor": 0.1, "unit": "KWh" }, + { + "position": [ + 113, + 114, + 115, + 116 + ], + "name": "TotalEnergyGeneratedWh", + "realname": "Erzeugte Energie Gesamt in Wh", + "datatype": "integer", + "openwbtopic": "setcounterwh", + "factor": 100, + "unit": "KWh" + }, { "position": [ 117, @@ -358,7 +370,6 @@ ], "name": "TotalEnergyWhPv1", "realname": "Erzeugte Energie Pv1 in Wh", - "openwbtopic": "setpv1counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -385,7 +396,6 @@ ], "name": "TotalEnergyWhPv2", "realname": "Erzeugte Energie Pv2 in Wh", - "openwbtopic": "setpv2counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -399,7 +409,6 @@ ], "name": "TotalEnergyKwhPv2", "realname": "Erzeugte Energie Pv2 in Kwh", - "openwbtopic": "setpv2counterwh", "datatype": "float", "factor": 0.1, "unit": "KWh" diff --git a/data/regs/Growatt-SPH.json b/data/regs/Growatt-SPH.json index bbef1292..d430622d 100644 --- a/data/regs/Growatt-SPH.json +++ b/data/regs/Growatt-SPH.json @@ -47,7 +47,6 @@ "position": [13, 14, 15, 16], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "setpv1w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -56,7 +55,6 @@ "position": [21, 22, 23, 24], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "setpv2w", "datatype": "float", "factor": 0.1, "unit": "W" @@ -73,7 +71,6 @@ "position": [125, 126, 127, 128], "name": "TotalEnergyWhPv1", "realname": "Erzeugte Energie Pv1 in Wh", - "openwbtopic": "setpv1counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" @@ -90,7 +87,6 @@ "position": [133, 134, 135, 136], "name": "TotalEnergyWhPv2", "realname": "Erzeugte Energie Pv2 in Wh", - "openwbtopic": "setpv2counterwh", "datatype": "integer", "factor": 100, "unit": "Wh" diff --git a/data/regs/QVolt.json b/data/regs/QVolt.json index 74ff20d7..74fe1065 100644 --- a/data/regs/QVolt.json +++ b/data/regs/QVolt.json @@ -169,7 +169,6 @@ "name": "PowerPv1", "realname": "Power PV 1", "datatype": "integer", - "openwbtopic": "setpv1w", "unit": "W" }, { @@ -180,7 +179,6 @@ "name": "PowerPv2", "realname": "Power PV 2", "datatype": "integer", - "openwbtopic": "setpv2w", "unit": "W" }, { diff --git a/data/regs/Sofar.json b/data/regs/Sofar.json index 303d0876..d21684b5 100644 --- a/data/regs/Sofar.json +++ b/data/regs/Sofar.json @@ -69,7 +69,6 @@ "position": [23, 24], "name": "PowerPv1", "realname": "Power PV 1", - "openwbtopic": "setpv1w", "datatype": "integer", "factor": 10, "unit": "W" @@ -78,7 +77,6 @@ "position": [25, 26], "name": "PowerPv2", "realname": "Power PV 2", - "openwbtopic": "setpv2w", "datatype": "integer", "factor": 10, "unit": "W" diff --git a/data/regs/Solax-MIC-Pro.json b/data/regs/Solax-MIC-Pro.json index d7b5f228..e4820ecf 100644 --- a/data/regs/Solax-MIC-Pro.json +++ b/data/regs/Solax-MIC-Pro.json @@ -281,7 +281,6 @@ ], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "setpv1w", "datatype": "float", "unit": "W" }, @@ -292,7 +291,6 @@ ], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "setpv2w", "datatype": "float", "unit": "W" }, diff --git a/data/regs/Solax-MIC.json b/data/regs/Solax-MIC.json index 0e91f294..b7b1db3f 100644 --- a/data/regs/Solax-MIC.json +++ b/data/regs/Solax-MIC.json @@ -183,7 +183,6 @@ "position": [43, 44], "name": "PowerPv1", "realname": "Erzeugungsleistung Pv1", - "openwbtopic": "setpv1w", "datatype": "float", "unit": "W" }, @@ -191,7 +190,6 @@ "position": [45, 46], "name": "PowerPv2", "realname": "Erzeugungsleistung Pv2", - "openwbtopic": "setpv2w", "datatype": "float", "unit": "W" }, diff --git a/data/regs/Solax-X1.json b/data/regs/Solax-X1.json index 07c69796..65a8f3fc 100644 --- a/data/regs/Solax-X1.json +++ b/data/regs/Solax-X1.json @@ -204,7 +204,6 @@ ], "name": "PowerPv1", "realname": "Power PV 1", - "openwbtopic": "setpv1w", "datatype": "integer", "unit": "W" }, @@ -215,7 +214,6 @@ ], "name": "PowerPv2", "realname": "Power PV 2", - "openwbtopic": "setpv2w", "datatype": "integer", "unit": "W" }, diff --git a/data/regs/Solax-X3-PRO.json b/data/regs/Solax-X3-PRO.json index 69df205c..3fc3a297 100644 --- a/data/regs/Solax-X3-PRO.json +++ b/data/regs/Solax-X3-PRO.json @@ -350,6 +350,7 @@ "name": "GridPower", "realname": "GridPower", "datatype": "integer", + "openwbtopic": "setpvw", "unit": "W" }, { diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index d9da86cf..1ff20640 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -290,7 +290,6 @@ "name": "PowerPv1", "realname": "Power PV 1", "datatype": "integer", - "openwbtopic": "setpv1w", "unit": "W" }, { @@ -301,7 +300,6 @@ "name": "PowerPv2", "realname": "Power PV 2", "datatype": "integer", - "openwbtopic": "setpv2w", "unit": "W" }, { From 92e22ef466c4ed4f4b763582d58e51c928286b44 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 13:16:23 +0100 Subject: [PATCH 018/106] fix --- data/misc/openwb.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/misc/openwb.json b/data/misc/openwb.json index a1ed92c7..bece0478 100644 --- a/data/misc/openwb.json +++ b/data/misc/openwb.json @@ -7,7 +7,7 @@ { "setbatsoc": "openWB/set/houseBattery/%Soc"}, { "setbatexpwh": "openWB/set/houseBattery/WhExported"}, { "setbatimpwh": "openWB/set/houseBattery/WhImported"}, - { "setcounterwh": "openWB/set/pv/#InverterID#/WhCounter"}, + { "setcounterwh": "openWB/set/pv/#InverterID#/WhCounter"} ] }, { From 5f1ed4ac05f5395d845f45b90772e4a2725b5622 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 13:24:51 +0100 Subject: [PATCH 019/106] priorize requesting ID Data higher than LiveData (#76) --- src/modbus.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 3189f3ce..71fe14a0 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1095,6 +1095,14 @@ void modbus::SetItemActiveStatus(String item, bool newstate) { * loop function *******************************************************/ void modbus::loop() { + // handle requesting ID Data into queue + if (millis() - this->LastTxIdData > this->TxIntervalIdData * 1000) { + this->LastTxIdData = millis(); + + if (this->InverterType.filename.length() > 1) {this->QueryIdData();} + } + + // handle requesting LiveData into queue if (millis() - this->LastTxLiveData > this->TxIntervalLiveData * 1000) { this->LastTxLiveData = millis(); @@ -1104,13 +1112,7 @@ void modbus::loop() { } } - if (millis() - this->LastTxIdData > this->TxIntervalIdData * 1000) { - this->LastTxIdData = millis(); - - if (this->InverterType.filename.length() > 1) {this->QueryIdData();} - } - - //its allowed to send a new request every 800ms, we use recommend 1000ms + //its allowed to send a new request every 800ms, we use recommended 1000ms if (millis() - this->LastTxInverter > 1000) { this->LastTxInverter = millis(); From 5523dc3c4ca441c896c47139ef357f8b1e6728b7 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 14:03:45 +0100 Subject: [PATCH 020/106] update WiFi credentials in platformio.ini to use fixed values --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 96d5cb2e..8f4f9244 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,8 +23,8 @@ build_flags = !python scripts/build_flags.py git_repo !python scripts/build_flags.py git_owner -D GITHUB_RUN=\"${sysenv.GITHUB_RUN}\" -; -D WIFISSID "gast" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board -; -D WIFIPASSWORD "12345678" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board + -D WIFISSID=\"gast\" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board + -D WIFIPASSWORD=\"12345678\" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board custom_build_flags_webserial = -D USE_WEBSERIAL=1 -Wall -Wextra From 24fd8ba13ce9d7c824b69ee484db19c40e84484a Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 29 Dec 2024 18:17:20 +0100 Subject: [PATCH 021/106] some fixes --- platformio.ini | 8 ++++---- src/baseconfig.cpp | 15 +++++++-------- src/modbus.cpp | 12 ++++++++++-- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/platformio.ini b/platformio.ini index 8f4f9244..6125aa89 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,8 +23,8 @@ build_flags = !python scripts/build_flags.py git_repo !python scripts/build_flags.py git_owner -D GITHUB_RUN=\"${sysenv.GITHUB_RUN}\" - -D WIFISSID=\"gast\" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board - -D WIFIPASSWORD=\"12345678\" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board +; -D WIFISSID=\"gast\" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board +; -D WIFIPASSWORD=\"12345678\" ; use fixed WiFi credentials if Improv-WiFi-Library isn´t supported by your board custom_build_flags_webserial = -D USE_WEBSERIAL=1 -Wall -Wextra @@ -42,8 +42,8 @@ lib_deps = https://github.com/YiannisBourkelis/Uptime-Library.git https://github.com/tobiasfaust/Improv-WiFi-Library.git https://github.com/tobiasfaust/ElegantOTA.git - https://github.com/mathieucarbou/AsyncTCP - https://github.com/mathieucarbou/ESPAsyncWebServer + ;https://github.com/mathieucarbou/AsyncTCP + ;https://github.com/mathieucarbou/ESPAsyncWebServer // installing by ElegantOTA custom_lib_webserial = https://github.com/ayushsharma82/WebSerial.git diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 21503438..a1db7d3a 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -9,7 +9,7 @@ BaseConfig::BaseConfig() : debuglevel(0), serial_rx(3), serial_tx(1), useAuth(fa LittleFS.mkdir("/config"); } } else { - dbg.println("LittleFS Mount Failed"); + this->log(1, "LittleFS Mount Failed"); } #endif @@ -24,10 +24,10 @@ void BaseConfig::LoadJsonConfig() { bool loadDefaultConfig = false; if (LittleFS.exists("/config/baseconfig.json")) { //file exists, reading and loading - dbg.println("reading config file"); + this->log(2, "reading config file"); File configFile = LittleFS.open("/config/baseconfig.json", "r"); if (configFile) { - dbg.println("opened config file"); + this->log(2, "opened config file"); JsonDocument doc; DeserializationError error = deserializeJson(doc, configFile); @@ -45,19 +45,18 @@ void BaseConfig::LoadJsonConfig() { if (doc["data"]["SelectConnectivity"]){if (strcmp(doc["data"]["SelectConnectivity"], "wifi")==0) { this->useETH=false;} else {this->useETH=true;}} else {this->useETH = false;} if (doc["data"]["debuglevel"]) { this->debuglevel = _max((int)(doc["data"]["debuglevel"]), 0);} else {this->debuglevel = 0; } if (doc["data"]["SelectLAN"]) { this->LANBoard = doc["data"]["SelectLAN"].as();} else {this->LANBoard = "";} - if (doc["data"]["serial_rx"]) { this->serial_rx = (doc["serial_rx"].as());} - if (doc["data"]["serial_tx"]) { this->serial_tx = (doc["serial_tx"].as());} + if (doc["data"]["serial_rx"]) { this->serial_rx = (int)(doc["serial_rx"]); } else {this->serial_rx = 3;} + if (doc["data"]["serial_tx"]) { this->serial_tx = (int)(doc["serial_tx"]); } else {this->serial_tx = 1;} if (doc["data"]["sel_auth"]) { if (strcmp(doc["data"]["sel_auth"], "off")==0) { this->useAuth=false;} else {this->useAuth=true;}} else {this->useAuth = false;} if (doc["data"]["auth_user"]) { this->auth_user = doc["data"]["auth_user"].as();} else {this->auth_user = "admin";} if (doc["data"]["auth_pass"]) { this->auth_pass = doc["data"]["auth_pass"].as();} else {this->auth_pass = "password";} - } else { - if (this->GetDebugLevel() >=1) {dbg.println("failed to load json config, load default config");} + this->log(1, "failed to load json config, load default config"); loadDefaultConfig = true; } } } else { - if (this->GetDebugLevel() >=3) {dbg.println("baseconfig.json config File not exists, load default config");} + this->log(3, "baseconfig.json config File not exists, load default config"); loadDefaultConfig = true; } diff --git a/src/modbus.cpp b/src/modbus.cpp index 71fe14a0..f329b411 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -3,7 +3,15 @@ /******************************************************* * Constructor *******************************************************/ -modbus::modbus() : enableRelays(false), Baudrate(19200), enableCrcCheck(true), enableLengthCheck(true), LastTxLiveData(0), LastTxIdData(0), LastTxInverter(0) { +modbus::modbus(): enableRelays(false), + Baudrate(19200), + enableCrcCheck(true), + enableLengthCheck(true), + LastTxLiveData(0), + LastTxIdData(0), + LastTxInverter(0), + Conf_OpenWBModulID(1), + Conf_OpenWBBatteryID(2) { DataFrame = new std::vector{}; SaveIdDataframe = new std::vector{}; SaveLiveDataframe = new std::vector{}; @@ -950,7 +958,7 @@ void modbus::GetLiveDataAsJson(AsyncResponseStream *response, String subaction) doc["active"]["name"] = this->InverterLiveData->at(i).Name.c_str(); doc["mqtttopic"] = std::move(this->mqtt->getTopic(this->InverterLiveData->at(i).Name, false)); - if (this->InverterLiveData->at(i).openwb.length() > 0) { + if (this->Conf_EnableOpenWB && this->InverterLiveData->at(i).openwb.length() > 0) { JsonArray wb = doc["openwb"].to(); wb[0]["openwbtopic"] = std::move(OpenWB->getOpenWbTopic(this->InverterLiveData->at(i).openwb)); } From dc6eca6ccf760f66017751249e1adefcbe7ebcda Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 30 Dec 2024 09:05:37 +0100 Subject: [PATCH 022/106] Update modbus.cpp --- src/modbus.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modbus.cpp b/src/modbus.cpp index 7cb0d931..029348ff 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -420,6 +420,7 @@ void modbus::QueryQueueToInverter() { if (rwtype == WRITE) { this->ReceiveSetData(&m); + this->LastTxIdData = millis() - this->TxIntervalIdData * 1000; //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen (Zeitnah die rückmeldung ob der Set Befehl ausgefürt wurde) } else if (rwtype == READ) { this->ReceiveReadData(); From 3c5359ed67540ceb2f7b9a1bc46e1ec2cdf1bca8 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 30 Dec 2024 09:08:04 +0100 Subject: [PATCH 023/106] Update modbus.cpp --- src/modbus.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 029348ff..6bc8e213 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -460,7 +460,6 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { //ret = true; //} } - this->QueryIdData(); return ret; } From 365c67399a7db174fd2f58dc4e5df1ac0e2115f3 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 30 Dec 2024 10:36:34 +0100 Subject: [PATCH 024/106] delete unnecessary newlines, fix serial-pins --- src/baseconfig.cpp | 14 ++++---- src/modbus.cpp | 88 ++++++++++++++++++++-------------------------- src/openwb.cpp | 8 ++--- 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index a1db7d3a..85c25594 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -1,6 +1,6 @@ #include "baseconfig.h" -BaseConfig::BaseConfig() : debuglevel(0), serial_rx(3), serial_tx(1), useAuth(false) { +BaseConfig::BaseConfig() : debuglevel(2), serial_rx(3), serial_tx(1), useAuth(false) { #ifdef ESP8266 LittleFS.begin(); #elif defined(ESP32) @@ -37,16 +37,16 @@ void BaseConfig::LoadJsonConfig() { if (doc["data"]["mqttroot"]) { this->mqtt_root = doc["data"]["mqttroot"].as();} else {this->mqtt_root = "solax";} if (doc["data"]["mqttserver"]) { this->mqtt_server = doc["data"]["mqttserver"].as();} else {this->mqtt_server = "test.mosquitto.org";} - if (doc["data"]["mqttport"]) { this->mqtt_port = (int)(doc["data"]["mqttport"]);} else {this->mqtt_port = 1883;} + if (doc["data"]["mqttport"]) { this->mqtt_port = doc["data"]["mqttport"].as();} else {this->mqtt_port = 1883;} if (doc["data"]["mqttuser"]) { this->mqtt_username = doc["data"]["mqttuser"].as();} else {this->mqtt_username = "";} if (doc["data"]["mqttpass"]) { this->mqtt_password = doc["data"]["mqttpass"].as();} else {this->mqtt_password = "";} if (doc["data"]["mqttbasepath"]) { this->mqtt_basepath = doc["data"]["mqttbasepath"].as();} else {this->mqtt_basepath = "home/";} if (doc["data"]["UseRandomClientID"]){ if (strcmp(doc["data"]["UseRandomClientID"], "none")==0) { this->mqtt_UseRandomClientID=false;} else {this->mqtt_UseRandomClientID=true;}} else {this->mqtt_UseRandomClientID = true;} if (doc["data"]["SelectConnectivity"]){if (strcmp(doc["data"]["SelectConnectivity"], "wifi")==0) { this->useETH=false;} else {this->useETH=true;}} else {this->useETH = false;} - if (doc["data"]["debuglevel"]) { this->debuglevel = _max((int)(doc["data"]["debuglevel"]), 0);} else {this->debuglevel = 0; } + if (doc["data"]["debuglevel"]) { this->debuglevel = _max(doc["data"]["debuglevel"].as(), 0);} else {this->debuglevel = 0; } if (doc["data"]["SelectLAN"]) { this->LANBoard = doc["data"]["SelectLAN"].as();} else {this->LANBoard = "";} - if (doc["data"]["serial_rx"]) { this->serial_rx = (int)(doc["serial_rx"]); } else {this->serial_rx = 3;} - if (doc["data"]["serial_tx"]) { this->serial_tx = (int)(doc["serial_tx"]); } else {this->serial_tx = 1;} + if (doc["data"]["serial_rx"]) { this->serial_rx = doc["data"]["serial_rx"].as(); } else {this->serial_rx = 3;} + if (doc["data"]["serial_tx"]) { this->serial_tx = doc["data"]["serial_tx"].as(); } else {this->serial_tx = 1;} if (doc["data"]["sel_auth"]) { if (strcmp(doc["data"]["sel_auth"], "off")==0) { this->useAuth=false;} else {this->useAuth=true;}} else {this->useAuth = false;} if (doc["data"]["auth_user"]) { this->auth_user = doc["data"]["auth_user"].as();} else {this->auth_user = "admin";} if (doc["data"]["auth_pass"]) { this->auth_pass = doc["data"]["auth_pass"].as();} else {this->auth_pass = "password";} @@ -69,7 +69,7 @@ void BaseConfig::LoadJsonConfig() { this->mqtt_basepath = "home/"; this->mqtt_UseRandomClientID = true; this->useETH = false; - this->debuglevel = 0; + this->debuglevel = 2; this->LANBoard = ""; loadDefaultConfig = false; //set back @@ -89,7 +89,6 @@ const String BaseConfig::GetReleaseName() { void BaseConfig::GetInitData(AsyncResponseStream *response) { String ret; JsonDocument json; - json["data"].to(); json["data"]["mqttroot"] = this->mqtt_root; json["data"]["mqttserver"] = this->mqtt_server; json["data"]["mqttport"] = this->mqtt_port; @@ -115,7 +114,6 @@ void BaseConfig::GetInitData(AsyncResponseStream *response) { json["data"]["GpioPin_serial_tx"] = this->serial_tx; #endif - json["response"].to(); json["response"]["status"] = 1; json["response"]["text"] = "successful"; serializeJson(json, ret); diff --git a/src/modbus.cpp b/src/modbus.cpp index f329b411..bf2c4a89 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -54,8 +54,8 @@ modbus::modbus(): enableRelays(false), * initialize transmission *******************************************************/ void modbus::init(bool firstrun) { - Config->log(3, "Start Hardwareserial 1 on RX (%d), TX(%d), RTS(%d)\n", this->pin_RX, this->pin_TX, this->pin_RTS); - Config->log(3, "Init Modbus to Client 0x%02X with %d Baud\n", this->ClientID, this->Baudrate); + Config->log(3, "Start Hardwareserial 1 on RX (%d), TX(%d), RTS(%d)", this->pin_RX, this->pin_TX, this->pin_RTS); + Config->log(3, "Init Modbus to Client 0x%02X with %d Baud", this->ClientID, this->Baudrate); // Configure Direction Control pin pinMode(this->pin_RTS, OUTPUT); @@ -136,7 +136,7 @@ void modbus::GenerateMqttSubscriptions() { s.request = t; this->mqtt->Subscribe(this->GetMqttSetTopic(s.command)); - Config->log(4, "Set command successfully parsed from JSON: %s with %s\n", s.command.c_str(), (this->PrintDataFrame(&(s.request))).c_str()); + Config->log(4, "Set command successfully parsed from JSON: %s with %s", s.command.c_str(), (this->PrintDataFrame(&(s.request))).c_str()); this->Setters->push_back(s); } else { @@ -144,7 +144,7 @@ void modbus::GenerateMqttSubscriptions() { } } else { - Config->log(1, "Failed to parse JSON Register Data: %s\n", error.c_str()); + Config->log(1, "Failed to parse JSON Register Data: %s", error.c_str()); } } while (regfile.findUntil(",","]")); @@ -157,7 +157,7 @@ void modbus::GenerateMqttSubscriptions() { *******************************************************/ void modbus::ReceiveMQTT(String topic, int msg) { if (!this->Conf_EnableSetters) { - Config->log(2, "Set command <%s> received, but setters over mqtt are currently disabled\n", topic.c_str()); + Config->log(2, "Set command <%s> received, but setters over mqtt are currently disabled", topic.c_str()); return; } @@ -175,8 +175,8 @@ void modbus::ReceiveMQTT(String topic, int msg) { request.push_back(bytes[2]); request.push_back(bytes[3]); - Config->log(3, "MQTT Setter found: %s\n" ,this->Setters->at(i).command.c_str()); - Config->log(3, "Initiate Set Request to queue: %s\n" ,(this->PrintDataFrame(&request)).c_str()); + Config->log(3, "MQTT Setter found: %s" ,this->Setters->at(i).command.c_str()); + Config->log(3, "Initiate Set Request to queue: %s" ,(this->PrintDataFrame(&request)).c_str()); this->SetQueue->enqueue(request); } @@ -196,14 +196,14 @@ void modbus::LoadInvertersFromJson() { File root = LittleFS.open("/regs/"); File file = root.openNextFile(); while(file){ - Config->log(3, "open register file from Filesystem: %s\n", file.name()); + Config->log(3, "open register file from Filesystem: %s", file.name()); DeserializationError error = deserializeJson(regjson, file, DeserializationOption::Filter(filter)); if (!error && regjson.size() > 0) { // https://arduinojson.org/v6/api/jsonobject/begin_end/ JsonObject root = regjson.as(); for (JsonPair kv : root) { - Config->log(3, "Inverter found: %s\n", kv.key().c_str()); + Config->log(3, "Inverter found: %s", kv.key().c_str()); regfiles_t wr = {}; wr.filename = file.name(); @@ -211,7 +211,7 @@ void modbus::LoadInvertersFromJson() { AvailableInverters->push_back(wr); } } else{ - Config->log(1, "Error: unable to load inverters from File %s: %s\n", file.name(), error.c_str()); + Config->log(1, "Error: unable to load inverters from File %s: %s", file.name(), error.c_str()); } file.close(); file = root.openNextFile(); @@ -233,18 +233,18 @@ void modbus::LoadInverterConfigFromJson() { File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file\n", this->InverterType.filename.c_str()); + Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); } filter[this->InverterType.name]["config"] = true; DeserializationError error = deserializeJson(doc, regfile, DeserializationOption::Filter(filter)); - if (error && Config->GetDebugLevel() >=1) { - dbg.printf("Error: unable to read configdata for inverter %s: %s\n", this->InverterType.name.c_str(), error.c_str()); + if (error) { + Config->log(1, "Error: unable to read configdata for inverter %s: %s", this->InverterType.name.c_str(), error.c_str()); } else { if (Config->GetDebugLevel() >=4) { - dbg.printf("Read config data for inverter %s\n", this->InverterType.name.c_str()); + Config->log(4, "Read config data for inverter %s", this->InverterType.name.c_str()); serializeJsonPretty(doc, dbg); dbg.println(); } @@ -495,8 +495,8 @@ void modbus::ReceiveReadData() { //CRC Check uint16_t crc = this->Calc_CRC(this->DataFrame, dataFrameStartPos, this->DataFrame->size()-2); - Config->log(4, "Received CRC: 0x%02X 0x%02X\n", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1)); - Config->log(4, "Calculated CRC: 0x%02X 0x%02X\n", lowByte(crc), highByte(crc)); + Config->log(4, "Received CRC: 0x%02X 0x%02X", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1)); + Config->log(4, "Calculated CRC: 0x%02X 0x%02X", lowByte(crc), highByte(crc)); if (this->DataFrame->at(this->DataFrame->size()-2) != lowByte(crc) || this->DataFrame->at(this->DataFrame->size()-1) != highByte(crc)) { @@ -507,18 +507,18 @@ void modbus::ReceiveReadData() { if (this->enableLengthCheck) { // Check datalength - Config->log(4, "Dataframe length should be: %d, is: %d bytes\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); + Config->log(4, "Dataframe length should be: %d, is: %d bytes", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); if (this->DataFrame->at(dataFrameStartPos+2) != this->DataFrame->size()-dataFrameStartPos-5) { valid = false; - Config->log(2, "data length check failed, should be %d but is %d bytes\n", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); + Config->log(2, "data length check failed, should be %d but is %d bytes", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); } } } else { valid = false; } if (valid) { // Dataframe valid - Config->log(3, "Dataframe valid, Dateframe size: %d bytes\n", this->DataFrame->size()); + Config->log(3, "Dataframe valid, Dateframe size: %d bytes", this->DataFrame->size()); } else { Config->log(2, "Dataframe invalid"); @@ -623,7 +623,7 @@ void modbus::ParseData() { for (uint16_t i = 0; iDataFrame->push_back(ReadBuffer[i]); } - Config->log(4, "%s\n", this->PrintDataFrame(this->DataFrame).c_str()); + Config->log(4, "%s", this->PrintDataFrame(this->DataFrame).c_str()); #endif // *********************************************** } @@ -640,12 +640,12 @@ void modbus::ParseData() { RequestType = "livedata"; } - Config->log(3, "parse %d bytes of data\n", this->DataFrame->size()); - Config->log(4, "identified datatype: %s\n", RequestType.c_str()); + Config->log(3, "parse %d bytes of data", this->DataFrame->size()); + Config->log(4, "identified datatype: %s", RequestType.c_str()); File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file\n", this->InverterType.filename.c_str()); + Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); } String streamString = ""; streamString = "\""+ this->InverterType.name +"\": {"; @@ -771,19 +771,19 @@ void modbus::ParseData() { } else { //****************** sonst, leer *******************// d.value = ""; - Config->log(2, "Error: for Name '%s\n' no valid datatype found", d.Name.c_str()); + Config->log(2, "Error: for Name '%s' no valid datatype found", d.Name.c_str()); } // map values if a mapping is specified if(!elem["mapping"].isNull() && elem["mapping"].is() && d.value != "") { - Config->log(4, "Map values for item %s\n", d.Name.c_str()); + Config->log(4, "Map values for item %s", d.Name.c_str()); JsonArray map = elem["mapping"].as(); d.value = this->MapItem(map, d.value); } - Config->log(4, "Data: %s -> %s %s\n", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); + Config->log(4, "Data: %s -> %s %s", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); if (this->mqtt && IsActiveItem && d.Name != "") { this->mqtt->Publish_String(d.Name.c_str(), d.value, false); @@ -800,7 +800,7 @@ void modbus::ParseData() { } else if(RequestType == "id") { this->ChangeRegItem(this->InverterIdData, d); - Config->log(3, "Inverter ID Data found -> %s: %s \n", d.Name.c_str(), d.value.c_str()); + Config->log(3, "Inverter ID Data found -> %s: %s ", d.Name.c_str(), d.value.c_str()); } @@ -834,7 +834,7 @@ String modbus::MapItem(JsonArray map, String value) { String v2 = mapItem[1].as(); - Config->log(5, "Check Map value: %s -> %s\n", v1.c_str(), v2.c_str()); + Config->log(5, "Check Map value: %s -> %s", v1.c_str(), v2.c_str()); if (value == v1) { ret = v2; @@ -1005,7 +1005,7 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - dbg.printf("failed to open %s file\n", this->InverterType.filename.c_str()); + Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); return; } String streamString = ""; @@ -1081,18 +1081,14 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { void modbus::SetItemActiveStatus(String item, bool newstate) { for (uint16_t j=0; j < this->InverterLiveData->size(); j++) { if (this->InverterLiveData->at(j).Name == item) { - if (Config->GetDebugLevel() >=3) { - dbg.printf("Set Item <%s> ActiveState to %s\n", item.c_str(), (newstate?"true":"false")); - } + Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); this->InverterLiveData->at(j).active = newstate; } } //Lazgar for (uint16_t j=0; j < this->InverterIdData->size(); j++) { if (this->InverterIdData->at(j).Name == item) { - if (Config->GetDebugLevel() >=3) { - dbg.printf("Set Item <%s> ActiveState to %s\n", item.c_str(), (newstate?"true":"false")); - } + Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); this->InverterIdData->at(j).active = newstate; } } @@ -1134,13 +1130,11 @@ void modbus::loop() { void modbus::LoadRegItems(std::vector* vector, String type) { vector->clear(); - if (Config->GetDebugLevel() >=4) { - dbg.printf("Load RegItems for Inverter %s and type <%s>\n", this->InverterType.name.c_str(), type.c_str()); - } + Config->log(4, "Load RegItems for Inverter %s and type <%s>", this->InverterType.name.c_str(), type.c_str()); File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - dbg.printf("failed to open %s file\n", this->InverterType.filename.c_str()); + Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); return; } @@ -1159,9 +1153,7 @@ void modbus::LoadRegItems(std::vector* vector, String type) { if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} } else { - if (Config->GetDebugLevel() >=1) { - dbg.printf("(Function LoadRegItems) Failed to parse JSON Register Data for Inverter <%s> and type <%s>: %s\n", this->InverterType.name.c_str(), type.c_str(), error.c_str()); - } + Config->log(1, "(Function LoadRegItems) Failed to parse JSON Register Data for Inverter <%s> and type <%s>: %s", this->InverterType.name.c_str(), type.c_str(), error.c_str()); } reg_t d = {}; @@ -1188,9 +1180,7 @@ void modbus::LoadRegItems(std::vector* vector, String type) { d.active = false; // set initial vector->push_back(d); - if (Config->GetDebugLevel() >=4) { - dbg.printf("processed RegItem: %s\n", d.Name.c_str()); - } + Config->log(4, "processed RegItem: %s", d.Name.c_str()); } while (regfile.findUntil(",","]")); @@ -1250,7 +1240,7 @@ void modbus::LoadJsonConfig(bool firstrun) { for (uint8_t i=0; iAvailableInverters->size(); i++) { if (this->AvailableInverters->at(i).name == (doc["data"]["invertertype"]).as()) { this->InverterType = this->AvailableInverters->at(i); - Config->log(3, "Invertertyp '%s' was found in register file '%s', set it as selected active Inverter\n", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); + Config->log(3, "Invertertyp '%s' was found in register file '%s', set it as selected active Inverter", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); found = true; } } @@ -1258,7 +1248,7 @@ void modbus::LoadJsonConfig(bool firstrun) { if (this->AvailableInverters->size()>0) { this->InverterType = this->AvailableInverters->at(0); } - Config->log(3, "Invertertyp '%s' was not found, use default '%s' instead\n", (doc["data"]["invertertype"]).as().c_str(), this->InverterType.name.c_str()); + Config->log(3, "Invertertyp '%s' was not found, use default '%s' instead", (doc["data"]["invertertype"]).as().c_str(), this->InverterType.name.c_str()); } } @@ -1350,7 +1340,7 @@ void modbus::LoadJsonItemConfig() { if (this->InverterLiveData->at(i).Name == ItemName ) { this->InverterLiveData->at(i).active = kv.value().as(); - Config->log(3, "item %s -> %s\n", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); + Config->log(3, "item %s -> %s", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); break; } @@ -1360,7 +1350,7 @@ void modbus::LoadJsonItemConfig() { if (this->InverterIdData->at(i).Name == ItemName ) { this->InverterIdData->at(i).active = kv.value().as(); - Config->log(3, "item %s -> %s\n", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); + Config->log(3, "item %s -> %s", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); break; } } diff --git a/src/openwb.cpp b/src/openwb.cpp index 894a54bc..21afd554 100644 --- a/src/openwb.cpp +++ b/src/openwb.cpp @@ -29,14 +29,14 @@ void openwb::LoadAvailableOpenWbVersions() { JsonDocument doc; DeserializationError error = deserializeJson(doc, file); if (error) { - Config->log(1, "Failed to parse /misc/openwb.json: %s\n", error.c_str()); + Config->log(1, "Failed to parse /misc/openwb.json: %s", error.c_str()); file.close(); return; } for (JsonObject v : doc.as()) { OpenWBVersions->push_back(v["version"].as()); - Config->log(3, "OpenWB Version found: %s\n", v["version"].as().c_str()); + Config->log(3, "OpenWB Version found: %s", v["version"].as().c_str()); } file.close(); @@ -53,7 +53,7 @@ void openwb::LoadOpenWBTopicsFromJson() { DeserializationError error = deserializeJson(doc, file); if (error) { - Config->log(1, "Failed to parse /misc/openwb.json: %s\n", error.c_str()); + Config->log(1, "Failed to parse /misc/openwb.json: %s", error.c_str()); file.close(); return; } @@ -69,7 +69,7 @@ void openwb::LoadOpenWBTopicsFromJson() { t.value = kv.value().as(); this->OpenWBTopics->push_back(t); - Config->log(3, "openWB topic loaded: %s\n", kv.value().as().c_str()); + Config->log(3, "openWB topic loaded: %s", kv.value().as().c_str()); } } From 982085dad01e28981aceff412da8a1d2c4ed68c6 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:08:00 +0100 Subject: [PATCH 025/106] Update modbus.cpp --- src/modbus.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 6bc8e213..3d2dff1e 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -337,6 +337,7 @@ void modbus::QueryIdData() { Config->log(4, this->PrintDataFrame(&this->Conf_RequestIdData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestIdData->at(i)); } + this->LastTxIdData = millis(); } } @@ -363,6 +364,7 @@ void modbus::QueryLiveData() { Config->log(4, this->PrintDataFrame(&this->Conf_RequestLiveData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestLiveData->at(i)); } + this->LastTxLiveData = millis(); } } @@ -1105,14 +1107,14 @@ void modbus::SetItemActiveStatus(String item, bool newstate) { void modbus::loop() { // handle requesting ID Data into queue if (millis() - this->LastTxIdData > this->TxIntervalIdData * 1000) { - this->LastTxIdData = millis(); + if (this->InverterType.filename.length() > 1) {this->QueryIdData();} } // handle requesting LiveData into queue if (millis() - this->LastTxLiveData > this->TxIntervalLiveData * 1000) { - this->LastTxLiveData = millis(); + if (this->InverterType.filename.length() > 1) { this->QueryLiveData(); From a41e6e77dd9750a273dc0162f8238da9b439131a Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:25:20 +0100 Subject: [PATCH 026/106] Update modbus.cpp --- src/modbus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 3d2dff1e..95271f23 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -422,7 +422,7 @@ void modbus::QueryQueueToInverter() { if (rwtype == WRITE) { this->ReceiveSetData(&m); - this->LastTxIdData = millis() - this->TxIntervalIdData * 1000; //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen (Zeitnah die rückmeldung ob der Set Befehl ausgefürt wurde) + this->LastTxIdData = millis() - (this->TxIntervalIdData * 1001); //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen Zeitnah die rückmeldung ob der Set Befehl ausgefürt wurde } else if (rwtype == READ) { this->ReceiveReadData(); From 35c2c2a2a77532ccf09d1e1ebbc396d1a6b141dc Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:46:28 +0100 Subject: [PATCH 027/106] Update modbus.cpp --- src/modbus.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 95271f23..bb312835 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -337,7 +337,7 @@ void modbus::QueryIdData() { Config->log(4, this->PrintDataFrame(&this->Conf_RequestIdData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestIdData->at(i)); } - this->LastTxIdData = millis(); + this->LastTxIdData = millis(); //erst setzen wenn erfolgreich in die Queue geschickt wurde } } @@ -364,7 +364,7 @@ void modbus::QueryLiveData() { Config->log(4, this->PrintDataFrame(&this->Conf_RequestLiveData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestLiveData->at(i)); } - this->LastTxLiveData = millis(); + this->LastTxLiveData = millis(); //erst setzen wenn erfolgreich in die Queue geschickt wurde } } @@ -422,7 +422,7 @@ void modbus::QueryQueueToInverter() { if (rwtype == WRITE) { this->ReceiveSetData(&m); - this->LastTxIdData = millis() - (this->TxIntervalIdData * 1001); //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen Zeitnah die rückmeldung ob der Set Befehl ausgefürt wurde + this->LastTxIdData = millis() - (this->TxIntervalIdData * 1001); //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen zwecks Rückmeldung ob der Set Befehl ausgeführt wurde } else if (rwtype == READ) { this->ReceiveReadData(); From 04f39f92bd0ab2a8ae3d87baf60029f954209507 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 30 Dec 2024 12:53:14 +0100 Subject: [PATCH 028/106] use checkbox for selection instead of radiobuttons --- ChangeLog.md | 4 +++- data/web/Javascript.js | 20 ++++++++++++++++++++ data/web/baseconfig.html | 7 ++++++- data/web/modbusconfig.html | 38 ++++++++++++++++++-------------------- src/MyWebServer.cpp | 4 ++-- src/MyWebServer.h | 5 ----- src/baseconfig.cpp | 8 +++++++- src/baseconfig.h | 2 ++ src/modbus.cpp | 12 +++++------- 9 files changed, 63 insertions(+), 37 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 66f65740..af2a9e32 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,8 @@ Release 3.3.1: - - BugFix: fix null-terminationof string handling (#96) + - BugFix: fix null-termination of string handling (#96) - new feature: support for OpenWB 2.0 Api (#100) + - new Advanced feature: setting up Browser cache-time of WebUI + - new feature: ability to setup fix WiFi credentials if web-installer doesn´t support the ESP-Board Release 3.3.0: - new feature: WebSerial as remote serial output (#74) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index 93bdc22a..d1f30878 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -71,6 +71,13 @@ function handleRadioSelections() { for( var i=0; i< objects.length; i++) { objects[i].click(); } + + var checkboxes = document.querySelectorAll('input[type=checkbox][onclick]'); + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].onclick) { + checkboxes[i].dispatchEvent(new Event('click', { bubbles: true, cancelable: true })); + } + } } /*############################################################ @@ -385,3 +392,16 @@ function radioselection(show, hide) { } } +/******************************* + * + * @param {*} checkbox object of the checkbox + * @param {*} show Array of shown IDs if checkbox is checked + * @param {*} hide Array of hidden IDs if checkbox is checked + */ +function onCheckboxSelection(checkbox, show, hide) { + if (checkbox.checked) { + radioselection(show, hide); + } else { + radioselection(hide, show); + } +} \ No newline at end of file diff --git a/data/web/baseconfig.html b/data/web/baseconfig.html index 4acacec9..f0f9026f 100644 --- a/data/web/baseconfig.html +++ b/data/web/baseconfig.html @@ -120,7 +120,12 @@ - LogLevel (0 [off] ... 5 [max] + Browser cache time in sec (default: 3600) + + + + + LogLevel (0 [off] ... 5 [max]) diff --git a/data/web/modbusconfig.html b/data/web/modbusconfig.html index d3ba4a5a..a7d61c0e 100644 --- a/data/web/modbusconfig.html +++ b/data/web/modbusconfig.html @@ -66,16 +66,15 @@ - -
- - -
- -
- - -
+ Enable reading inverter relay + +
+ + +
@@ -90,16 +89,15 @@ - -
- - -
- -
- - -
+ Enable OpenWB support + +
+ + +
diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index cfa6a746..c6030287 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -28,11 +28,11 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): DoReboot(false //ElegantOTA.onEnd(std::bind(&MyWebServer::onOTAEnd, this, std::placeholders::_1)); if (Config->GetUseAuth()) { - server->serveStatic("/", LittleFS, "/", "max-age=3600") + server->serveStatic("/", LittleFS, "/", String("max-age="+Config->GetCacheTime()).c_str()) .setDefaultFile("/web/index.html") .setAuthentication(Config->GetAuthUser().c_str(), Config->GetAuthPass().c_str()); } else { - server->serveStatic("/", LittleFS, "/", "max-age=3600") + server->serveStatic("/", LittleFS, "/", String("max-age="+Config->GetCacheTime()).c_str()) .setDefaultFile("/web/index.html"); } diff --git a/src/MyWebServer.h b/src/MyWebServer.h index b297a18b..141582bc 100644 --- a/src/MyWebServer.h +++ b/src/MyWebServer.h @@ -25,8 +25,6 @@ class MyWebServer { - //enum page_t {ROOT, BASECONFIG, MODBUSCONFIG, MODBUSITEMCONFIG, MODBUSRAWDATA, FSFILES}; - public: MyWebServer(AsyncWebServer *server, DNSServer* dns); @@ -42,9 +40,6 @@ class MyWebServer { handleFiles* fsfiles; -// void handle_update_page(AsyncWebServerRequest *request); -// void handle_update_progress(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); -// void handle_update_response(AsyncWebServerRequest *request); void handleNotFound(AsyncWebServerRequest *request); void handleReboot(AsyncWebServerRequest *request); void handleReset(AsyncWebServerRequest *request); diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 85c25594..1c28cf23 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -1,6 +1,10 @@ #include "baseconfig.h" -BaseConfig::BaseConfig() : debuglevel(2), serial_rx(3), serial_tx(1), useAuth(false) { +BaseConfig::BaseConfig(): debuglevel(2), + serial_rx(3), + serial_tx(1), + cachetime(3600), + useAuth(false) { #ifdef ESP8266 LittleFS.begin(); #elif defined(ESP32) @@ -50,6 +54,7 @@ void BaseConfig::LoadJsonConfig() { if (doc["data"]["sel_auth"]) { if (strcmp(doc["data"]["sel_auth"], "off")==0) { this->useAuth=false;} else {this->useAuth=true;}} else {this->useAuth = false;} if (doc["data"]["auth_user"]) { this->auth_user = doc["data"]["auth_user"].as();} else {this->auth_user = "admin";} if (doc["data"]["auth_pass"]) { this->auth_pass = doc["data"]["auth_pass"].as();} else {this->auth_pass = "password";} + if (doc["data"]["cachetime"]) { this->cachetime = doc["data"]["cachetime"].as();} else {this->cachetime = 3600;} } else { this->log(1, "failed to load json config, load default config"); loadDefaultConfig = true; @@ -104,6 +109,7 @@ void BaseConfig::GetInitData(AsyncResponseStream *response) { json["data"]["sel_auth_on"] = ((this->useAuth)?1:0); json["data"]["auth_user"] = this->auth_user; json["data"]["auth_pass"] = this->auth_pass; + json["data"]["cachetime"] = this->cachetime; #ifdef USE_WEBSERIAL diff --git a/src/baseconfig.h b/src/baseconfig.h index a4d07043..943f12e1 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -35,6 +35,7 @@ class BaseConfig { const bool& GetUseAuth() const { return useAuth; } const String& GetAuthUser() const {return auth_user;} const String& GetAuthPass() const {return auth_pass;} + const uint16_t& GetCacheTime() const {return cachetime;} const String GetReleaseName(); private: @@ -50,6 +51,7 @@ class BaseConfig { uint8_t debuglevel; uint8_t serial_rx; uint8_t serial_tx; + uint16_t cachetime; bool useAuth; String auth_user; String auth_pass; diff --git a/src/modbus.cpp b/src/modbus.cpp index bf2c4a89..dd599dab 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1229,11 +1229,11 @@ void modbus::LoadJsonConfig(bool firstrun) { if (doc["data"]["openwbmodulid"]) { this->Conf_OpenWBModulID = doc["data"]["openwbmodulid"].as(); this->OpenWB->addMapping("InverterID", String(this->Conf_OpenWBModulID)); } if (doc["data"]["openwbbatteryid"]) { this->Conf_OpenWBBatteryID = doc["data"]["openwbbatteryid"].as(); this->OpenWB->addMapping("BatteryID", String(this->Conf_OpenWBBatteryID)); } - this->Conf_EnableOpenWB = (bool)doc["data"]["EnableOpenWb"].as(); + this->Conf_EnableOpenWB = doc["data"]["enableOpenWb"].as(); this->Conf_EnableSetters = doc["data"]["enable_setters"].as(); this->enableCrcCheck = doc["data"]["enableCrcCheck"].as(); this->enableLengthCheck = doc["data"]["enableLengthCheck"].as(); - this->enableRelays = (bool)(doc["data"]["EnableRelays"]).as(); + this->enableRelays = doc["data"]["enableRelays"].as(); if (doc["data"]["invertertype"]) { bool found = false; @@ -1383,11 +1383,9 @@ void modbus::GetInitData(AsyncResponseStream *response) { json["data"]["txintervalid"] = this->TxIntervalIdData; json["data"]["GpioPin_Relay1"] = this->pin_Relay1; json["data"]["GpioPin_Relay2"] = this->pin_Relay2; - json["data"]["EnableRelays_On"] = ((this->enableRelays)?1:0); - json["data"]["EnableRelays_Off"] = ((this->enableRelays)?0:1); - - json["data"]["EnableOpenWb_On"] = ((this->Conf_EnableOpenWB)?1:0); - json["data"]["EnableOpenWb_Off"] = ((this->Conf_EnableOpenWB)?0:1); + + json["data"]["enableRelays"] = ((this->enableRelays)?1:0); + json["data"]["enableOpenWb"] = ((this->Conf_EnableOpenWB)?1:0); json["data"]["openwbmodulid"] = this->Conf_OpenWBModulID; json["data"]["openwbbatteryid"] = this->Conf_OpenWBBatteryID; From 4f95363f78d09507989ec9db46c5c772947456a9 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 30 Dec 2024 13:49:47 +0100 Subject: [PATCH 029/106] Revert "use checkbox for selection instead of radiobuttons" This reverts commit 04f39f92bd0ab2a8ae3d87baf60029f954209507. --- ChangeLog.md | 4 +--- data/web/Javascript.js | 20 -------------------- data/web/baseconfig.html | 7 +------ data/web/modbusconfig.html | 38 ++++++++++++++++++++------------------ src/MyWebServer.cpp | 4 ++-- src/MyWebServer.h | 5 +++++ src/baseconfig.cpp | 8 +------- src/baseconfig.h | 2 -- src/modbus.cpp | 12 +++++++----- 9 files changed, 37 insertions(+), 63 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index af2a9e32..66f65740 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,8 +1,6 @@ Release 3.3.1: - - BugFix: fix null-termination of string handling (#96) + - BugFix: fix null-terminationof string handling (#96) - new feature: support for OpenWB 2.0 Api (#100) - - new Advanced feature: setting up Browser cache-time of WebUI - - new feature: ability to setup fix WiFi credentials if web-installer doesn´t support the ESP-Board Release 3.3.0: - new feature: WebSerial as remote serial output (#74) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index d1f30878..93bdc22a 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -71,13 +71,6 @@ function handleRadioSelections() { for( var i=0; i< objects.length; i++) { objects[i].click(); } - - var checkboxes = document.querySelectorAll('input[type=checkbox][onclick]'); - for (var i = 0; i < checkboxes.length; i++) { - if (checkboxes[i].onclick) { - checkboxes[i].dispatchEvent(new Event('click', { bubbles: true, cancelable: true })); - } - } } /*############################################################ @@ -392,16 +385,3 @@ function radioselection(show, hide) { } } -/******************************* - * - * @param {*} checkbox object of the checkbox - * @param {*} show Array of shown IDs if checkbox is checked - * @param {*} hide Array of hidden IDs if checkbox is checked - */ -function onCheckboxSelection(checkbox, show, hide) { - if (checkbox.checked) { - radioselection(show, hide); - } else { - radioselection(hide, show); - } -} \ No newline at end of file diff --git a/data/web/baseconfig.html b/data/web/baseconfig.html index f0f9026f..4acacec9 100644 --- a/data/web/baseconfig.html +++ b/data/web/baseconfig.html @@ -120,12 +120,7 @@ - Browser cache time in sec (default: 3600) - - - - - LogLevel (0 [off] ... 5 [max]) + LogLevel (0 [off] ... 5 [max] diff --git a/data/web/modbusconfig.html b/data/web/modbusconfig.html index a7d61c0e..d3ba4a5a 100644 --- a/data/web/modbusconfig.html +++ b/data/web/modbusconfig.html @@ -66,15 +66,16 @@ - Enable reading inverter relay - -
- - -
+ +
+ + +
+ +
+ + +
@@ -89,15 +90,16 @@ - Enable OpenWB support - -
- - -
+ +
+ + +
+ +
+ + +
diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index c6030287..cfa6a746 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -28,11 +28,11 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): DoReboot(false //ElegantOTA.onEnd(std::bind(&MyWebServer::onOTAEnd, this, std::placeholders::_1)); if (Config->GetUseAuth()) { - server->serveStatic("/", LittleFS, "/", String("max-age="+Config->GetCacheTime()).c_str()) + server->serveStatic("/", LittleFS, "/", "max-age=3600") .setDefaultFile("/web/index.html") .setAuthentication(Config->GetAuthUser().c_str(), Config->GetAuthPass().c_str()); } else { - server->serveStatic("/", LittleFS, "/", String("max-age="+Config->GetCacheTime()).c_str()) + server->serveStatic("/", LittleFS, "/", "max-age=3600") .setDefaultFile("/web/index.html"); } diff --git a/src/MyWebServer.h b/src/MyWebServer.h index 141582bc..b297a18b 100644 --- a/src/MyWebServer.h +++ b/src/MyWebServer.h @@ -25,6 +25,8 @@ class MyWebServer { + //enum page_t {ROOT, BASECONFIG, MODBUSCONFIG, MODBUSITEMCONFIG, MODBUSRAWDATA, FSFILES}; + public: MyWebServer(AsyncWebServer *server, DNSServer* dns); @@ -40,6 +42,9 @@ class MyWebServer { handleFiles* fsfiles; +// void handle_update_page(AsyncWebServerRequest *request); +// void handle_update_progress(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); +// void handle_update_response(AsyncWebServerRequest *request); void handleNotFound(AsyncWebServerRequest *request); void handleReboot(AsyncWebServerRequest *request); void handleReset(AsyncWebServerRequest *request); diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 1c28cf23..85c25594 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -1,10 +1,6 @@ #include "baseconfig.h" -BaseConfig::BaseConfig(): debuglevel(2), - serial_rx(3), - serial_tx(1), - cachetime(3600), - useAuth(false) { +BaseConfig::BaseConfig() : debuglevel(2), serial_rx(3), serial_tx(1), useAuth(false) { #ifdef ESP8266 LittleFS.begin(); #elif defined(ESP32) @@ -54,7 +50,6 @@ void BaseConfig::LoadJsonConfig() { if (doc["data"]["sel_auth"]) { if (strcmp(doc["data"]["sel_auth"], "off")==0) { this->useAuth=false;} else {this->useAuth=true;}} else {this->useAuth = false;} if (doc["data"]["auth_user"]) { this->auth_user = doc["data"]["auth_user"].as();} else {this->auth_user = "admin";} if (doc["data"]["auth_pass"]) { this->auth_pass = doc["data"]["auth_pass"].as();} else {this->auth_pass = "password";} - if (doc["data"]["cachetime"]) { this->cachetime = doc["data"]["cachetime"].as();} else {this->cachetime = 3600;} } else { this->log(1, "failed to load json config, load default config"); loadDefaultConfig = true; @@ -109,7 +104,6 @@ void BaseConfig::GetInitData(AsyncResponseStream *response) { json["data"]["sel_auth_on"] = ((this->useAuth)?1:0); json["data"]["auth_user"] = this->auth_user; json["data"]["auth_pass"] = this->auth_pass; - json["data"]["cachetime"] = this->cachetime; #ifdef USE_WEBSERIAL diff --git a/src/baseconfig.h b/src/baseconfig.h index 943f12e1..a4d07043 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -35,7 +35,6 @@ class BaseConfig { const bool& GetUseAuth() const { return useAuth; } const String& GetAuthUser() const {return auth_user;} const String& GetAuthPass() const {return auth_pass;} - const uint16_t& GetCacheTime() const {return cachetime;} const String GetReleaseName(); private: @@ -51,7 +50,6 @@ class BaseConfig { uint8_t debuglevel; uint8_t serial_rx; uint8_t serial_tx; - uint16_t cachetime; bool useAuth; String auth_user; String auth_pass; diff --git a/src/modbus.cpp b/src/modbus.cpp index 045485f1..c38ca1a0 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1231,11 +1231,11 @@ void modbus::LoadJsonConfig(bool firstrun) { if (doc["data"]["openwbmodulid"]) { this->Conf_OpenWBModulID = doc["data"]["openwbmodulid"].as(); this->OpenWB->addMapping("InverterID", String(this->Conf_OpenWBModulID)); } if (doc["data"]["openwbbatteryid"]) { this->Conf_OpenWBBatteryID = doc["data"]["openwbbatteryid"].as(); this->OpenWB->addMapping("BatteryID", String(this->Conf_OpenWBBatteryID)); } - this->Conf_EnableOpenWB = doc["data"]["enableOpenWb"].as(); + this->Conf_EnableOpenWB = (bool)doc["data"]["EnableOpenWb"].as(); this->Conf_EnableSetters = doc["data"]["enable_setters"].as(); this->enableCrcCheck = doc["data"]["enableCrcCheck"].as(); this->enableLengthCheck = doc["data"]["enableLengthCheck"].as(); - this->enableRelays = doc["data"]["enableRelays"].as(); + this->enableRelays = (bool)(doc["data"]["EnableRelays"]).as(); if (doc["data"]["invertertype"]) { bool found = false; @@ -1385,9 +1385,11 @@ void modbus::GetInitData(AsyncResponseStream *response) { json["data"]["txintervalid"] = this->TxIntervalIdData; json["data"]["GpioPin_Relay1"] = this->pin_Relay1; json["data"]["GpioPin_Relay2"] = this->pin_Relay2; - - json["data"]["enableRelays"] = ((this->enableRelays)?1:0); - json["data"]["enableOpenWb"] = ((this->Conf_EnableOpenWB)?1:0); + json["data"]["EnableRelays_On"] = ((this->enableRelays)?1:0); + json["data"]["EnableRelays_Off"] = ((this->enableRelays)?0:1); + + json["data"]["EnableOpenWb_On"] = ((this->Conf_EnableOpenWB)?1:0); + json["data"]["EnableOpenWb_Off"] = ((this->Conf_EnableOpenWB)?0:1); json["data"]["openwbmodulid"] = this->Conf_OpenWBModulID; json["data"]["openwbbatteryid"] = this->Conf_OpenWBBatteryID; From e7217b353a23636aea1771ba4d72215d84b0ce51 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 30 Dec 2024 14:14:50 +0100 Subject: [PATCH 030/106] prevent watchdog timer event --- src/modbus.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modbus.cpp b/src/modbus.cpp index c38ca1a0..15c1742c 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -970,6 +970,7 @@ void modbus::GetLiveDataAsJson(AsyncResponseStream *response, String subaction) response->print(s); count++; } + yield(); //Lazgar for (uint16_t i=0; i < this->InverterIdData->size(); i++) { if (subaction == "onlyactive" && !this->InverterIdData->at(i).active) continue; From cb7516a70d836912b83f9ec761da3a9f67228d50 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 30 Dec 2024 15:35:10 +0100 Subject: [PATCH 031/106] update --- README.md | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f65c9569..ed163182 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -# Modbus-RTU to MQTT Gateway -**for Solar Inverters and other Modbus-RTU Devices** +# Modbus-RTU to MQTT Gateway for Solar Inverter [![license](https://img.shields.io/badge/Licence-GNU%20v3.0-green)](https://github.com/desktop/desktop/blob/master/LICENSE) ![ESP32 Architecture](https://img.shields.io/badge/Architecture-ESP32-blue) @@ -14,13 +13,12 @@ I want to integrate the professional WebSerial and professional ElegantOTA Editi ----- -This project implements a Gateway for Solar Inverters with Modbus-RTU communication to MQTT on ESP32 basis. -Direkt Communication with [OpenWB](https://openwb.de) is implemented. -Sending "set" commands to inverter are basically implemented too. +This project implements a Gatewayx for Solar Inverter with with Modbus-RTU communication to MQTT on ESP32 basis. +Direkt Communication wit [OpenWB](https://openwb.de) is implemented. -### Supported Solar Inverters -Basically, all Inverters with Modbus RS485 RTU communication are supported. -Currently the following Inverters with their special registers are integrated: +### Supported Solar Inverter +Basically, all Inverters with Modbus RS485 RTU communication. +Currently the following Inverters are with thier special registers integrated: * Solax Hybrid X1 * Solax Hybrid X3 * Solax MIC @@ -29,28 +27,21 @@ Currently the following Inverters with their special registers are integrated: * Deye Sun SG04LP3 * QVolt-HYP-G3-3P -If your Solar Inverter is not listed, it´s quite simple to add it by yourself. Feel free to add the special registers, please check the [wiki page](https://github.com/tobiasfaust/SolaxModbusGateway/wiki/configuration-register) or contact me by opening a [new issue](https://github.com/tobiasfaust/SolaxModbusGateway/issues) in github. +If your Solar Inverter is not listed, feel free to add the special register simply, please check th [wiki page](configuration-register) or contact me by opening a [new issue](https://github.com/tobiasfaust/SolaxModbusGateway/issues) in github. ### What you need -* ESP32, ESP32-C3, ESP-S2 or ESP-S3 NodeMCU -* RS-485 TTL UART Module with MAX485 Semiconductor +* ESP32 NodeMCU +* MAX485 Module TTL Switch Schalter to RS-485 Module RS485 5V Modul (this can also handle 3.3V from ESP) An ESP8266 is actually not sufficient, because Modbus communication works fail-free only with hardwareserial. ESP8266 has only one Hardwareserial port which is used by serial/debug output. ESP32 has 3 Hardewareserial ports and we use one of them. Another reason is available memory for such huge json definition or such large modbus answers. -Please check also the wiki page, [how to wire the circuit](https://github.com/tobiasfaust/SolaxModbusGateway/wiki/wiring-the-circuit). +Please check also the wiki page, [how to wire the circuit](wiring-the-circuit). ### How to start -It´s recommend to start with one example to check wiring works correctly. Both LED´s (TX and RX) on your RS-485 module should blink. If only TX-LED blinks, please check: -* wiring -* baud rate +It's the easiest way you've ever heard: Just go to the [Web-Installer](https://tobiasfaust.github.io/SolaxModbusGateway/) and follow the [Wiki documentation](https://github.com/tobiasfaust/SolaxModbusGateway/wiki/start-and-integration-into-locale-wifi) -The example requests the inverter SerialNumber and if wiring is correct, the inverter will answer with his number or something like this. +After that your Device is ready and looks like this: -
-request: 01 03 00 00 00 07 08 04
+[[images/Solax_Status.png]]
 
-Response: 01 03 .....
-
-Burn firmware on your device via web-installer: https://tobiasfaust.github.io/SolaxModbusGateway/ - -## please refer full documentation and How-To´s in our [Wiki](https://github.com/tobiasfaust/SolaxModbusGateway/wiki) +There is also a page with all configured livedata items available. This page is refreshing every 5 seconds. Please check [Modbus Item Configuration page](configuration-modbusitems). From 0e2bb33dc4d691fc947dba70e1f5d6c755e3e581 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:38:39 +0100 Subject: [PATCH 032/106] Update modbus.cpp --- src/modbus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index 15c1742c..415ed531 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -422,7 +422,7 @@ void modbus::QueryQueueToInverter() { if (rwtype == WRITE) { this->ReceiveSetData(&m); - this->LastTxIdData = millis() - (this->TxIntervalIdData * 1001); //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen zwecks Rückmeldung ob der Set Befehl ausgeführt wurde + this->LastTxIdData = 0; //Setze den Timer zurück um nach einem Set Befehl die ID Daten abzufragen zwecks Rückmeldung ob der Set Befehl ausgeführt wurde } else if (rwtype == READ) { this->ReceiveReadData(); From c968d3d0211db9d14399127ddf67f90429e39e3b Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Tue, 31 Dec 2024 15:10:46 +0100 Subject: [PATCH 033/106] add TotalEnergyPV for GroWatt (#100) --- data/regs/Growatt-SPH.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/data/regs/Growatt-SPH.json b/data/regs/Growatt-SPH.json index d430622d..ea669adb 100644 --- a/data/regs/Growatt-SPH.json +++ b/data/regs/Growatt-SPH.json @@ -67,6 +67,20 @@ "factor": 0.01, "unit": "Hz" }, + { + "position": [ + 185, + 186, + 187, + 188 + ], + "name": "TotalEnergyPV", + "realname": "Erzeugte Energie PV", + "openwbtopic": "setcounterwh", + "datatype": "integer", + "factor": 100, + "unit": "Wh" + }, { "position": [125, 126, 127, 128], "name": "TotalEnergyWhPv1", From 13cde6a502f64046adaf3b76070a34dcf0dac272 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Fri, 3 Jan 2025 17:21:45 +0100 Subject: [PATCH 034/106] bugfix: fix esp crash for /getitems if using an huge register table (#76) --- ChangeLog.md | 1 + data/web/Javascript.js | 206 +++++++++++++++++++++++++++-------- data/web/baseconfig.html | 29 ++--- data/web/baseconfig.js | 2 + data/web/modbusconfig.html | 28 ++--- data/web/modbusconfig.js | 2 + data/web/modbusitemconfig.js | 1 + src/MyWebServer.cpp | 30 +++-- src/baseconfig.cpp | 18 +-- src/modbus.cpp | 167 ++++++++++++++++------------ src/modbus.h | 5 +- 11 files changed, 318 insertions(+), 171 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 66f65740..e604f311 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,7 @@ Release 3.3.1: - BugFix: fix null-terminationof string handling (#96) - new feature: support for OpenWB 2.0 Api (#100) + - bugfix: fix esp crash for /getitems if using an huge register table (#76) Release 3.3.0: - new feature: WebSerial as remote serial output (#74) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index 93bdc22a..5474846d 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -1,8 +1,21 @@ -/*############################################################ -# -# definition of constants -# -############################################################*/ +/***************************************************************************************** + * @file /data/web/Javascript.js + * @description This file contains various JavaScript functions and constants used for handling GPIO configurations, + * data fetching, applying JSON data to HTML templates, creating selection lists from input fields, + * and transforming checkboxes into styled on/off switches. The functions are designed to work with + * a web interface for managing and configuring GPIO ports and other related settings. + * @version 1.0.0 + * @date 2023-10-01 + * + * @author Tobias.Faust + * + * @license MIT + * + *****************************************************************************************/ + +/***************************************************************************************** + * Definition of constants + *****************************************************************************************/ const gpio_disabled = []; @@ -60,24 +73,42 @@ const gpioanalog = [ {port: 36, name:'ADC1_CH0 - GPIO36'}, var timer; // ID of setTimout Timer -> setResponse -/*############################################################ -# - -# activate all radioselections after pageload to hide unnecessary elements -# -############################################################*/ +/****************************************************************************************** + * activate all radioselections after pageload to hide unnecessary elements + * Works for all checkbox and radio elements with onclick="radioselection(show, hide)" + * + ******************************************************************************************/ function handleRadioSelections() { - var objects = document.querySelectorAll('input[type=radio][onclick*=radioselection]:checked'); - for( var i=0; i< objects.length; i++) { - objects[i].click(); + var radios = document.querySelectorAll('input[type=radio][onclick*=radioselection]:checked'); + for (var i = 0; i < radios.length; i++) { + if (radios[i].onclick) { + var onclickStr = radios[i].getAttribute('onclick'); + var match = onclickStr.match(/radioselection\((.*)\)/); + if (match) { + eval("radioselection(" + match[1] + ")"); + } + } + } + + var checkboxes = document.querySelectorAll('input[type=checkbox][onclick*=onCheckboxSelection]'); + for (var i = 0; i < checkboxes.length; i++) { + if (checkboxes[i].onclick) { + var onclickStr = checkboxes[i].getAttribute('onclick'); + var match = onclickStr.match(/onCheckboxSelection\((.*)\)/); + if (match) { + eval("onCheckboxSelection(" + match[1] + ")"); + } + } } } -/*############################################################ -# -# central function to initiate data fetch -# -############################################################*/ +/***************************************************************************************** + * central function to initiate data fetch + * @param {*} json -> json object to send + * @param {*} highlight -> highlight on/off + * @param {*} callbackFn -> callback function to call after data is fetched + * @returns {*} void +******************************************************************************************/ function requestData(json, highlight, callbackFn) { const data = new URLSearchParams(); @@ -92,11 +123,18 @@ function requestData(json, highlight, callbackFn) { .then (json => { handleJsonItems(json, highlight, callbackFn)}); } -/*############################################################ -# -# definition of applying jsondata to html templates -# -############################################################*/ +/***************************************************************************************** + * + * definition of applying jsondata to html templates + * @param {*} _obj -> object to apply key + * @param {*} _key -> key to apply + * @param {*} _val -> value to apply + * @param {*} counter -> counter of array + * @param {*} tplHierarchie -> hierarchie of template + * @param {*} highlight -> highlight on/off + * @returns {*} void + * +******************************************************************************************/ function applyKey (_obj, _key, _val, counter, tplHierarchie, highlight) { if (_obj.id == _key || _obj.id == tplHierarchie +"."+ _key) { if (['SPAN', 'DIV', 'TD', 'DFN'].includes(_obj.tagName)) { @@ -122,14 +160,15 @@ function applyKey (_obj, _key, _val, counter, tplHierarchie, highlight) { } } -/*############################################################################ -json -> der json teil der angewendet werden soll -_tpl -> das documentFragment auf welches das json agewendet werden soll -ObjID -> ggf eine ID im _tpl auf die "key" und "value" des jsons agewendet werden soll - wenn diese undefinied ist, ist die ID = json[key] -counter -> gesetzt, wenn innerhalb eines _tpl arrays die ID des Objektes hochgezählt wurde -highlight -> wenn in einem objekt die klasse "ajaxchange" gesetzt ist, so wird die Klasse "highlightOn" angewendet -#############################################################################*/ +/***************************************************************************************** + * Apply a set of keys from an Array or an Object + * @param {*} json -> der json teil der angewendet werden soll + * @param {*} _tpl -> das documentFragment auf welches das json agewendet werden soll + * @param {*} ObjID -> ggf eine ID im _tpl auf die "key" und "value" des jsons agewendet werden soll + * wenn diese undefinied ist, ist die ID = json[key] + * @param {*} counter -> gesetzt, wenn innerhalb eines _tpl arrays die ID des Objektes hochgezählt wurde + * @param {*} highlight -> wenn in einem objekt die klasse "ajaxchange" gesetzt ist, so wird die Klasse "highlightOn" angewendet +*****************************************************************************************/ function applyKeys(json, _tpl, ObjID, counter, tplHierarchie, highlight) { for (var key in json) { @@ -168,6 +207,15 @@ function applyKeys(json, _tpl, ObjID, counter, tplHierarchie, highlight) { } } +/***************************************************************************************** + * apply template to document + * @param {*} TemplateJson: json object to apply + * @param {*} templateID: id of template to apply + * @param {*} doc: document to apply + * @param {*} tplHierarchie: hierarchie of template + * @param {*} highlight: highlight on/off + * @returns {*} void +*****************************************************************************************/ function applyTemplate(TemplateJson, templateID, doc, tplHierarchie, highlight) { if (Array.isArray(TemplateJson)) { for (var i=0; i < TemplateJson.length; i++) { @@ -220,15 +268,14 @@ function handleJsonItems(json, highlight, callbackFn) { } // DOM objects now ready - handleRadioSelections(); - if (callbackFn) {callbackFn();} + if (callbackFn) {callbackFn();} } -// *********************************** -// show response -// b => bool => true = OK; false = Error -// s => String => text to show -// *********************************** +/***************************************************************************************** + * show response + * @param {*} b (bool): true = OK; false = Error + * @param {*} s (String): text to show +*****************************************************************************************/ function setResponse(b, s) { try { // clear if previous timer still run @@ -246,7 +293,7 @@ function setResponse(b, s) { } catch(e) {} } -/*############################################################ +/****************************************************************************************** # # definition of creating selectionlists from input fields # querySelector -> select input fields to convert @@ -256,7 +303,7 @@ function setResponse(b, s) { # example: # CreateSelectionListFromInputField('input[type=number][id^=AllePorts], input[type=number][id^=GpioPin]', # [gpio, gpio_analog], gpio_disabled); -############################################################*/ +******************************************************************************************/ function CreateSelectionListFromInputField(querySelector, jsonLists, blacklist) { var _parent, _select, _option, i, j, k; var objects = document.querySelectorAll(querySelector); @@ -282,9 +329,9 @@ function CreateSelectionListFromInputField(querySelector, jsonLists, blacklist) } } -/*############################################################ -# returns, if a element is visible or not -############################################################*/ +/***************************************************************************************** + * returns, if a element is visible or not +****************************************************************************************/ function isVisible(_obj) { var ret = true; if (_obj && _obj.style.display == "none") { ret = false;} @@ -292,12 +339,12 @@ function isVisible(_obj) { return ret; } -/******************************* +/**************************************************************************************** separator: regex of item ID to identify first element in row - if set, returned json is an array, all elements per row, example: "^myonoffswitch.*" - if emty, all elements at one level together, ONLY for small json´s (->memory issue) -*******************************/ +****************************************************************************************/ function onSubmit(DataForm, separator='') { // init json Objects var JsonData, tempData; @@ -371,11 +418,11 @@ function onSubmit(DataForm, separator='') { } -/******************************* +/**************************************************************************************** blendet Zeilen der Tabelle aus show: Array of shown IDs return true; hide: Array of hidden IDs -*******************************/ +****************************************************************************************/ function radioselection(show, hide) { for(var i = 0; i < show.length; i++){ if (document.getElementById(show[i])) {document.getElementById(show[i]).style.display = 'table-row';} @@ -385,3 +432,66 @@ function radioselection(show, hide) { } } +/**************************************************************************************** + * Show elements on checkbox is set, otherwise hide elements + * @param {*} checkbox object of the checkbox + * @param {*} show Array of shown IDs if checkbox is checked + * @param {*} hide Array of hidden IDs if checkbox is checked + * @returns {*} void + ****************************************************************************************/ +function onCheckboxSelection(checkbox, show, hide) { + if (checkbox.checked) { + radioselection(show, hide); + } else { + radioselection(hide, show); + } +} + +/**************************************************************************************** + * Transforms all checkboxes in the document that are not within a div with the style class "onoffswitch". + * + * This function searches for all input elements of type checkbox that do not have the class "onoffswitch-checkbox". + * For each of these checkboxes, it creates a new div element with the class "onoffswitch", clones the checkbox into this div, + * and adds a label with the necessary span elements for styling. Finally, it replaces the original checkbox with the new div element. + ****************************************************************************************/ +function transformCheckboxes() { + // Alle Checkboxen im Dokument suchen deren elternelement kein div mit der Style class "onoffswitch" ist + const checkboxes = document.querySelectorAll("input[type='checkbox']:not(.onoffswitch-checkbox)"); + + for (var i = 0; i < checkboxes.length; i++) { + // Eltern-Element der Checkbox + const parent = checkboxes[i].parentElement; + + // Neues Div-Element erstellen + const div = document.createElement('div'); + div.className = 'onoffswitch'; + + // Checkbox in das neue Div-Element kopieren + const newCheckbox = checkboxes[i].cloneNode(true) + newCheckbox.className = 'onoffswitch-checkbox'; + div.appendChild(newCheckbox); + + // Neues Label-Element erstellen + const label = document.createElement('label'); + label.className = 'onoffswitch-label'; + label.setAttribute('for', checkboxes[i].id); + + // Span-Elemente für das Label erstellen + const spanInner = document.createElement('span'); + spanInner.className = 'onoffswitch-inner'; + + const spanSwitch = document.createElement('span'); + spanSwitch.className = 'onoffswitch-switch'; + + // Span-Elemente zum Label hinzufügen + label.appendChild(spanInner); + label.appendChild(spanSwitch); + + // Label zum Div-Element hinzufügen + div.appendChild(label); + + // Neues Div-Element anstelle der ursprünglichen Checkbox einfügen + parent.replaceChild(div, checkboxes[i]); + + } +} diff --git a/data/web/baseconfig.html b/data/web/baseconfig.html index 4acacec9..cb204305 100644 --- a/data/web/baseconfig.html +++ b/data/web/baseconfig.html @@ -73,15 +73,11 @@ - -
- - -
-
- - -
+ + use dynamic MQTT ClientID + + + @@ -96,16 +92,11 @@ - -
- - -
- -
- - -
+ + use Authentification for WebUI + + + diff --git a/data/web/baseconfig.js b/data/web/baseconfig.js index 2b2868ac..d3a815f2 100644 --- a/data/web/baseconfig.js +++ b/data/web/baseconfig.js @@ -14,6 +14,8 @@ function GetInitData() { // ************************************************ function MyCallback() { + transformCheckboxes(); + handleRadioSelections(); CreateSelectionListFromInputField('input[type=number][id^=GpioPin]', [gpio]); document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; diff --git a/data/web/modbusconfig.html b/data/web/modbusconfig.html index d3ba4a5a..6f46b960 100644 --- a/data/web/modbusconfig.html +++ b/data/web/modbusconfig.html @@ -66,16 +66,11 @@ - -
- - -
- -
- - -
+ + Enable reading Inverter relays + + + @@ -90,16 +85,9 @@ - -
- - -
- -
- - -
+ Enable OpenWB support + + diff --git a/data/web/modbusconfig.js b/data/web/modbusconfig.js index 3a7038da..f24e83c8 100644 --- a/data/web/modbusconfig.js +++ b/data/web/modbusconfig.js @@ -14,6 +14,8 @@ function GetInitData() { // ************************************************ function MyCallback() { + transformCheckboxes(); + handleRadioSelections(); CreateSelectionListFromInputField('input[type=number][id^=GpioPin]', [gpio]); document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; diff --git a/data/web/modbusitemconfig.js b/data/web/modbusitemconfig.js index a8f7fa58..d9122b3b 100644 --- a/data/web/modbusitemconfig.js +++ b/data/web/modbusitemconfig.js @@ -17,6 +17,7 @@ function GetInitData() { // ************************************************ function MyCallback() { + //transformCheckboxes() document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; } diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index cfa6a746..f97da53e 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -117,6 +117,7 @@ void MyWebServer::handleWiFiReset(AsyncWebServerRequest *request) { } void MyWebServer::handleGetItemJson(AsyncWebServerRequest *request) { + /* AsyncResponseStream *response = request->beginResponseStream("application/json"); response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response->addHeader("Pragma", "no-cache"); @@ -125,6 +126,13 @@ void MyWebServer::handleGetItemJson(AsyncWebServerRequest *request) { mb->GetLiveDataAsJson(response, ""); request->send(response); + */ + + mb->GetLiveDataAsJson(request); + + + + } void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { @@ -146,9 +154,6 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { String action, subaction, item, newState; String json = "{}"; - AsyncResponseStream *response = request->beginResponseStream("text/json"); - response->addHeader("Server","ESP Async Web Server"); - if(request->hasArg("json")) { json = request->arg("json"); } @@ -156,9 +161,6 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { JsonDocument jsonGet; // TODO Use computed size?? DeserializationError error = deserializeJson(jsonGet, json.c_str()); - JsonDocument jsonReturn; - jsonReturn["response"].to(); - if (Config->GetDebugLevel() >=4) { dbg.print("Ajax Json Empfangen: "); } if (!error) { if (Config->GetDebugLevel() >=4) { serializeJsonPretty(jsonGet, dbg); dbg.println(); } @@ -173,6 +175,17 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { RaiseError = true; } + if (action && action == "RefreshLiveData") { + mb->GetLiveDataAsJson(request); + return; + } + + AsyncResponseStream *response = request->beginResponseStream("text/json"); + response->addHeader("Server","ESP Async Web Server"); + + JsonDocument jsonReturn; + jsonReturn["response"].to(); + if (RaiseError) { jsonReturn["response"]["status"] = 0; jsonReturn["response"]["text"] = buffer; @@ -212,8 +225,9 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { serializeJson(jsonReturn, ret); response->print(ret); - } else if (action && action == "RefreshLiveData") { - mb->GetLiveDataAsJson(response, subaction); + //} else if (action && action == "RefreshLiveData") { + //TODO + //mb->GetLiveDataAsJson(response, subaction); } else if (action && action == "SetActiveStatus") { if (strcmp(newState.c_str(),"true")==0) mb->SetItemActiveStatus(item, true); diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 85c25594..d5b212d2 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -1,6 +1,10 @@ #include "baseconfig.h" -BaseConfig::BaseConfig() : debuglevel(2), serial_rx(3), serial_tx(1), useAuth(false) { +BaseConfig::BaseConfig(): debuglevel(2), + serial_rx(3), + serial_tx(1), + mqtt_UseRandomClientID(true), + useAuth(false) { #ifdef ESP8266 LittleFS.begin(); #elif defined(ESP32) @@ -41,15 +45,17 @@ void BaseConfig::LoadJsonConfig() { if (doc["data"]["mqttuser"]) { this->mqtt_username = doc["data"]["mqttuser"].as();} else {this->mqtt_username = "";} if (doc["data"]["mqttpass"]) { this->mqtt_password = doc["data"]["mqttpass"].as();} else {this->mqtt_password = "";} if (doc["data"]["mqttbasepath"]) { this->mqtt_basepath = doc["data"]["mqttbasepath"].as();} else {this->mqtt_basepath = "home/";} - if (doc["data"]["UseRandomClientID"]){ if (strcmp(doc["data"]["UseRandomClientID"], "none")==0) { this->mqtt_UseRandomClientID=false;} else {this->mqtt_UseRandomClientID=true;}} else {this->mqtt_UseRandomClientID = true;} if (doc["data"]["SelectConnectivity"]){if (strcmp(doc["data"]["SelectConnectivity"], "wifi")==0) { this->useETH=false;} else {this->useETH=true;}} else {this->useETH = false;} if (doc["data"]["debuglevel"]) { this->debuglevel = _max(doc["data"]["debuglevel"].as(), 0);} else {this->debuglevel = 0; } if (doc["data"]["SelectLAN"]) { this->LANBoard = doc["data"]["SelectLAN"].as();} else {this->LANBoard = "";} if (doc["data"]["serial_rx"]) { this->serial_rx = doc["data"]["serial_rx"].as(); } else {this->serial_rx = 3;} if (doc["data"]["serial_tx"]) { this->serial_tx = doc["data"]["serial_tx"].as(); } else {this->serial_tx = 1;} - if (doc["data"]["sel_auth"]) { if (strcmp(doc["data"]["sel_auth"], "off")==0) { this->useAuth=false;} else {this->useAuth=true;}} else {this->useAuth = false;} if (doc["data"]["auth_user"]) { this->auth_user = doc["data"]["auth_user"].as();} else {this->auth_user = "admin";} if (doc["data"]["auth_pass"]) { this->auth_pass = doc["data"]["auth_pass"].as();} else {this->auth_pass = "password";} + + this->useAuth = doc["data"]["sel_auth"].as(); + this->mqtt_UseRandomClientID = doc["data"]["useRandomClientID"].as(); + } else { this->log(1, "failed to load json config, load default config"); loadDefaultConfig = true; @@ -98,10 +104,8 @@ void BaseConfig::GetInitData(AsyncResponseStream *response) { json["data"]["debuglevel"] = this->debuglevel; json["data"]["sel_wifi"] = ((this->useETH)?0:1); json["data"]["sel_eth"] = ((this->useETH)?1:0); - json["data"]["sel_URCID1"] = ((this->mqtt_UseRandomClientID)?0:1); - json["data"]["sel_URCID2"] = ((this->mqtt_UseRandomClientID)?1:0); - json["data"]["sel_auth_off"]= ((this->useAuth)?0:1); - json["data"]["sel_auth_on"] = ((this->useAuth)?1:0); + json["data"]["useRandomClientID"] = ((this->mqtt_UseRandomClientID)?1:0); + json["data"]["sel_auth"]= ((this->useAuth)?1:0); json["data"]["auth_user"] = this->auth_user; json["data"]["auth_pass"] = this->auth_pass; diff --git a/src/modbus.cpp b/src/modbus.cpp index 415ed531..acf5b065 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -944,59 +944,100 @@ String modbus::GetInverterSN() { * Return all LiveData as jsonArray * {data: [{"name": "xx", "value": "xx", ...}, ...] } *******************************************************/ -void modbus::GetLiveDataAsJson(AsyncResponseStream *response, String subaction) { - int count = 0; - response->print("{\"data\": {\"items\": ["); +void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { + std::shared_ptr counter = std::make_shared(0); + String subaction(""), json("{}"); - for (uint16_t i=0; i < this->InverterLiveData->size(); i++) { - if (subaction == "onlyactive" && !this->InverterLiveData->at(i).active) continue; - JsonDocument doc; - String s = ""; - - doc["name"] = this->InverterLiveData->at(i).Name.c_str(); - doc["realname"] = this->InverterLiveData->at(i).RealName.c_str(); - doc["value"] = std::move(this->InverterLiveData->at(i).value + " " + this->InverterLiveData->at(i).unit); - doc["active"]["checked"] = (this->InverterLiveData->at(i).active?1:0); - doc["active"]["name"] = this->InverterLiveData->at(i).Name.c_str(); - doc["mqtttopic"] = std::move(this->mqtt->getTopic(this->InverterLiveData->at(i).Name, false)); - - if (this->Conf_EnableOpenWB && this->InverterLiveData->at(i).openwb.length() > 0) { - JsonArray wb = doc["openwb"].to(); - wb[0]["openwbtopic"] = std::move(OpenWB->getOpenWbTopic(this->InverterLiveData->at(i).openwb)); - } - - serializeJson(doc, s); - if(count>0) response->print(", "); - response->print(s); - count++; + if(request->hasArg("json")) { + json = request->arg("json"); } - yield(); - //Lazgar - for (uint16_t i=0; i < this->InverterIdData->size(); i++) { - if (subaction == "onlyactive" && !this->InverterIdData->at(i).active) continue; - JsonDocument doc; - String s = ""; - doc["name"] = this->InverterIdData->at(i).Name.c_str(); - doc["realname"] = this->InverterIdData->at(i).RealName.c_str(); - doc["value"] = std::move(this->InverterIdData->at(i).value + " " + this->InverterIdData->at(i).unit); - doc["active"]["checked"] = (this->InverterIdData->at(i).active?1:0); - doc["active"]["name"] = this->InverterIdData->at(i).Name.c_str(); - doc["mqtttopic"] = std::move(this->mqtt->getTopic(this->InverterIdData->at(i).Name, false)); - //Wird für die ID Daten nicht benötigt gibt keine Infos für die OpenWallbox - //if (this->InverterIdData->at(i).openwb.length() > 0) { - // JsonArray wb = doc["openwb"].to(); - // wb[0]["openwbtopic"] = this->InverterIdData->at(i).openwb.c_str(); - //} + JsonDocument jsonGet; + DeserializationError error = deserializeJson(jsonGet, json.c_str()); + + Config->log(4, "[GetLiveDataAsJson] Json command empfangen: "); + if (!error) { + if (Config->GetDebugLevel() >=4) { serializeJsonPretty(jsonGet, dbg); dbg.println(); } - serializeJson(doc, s); - if(count>0) response->print(", "); - response->print(s); - count++; + if (jsonGet["subaction"]){subaction = jsonGet["subaction"].as();} + + } else { + Config->log(2, "[GetLiveDataAsJson] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); } - //Lazgar - response->printf(" ]}, \"object_id\": \"%s/%s\"}", Config->GetMqttBasePath().c_str(), Config->GetMqttRoot().c_str()); + + AsyncWebServerResponse *response = request->beginChunkedResponse("application/json", [this, counter, subaction](uint8_t *buffer, size_t maxLen, size_t index) { + String ret(""); + ret.reserve(maxLen); + maxLen -= 500; // use a puffer of 500 bytes, every item is assumed to be 200 bytes + if (*counter == 0) { + // send start of JSON + ret += "{\"data\": {\"items\": ["; + (*counter)++; + } + + if (*counter <= this->InverterIdData->size() && ret.length() < maxLen) { + // send IdData + uint16_t i = *counter - 1; + + // jedes JsonObject wird mit 200 bytes angenommen, + 100 bytes puffer am Ende + while (i < this->InverterIdData->size() && ret.length() < maxLen) { + if (!(subaction == "onlyactive" && !this->InverterIdData->at(i).active)) { + if(*counter > 1) ret += ","; + ret += "{\"name\": \"" + this->InverterIdData->at(i).Name + "\","; + ret += "\"realname\": \"" + this->InverterIdData->at(i).RealName + "\","; + ret += "\"value\": \"" + this->InverterIdData->at(i).value + " " + this->InverterIdData->at(i).unit + "\","; + ret += "\"active\": {\"checked\": " + String(this->InverterIdData->at(i).active ? 1 : 0) + ", \"name\": \"" + this->InverterIdData->at(i).Name + "\"},"; + ret += "\"mqtttopic\": \"" + this->mqtt->getTopic(this->InverterIdData->at(i).Name, false) + "\""; + + if (this->Conf_EnableOpenWB && this->InverterIdData->at(i).openwb.length() > 0) { + ret += ",\"openwb\": [{\"openwbtopic\": \"" + OpenWB->getOpenWbTopic(this->InverterIdData->at(i).openwb) + "\"}]"; + } + ret += "}"; + } + + (*counter)++; + i++; + } + } + + if (*counter <= this->InverterIdData->size() + this->InverterLiveData->size() && ret.length() < maxLen) { + // send LiveData + uint16_t i = *counter - this->InverterIdData->size() - 1; + + while (i < this->InverterLiveData->size() && ret.length() < maxLen) { + if (!(subaction == "onlyactive" && !this->InverterLiveData->at(i).active)) { + if(*counter > 1) ret += ","; + ret += "{\"name\": \"" + this->InverterLiveData->at(i).Name + "\","; + ret += "\"realname\": \"" + this->InverterLiveData->at(i).RealName + "\","; + ret += "\"value\": \"" + this->InverterLiveData->at(i).value + " " + this->InverterLiveData->at(i).unit + "\","; + ret += "\"active\": {\"checked\": " + String(this->InverterLiveData->at(i).active ? 1 : 0) + ", \"name\": \"" + this->InverterLiveData->at(i).Name + "\"},"; + ret += "\"mqtttopic\": \"" + this->mqtt->getTopic(this->InverterLiveData->at(i).Name, false) + "\""; + + if (this->Conf_EnableOpenWB && this->InverterLiveData->at(i).openwb.length() > 0) { + ret += ",\"openwb\": [{\"openwbtopic\": \"" + OpenWB->getOpenWbTopic(this->InverterLiveData->at(i).openwb) + "\"}]"; + } + ret += "}"; + } + + (*counter)++; + i++; + } + + } + + if (this->InverterIdData->size() + this->InverterLiveData->size() + 1 == *counter) { + // send end of JSON + ret += " ]}, \"object_id\": \"" + Config->GetMqttBasePath() + "/" + Config->GetMqttRoot() + "\"}"; + (*counter)++; + } + int len = sprintf((char*)buffer, ret.c_str()); + return len; + + }); + + request->send(response); } + /******************************************************* * Return all LiveData as jsonArray * {data: [{"name": "xx", "value": "xx"}], } @@ -1004,13 +1045,15 @@ void modbus::GetLiveDataAsJson(AsyncResponseStream *response, String subaction) *******************************************************/ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { int count = 0; - response->print("{\"data\": ["); File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); return; } + + response->print("{\"data\": ["); + String streamString = ""; streamString = "\""+ this->InverterType.name +"\": {"; regfile.find(streamString.c_str()); @@ -1024,14 +1067,10 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { if (!error) { // Print the result - if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } + Config->log(4, "parsing JSON ok"); if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} } else { - if (Config->GetDebugLevel() >=1) { - dbg.print("(Function GetRegisterAsJson) Failed to parse JSON Register Data: "); - dbg.print(error.c_str()); - dbg.println(); - } + Config->log(4, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); } String s = ""; @@ -1055,14 +1094,10 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { if (!error) { // Print the result - if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } + Config->log(4, "parsing JSON ok"); if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} } else { - if (Config->GetDebugLevel() >=1) { - dbg.print("(Function GetRegisterAsJson) Failed to parse JSON Register Data: "); - dbg.print(error.c_str()); - dbg.println(); - } + Config->log(1, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); } String s = ""; @@ -1153,7 +1188,7 @@ void modbus::LoadRegItems(std::vector* vector, String type) { if (!error) { // Print the result - if (Config->GetDebugLevel() >=4) {dbg.println("parsing JSON ok"); } + Config->log(4, "parsing JSON ok"); if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} } else { Config->log(1, "(Function LoadRegItems) Failed to parse JSON Register Data for Inverter <%s> and type <%s>: %s", this->InverterType.name.c_str(), type.c_str(), error.c_str()); @@ -1232,11 +1267,11 @@ void modbus::LoadJsonConfig(bool firstrun) { if (doc["data"]["openwbmodulid"]) { this->Conf_OpenWBModulID = doc["data"]["openwbmodulid"].as(); this->OpenWB->addMapping("InverterID", String(this->Conf_OpenWBModulID)); } if (doc["data"]["openwbbatteryid"]) { this->Conf_OpenWBBatteryID = doc["data"]["openwbbatteryid"].as(); this->OpenWB->addMapping("BatteryID", String(this->Conf_OpenWBBatteryID)); } - this->Conf_EnableOpenWB = (bool)doc["data"]["EnableOpenWb"].as(); + this->Conf_EnableOpenWB = doc["data"]["enableOpenWb"].as(); this->Conf_EnableSetters = doc["data"]["enable_setters"].as(); this->enableCrcCheck = doc["data"]["enableCrcCheck"].as(); this->enableLengthCheck = doc["data"]["enableLengthCheck"].as(); - this->enableRelays = (bool)(doc["data"]["EnableRelays"]).as(); + this->enableRelays = doc["data"]["enableRelays"].as(); if (doc["data"]["invertertype"]) { bool found = false; @@ -1386,11 +1421,9 @@ void modbus::GetInitData(AsyncResponseStream *response) { json["data"]["txintervalid"] = this->TxIntervalIdData; json["data"]["GpioPin_Relay1"] = this->pin_Relay1; json["data"]["GpioPin_Relay2"] = this->pin_Relay2; - json["data"]["EnableRelays_On"] = ((this->enableRelays)?1:0); - json["data"]["EnableRelays_Off"] = ((this->enableRelays)?0:1); + json["data"]["enableRelays"] = ((this->enableRelays)?1:0); - json["data"]["EnableOpenWb_On"] = ((this->Conf_EnableOpenWB)?1:0); - json["data"]["EnableOpenWb_Off"] = ((this->Conf_EnableOpenWB)?0:1); + json["data"]["enableOpenWb"] = ((this->Conf_EnableOpenWB)?1:0); json["data"]["openwbmodulid"] = this->Conf_OpenWBModulID; json["data"]["openwbbatteryid"] = this->Conf_OpenWBBatteryID; diff --git a/src/modbus.h b/src/modbus.h index 25169cae..d0e056dd 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -13,7 +13,7 @@ #include #include -#define DEBUGMODE +//#define DEBUGMODE class modbus { @@ -57,7 +57,8 @@ class modbus { void GetInitData(AsyncResponseStream *response); void GetInitRawData(AsyncResponseStream *response); String GetInverterSN(); - void GetLiveDataAsJson(AsyncResponseStream *response, String action); + + void GetLiveDataAsJson(AsyncWebServerRequest *request); void GetRegisterAsJson(AsyncResponseStream *response); void SetItemActiveStatus(String item, bool newstate); void ReceiveMQTT(String topic, int msg); From e081b231e7230249592532adb343ef13e9d6a963 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 4 Jan 2025 08:12:02 +0100 Subject: [PATCH 035/106] use internal logging method --- src/MyWebServer.cpp | 52 +++++++--------------- src/baseconfig.cpp | 14 +++++- src/baseconfig.h | 1 + src/handleFiles.cpp | 38 ++++++----------- src/main.cpp | 26 +++++------ src/modbus.cpp | 25 +++++------ src/mqtt.cpp | 102 +++++++++++++++++++------------------------- 7 files changed, 108 insertions(+), 150 deletions(-) diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index f97da53e..6ee3090d 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -39,17 +39,16 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): DoReboot(false // try to start the server if wifi is connected, otherwise wait for wifi connection if (mqtt->GetConnectStatusWifi()) { server->begin(); - dbg.println(F("WebServer has been started ...")); + Config->log(1, "WebServer has been started ..."); } else { mqtt->improvSerial.onImprovConnected(std::bind(&MyWebServer::onImprovWiFiConnectedCb, this, std::placeholders::_1, std::placeholders::_2)); } } -void MyWebServer::onImprovWiFiConnectedCb(const char *ssid, const char *password) -{ +void MyWebServer::onImprovWiFiConnectedCb(const char *ssid, const char *password) { server->begin(); - dbg.println(F("WebServer has been started now ...")); + Config->log(1, "WebServer has been started now ..."); } void MyWebServer::loop() { @@ -57,10 +56,10 @@ void MyWebServer::loop() { if (this->DoReboot) { if (this->RequestRebootTime == 0) { this->RequestRebootTime = millis(); - dbg.println("Request to Reboot, wait 5sek ..."); + Config->log(1, "Request to Reboot, wait 5sek ..."); } if (millis() - this->RequestRebootTime > 5000) { // wait 3sek until reboot - dbg.println("Rebooting..."); + Config->log(1, "Rebooting..."); ESP.restart(); } } @@ -87,18 +86,16 @@ void MyWebServer::handleReboot(AsyncWebServerRequest *request) { } void MyWebServer::handleReset(AsyncWebServerRequest *request) { - if (Config->GetDebugLevel() >= 3) { dbg.println("deletion of all config files was requested ...."); } + Config->log(3, "deletion of all config files was requested ...."); //LittleFS.format(); // Werkszustand -> nur die config dateien loeschen, die register dateien muessen erhalten bleiben - File root = LittleFS.open("/"); + File root = LittleFS.open("/config/"); File file = root.openNextFile(); while(file){ - String path("/"); path.concat(file.name()); - if (path.indexOf(".json") == -1) {dbg.println("Continue"); file = root.openNextFile(); continue;} + String path("/config/"); path.concat(file.name()); + if (path.indexOf(".json") == -1) {file = root.openNextFile(); continue;} file.close(); bool f = LittleFS.remove(path); - if (Config->GetDebugLevel() >= 3) { - dbg.printf("deletion of configuration file '%s' %s\n", file.name(), (f?"was successful":"has failed"));; - } + Config->log(3, "deletion of configuration file '%s' %s", file.name(), (f?"was successful":"has failed")); file = root.openNextFile(); } root.close(); @@ -117,22 +114,7 @@ void MyWebServer::handleWiFiReset(AsyncWebServerRequest *request) { } void MyWebServer::handleGetItemJson(AsyncWebServerRequest *request) { - /* - AsyncResponseStream *response = request->beginResponseStream("application/json"); - response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - response->addHeader("Pragma", "no-cache"); - response->addHeader("Expires", "-1"); - - mb->GetLiveDataAsJson(response, ""); - - request->send(response); - */ - mb->GetLiveDataAsJson(request); - - - - } void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { @@ -161,9 +143,9 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { JsonDocument jsonGet; // TODO Use computed size?? DeserializationError error = deserializeJson(jsonGet, json.c_str()); - if (Config->GetDebugLevel() >=4) { dbg.print("Ajax Json Empfangen: "); } + Config->log(4, "Ajax Json Empfangen: "); if (!error) { - if (Config->GetDebugLevel() >=4) { serializeJsonPretty(jsonGet, dbg); dbg.println(); } + Config->log(4, jsonGet); if (jsonGet["action"]) {action = jsonGet["action"].as();} if (jsonGet["subaction"]){subaction = jsonGet["subaction"].as();} @@ -192,9 +174,7 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { serializeJson(jsonReturn, ret); response->print(ret); - if (Config->GetDebugLevel() >=2) { - dbg.println(FPSTR(buffer)); - } + Config->log(4, buffer); return; @@ -248,12 +228,10 @@ void MyWebServer::handleAjax(AsyncWebServerRequest *request) { serializeJson(jsonReturn, ret); response->print(ret); - if (Config->GetDebugLevel() >=1) { - dbg.println(buffer); - } + Config->log(1, buffer); } - if (Config->GetDebugLevel() >=4) { dbg.print("Ajax Json Antwort: "); dbg.println(ret); } + Config->log(4, "Ajax Json Antwort: ", ret); request->send(response); } diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index d5b212d2..38724d58 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -37,7 +37,7 @@ void BaseConfig::LoadJsonConfig() { DeserializationError error = deserializeJson(doc, configFile); if (!error && doc["data"]) { - serializeJsonPretty(doc, dbg); + this->log(1, doc); if (doc["data"]["mqttroot"]) { this->mqtt_root = doc["data"]["mqttroot"].as();} else {this->mqtt_root = "solax";} if (doc["data"]["mqttserver"]) { this->mqtt_server = doc["data"]["mqttserver"].as();} else {this->mqtt_server = "test.mosquitto.org";} @@ -139,4 +139,16 @@ void BaseConfig::log(const int loglevel, const char* format, ...) { Serial.println(buffer); #endif va_end(args); +} + +void BaseConfig::log(const int loglevel, const JsonDocument& json) { + if (this->GetDebugLevel() < loglevel) return; + + #ifdef USE_WEBSERIAL + serializeJsonPretty(json, WebSerial); + WebSerial.println(); + #else + serializeJsonPretty(json, Serial); + Serial.println(); + #endif } \ No newline at end of file diff --git a/src/baseconfig.h b/src/baseconfig.h index a4d07043..ebede46e 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -19,6 +19,7 @@ class BaseConfig { * @param ... the arguments */ void log(const int loglevel, const char* format, ...); + void log(const int loglevel, const JsonDocument& json); const String& GetMqttServer() const {return mqtt_server;} const uint16_t& GetMqttPort() const {return mqtt_port;} diff --git a/src/handleFiles.cpp b/src/handleFiles.cpp index 9b6c9d00..df0b5f15 100644 --- a/src/handleFiles.cpp +++ b/src/handleFiles.cpp @@ -55,9 +55,7 @@ void handleFiles::HandleAjaxRequest(JsonDocument& jsonGet, AsyncResponseStream* String subaction = ""; if (jsonGet["subaction"]) {subaction = jsonGet["subaction"].as();} - if (Config->GetDebugLevel() >= 3) { - dbg.printf("handle Ajax Request in handleFiles.cpp: %s\n", subaction.c_str()); - } + Config->log(3, "handle Ajax Request in handleFiles.cpp: %s", subaction.c_str()); if (subaction == "listDir") { JsonDocument doc; @@ -66,18 +64,15 @@ void handleFiles::HandleAjaxRequest(JsonDocument& jsonGet, AsyncResponseStream* this->getDirList(&content, "/"); String ret(""); serializeJson(content, ret); - if (Config->GetDebugLevel() >= 5) { - serializeJsonPretty(content, dbg); - dbg.println(); - } + Config->log(5, content); + response->print(ret); } else if (subaction == "deleteFile") { String filename(""), ret(""); JsonDocument jsonReturn; - if (Config->GetDebugLevel() >=3) { - dbg.printf("Request to delete file %s", filename.c_str()); - } + Config->log(3, "Request to delete file %s", filename.c_str()); + if (jsonGet["filename"]) {filename = jsonGet["filename"].as();} if (LittleFS.remove(filename)) { @@ -87,9 +82,8 @@ void handleFiles::HandleAjaxRequest(JsonDocument& jsonGet, AsyncResponseStream* jsonReturn["response_status"] = 0; jsonReturn["response_text"] = "deletion failed"; } - if (Config->GetDebugLevel() >=3) { - serializeJson(jsonReturn, Serial);dbg.println(); - } + Config->log(3, jsonReturn); + serializeJson(jsonReturn, ret); response->print(ret); } @@ -100,32 +94,24 @@ void handleFiles::HandleAjaxRequest(JsonDocument& jsonGet, AsyncResponseStream* //############################################################### void handleFiles::handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - if (Config->GetDebugLevel() >=5) { - dbg.printf("Client: %s %s\n", request->client()->remoteIP().toString().c_str(), request->url().c_str());; - } - + Config->log(5, "Client: %s %s", request->client()->remoteIP().toString().c_str(), request->url().c_str());; + if (!index) { // open the file on first call and store the file handle in the request object request->_tempFile = LittleFS.open(filename, "w"); - if (Config->GetDebugLevel() >=5) { - dbg.printf("Upload Start: %s\n", filename.c_str()); - } + Config->log(5, "Upload Start: %s", filename.c_str()); } if (len) { // stream the incoming chunk to the opened file request->_tempFile.write(data, len); - if (Config->GetDebugLevel() >=5) { - dbg.printf("Writing file: %s ,index=%d len=%d bytes, FreeMem: %d\n", filename.c_str(), index, len, ESP.getFreeHeap()); - } + Config->log(3, "Writing file: %s ,index=%d len=%d bytes, FreeMem: %d", filename.c_str(), index, len, ESP.getFreeHeap()); } if (final) { // close the file handle as the upload is now done request->_tempFile.close(); - if (Config->GetDebugLevel() >=3) { - dbg.printf("Upload Complete: %s ,size: %d Bytes\n", filename.c_str(), (index + len)); - } + Config->log(3, "Upload Complete: %s ,size: %d Bytes", filename.c_str(), (index + len)); AsyncResponseStream *response = request->beginResponseStream("text/json"); response->addHeader("Server","ESP Async Web Server"); diff --git a/src/main.cpp b/src/main.cpp index 0c4824ef..77bf91c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,32 +26,25 @@ MyWebServer* mywebserver = NULL; void myMQTTCallBack(char* topic, byte* payload, unsigned int length) { String msg; - if (Config->GetDebugLevel() >=3) { - dbg.print("Message arrived ["); dbg.print(topic); dbg.print("] "); - } + Config->log(3, "Message arrived [%s]", topic); for (unsigned int i = 0; i < length; i++) { msg.concat((char)payload[i]); } - if (Config->GetDebugLevel() >=3) { - dbg.print("Message: "); dbg.println(msg.c_str()); - } - + Config->log(3, "Message: %s", msg.c_str()); + mb->ReceiveMQTT(topic, atoi(msg.c_str())); } void setup() { Serial.begin(115200); - - dbg.println("Start of Modbus-RTU MQTT Gateway"); - dbg.println("Starting BaseConfig"); Config = new BaseConfig(); #ifndef USE_WEBSERIAL - dbg.begin(115200, SERIAL_8N1, Config->GetSerialRx(), Config->GetSerialTx()); // RX, TX, zb.: 33, 32 - dbg.println(""); - dbg.println("ready"); + Serial.begin(115200, SERIAL_8N1, Config->GetSerialRx(), Config->GetSerialTx()); // RX, TX, zb.: 33, 32 + Serial.println(""); + Serial.println("ready"); #endif #ifdef USE_WEBSERIAL @@ -59,8 +52,11 @@ void setup() { WebSerial.begin(&server); WebSerial.setBuffer(100); #endif + + Config->log(1, "Start of Modbus-RTU MQTT Gateway"); + Config->log(1, "Starting BaseConfig"); - dbg.println("Starting Wifi and MQTT"); + Config->log(1, "Starting Wifi and MQTT"); mqtt = new MQTT(&server, &dns, Config->GetMqttServer().c_str(), Config->GetMqttPort(), @@ -74,7 +70,7 @@ void setup() { mb = new modbus(); mb->enableMqtt(mqtt); - dbg.println("attempting to start WebServer"); + Config->log(1, "attempting to start WebServer"); mywebserver = new MyWebServer(&server, &dns); } diff --git a/src/modbus.cpp b/src/modbus.cpp index acf5b065..ff1535fd 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -97,7 +97,7 @@ String modbus::GetMqttSetTopic(String command) { char s[100] = {0}; memset(s, 0, sizeof(s)); - snprintf(s, sizeof(dbg), "%s/%s/set/%s", Config->GetMqttBasePath().c_str(), Config->GetMqttRoot().c_str(), command.c_str()); + snprintf(s, sizeof(s), "%s/%s/set/%s", Config->GetMqttBasePath().c_str(), Config->GetMqttRoot().c_str(), command.c_str()); return (String)s; } @@ -121,7 +121,7 @@ void modbus::GenerateMqttSubscriptions() { if (!error) { // Print the result Config->log(4, "parsing JSON for data ok"); - if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + Config->log(5, elem); if(!elem["name"].isNull() && elem["request"].is()) { subscription_t s = {}; @@ -243,11 +243,8 @@ void modbus::LoadInverterConfigFromJson() { if (error) { Config->log(1, "Error: unable to read configdata for inverter %s: %s", this->InverterType.name.c_str(), error.c_str()); } else { - if (Config->GetDebugLevel() >=4) { - Config->log(4, "Read config data for inverter %s", this->InverterType.name.c_str()); - serializeJsonPretty(doc, dbg); - dbg.println(); - } + Config->log(4, "Read config data for inverter %s", this->InverterType.name.c_str()); + Config->log(4, doc); } //this->Conf_LiveDataFunctionCode = this->String2Byte(doc[this->InverterType.name]["config"]["LiveDataFunctionCode"].as()); @@ -662,7 +659,7 @@ void modbus::ParseData() { if (!error) { // Print the result Config->log(4, "parsing JSON ok"); - if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + Config->log(5, elem); } else { Config->log(1, "(Function ParseData) Failed to parse JSON Register Data: %s", error.c_str()); } @@ -956,7 +953,7 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { Config->log(4, "[GetLiveDataAsJson] Json command empfangen: "); if (!error) { - if (Config->GetDebugLevel() >=4) { serializeJsonPretty(jsonGet, dbg); dbg.println(); } + Config->log(4, jsonGet); if (jsonGet["subaction"]){subaction = jsonGet["subaction"].as();} @@ -1068,7 +1065,7 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { if (!error) { // Print the result Config->log(4, "parsing JSON ok"); - if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + Config->log(5, elem); } else { Config->log(4, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); } @@ -1095,7 +1092,7 @@ void modbus::GetRegisterAsJson(AsyncResponseStream *response) { if (!error) { // Print the result Config->log(4, "parsing JSON ok"); - if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + Config->log(5, elem); } else { Config->log(1, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); } @@ -1189,7 +1186,7 @@ void modbus::LoadRegItems(std::vector* vector, String type) { if (!error) { // Print the result Config->log(4, "parsing JSON ok"); - if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + Config->log(5, elem); } else { Config->log(1, "(Function LoadRegItems) Failed to parse JSON Register Data for Inverter <%s> and type <%s>: %s", this->InverterType.name.c_str(), type.c_str(), error.c_str()); } @@ -1251,7 +1248,7 @@ void modbus::LoadJsonConfig(bool firstrun) { DeserializationError error = deserializeJson(doc, configFile); if (!error && doc["data"]) { - if (Config->GetDebugLevel() >=3) { serializeJsonPretty(doc, dbg); dbg.println(); } + Config->log(3, doc); OpenWB->clearMappings(); if (doc["data"]["pin_rx"]) { this->pin_RX = (int)(doc["data"]["pin_rx"]);} else {this->pin_RX = this->default_pin_RX;} @@ -1366,7 +1363,7 @@ void modbus::LoadJsonItemConfig() { if (!error) { // Print the result Config->log(4, "parsing JSON ok"); - if (Config->GetDebugLevel() >=5) {serializeJsonPretty(elem, dbg);} + Config->log(5, elem); } else { Config->log(1, "(Function LoadJsonItemConfig) Failed to parse JSON Register Data: %s", error.c_str()); } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 1b8164ac..a32d27bb 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -18,9 +18,7 @@ MQTT::MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint1 WiFi.onEvent(std::bind(&MQTT::WifiOnEvent, this, std::placeholders::_1)); #endif - if (Config->GetDebugLevel() >=3) { - dbg.printf("Go into %s Mode\n", (Config->GetUseETH()?"ETH":"Wifi")); - } + Config->log(3, "Go into %s Mode", (Config->GetUseETH()?"ETH":"Wifi")); ImprovTypes::ChipFamily variant; @@ -67,7 +65,7 @@ MQTT::MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint1 if (Config->GetDebugLevel() >=4) WiFi.printDiag(dbg); - dbg.printf("Initializing MQTT (%s:%d)\n", Config->GetMqttServer().c_str(), Config->GetMqttPort()); + Config->log(1, "Initializing MQTT (%s:%d)", Config->GetMqttServer().c_str(), Config->GetMqttPort()); espClient = WiFiClient(); PubSubClient::setClient(espClient); @@ -87,97 +85,97 @@ void MQTT::onImprovWiFiErrorCb(ImprovTypes::Error err) #ifdef ESP32 void MQTT::WifiOnEvent(WiFiEvent_t event) { - if (Config->GetDebugLevel()>=4) {dbg.printf("[WiFi-event] event: %d\n", event);} + Config->log(4, "[WiFi-event] event: %d", event); switch (event) { case ARDUINO_EVENT_WIFI_READY: - dbg.println("WiFi interface ready"); + Config->log(1, "WiFi interface ready"); break; case ARDUINO_EVENT_WIFI_SCAN_DONE: - dbg.println("Completed scan for access points"); + Config->log(1, "Completed scan for access points"); break; case ARDUINO_EVENT_WIFI_STA_START: - dbg.println("WiFi client started"); + Config->log(1, "WiFi client started"); break; case ARDUINO_EVENT_WIFI_STA_STOP: - dbg.println("WiFi clients stopped"); + Config->log(1, "WiFi clients stopped"); break; case ARDUINO_EVENT_WIFI_STA_CONNECTED: - dbg.println("Connected to access point"); + Config->log(1, "Connected to access point"); break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - dbg.println("Disconnected from WiFi access point"); + Config->log(1, "Disconnected from WiFi access point"); this->ConnectStatusWifi = false; break; case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - dbg.println("Authentication mode of access point has changed"); + Config->log(1, "Authentication mode of access point has changed"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP: - dbg.printf("WiFi connected with local IP: %s\n", WiFi.localIP().toString().c_str()); + Config->log(1, "WiFi connected with local IP: %s", WiFi.localIP().toString().c_str()); this->ipadresse = WiFi.localIP(); this->ConnectStatusWifi = true; break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: - dbg.println("Lost IP address and IP address is reset to 0"); + Config->log(1, "Lost IP address and IP address is reset to 0"); this->ConnectStatusWifi = false; this->ipadresse = (0,0,0,0); break; case ARDUINO_EVENT_WPS_ER_SUCCESS: - dbg.println("WiFi Protected Setup (WPS): succeeded in enrollee mode"); + Config->log(1, "WiFi Protected Setup (WPS): succeeded in enrollee mode"); break; case ARDUINO_EVENT_WPS_ER_FAILED: - dbg.println("WiFi Protected Setup (WPS): failed in enrollee mode"); + Config->log(1, "WiFi Protected Setup (WPS): failed in enrollee mode"); break; case ARDUINO_EVENT_WPS_ER_TIMEOUT: - dbg.println("WiFi Protected Setup (WPS): timeout in enrollee mode"); + Config->log(1, "WiFi Protected Setup (WPS): timeout in enrollee mode"); break; case ARDUINO_EVENT_WPS_ER_PIN: - dbg.println("WiFi Protected Setup (WPS): pin code in enrollee mode"); + Config->log(1, "WiFi Protected Setup (WPS): pin code in enrollee mode"); break; case ARDUINO_EVENT_WIFI_AP_START: - dbg.println("WiFi access point started"); + Config->log(1, "WiFi access point started"); break; case ARDUINO_EVENT_WIFI_AP_STOP: - dbg.println("WiFi access point stopped"); + Config->log(1, "WiFi access point stopped"); break; case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - dbg.println("Client connected"); + Config->log(1, "Client connected"); break; case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - dbg.println("Client disconnected"); + Config->log(1, "Client disconnected"); break; case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: - dbg.println("Assigned IP address to client"); + Config->log(1, "Assigned IP address to client"); break; case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: - dbg.println("Received probe request"); + Config->log(1, "Received probe request"); break; case ARDUINO_EVENT_WIFI_AP_GOT_IP6: - dbg.println("AP IPv6 is preferred"); + Config->log(1, "AP IPv6 is preferred"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - dbg.println("STA IPv6 is preferred"); + Config->log(1, "STA IPv6 is preferred"); break; case ARDUINO_EVENT_ETH_GOT_IP6: - dbg.println("Ethernet IPv6 is preferred"); + Config->log(1, "Ethernet IPv6 is preferred"); break; case ARDUINO_EVENT_ETH_START: - dbg.println("Ethernet started"); + Config->log(1, "Ethernet started"); break; case ARDUINO_EVENT_ETH_STOP: - dbg.println("Ethernet stopped"); + Config->log(1, "Ethernet stopped"); break; case ARDUINO_EVENT_ETH_CONNECTED: - dbg.println("Ethernet connected"); + Config->log(1, "Ethernet connected"); break; case ARDUINO_EVENT_ETH_DISCONNECTED: - dbg.println("Ethernet disconnected"); + Config->log(1, "Ethernet disconnected"); this->ConnectStatusWifi = false; this->ipadresse = (0,0,0,0); break; case ARDUINO_EVENT_ETH_GOT_IP: if (!this->ConnectStatusWifi) { - dbg.printf("ETH MAC: %s, IPv4: %s, %s, Mbps: %d\n", + Config->log(1, "ETH MAC: %s, IPv4: %s, %s, Mbps: %d", ETH.macAddress().c_str(), ETH.localIP().toString().c_str(), (ETH.fullDuplex()?"FULL_DUPLEX":"HALF_DUPLEX"), @@ -208,7 +206,7 @@ eth_shield_t* MQTT::GetEthShield(String ShieldName) { void MQTT::WaitForConnect() { while (!this->ConnectStatusWifi) delay(100); - dbg.println("Wait for connect"); + Config->log(1, "Wait for connect"); //yield(); } @@ -225,10 +223,10 @@ void MQTT::reconnect() { } snprintf(LWT, sizeof(LWT), "%s/state", this->mqtt_root.c_str()); - dbg.printf("Attempting MQTT connection as %s \n", topic); + Config->log(1, "Attempting MQTT connection as %s ", topic); if (PubSubClient::connect(topic, Config->GetMqttUsername().c_str(), Config->GetMqttPassword().c_str(), LWT, true, false, "Offline")) { - dbg.println("connected... "); + Config->log(1, "connected... "); // Once connected, publish basics ... this->Publish_IP(); this->Publish_String("ssid", WiFi.SSID(), false); @@ -238,13 +236,11 @@ void MQTT::reconnect() { // ... and resubscribe if needed for (uint8_t i=0; i< this->subscriptions->size(); i++) { PubSubClient::subscribe(this->subscriptions->at(i).c_str()); - dbg.print("MQTT resubscribed to: "); dbg.println(this->subscriptions->at(i).c_str()); + Config->log(1, "MQTT resubscribed to: %s", this->subscriptions->at(i).c_str()); } } else { - dbg.print(F("failed, rc=")); - dbg.print(PubSubClient::state()); - dbg.println(F(" try again in few seconds")); + Config->log(1, "failed, rc=%d - Trying again in 5 seconds", PubSubClient::state()); } } @@ -277,10 +273,8 @@ void MQTT::Publish_String(const char* subtopic, String value, bool fulltopic) { if (PubSubClient::connected()) { PubSubClient::publish((const char*)topic.c_str(), value.c_str(), true); - if (Config->GetDebugLevel() >=3) { - dbg.printf("Publish %s: %s \n", topic.c_str(), value.c_str()); - } - } else { if (Config->GetDebugLevel() >=2) {dbg.println(F("Request for MQTT Publish, but not connected to Broker")); }} + Config->log(3, "Publish %s: %s ", topic.c_str(), value.c_str()); + } else Config->log(2, "Request for MQTT Publish, but not connected to Broker"); } String MQTT::getTopic(String subtopic, bool fulltopic) { @@ -304,9 +298,7 @@ void MQTT::Subscribe(String topic) { this->subscriptions->push_back(topic); if (PubSubClient::connected()) { PubSubClient::subscribe(topic.c_str()); - if (Config->GetDebugLevel() >=3) { - dbg.printf("MQTT now subscribed to: %s\n", topic.c_str()); - } + Config->log(3, "MQTT now subscribed to: %s", topic.c_str()); } } @@ -317,9 +309,7 @@ bool MQTT::UnSubscribe(String topic) { if (PubSubClient::connected()) { PubSubClient::unsubscribe(this->subscriptions->at(i).c_str()); } - if (Config->GetDebugLevel()>=3) { - dbg.printf("MQTT unsubscribed from: %s\n", this->subscriptions->at(i).c_str()); - } + Config->log(3, "MQTT unsubscribed from: %s", this->subscriptions->at(i).c_str()); this->subscriptions->erase(this->subscriptions->begin()+i); ret = true; break; @@ -352,19 +342,17 @@ void MQTT::loop() { #endif if (this->mqtt_root != Config->GetMqttRoot()) { - if (Config->GetDebugLevel() >=3) { - dbg.printf("MQTT DeviceName has changed via Web Configuration from %s to %s \n", this->mqtt_root.c_str(), Config->GetMqttRoot().c_str()); - dbg.println(F("Initiate Reconnect")); - } + Config->log(3, "MQTT DeviceName has changed via Web Configuration from %s to %s ", this->mqtt_root.c_str(), Config->GetMqttRoot().c_str()); + Config->log(3, "Initiate Reconnect"); + this->mqtt_root = Config->GetMqttRoot(); if (PubSubClient::connected()) PubSubClient::disconnect(); } if (this->mqtt_basepath != Config->GetMqttBasePath()) { - if (Config->GetDebugLevel() > 3) { - dbg.printf("MQTT Basepath has changed via Web Configuration from %s to %s \n", this->mqtt_basepath.c_str(), Config->GetMqttBasePath().c_str()); - dbg.println(F("Initiate Reconnect")); - } + Config->log(3, "MQTT Basepath has changed via Web Configuration from %s to %s ", this->mqtt_basepath.c_str(), Config->GetMqttBasePath().c_str()); + Config->log(3, "Initiate Reconnect"); + this->mqtt_basepath = Config->GetMqttBasePath(); if (PubSubClient::connected()) PubSubClient::disconnect(); } From 8494d236c1ece67d4e3ebdb50d54b92ef645add3 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 4 Jan 2025 14:29:42 +0100 Subject: [PATCH 036/106] fix register id definition (#113) --- data/regs/Deye_SUN_SG04LP3.json | 4 ++-- data/regs/Growatt-SPH.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/regs/Deye_SUN_SG04LP3.json b/data/regs/Deye_SUN_SG04LP3.json index 7bb3cab9..a5c0298a 100644 --- a/data/regs/Deye_SUN_SG04LP3.json +++ b/data/regs/Deye_SUN_SG04LP3.json @@ -1240,8 +1240,8 @@ 15, 16 ], - "name": "SN", - "realname": "SerialNumber", + "name": "InverterSN", + "realname": "Inverter SerialNumber", "datatype": "string" } ] diff --git a/data/regs/Growatt-SPH.json b/data/regs/Growatt-SPH.json index ea669adb..1bebbb8a 100644 --- a/data/regs/Growatt-SPH.json +++ b/data/regs/Growatt-SPH.json @@ -7,7 +7,7 @@ ["#ClientID", "0x04", "0x03", "0xE8", "0x00", "0x77"] ], "RequestIdData": [ - ["#ClientID", "0x03", "0x00", "0x00", "0x00", "0x14"] + ["#ClientID", "0x03", "0x00", "0x00", "0x00", "0x20"] ], "ClientIdPos": 0, "LiveDataFunctionCodePos": 1, From f2a3c14b0680c045da4887b6790f677b5b455ce8 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 4 Jan 2025 14:56:25 +0100 Subject: [PATCH 037/106] remove unnecessary webserver in mqtt class --- src/mqtt.cpp | 4 +--- src/mqtt.h | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index a32d27bb..2f1cc479 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -1,8 +1,6 @@ #include "mqtt.h" -MQTT::MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot, char* APName, char* APpassword): - server(server), - dns(dns), +MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot, char* APName, char* APpassword): improvSerial(&Serial), mqtt_root(MqttRoot), mqtt_basepath(MqttBasepath), diff --git a/src/mqtt.h b/src/mqtt.h index 50e72e26..ed991a99 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -5,7 +5,7 @@ #include #include #include -#include "baseconfig.h" +#include #ifdef ESP8266 //#define SetHostName(x) wifi_station_set_hostname(x); @@ -47,7 +47,7 @@ class MQTT: PubSubClient { public: - MQTT(AsyncWebServer* server, DNSServer *dns, const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot, char* APName, char* APpassword); + MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot, char* APName, char* APpassword); void loop(); void Publish_Bool(const char* subtopic, bool b, bool fulltopic); void Publish_Int(const char* subtopic, int number, bool fulltopic); @@ -74,8 +74,6 @@ class MQTT: PubSubClient { void reconnect(); private: - AsyncWebServer* server; - DNSServer* dns; WiFiClient espClient; std::vector* subscriptions = NULL; @@ -95,7 +93,6 @@ class MQTT: PubSubClient { void WaitForConnect(); void onImprovWiFiErrorCb(ImprovTypes::Error err); eth_shield_t* GetEthShield(String ShieldName); - }; extern MQTT* mqtt; From 6c4918a3cbc38720ddf7028795780347e6d627bb Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 4 Jan 2025 14:57:38 +0100 Subject: [PATCH 038/106] remove unnecessary webserver in mqtt class --- src/main.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 77bf91c0..ec89993b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,8 +57,7 @@ void setup() { Config->log(1, "Starting BaseConfig"); Config->log(1, "Starting Wifi and MQTT"); - mqtt = new MQTT(&server, &dns, - Config->GetMqttServer().c_str(), + mqtt = new MQTT(Config->GetMqttServer().c_str(), Config->GetMqttPort(), Config->GetMqttBasePath().c_str(), Config->GetMqttRoot().c_str(), @@ -69,7 +68,7 @@ void setup() { mb = new modbus(); mb->enableMqtt(mqtt); - + Config->log(1, "attempting to start WebServer"); mywebserver = new MyWebServer(&server, &dns); } From adddfc81674b75107c50d1b8164b66956ebc5c49 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 4 Jan 2025 15:36:46 +0100 Subject: [PATCH 039/106] handle out-of-range crash (#96 #113) --- src/modbus.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index ff1535fd..ad5eec17 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -548,7 +548,7 @@ int modbus::JsonPosArrayToInt(JsonArray posArray, JsonArray posArray2) { if (v < this->DataFrame->size()) { val_i = (val_i << 8) | this->DataFrame->at(v); val_max = (val_max << 8) | 0xFF; - } + } else Config->log(1, "Error: position %d out of dataframe range", v); } } @@ -557,7 +557,7 @@ int modbus::JsonPosArrayToInt(JsonArray posArray, JsonArray posArray2) { for(uint16_t w : posArray2) { if (w < this->DataFrame->size()) { val2_i = (val2_i << 8) | this->DataFrame->at(w); - } + } else Config->log(1, "Error: position %d out of dataframe range", w); } } @@ -728,20 +728,20 @@ void modbus::ParseData() { posArray2 = elem["position2"].as(); } - // check if item is active to send data out via mqtt + // check if LiveData item is active to send data out via mqtt for (uint16_t i=0; i < this->InverterLiveData->size(); i++) { if (this->InverterLiveData->at(i).Name == d.Name && this->InverterLiveData->at(i).active) { IsActiveItem = true; } } - //Lazgar - // check if item is active to send data out via mqtt + + // check if ID-Data item is active to send data out via mqtt for (uint16_t i=0; i < this->InverterIdData->size(); i++) { if (this->InverterIdData->at(i).Name == d.Name && this->InverterIdData->at(i).active) { IsActiveItem = true; } } - //Lazgar + // ************* processing data ****************** if (datatype == "float") { //********** handle Datatype FLOAT ***********// @@ -761,8 +761,10 @@ void modbus::ParseData() { char buffer[posArray.size()+1]; uint8_t i=0; for(int v : posArray) { - buffer[i] = static_cast(DataFrame->at(v)); - i++; + if (v < DataFrame->size()) { + buffer[i] = static_cast(DataFrame->at(v)); + i++; + } else Config->log(1, "Error: for item '%s' position %d out of dataframe range", d.Name.c_str(), v); } buffer[i] = '\0'; d.value = String(buffer); From 2014a6fb0ae98a4932939f0594381476741768f4 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 4 Jan 2025 17:33:54 +0100 Subject: [PATCH 040/106] correct unit from kVarh to kWh in Deye_SUN_SG04LP3.json --- data/regs/Deye_SUN_SG04LP3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/regs/Deye_SUN_SG04LP3.json b/data/regs/Deye_SUN_SG04LP3.json index a5c0298a..d291499b 100644 --- a/data/regs/Deye_SUN_SG04LP3.json +++ b/data/regs/Deye_SUN_SG04LP3.json @@ -99,7 +99,7 @@ "realname": "Reactive Power Generation of Today", "datatype": "float", "factor": 0.1, - "unit": "kVarh" + "unit": "kWh" }, { "position": [ From f7ffcc325e5f14eafd29f176be2aa460b5ad8b00 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 5 Jan 2025 15:56:58 +0100 Subject: [PATCH 041/106] add bitwise mappings --- src/modbus.cpp | 22 ++++++++++++++++++++-- src/modbus.h | 1 + 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index ad5eec17..917d0bc7 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -749,7 +749,7 @@ void modbus::ParseData() { sprintf(buffer, "%.2f", val_f); d.value = String(buffer); - } else if (datatype == "integer") { + } else if (datatype == "integer" || datatype == "bitwise") { //********** handle Datatype Integer ***********// val_i = (this->JsonPosArrayToInt(posArray, posArray2) * factor) + valueAdd; sprintf(buffer, "%d", val_i); @@ -781,7 +781,8 @@ void modbus::ParseData() { Config->log(4, "Map values for item %s", d.Name.c_str()); JsonArray map = elem["mapping"].as(); - d.value = this->MapItem(map, d.value); + if (datatype == "bitwise") d.value = this->MapBitwise(map, d.value); + else d.value = this->MapItem(map, d.value); } Config->log(4, "Data: %s -> %s %s", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); @@ -824,6 +825,23 @@ void modbus::ParseData() { this->DataFrame->clear(); } +String modbus::MapBitwise(JsonArray map, String value) { + String ret(""); + + for (uint8_t i=0; i()) { + //Serial.printf("Check Bitwise map: %s -> %s - %s \n", String(value[i]).c_str(), map[i][0].as().c_str(), map[i][1].as().c_str()); + if (String(value[i]) == map[i][0].as()) { + Serial.printf("Mapped Bitwise value: %s -> %s\n", String(value[i]).c_str(), map[i][1].as().c_str()); + if (ret.length() > 0) ret += ","; + ret += map[i][1].as(); + } + } + } + return ret; +} + /******************************************************* * Map a value to a predefined constant string *******************************************************/ diff --git a/src/modbus.h b/src/modbus.h index d0e056dd..9b728d0e 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -119,6 +119,7 @@ class modbus { void ChangeRegItem(std::vector* vector, reg_t item); void LoadRegItems(std::vector* vector, String type); String MapItem(JsonArray map, String value); + String MapBitwise(JsonArray map, String value); void ReadRelays(); // inverter config, in sync with register.h ->config From 1d31efe496797d1a1b22c2e115484962e37e5c36 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 6 Jan 2025 06:15:53 +0100 Subject: [PATCH 042/106] datatype "binary" now available for json register definitions (PR #115) --- ChangeLog.md | 1 + src/modbus.cpp | 41 ++++++++++++++++++++++++++++------------- src/modbus.h | 1 + 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index e604f311..3e54b7c1 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,5 @@ Release 3.3.1: + - new Feature: datatype "binary" now available for json register definitions (PR #115) - BugFix: fix null-terminationof string handling (#96) - new feature: support for OpenWB 2.0 Api (#100) - bugfix: fix esp crash for /getitems if using an huge register table (#76) diff --git a/src/modbus.cpp b/src/modbus.cpp index 917d0bc7..e8b084eb 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -749,12 +749,17 @@ void modbus::ParseData() { sprintf(buffer, "%.2f", val_f); d.value = String(buffer); - } else if (datatype == "integer" || datatype == "bitwise") { + } else if (datatype == "integer") { //********** handle Datatype Integer ***********// val_i = (this->JsonPosArrayToInt(posArray, posArray2) * factor) + valueAdd; sprintf(buffer, "%d", val_i); d.value = String(buffer); + } else if (datatype == "binary") { + //********** handle Datatype Integer ***********// + val_i = (this->JsonPosArrayToInt(posArray, posArray2) * factor) + valueAdd; + d.value = this->ConvertIntToBinaryString(val_i, posArray.size() * 8); + } else if (datatype == "string") { //********** handle Datatype String ***********// if (!posArray.isNull()) { @@ -781,7 +786,7 @@ void modbus::ParseData() { Config->log(4, "Map values for item %s", d.Name.c_str()); JsonArray map = elem["mapping"].as(); - if (datatype == "bitwise") d.value = this->MapBitwise(map, d.value); + if (datatype == "binary") d.value = this->MapBitwise(map, d.value); else d.value = this->MapItem(map, d.value); } @@ -825,20 +830,33 @@ void modbus::ParseData() { this->DataFrame->clear(); } +String modbus::ConvertIntToBinaryString(int n, int numBits) { + String binaryString = ""; + binaryString.reserve(numBits); + + for (int i = numBits - 1; i >= 0; i--) { + binaryString += ((n >> i) & 1) ? "1" : "0"; + } + return binaryString; +} + +/******************************************************* + * Map a Binary to a predefined constant string +*******************************************************/ String modbus::MapBitwise(JsonArray map, String value) { String ret(""); for (uint8_t i=0; i()) { - //Serial.printf("Check Bitwise map: %s -> %s - %s \n", String(value[i]).c_str(), map[i][0].as().c_str(), map[i][1].as().c_str()); - if (String(value[i]) == map[i][0].as()) { - Serial.printf("Mapped Bitwise value: %s -> %s\n", String(value[i]).c_str(), map[i][1].as().c_str()); - if (ret.length() > 0) ret += ","; - ret += map[i][1].as(); - } + if (String(value[i]) == "1") { + Config->log(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[i].as().c_str()); + if (ret.length() > 0) ret += ", "; + if (map[i]) ret += map[i].as(); + else ret += "undefined"; } } + + // if nothing found, set default value (is last item in array) + if (ret.length() == 0) ret = map[map.size() - 1].as(); return ret; } @@ -852,9 +870,6 @@ String modbus::MapItem(JsonArray map, String value) { String v1 = mapItem[0].as(); String v2 = mapItem[1].as(); - - Config->log(5, "Check Map value: %s -> %s", v1.c_str(), v2.c_str()); - if (value == v1) { ret = v2; Config->log(4, "Mapped value: %s -> %s", v1.c_str(), v2.c_str()); diff --git a/src/modbus.h b/src/modbus.h index 9b728d0e..438911c2 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -120,6 +120,7 @@ class modbus { void LoadRegItems(std::vector* vector, String type); String MapItem(JsonArray map, String value); String MapBitwise(JsonArray map, String value); + String ConvertIntToBinaryString(int n, int numBits); void ReadRelays(); // inverter config, in sync with register.h ->config From b5fb2d29368a5c2f048ee5d9ec1662b44f42020c Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 6 Jan 2025 06:24:04 +0100 Subject: [PATCH 043/106] fix openwb topic (#114) --- data/regs/Growatt-SPH.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/regs/Growatt-SPH.json b/data/regs/Growatt-SPH.json index 1bebbb8a..a4c4aa56 100644 --- a/data/regs/Growatt-SPH.json +++ b/data/regs/Growatt-SPH.json @@ -126,7 +126,7 @@ "position2": [264, 265, 266, 267], "name": "BatChargingPower", "realname": "Battery Charging Power", - "openwbtopic": "setbatimpwh", + "openwbtopic": "setbatw", "datatype": "float", "factor": 0.1, "unit": "W" From 3aaf7869873e4acc6ea781adfe92515883439aab Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 6 Jan 2025 13:33:49 +0100 Subject: [PATCH 044/106] fix array access direction for datatype binary (PR #115) --- src/modbus.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index e8b084eb..bc82a787 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -848,9 +848,9 @@ String modbus::MapBitwise(JsonArray map, String value) { for (uint8_t i=0; ilog(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[i].as().c_str()); + Config->log(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[map.size() -1 -i].as().c_str()); if (ret.length() > 0) ret += ", "; - if (map[i]) ret += map[i].as(); + if (map[map.size() -1 -i]) ret += map[map.size() -1 -i].as(); else ret += "undefined"; } } From 8fb7cda05265b80e7656422ad666571e5fd580a0 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 6 Jan 2025 14:24:57 +0100 Subject: [PATCH 045/106] fix: 1 item less than map size, because last item is default value --- src/modbus.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index bc82a787..9ab71947 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -848,9 +848,10 @@ String modbus::MapBitwise(JsonArray map, String value) { for (uint8_t i=0; ilog(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[map.size() -1 -i].as().c_str()); + //note: 1 item less than map size, because last item is default value + Config->log(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[map.size() -2 -i].as().c_str()); if (ret.length() > 0) ret += ", "; - if (map[map.size() -1 -i]) ret += map[map.size() -1 -i].as(); + if ((map.size() -2 -i) >=0 && map[map.size() -2 -i]) ret += map[map.size() -2 -i].as(); else ret += "undefined"; } } From b5bcb5ce90e8a43d804d376300f2d7d91408b81f Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sat, 11 Jan 2025 18:28:09 +0100 Subject: [PATCH 046/106] fix: resolve CORS issue when downloading a stable release --- ChangeLog.md | 1 + src/main.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 3e54b7c1..1ce829e9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,7 @@ Release 3.3.1: - BugFix: fix null-terminationof string handling (#96) - new feature: support for OpenWB 2.0 Api (#100) - bugfix: fix esp crash for /getitems if using an huge register table (#76) + - fix CORS Issue when download a stable release Release 3.3.0: - new feature: WebSerial as remote serial output (#74) diff --git a/src/main.cpp b/src/main.cpp index ec89993b..e42bac8c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,7 +7,7 @@ _________________________________________________________________ | Any feedback is welcome | | | _________________________________________________________________ - + */ #include "commonlibs.h" From a858970ca9ad025f2075056dda47ea5efa3af89a Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 12 Jan 2025 16:18:15 +0100 Subject: [PATCH 047/106] fix: update button elements for actions and add confirmation dialogs (Sicherheitsabfrage beim Reset #120) --- data/web/Style.css | 7 +++++-- data/web/status.html | 30 +++++++++++++++++++++++++----- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/data/web/Style.css b/data/web/Style.css index 7fdbc505..86b08585 100644 --- a/data/web/Style.css +++ b/data/web/Style.css @@ -18,7 +18,7 @@ body { display: none; } - input[type="submit"] { + input[type="submit"], button { padding: 4px 16px; margin: 4px; background-color: #07D; @@ -27,7 +27,10 @@ body { border-radius: 4px; border: none; } - input[type="submit"]:hover { background: #336699; } + + input[type="submit"]:hover, button:hover { + background: #336699; + } input, select, textarea { margin: 4px; diff --git a/data/web/status.html b/data/web/status.html index 33e70726..689e2b8a 100644 --- a/data/web/status.html +++ b/data/web/status.html @@ -71,27 +71,37 @@ show logs via webserial -
+ + + Firmware Update -
+ + + Device Reboot -
+ + + Werkszustand herstellen (ohne WiFi) -
+ + + WiFi Zugangsdaten entfernen -
+ + + @@ -113,5 +123,15 @@ +
+

Are you sure you want to reset?

+ + +
+
+

Are you sure you want to reset WiFi Credentials?

+ + +
\ No newline at end of file From c9f6f03745d130fe6acb1472e604e1d9dc0aa51a Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 12 Jan 2025 16:26:35 +0100 Subject: [PATCH 048/106] add confirmation dialog for ESP reset or Wifi-Reset (#120) --- ChangeLog.md | 3 +++ include/_Release.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1ce829e9..fb5b4d0e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,6 @@ +Release 3.3.2: + - new feature: add confirmation dialog for ESP reset or Wifi-Reset + Release 3.3.1: - new Feature: datatype "binary" now available for json register definitions (PR #115) - BugFix: fix null-terminationof string handling (#96) diff --git a/include/_Release.h b/include/_Release.h index d6f5edc4..8881175d 100644 --- a/include/_Release.h +++ b/include/_Release.h @@ -1 +1 @@ -#define Release "3.3.1" +#define Release "3.3.2" From 0a50686e52fbe03e66c10334fe0e01b16bde0ad1 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Sun, 12 Jan 2025 16:37:16 +0100 Subject: [PATCH 049/106] feature: add confirmation dialog for file deletion --- data/web/handlefiles.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/web/handlefiles.html b/data/web/handlefiles.html index 75eb1710..99438ca8 100644 --- a/data/web/handlefiles.html +++ b/data/web/handlefiles.html @@ -50,11 +50,16 @@ - + +
+

Are you sure you want to delete this file?

+ + +
\ No newline at end of file From c831fb6865f89fa768506a2752b9bd741edd71f1 Mon Sep 17 00:00:00 2001 From: tobiasfaust Date: Mon, 20 Jan 2025 10:12:42 +0100 Subject: [PATCH 050/106] Enhance WebSocket and JSON handling with new features (#126) --- .github/scripts/myUtils.py | 2 +- CPPLINT.cfg | 3 + ChangeLog.md | 4 +- data/web/Javascript.js | 285 ++++++++++++++++++++++----- data/web/Style.css | 10 + data/web/baseconfig.html | 37 +++- data/web/baseconfig.js | 87 +++++++-- data/web/handlefiles.html | 29 ++- data/web/handlefiles.js | 148 ++++++++------ data/web/index.html | 2 +- data/web/modbusconfig.html | 32 ++- data/web/modbusconfig.js | 85 ++++++-- data/web/modbusitemconfig.html | 33 +++- data/web/modbusitemconfig.js | 109 ++++++++--- data/web/navi.html | 12 +- data/web/navi.js | 28 ++- data/web/rawdata.html | 30 ++- data/web/rawdata.js | 62 ++++-- data/web/status.html | 36 ++-- data/web/status.js | 121 ++++++++++-- src/MyWebServer.cpp | 342 ++++++++++++++++++++------------- src/MyWebServer.h | 28 +-- src/baseconfig.cpp | 6 +- src/baseconfig.h | 2 +- src/commonlibs.h | 1 - src/handleFiles.cpp | 40 ++-- src/handleFiles.h | 5 +- src/html_update.h | 91 --------- src/main.cpp | 42 ++-- src/modbus.cpp | 85 +++++--- src/modbus.h | 20 +- src/mqtt.cpp | 138 +++++++------ src/mqtt.h | 68 ++++--- 33 files changed, 1368 insertions(+), 655 deletions(-) create mode 100644 CPPLINT.cfg delete mode 100644 src/html_update.h diff --git a/.github/scripts/myUtils.py b/.github/scripts/myUtils.py index 5f80746a..445eebc9 100644 --- a/.github/scripts/myUtils.py +++ b/.github/scripts/myUtils.py @@ -288,7 +288,7 @@ def deleteVersions(root: str, keepVersions: int, json: list = None) -> None: # Lade die 'versions.json' Datei versions_file = os.path.join(root, 'versions.json') json = read_json_file(versions_file) - if versions is None: + if json is None: logging.error(f"Fehler beim Verarbeiten der Datei {versions_file}") return diff --git a/CPPLINT.cfg b/CPPLINT.cfg new file mode 100644 index 00000000..3c255f5e --- /dev/null +++ b/CPPLINT.cfg @@ -0,0 +1,3 @@ +linelength=120 +root=src +filter=-build/include_what_you_use \ No newline at end of file diff --git a/ChangeLog.md b/ChangeLog.md index fb5b4d0e..690c7215 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,6 @@ Release 3.3.2: - - new feature: add confirmation dialog for ESP reset or Wifi-Reset + - new feature: add confirmation dialog for ESP reset + - migrate from old ajax communication to standard websocket communication Release 3.3.1: - new Feature: datatype "binary" now available for json register definitions (PR #115) @@ -7,6 +8,7 @@ Release 3.3.1: - new feature: support for OpenWB 2.0 Api (#100) - bugfix: fix esp crash for /getitems if using an huge register table (#76) - fix CORS Issue when download a stable release + - bugfix: fix register id definition (#113) Release 3.3.0: - new feature: WebSerial as remote serial output (#74) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index 5474846d..cf47fc32 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -17,9 +17,9 @@ * Definition of constants *****************************************************************************************/ -const gpio_disabled = []; +export const gpio_disabled = []; -const gpio = [ {port: 1, name:'D1/TX0'}, +export const gpio = [ {port: 1, name:'D1/TX0'}, {port: 2 , name:'D2'}, {port: 3, name:'D3/RX0'}, {port: 4 , name:'D4'}, @@ -51,7 +51,7 @@ const gpio = [ {port: 1, name:'D1/TX0'}, {port: 39, name:'D39'} ]; -const gpioanalog = [ {port: 36, name:'ADC1_CH0 - GPIO36'}, +export const gpioanalog = [ {port: 36, name:'ADC1_CH0 - GPIO36'}, {port: 37, name:'ADC1_CH1 - GPIO37'}, {port: 38, name:'ADC1_CH2 - GPIO38'}, {port: 39, name:'ADC1_CH3 - GPIO39'}, @@ -71,14 +71,97 @@ const gpioanalog = [ {port: 36, name:'ADC1_CH0 - GPIO36'}, {port: 26, name:'ADC2_CH9 - GPIO26'} ]; +import { functionMap as statusFunctionMap } from './status.js'; +import { functionMap as baseconfigFunctionMap } from './baseconfig.js'; +import { functionMap as mbconfigFunctionMap } from './modbusconfig.js'; +import { functionMap as mbitemconfigFunctionMap } from './modbusitemconfig.js'; +import { functionMap as rawdataFunctionMap } from './rawdata.js'; +import { functionMap as filesFunctionMap } from './handlefiles.js'; + +const combinedFunctionMap = { + ...statusFunctionMap, + ...baseconfigFunctionMap, + ...mbconfigFunctionMap, + ...mbitemconfigFunctionMap, + ...rawdataFunctionMap, + ...filesFunctionMap +}; + +export let ws; // websocket handle +var datavalues; // form data values as string to check, if "needToSave" Dialog should be shown + var timer; // ID of setTimout Timer -> setResponse +let reconnectInterval = 5000; // 5 seconds interval to reconnect websocket connection + +/****************************************************************************************** + * Connect to WebSocket server + * *****************************************************************************************/ +export function connectWebSocket() { + window.addEventListener('beforeunload', function() { + if (ws) { + ws.close(); + } + }, false); + + document.addEventListener('visibilitychange', function() { + if (document.visibilityState === 'visible') { + if (!ws || ws.readyState === WebSocket.CLOSED) { + console.log('Reconnecting WebSocket due to visibility change'); + connectWebSocket(); + } + } else { + if (ws) { + console.log('Closing WebSocket due to visibility change'); + ws.close(); + } + } + }); + + if (document.visibilityState != 'visible') { + console.log('Not connecting WebSocket due to visibility state:', document.visibilityState); + return; + } + + //ws = new WebSocket(location.origin.replace(/^http/, 'ws') + '/ajaxws'); + ws = new WebSocket('ws://10.0.2.150/ajaxws'); + var wsStatus = document.getElementById('ws-status'); + + ws.onopen = function() { + console.log('WebSocket connection opened'); + if (wsStatus) wsStatus.style.backgroundColor = 'green'; + }; + + ws.onmessage = function(event) { + //try { + const json = JSON.parse(event.data); + console.log('Received JSON:', json); + handleJsonItems(json); + //} catch (e) { + // console.error('Invalid JSON received:', event.data); + //} + }; + + ws.onclose = function() { + console.log('WebSocket connection closed, attempting to reconnect in ' + reconnectInterval / 1000 + ' seconds'); + if (wsStatus) wsStatus.style.backgroundColor = 'yellow'; + setTimeout(connectWebSocket, reconnectInterval); + }; + + ws.onerror = function(error) { + console.error('WebSocket error:', error); + if (wsStatus) wsStatus.style.backgroundColor = 'red'; + ws.close(); + }; +} + + /****************************************************************************************** * activate all radioselections after pageload to hide unnecessary elements * Works for all checkbox and radio elements with onclick="radioselection(show, hide)" * ******************************************************************************************/ -function handleRadioSelections() { +export function handleRadioSelections() { var radios = document.querySelectorAll('input[type=radio][onclick*=radioselection]:checked'); for (var i = 0; i < radios.length; i++) { if (radios[i].onclick) { @@ -103,26 +186,46 @@ function handleRadioSelections() { } /***************************************************************************************** - * central function to initiate data fetch + * central function to send data to server * @param {*} json -> json object to send * @param {*} highlight -> highlight on/off * @param {*} callbackFn -> callback function to call after data is fetched * @returns {*} void ******************************************************************************************/ +export function requestData(json) { + if (typeof ws !== 'undefined' && ws.readyState === WebSocket.OPEN) { + console.log('WebSocket is open, sending data:', json); + ws.send(JSON.stringify(json)); + } else { + console.log('WebSocket not open'); + setResponse(false, 'WebSocket not open, could not send data'); + } +} -function requestData(json, highlight, callbackFn) { - const data = new URLSearchParams(); - data.append('json', json); - - fetch('/ajax', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: data - }) - .then (response => response.json()) - .then (json => { handleJsonItems(json, highlight, callbackFn)}); +/***************************************************************************************** + * @description This function updated values according their data-id in DOM elements. + * @param {*} json: JSON object containing the data-id values to update + * @param {*} highlight: boolean value to highlight the updated elements + * @returns {*} void + * @example updateDataID({"data-id":{"InverterSN.value":"123456789"}}, true) + * *****************************************************************************************/ +function updateDataID(json, highlight) { + if (json["data-id"]) { + for (const key in json["data-id"]) { + const elements = document.querySelectorAll(`[data-id="${key}"]`); + elements.forEach(element => { + element.innerHTML = json["data-id"][key]; + if (highlight && element.classList.contains('ajaxchange')) { + element.classList.add('highlightOn'); + setTimeout(function() {document.getElementById(element.id).classList.remove('highlightOn')}, 1000); + } + + }); + } + } } + /***************************************************************************************** * * definition of applying jsondata to html templates @@ -140,13 +243,10 @@ function applyKey (_obj, _key, _val, counter, tplHierarchie, highlight) { if (['SPAN', 'DIV', 'TD', 'DFN'].includes(_obj.tagName)) { if (highlight && _obj.classList.contains('ajaxchange')) { _obj.classList.add('highlightOn'); - _obj.innerHTML = _val; - } if (!highlight && _obj.classList.contains('ajaxchange')) { - _obj.classList.remove('highlightOn'); - _obj.innerHTML = _val; - } else { - _obj.innerHTML = _val; - } + setTimeout(function() {document.getElementById(_obj.id).classList.remove('highlightOn')}, 1000); + } + _obj.innerHTML = _val; + } else if (_obj.tagName == 'INPUT' && ['checkbox','radio'].includes(_obj.type)) { if (_val == true) _obj.checked = true; } else if (_obj.tagName == 'OPTION') { @@ -155,8 +255,17 @@ function applyKey (_obj, _key, _val, counter, tplHierarchie, highlight) { _obj.value = _val; } } else { - // using parenet object - _obj[_key] = _val; + // using parent object + if (_key in _obj) { + if (highlight && _obj.classList.contains('ajaxchange')) { + _obj.classList.add('highlightOn'); + setTimeout(function() {document.getElementById(_obj.id).classList.remove('highlightOn')}, 1000); + } + + _obj[_key] = _val; + } else { + _obj.setAttribute(_key, _val); + } } } @@ -255,11 +364,37 @@ function applyTemplate(TemplateJson, templateID, doc, tplHierarchie, highlight) } } -function handleJsonItems(json, highlight, callbackFn) { +/***************************************************************************************** + * apply Javascript variables to the window object from a JSON object + * @param {*} json: JSON object containing the variables to apply + * @returns {*} void + * @example applyJS({"myVar": "myValue"}) + * *****************************************************************************************/ +function applyJS(json) { + for (var key in json) { + window[key] = json[key]; + } +} + +/***************************************************************************************** + * Main function to handle JSON response + * @param {*} json: JSON object containing the response data + * @returns {*} void + * @example handleJsonItems({"data-id": {"InverterSN": "123456789"}, "cmd": {"action": "GetInitData", "subaction": "status", "callbackFn": "MyCallback"}, " + * "response": {"status": 1, "text": "OK"}}) + * *****************************************************************************************/ +export function handleJsonItems(json) { + const callbackFn = (typeof json['cmd'] !== 'undefined' && typeof json['cmd']['callbackFn'] !== 'undefined') ? json['cmd']['callbackFn'] : undefined; + const highlight = (typeof json['cmd'] !== 'undefined' && typeof json['cmd']['highlight'] !== 'undefined') ? json['cmd']['highlight'] : false; + if ("data" in json) { applyKeys(json.data, document, undefined, undefined, '', highlight); } + if ('js' in json) { + applyJS(json.js); + } + if ('response' in json) { try { if (json.response.status == 1) {setResponse(true, json.response.text);} @@ -267,8 +402,14 @@ function handleJsonItems(json, highlight, callbackFn) { } catch(e) {setResponse(false, 'unknow error');} } + if ("data-id" in json) { + updateDataID(json, highlight); + } + // DOM objects now ready - if (callbackFn) {callbackFn();} + if (callbackFn && typeof combinedFunctionMap[callbackFn] === 'function') { + combinedFunctionMap[callbackFn](json); + } } /***************************************************************************************** @@ -276,7 +417,7 @@ function handleJsonItems(json, highlight, callbackFn) { * @param {*} b (bool): true = OK; false = Error * @param {*} s (String): text to show *****************************************************************************************/ -function setResponse(b, s) { +export function setResponse(b, s) { try { // clear if previous timer still run clearTimeout(timer); @@ -294,17 +435,14 @@ function setResponse(b, s) { } /****************************************************************************************** -# -# definition of creating selectionlists from input fields -# querySelector -> select input fields to convert -# jsonLists -> define multiple predefined lists to set as option as array -# blacklist -> simple list of ports (numbers) to set as disabled option -# -# example: -# CreateSelectionListFromInputField('input[type=number][id^=AllePorts], input[type=number][id^=GpioPin]', -# [gpio, gpio_analog], gpio_disabled); + * definition of creating selectionlists from input fields + * @param {*} querySelector -> select input fields to convert + * @param {*} jsonLists -> define multiple predefined lists to set as option as array + * @param {*}blacklist -> simple list of ports (numbers) to set as disabled option + * @example + * CreateSelectionListFromInputField('input[type=number][id^=AllePorts], input[type=number][id^=GpioPin]', [gpio, gpio_analog], gpio_disabled); ******************************************************************************************/ -function CreateSelectionListFromInputField(querySelector, jsonLists, blacklist) { +export function CreateSelectionListFromInputField(querySelector, jsonLists, blacklist) { var _parent, _select, _option, i, j, k; var objects = document.querySelectorAll(querySelector); for( j=0; j< objects.length; j++) { @@ -312,8 +450,8 @@ function CreateSelectionListFromInputField(querySelector, jsonLists, blacklist) _select = document.createElement('select'); _select.id = objects[j].id; _select.name = objects[j].name; - for ( k = 0; k < jsonLists.length; k += 1 ) { - for ( i = 0; i < jsonLists[k].length; i += 1 ) { + for ( k = 0; k < jsonLists.length; k++ ) { + for ( i = 0; i < jsonLists[k].length; i++ ) { _option = document.createElement( 'option' ); _option.value = jsonLists[k][i].port; _option.text = jsonLists[k][i].name; @@ -345,7 +483,7 @@ regex of item ID to identify first element in row - if set, returned json is an array, all elements per row, example: "^myonoffswitch.*" - if emty, all elements at one level together, ONLY for small json´s (->memory issue) ****************************************************************************************/ -function onSubmit(DataForm, separator='') { +export function onSubmit(DataForm, separator='') { // init json Objects var JsonData, tempData; @@ -411,19 +549,21 @@ function onSubmit(DataForm, separator='') { }) .then (() => { var data = {}; - data['action'] = "ReloadConfig"; - data['subaction'] = filename; - requestData(JSON.stringify(data), false); + data['cmd'] = {}; + data['cmd']['action'] = "ReloadConfig"; + data['cmd']['subaction'] = filename; + requestData(data); }); } /**************************************************************************************** -blendet Zeilen der Tabelle aus - show: Array of shown IDs return true; - hide: Array of hidden IDs + * blendet Zeilen der Tabelle aus + * @param {*} show: Array of shown IDs return true; + * @param {*} hide: Array of hidden IDs + * @example radioselection(["row1", "row2"], ["row3", "row4"]) ****************************************************************************************/ -function radioselection(show, hide) { +export function radioselection(show, hide) { for(var i = 0; i < show.length; i++){ if (document.getElementById(show[i])) {document.getElementById(show[i]).style.display = 'table-row';} } @@ -439,7 +579,7 @@ function radioselection(show, hide) { * @param {*} hide Array of hidden IDs if checkbox is checked * @returns {*} void ****************************************************************************************/ -function onCheckboxSelection(checkbox, show, hide) { +export function onCheckboxSelection(checkbox, show, hide) { if (checkbox.checked) { radioselection(show, hide); } else { @@ -454,7 +594,7 @@ function onCheckboxSelection(checkbox, show, hide) { * For each of these checkboxes, it creates a new div element with the class "onoffswitch", clones the checkbox into this div, * and adds a label with the necessary span elements for styling. Finally, it replaces the original checkbox with the new div element. ****************************************************************************************/ -function transformCheckboxes() { +export function transformCheckboxes() { // Alle Checkboxen im Dokument suchen deren elternelement kein div mit der Style class "onoffswitch" ist const checkboxes = document.querySelectorAll("input[type='checkbox']:not(.onoffswitch-checkbox)"); @@ -468,6 +608,7 @@ function transformCheckboxes() { // Checkbox in das neue Div-Element kopieren const newCheckbox = checkboxes[i].cloneNode(true) + newCheckbox.className = 'onoffswitch-checkbox'; div.appendChild(newCheckbox); @@ -495,3 +636,47 @@ function transformCheckboxes() { } } + +/**************************************************************************************** + * Get all form data values as a string + * @param {*} formElement: id of the form element + * + * @returns {*} string containing all form data values + * ****************************************************************************************/ +export function getFormData(formElement) { + const form = document.getElementById(formElement); + if (form) { + const formData = new FormData(form); + let dataString = ''; + formData.forEach((value, key) => { + dataString += `${key}=${value}|`; + }); + // Remove the last '|' character + dataString = dataString.slice(0, -1); + return dataString; + } +} + +/**************************************************************************************** + * Show a dialog if the form data has been changed + * ****************************************************************************************/ +export function showMustSaveDialog() { + if (document.getElementById('needToSave') && datavalues !== getFormData("DataForm")) { + document.getElementById('needToSave').classList.remove('hide'); + } else { + document.getElementById('needToSave').classList.add('hide'); + } +} + +/**************************************************************************************** + * Initialize the data values in variable "datavalues" + * ****************************************************************************************/ +export function initDataValues() { + datavalues = getFormData("DataForm"); + + if (document.getElementById('needToSave')) { + document.getElementById('needToSave').classList.add('hide'); + } +} +/**************************************************************************************** +****************************************************************************************/ diff --git a/data/web/Style.css b/data/web/Style.css index 86b08585..91cfdff8 100644 --- a/data/web/Style.css +++ b/data/web/Style.css @@ -236,4 +236,14 @@ body { to { transform: rotate(360deg); } +} + +#needToSave { + width: 300px; + margin: 0 auto; + border: 1px solid red; + border-radius: 25px; + text-align: center; + margin-bottom: 5px; + padding-bottom: 3px; } \ No newline at end of file diff --git a/data/web/baseconfig.html b/data/web/baseconfig.html index cb204305..7bb5b7cc 100644 --- a/data/web/baseconfig.html +++ b/data/web/baseconfig.html @@ -4,12 +4,33 @@ - - + + + + + + Modbus MQTT Gateway
+ + +
+ + Änderungen nicht gespeichert +
+
@@ -118,9 +139,13 @@
- -
- - +

+
+
+ Connection Status: + +
+ x +
\ No newline at end of file diff --git a/data/web/baseconfig.js b/data/web/baseconfig.js index d3a815f2..12603507 100644 --- a/data/web/baseconfig.js +++ b/data/web/baseconfig.js @@ -1,24 +1,89 @@ +import * as global from './Javascript.js'; + // ************************************************ -window.addEventListener('DOMContentLoaded', init, false); -function init() { - GetInitData(); +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + GetInitData(); + } + }, 100); +} + +export function init1() { + var data = { + "data": { + "mqttroot": "exampleRoot", + "mqttserver": "exampleServer", + "mqttport": 1883, + "mqttuser": "exampleUser", + "mqttpass": "examplePass", + "mqttbasepath": "exampleBasePath", + "debuglevel": 2, + "sel_wifi": 1, + "sel_eth": 0, + "useRandomClientID": 1, + "sel_auth": 0, + "auth_user": "authUser", + "auth_pass": "authPass", + "GpioPin_serial_rx": 15, + "GpioPin_serial_tx": 16 + }, + "response": { + "status": 1, + "text": "successful" + } + , "cmd": { + "action": "GetInitData", + "subaction": "baseconfig" + ,"callbackFn": "baseconfig_Callback" + , "highlight": "true" + } + }; + + global.handleJsonItems(data); + + global.initDataValues(); } +export const functionMap = { + baseconfig_Callback: MyCallback +}; + // ************************************************ function GetInitData() { var data = {}; - data.action = "GetInitData"; - data.subaction = "baseconfig"; - requestData(JSON.stringify(data), true, MyCallback); + data['cmd'] = {}; + data['cmd']['action'] = "GetInitData"; + data['cmd']['subaction'] = "baseconfig"; + data['cmd']['callbackFn'] = "baseconfig_Callback"; + + global.requestData(data); } // ************************************************ -function MyCallback() { - transformCheckboxes(); - handleRadioSelections(); - CreateSelectionListFromInputField('input[type=number][id^=GpioPin]', [gpio]); +function MyCallback(json) { + global.transformCheckboxes(); + global.handleRadioSelections(); + global.CreateSelectionListFromInputField('input[type=number][id^=GpioPin]', [global.gpio]); + + document.querySelectorAll('#DataForm input:not([type=checkbox]):not([type=radio]), #DataForm select').forEach(element => { + element.addEventListener('blur', global.showMustSaveDialog); + }); + + document.querySelectorAll('#DataForm input[type=checkbox], #DataForm input[type=radio]').forEach(element => { + element.addEventListener('click', global.showMustSaveDialog); + }); + + global.initDataValues(); + + // Hide loader and show body document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; } -// ************************************************ \ No newline at end of file +// ************************************************ diff --git a/data/web/handlefiles.html b/data/web/handlefiles.html index 99438ca8..d3467469 100644 --- a/data/web/handlefiles.html +++ b/data/web/handlefiles.html @@ -4,9 +4,21 @@ - - + + + + + HandleFiles @@ -48,9 +60,9 @@ filename: - - - + + + @@ -61,5 +73,12 @@ +

+
+
+ Connection Status: + +
+
\ No newline at end of file diff --git a/data/web/handlefiles.js b/data/web/handlefiles.js index 9358a682..e60fbe93 100644 --- a/data/web/handlefiles.js +++ b/data/web/handlefiles.js @@ -1,69 +1,91 @@ // https://jsfiddle.net/tobiasfaust/uc1jfpgb/ +import * as global from './Javascript.js'; + var DirJson; -window.addEventListener('load', initHandleFS, false); -function initHandleFS() { - init("/"); +// ************************************************ +export const functionMap = { + files_Callback: MyCallback +}; + +// ************************************************ +export function init1() { + var data = {"JS": {"listdir": [ + {"path": "/", "content": [{"name": "file1.txt", "isDir": 0}, {"name": "file2.txt", "isDir": 0}, {"name": "dir1", "isDir": 1}]}, + {"path": "/dir1", "content": [{"name": "file3.txt", "isDir": 0}, {"name": "file4.txt", "isDir": 0}]} + ]}, + "response": {"status": 1, "text": "successful"}, + "cmd": {"callbackFn": "files_Callback", "startpath": "/"} + }; + + global.handleJsonItems(data); + + document.getElementById('fullpath').innerHTML = ''; // div + document.getElementById('filename').value = ''; // input field + document.getElementById('content').value = ''; + document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; } -function init(startpath) { - requestListDir(startpath); - obj = document.getElementById('fullpath').innerHTML = ''; // div - obj = document.getElementById('filename').value = ''; // input field - obj = document.getElementById('content').value = ''; +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + GetInitData("/"); + } + }, 100); } -// *********************************** -// Ajax Request to update -// *********************************** -function requestListDir(startpath) { +// ************************************************ +export function GetInitData(startpath) { var data = {}; - data['action'] = "handlefiles"; - data['subaction'] = "listDir" - //ajax_send(JSON.stringify(data)); - - var http = null; - if (window.XMLHttpRequest) { http =new XMLHttpRequest(); } - else { http =new ActiveXObject("Microsoft.XMLHTTP"); } - - if(!http){ alert("AJAX is not supported."); return; } - - var url = '/ajax'; - var params = 'json=' + JSON.stringify(data); - - http.open('POST', url, true); - http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - http.onreadystatechange = function() { //Call a function when the state changes. - if(http.readyState == 4 && http.status == 200) { - DirJson = JSON.parse(http.responseText); - listFiles(startpath); - } - } - http.send(params); + data['cmd'] = {}; + data['cmd']['action'] = "handlefiles"; + data['cmd']['subaction'] = "listDir" + data['cmd']['startpath'] = startpath; + data['cmd']['callbackFn'] = "files_Callback"; + + global.requestData(data); + + document.getElementById('fullpath').innerHTML = ''; // div + document.getElementById('filename').value = ''; // input field + document.getElementById('content').value = ''; + + document.querySelector("#loader").style.visibility = "hidden"; + document.querySelector("body").style.visibility = "visible"; } -// *********************************** +// ************************************************ +function MyCallback(json) { + DirJson = json["JS"].listdir; + listFiles(json["cmd"]["startpath"]); +} + +// ************************************************ // show content of fetched file -// *********************************** +// ************************************************ function setContent(string, file) { - obj = document.getElementById('fullpath').innerHTML = file; // div - obj = document.getElementById('filename').value = basename(file); // input field + document.getElementById('fullpath').innerHTML = file; // div + document.getElementById('filename').value = basename(file); // input field if (file.endsWith("json")) { - obj = document.getElementById('content').value = JSON.stringify(JSON.parse(string), null, 2); + document.getElementById('content').value = JSON.stringify(JSON.parse(string), null, 2); } else { - obj = document.getElementById('content').value = string; + document.getElementById('content').value = string; } } // *********************************** // fetch file from host // *********************************** -function fetchFile(file) { - obj = document.getElementById('content').value = "loading "+file+"..."; +export function fetchFile(file) { + document.getElementById('content').value = "loading "+file+"..."; fetch(file) .then(response => response.text()) @@ -73,10 +95,10 @@ function fetchFile(file) { // *********************************** // show directory structure // *********************************** -function listFiles(path) { +export function listFiles(path) { var table = document.querySelector('#files'), row = document.querySelector('#NewRow'), - tr_tpl, DirJsonLocal; + cells, tr_tpl, DirJsonLocal; // cleanup table table.replaceChildren(); @@ -87,7 +109,7 @@ function listFiles(path) { DirJsonLocal = DirJson[i] } } - + // show path information document.getElementById('path').innerHTML = path; @@ -170,7 +192,7 @@ function validateJson(json) { // *********************************** // download content of textarea as filename on local pc // *********************************** -function downloadFile() { +export function downloadFile() { var textToSave = document.getElementById("content").value; var textToSaveAsBlob = new Blob([textToSave], {type:"text/plain"}); var textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob); @@ -182,11 +204,11 @@ function downloadFile() { downloadLink.innerHTML = "Download File"; downloadLink.href = textToSaveAsURL; - downloadLink.onclick = destroyClickedElement; + downloadLink.onclick = destroyClickedElement; downloadLink.style.display = "none"; document.body.appendChild(downloadLink); downloadLink.click(); - } else { setResponse(false, 'Filename is empty, Please define it.');} + } else { global.setResponse(false, 'Filename is empty, Please define it.');} } function destroyClickedElement(event) @@ -197,7 +219,7 @@ function destroyClickedElement(event) // *********************************** // store content of textarea // *********************************** -function uploadFile() { +export function uploadFile() { var textToSave = document.getElementById("content").value; var textToSaveAsBlob = new Blob([textToSave], {type:"text/plain"}); var fileNameToSaveAs = document.getElementById("filename").value; @@ -206,12 +228,12 @@ function uploadFile() { if (fileNameToSaveAs != '') { if (fileNameToSaveAs.toLowerCase().endsWith('.json')) { if (!validateJson(textToSave)) { - setResponse(false, 'Json invalid') + global.setResponse(false, 'Json invalid') return; } } - setResponse(true, 'Please wait for saving ...'); + global.setResponse(true, 'Please wait for saving ...'); const formData = new FormData(); formData.append(fileNameToSaveAs, textToSaveAsBlob, pathOfFile + '/' + fileNameToSaveAs); @@ -222,24 +244,26 @@ function uploadFile() { }) .then (response => response.json()) .then (json => { - setResponse(true, json.text) + global.setResponse(true, json.text) }); - } else { setResponse(false, 'Filename is empty, Please define it.');} + } else { global.setResponse(false, 'Filename is empty, Please define it.');} } -function deleteFile() { +// ************************************************ +export function deleteFile() { var pathOfFile = document.getElementById('path').innerHTML; var fileName = document.getElementById("filename").value; if (fileName != '') { var data = {}; - data['action'] = 'handlefiles'; - data['subaction'] = "deleteFile"; - data['filename'] = pathOfFile + '/' + fileName; - - setResponse(true, 'Please wait for deleting ...'); - requestData(JSON.stringify(data)); - init(pathOfFile); - } else { setResponse(false, 'Filename is empty, Please define it.');} + data['cmd'] = {}; + data['cmd']['action'] = 'handlefiles'; + data['cmd']['subaction'] = "deleteFile"; + data['cmd']['filename'] = pathOfFile + '/' + fileName; + + global.setResponse(true, 'Please wait for deleting ...'); + global.requestData(data); + GetInitData(pathOfFile); + } else { global.setResponse(false, 'Filename is empty, Please define it.');} } \ No newline at end of file diff --git a/data/web/index.html b/data/web/index.html index d66a03f6..a236d11a 100644 --- a/data/web/index.html +++ b/data/web/index.html @@ -3,7 +3,7 @@ Modbus MQTT Gateway - + diff --git a/data/web/modbusconfig.html b/data/web/modbusconfig.html index 6f46b960..fada88a8 100644 --- a/data/web/modbusconfig.html +++ b/data/web/modbusconfig.html @@ -4,12 +4,31 @@ - - + + + + + + ModbusConfig
+ + +
+ + Änderungen nicht gespeichert +
+
@@ -155,7 +174,12 @@

- - +
+
+ Connection Status: + +
+ x +
\ No newline at end of file diff --git a/data/web/modbusconfig.js b/data/web/modbusconfig.js index f24e83c8..d1d6e956 100644 --- a/data/web/modbusconfig.js +++ b/data/web/modbusconfig.js @@ -1,24 +1,87 @@ +import * as global from './Javascript.js'; + // ************************************************ -window.addEventListener('DOMContentLoaded', init, false); -function init() { - GetInitData(); +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + GetInitData(); + } + }, 100); +} + +export function init1() { + // erstelle ein Beispiel json mit Beispielwerten welches die funktion modbus::GetInitData generieren würde und weise das json der variable data zu. + var data = { + "data": { + "pin_rx": 16, + "pin_tx": 17, + "pin_rts": 5, + "clientid": 1, + "baudrate": 19200, + "txintervallive": 10, + "txintervalid": 60, + "pin_RELAY1": 18, + "pin_RELAY2": 19, + "openwbversion": "1.2.3", + "openwbmodulid": 1, + "openwbbatteryid": 2, + "inverters": [ [ { "inverter": {"value": "Kostal", "text": "Kostal"}}]], + }, + "response": { + "status": 1, + "text": "successful" + }, + "cmd": { + "action": "GetInitData", + "subaction": "status", + "callbackFn": "mbconfig_Callback" + } + } + + global.handleJsonItems(data); + + datavalues = global.getFormData("DataForm"); } +export const functionMap = { + mbconfig_Callback: MyCallback +}; + // ************************************************ function GetInitData() { var data = {}; - data.action = "GetInitData"; - data.subaction = "modbusconfig"; - requestData(JSON.stringify(data), false, MyCallback); + data['cmd'] = {}; + data['cmd']['action'] = "GetInitData"; + data['cmd']['subaction'] = "modbusconfig"; + data['cmd']['callbackFn'] = "mbconfig_Callback"; + + global.requestData(data); } // ************************************************ -function MyCallback() { - transformCheckboxes(); - handleRadioSelections(); - CreateSelectionListFromInputField('input[type=number][id^=GpioPin]', [gpio]); +function MyCallback(json) { + global.transformCheckboxes(); + global.handleRadioSelections(); + global.CreateSelectionListFromInputField('input[type=number][id^=GpioPin]', [global.gpio]); + + document.querySelectorAll('#DataForm input:not([type=checkbox]):not([type=radio]), #DataForm select').forEach(element => { + element.addEventListener('blur', global.showMustSaveDialog); + }); + + document.querySelectorAll('#DataForm input[type=checkbox], #DataForm input[type=radio]').forEach(element => { + element.addEventListener('click', global.showMustSaveDialog); + }); + + global.initDataValues(); + + document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; } -// ************************************************ \ No newline at end of file +// ************************************************ diff --git a/data/web/modbusitemconfig.html b/data/web/modbusitemconfig.html index 240363c0..af0007dc 100644 --- a/data/web/modbusitemconfig.html +++ b/data/web/modbusitemconfig.html @@ -4,12 +4,31 @@ - - + + + + + + status
+ + +
+ + Änderungen nicht gespeichert +
+
@@ -46,7 +65,13 @@

- - + +
+
+ Connection Status: + +
+ x +
\ No newline at end of file diff --git a/data/web/modbusitemconfig.js b/data/web/modbusitemconfig.js index d9122b3b..8ede60fb 100644 --- a/data/web/modbusitemconfig.js +++ b/data/web/modbusitemconfig.js @@ -1,41 +1,104 @@ +import * as global from './Javascript.js'; + // ************************************************ -window.addEventListener('DOMContentLoaded', init, false); -function init() { - GetInitData(); +export function init1() { + // erstelle ein Beispiel json mit Beispielwerten welches die funktion modbus::GetLiveDataAsJsonToWebserver generieren würde und weise das json der variable data zu. + + var data = {"data": {"items": [ + {"name": "InverterIdData", "realname": "InverterIdData", + "value": {"innerHTML": "1234", "data-id": "InverterIdData.value"}, + "active": {"checked": 1, "name": "InverterIdData"}, + "mqtttopic": "InverterIdData" + }, + {"name": "Power", "realname": "Power", + "value": {"innerHTML": "1234", "data-id": "Power.value"}, + "active": {"checked": 0, "name": "Power"}, + "mqtttopic": "Power" + } + ]}, + "response": {"status": 1, "text": "successful"}, + "cmd": { + "callbackFn": "mbitemconfig_Callback" + }} + + global.handleJsonItems(data); + datavalues = global.getFormData("DataForm"); + + data = {"data-id": { "InverterIdData.value" : "684453556"}, + "response": {"status": 1, "text": "successful"}, + "cmd": { + "highlight": "true" + }} + global.handleJsonItems(data); } // ************************************************ -var myInterval = setInterval(RefreshLiveData, 5000); +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); -// ************************************************ -function GetInitData() { - var data = {}; - data.action = "RefreshLiveData"; - data.subaction = "all"; - requestData(JSON.stringify(data), false, MyCallback); + fetch('/getitems') + .then(response => response.json()) + .then(data => { + data['cmd'] = {}; + data['cmd']['callbackFn'] = "mbitemconfig_Callback"; + global.handleJsonItems(data); + }) + .catch(error => console.error('Error fetching items:', error)); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + RefreshLiveData(); + } + }, 100); } // ************************************************ -function MyCallback() { - //transformCheckboxes() +export const functionMap = { + mbitemconfig_Callback: MyCallback +}; + +// ************************************************ +function MyCallback(json) { + global.transformCheckboxes() + + document.querySelectorAll('#DataForm input:not([type=checkbox]):not([type=radio]), #DataForm select').forEach(element => { + element.addEventListener('blur', global.showMustSaveDialog); + }); + + document.querySelectorAll('#DataForm input[type=checkbox], #DataForm input[type=radio]').forEach(element => { + element.addEventListener('click', global.showMustSaveDialog); + }); + + global.initDataValues(); + document.querySelector("#loader").style.visibility = "hidden"; document.querySelector("body").style.visibility = "visible"; } +// ************************************************ function RefreshLiveData() { var data = {}; - data.action = "RefreshLiveData"; - data.subaction = "all"; - requestData(JSON.stringify(data), true); + data['cmd'] = {}; + data['cmd']['action'] = "GetItemsAsStream"; + data['cmd']['highlight'] = "true"; + + global.requestData(data); } // ************************************************ -function ChangeActiveStatus(id) { - obj = document.getElementById(id); - //item = id.replace(/^activeswitch_(.*)$/g, "$1"); +export function ChangeActiveStatus(id) { + var obj = document.getElementById(id); + var data = {}; - data.action = "SetActiveStatus"; - data.newState = (obj.checked?"true":"false"); - data.item = obj.name; - requestData(JSON.stringify(data)); -} \ No newline at end of file + data['cmd'] = {}; + data['cmd']['action'] = "SetActiveStatus"; + data['cmd']['newState'] = (obj.checked?"true":"false"); + data['cmd']["item"] = obj.name; + + global.requestData(data); +} + +// ************************************************ diff --git a/data/web/navi.html b/data/web/navi.html index 41237263..b1dc73f8 100644 --- a/data/web/navi.html +++ b/data/web/navi.html @@ -4,8 +4,16 @@ - - + + + + + Modbus MQTT Gateway diff --git a/data/web/navi.js b/data/web/navi.js index 17d7be61..90fb7bf2 100644 --- a/data/web/navi.js +++ b/data/web/navi.js @@ -1,20 +1,32 @@ +import * as global from './Javascript.js'; + // ************************************************ -window.addEventListener('load', init, false); -function init() { - GetInitData(); +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + GetInitData(); + } + }, 100); } // ************************************************ function GetInitData() { var data = {}; - data['action'] = "GetInitData"; - data['subaction'] = "navi"; - requestData(JSON.stringify(data)); + data['cmd'] = {}; + data['cmd']['action'] = "GetInitData"; + data['cmd']['subaction'] = "navi"; + + global.requestData(data); } // ************************************************ -function highlightNavi(item) { - collection = document.getElementsByName('navi') +export function highlightNavi(item) { + const collection = document.getElementsByName('navi') for (let i = 0; i < collection.length; i++) { if (item.id == collection[i].id ) { diff --git a/data/web/rawdata.html b/data/web/rawdata.html index efe6a5ea..64b351c3 100644 --- a/data/web/rawdata.html +++ b/data/web/rawdata.html @@ -4,9 +4,18 @@ - - + + + + + RawData @@ -19,8 +28,7 @@ - - +
Raw Data
RawData of ID-Data @@ -40,9 +48,8 @@
-

+

- @@ -69,9 +76,14 @@
Insert your positions (comma separated) to test
- - - +

+
+
+ Connection Status: + +
+ x +
\ No newline at end of file diff --git a/data/web/rawdata.js b/data/web/rawdata.js index 08710fd8..1746c4ed 100644 --- a/data/web/rawdata.js +++ b/data/web/rawdata.js @@ -1,20 +1,56 @@ /* https://jsfiddle.net/tobiasfaust/p5q9hgsL/ */ +import * as global from './Javascript.js'; + // ************************************************ -window.addEventListener('DOMContentLoaded', init, false); -function init() { - GetInitData(); +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + GetInitData(); + } + }, 100); } -// ************************************************ +export function init1() { + var data = {"data": {"id_rawdata_org": "0103EEFF8A44130000281F0A0B0C0D0E0F", + "live_rawdata_org": "0102030405060708090a0b0c0d0e0f" + }, + "response": {"status": 1, "text": "successful"}, + "cmd": {"action": "GetInitData", "subaction": "rawdata", "callbackFn": "rawdata_Callback" + }}; + + global.handleJsonItems(data); +} + +/******************************* + * define all callback functions here to make them accessible from other modules by the global combinedFunctionMap +*******************************/ +export const functionMap = { + rawdata_Callback: MyCallback +}; + +/******************************* + * get initial data after page load +*******************************/ function GetInitData() { var data = {}; - data.action = "GetInitData"; - data.subaction = "rawdata"; - requestData(JSON.stringify(data), false, MyCallback); + data['cmd'] = {}; + data['cmd']['action'] = "GetInitData"; + data['cmd']['subaction'] = "rawdata"; + data['cmd']['callbackFn'] = "rawdata_Callback"; + + global.requestData(data); } -function MyCallback() { +/******************************* + * Callback function after receiving the data +*******************************/ +function MyCallback(json) { reset_rawdata('id_rawdata'); reset_rawdata('live_rawdata'); @@ -23,7 +59,7 @@ function MyCallback() { } /******************************* -split long byte-string into array + * split long byte-string into array *******************************/ function chunk(str, size) { return str.match(new RegExp('.{1,' + size + '}', 'g')) || []; @@ -45,7 +81,7 @@ insert Tooltips and linebreaks *******************************/ function prettyprint_rawdata(rawdatatype, bytearray, bytearray_org) { - for( i=0; i< bytearray.length; i++) { + for( var i=0; i< bytearray.length; i++) { const bstr = byte2string(bytearray_org[i]); const bint = byte2int(bytearray_org[i]); @@ -61,7 +97,7 @@ function prettyprint_rawdata(rawdatatype, bytearray, bytearray_org) { /******************************* take over the clicked byte position into posTextField *******************************/ -function cpRawDataPos(pos) { +export function cpRawDataPos(pos) { let posarray; const obj = document.getElementById('positions'); @@ -94,7 +130,7 @@ function byte2int(bytestring) { /******************************* compute result from selected positions *******************************/ -function check_rawdata() { +export function check_rawdata() { const datatype = document.querySelector('input[name="datatype"]:checked').value; const rawdatatype = document.querySelector('input[name="rawdatatype"]:checked').value; const string_positions = document.getElementById('positions').value; @@ -112,7 +148,7 @@ function check_rawdata() { if (datatype == 'int') { result = 0; } if (datatype == 'string') { result = "";} - for( j=0; j< pos.length; j++) { + for( var j=0; j< pos.length; j++) { if (datatype == 'int') { result = result << 8 | byte2int(bytes[Number(pos[j])]); } diff --git a/data/web/status.html b/data/web/status.html index 689e2b8a..fbb8c156 100644 --- a/data/web/status.html +++ b/data/web/status.html @@ -4,8 +4,17 @@ - - + + + + + status @@ -86,7 +95,7 @@ Device Reboot - + @@ -97,12 +106,6 @@ - - WiFi Zugangsdaten entfernen - - - - @@ -123,15 +126,20 @@ +

Are you sure you want to reset?

- +
-
-

Are you sure you want to reset WiFi Credentials?

- - + +
+
+
+ Connection Status: + +
+ x
\ No newline at end of file diff --git a/data/web/status.js b/data/web/status.js index e6a662ac..07548536 100644 --- a/data/web/status.js +++ b/data/web/status.js @@ -1,31 +1,124 @@ +import * as global from './Javascript.js'; + // ************************************************ -window.addEventListener('DOMContentLoaded', init, false); -function init() { - GetInitData(); +export function init() { + // Initiale Verbindung aufbauen + global.connectWebSocket(); + + // Warte bis die WebSocket-Verbindung aufgebaut ist + let checkWebSocketInterval = setInterval(() => { + if (global.ws && global.ws.readyState === WebSocket.OPEN) { + clearInterval(checkWebSocketInterval); + GetInitData(); + } + }, 100); } -var myInterval = setInterval(RefreshLiveData, 5000); + +export function init1() { + + var data = { + "data": { + "ipaddress": "192.168.1.1", + "wifiname": "MyWiFi", + "macaddress": "00:1A:2B:3C:4D:5E", + "rssi": -50, + "bssid": "00:1A:2B:3C:4D:5F", + "mqtt_status": "Connected", + "inverter_type": "Solax-TypeA", + "inverter_serial": "SN123456789", + "uptime": "24h 15m", + "freeheapmem": 20480, + "tr_webserial": { + "className": "hide" + } + }, + "response": { + "status": 1, + "text": "successful" + } + , "cmd": { + "action": "GetInitData", + "subaction": "status" + ,"callbackFn": "status_Callback" + //, "highlight": "true" + } + }; + + global.handleJsonItems(data); +} + +export const functionMap = { + status_Callback: MyCallback, + status_CallRebootPage: CallRebootPage +}; // ************************************************ function GetInitData() { var data = {}; - data['action'] = "GetInitData"; - data['subaction'] = "status"; - requestData(JSON.stringify(data), false, MyCallback); + data['cmd'] = {}; + data['cmd']['action'] = "GetInitData"; + data['cmd']['subaction'] = "status"; + data['cmd']['callbackFn'] = "status_Callback"; + + global.requestData(data); +} + +// ************************************************ +function MyCallback(json) { + + fetch('/getitems') + .then(response => response.json()) + .then(data => { + global.handleJsonItems(ReduceJsonOnlyActiveItems(data)); + }) + .catch(error => console.error('Error fetching items:', error)); + + RefreshLiveData(); + document.querySelector("#loader").style.visibility = "hidden"; + document.querySelector("body").style.visibility = "visible"; +} + +// ************************************************ +function ReduceJsonOnlyActiveItems(json) { + if (json.data && json.data.items) { + json.data.items = json.data.items.filter(item => item.active && item.active.checked !== 0); + } + return json; } // ************************************************ function RefreshLiveData() { var data = {}; - data.action = "RefreshLiveData"; - data.subaction = "onlyactive"; - requestData(JSON.stringify(data), true); + data['cmd'] = {}; + data['cmd']['action'] = "GetItemsAsStream"; + data['cmd']['subaction'] = "onlyactive"; + data['cmd']['highlight'] = "true"; + + global.requestData(data); } // ************************************************ -function MyCallback() { - document.querySelector("#loader").style.visibility = "hidden"; - document.querySelector("body").style.visibility = "visible"; +export function DoReboot() { + var data = {}; + data['cmd'] = {}; + data['cmd']['action'] = "reboot"; + data['cmd']['callbackFn'] = "status_CallRebootPage"; + global.requestData(data); } -// ************************************************ \ No newline at end of file +// ************************************************ +export function DoReset() { + var data = {}; + data['cmd'] = {}; + data['cmd']['action'] = "reset"; + data['cmd']['callbackFn'] = "status_CallRebootPage"; + global.requestData(data); +} + +// ************************************************ +export function CallRebootPage(json) { + window.location.href = "reboot.html"; +} + +// ************************************************ diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index 6ee3090d..9d1bff66 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -1,28 +1,37 @@ #include "MyWebServer.h" -MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): DoReboot(false), RequestRebootTime(0), server(server), dns(dns) { +MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): + DoReboot(false), + RequestRebootTime(0), + server(server), + dns(dns) { fsfiles = new handleFiles(server); + ws = new AsyncWebSocket("/ajaxws"); server->onNotFound(std::bind(&MyWebServer::handleNotFound, this, std::placeholders::_1)); server->on("/", HTTP_GET, std::bind(&MyWebServer::handleRoot, this, std::placeholders::_1)); server->on("/favicon.ico", HTTP_GET, std::bind(&MyWebServer::handleFavIcon, this, std::placeholders::_1)); - server->on("/reboot", HTTP_GET, std::bind(&MyWebServer::handleReboot, this, std::placeholders::_1)); - server->on("/reset", HTTP_GET, std::bind(&MyWebServer::handleReset, this, std::placeholders::_1)); - server->on("/wifireset", HTTP_GET, std::bind(&MyWebServer::handleWiFiReset, this, std::placeholders::_1)); - - server->on("/ajax", HTTP_POST, std::bind(&MyWebServer::handleAjax, this, std::placeholders::_1)); server->on("/getitems", HTTP_GET, std::bind(&MyWebServer::handleGetItemJson, this, std::placeholders::_1)); server->on("/getregister", HTTP_GET, std::bind(&MyWebServer::handleGetRegisterJson, this, std::placeholders::_1)); + ws->onEvent(std::bind(&MyWebServer::onWsEvent, this, std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3, + std::placeholders::_4, + std::placeholders::_5, + std::placeholders::_6 )); + + server->addHandler(ws); + ElegantOTA.begin(server); // Start ElegantOTA ElegantOTA.setGitEnv(String(GIT_OWNER), String(GIT_REPO), String(GIT_BRANCH)); ElegantOTA.setFWVersion(String(Config->GetReleaseName() + " / Build: " + GITHUB_RUN )); ElegantOTA.setBackupRestoreFS("/config"); ElegantOTA.setAutoReboot(true); - // ElegantOTA callbacks + //ElegantOTA callbacks //ElegantOTA.onStart(onOTAStart); //ElegantOTA.onProgress(onOTAProgress); //ElegantOTA.onEnd(std::bind(&MyWebServer::onOTAEnd, this, std::placeholders::_1)); @@ -45,12 +54,151 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): DoReboot(false } } +void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { + if (type == WS_EVT_CONNECT) { + Config->log(2, "[Client: %u] WebSocket client connected", client->id()); + + } else if (type == WS_EVT_DISCONNECT) { + Config->log(2, "[Client: %u] WebSocket client disconnected", client->id()); + + // wenn client->id() in der Liste WsConnectedClientsForBroadcast vorhanden ist, dann entfernen + + auto it = std::find_if(WsConnectedClientsForBroadcast.begin(), WsConnectedClientsForBroadcast.end(), + [client](const WsConnClient_t& c) { return c.id == client->id(); }); + if (it != WsConnectedClientsForBroadcast.end()) { + WsConnectedClientsForBroadcast.erase(it); + } + + // wenn keine clients mehr in der Liste sind, dann den Callback für die modbuswerte entfernen + if (this->WsConnectedClientsForBroadcast.size() == 0) { + mb->setWebSocketCallback(nullptr); + } + + } else if (type == WS_EVT_DATA) { + String msg(""); msg.reserve(len + 1); + for (size_t i = 0; i < len; i++) { msg += (char)data[i]; } msg += '\0'; + Config->log(2, "[Client: %u] WebSocket data received: %s", client->id(), msg.c_str()); + + // message json request format: {"cmd": {"action": "GetInitData", "subaction": "status"}} // subaction optional + // message json response format: die Antwort wird im json ergänzt, so weiß der Requestor zu welchem Command die Antwort gehört: + // Example: {"cmd": {"action": "GetInitData", "subaction": "status"}, "response": {"status": 1, "text": "successful"}, "data": {"ipaddress": "", "wifiname": "", "macaddress": "", "rssi": "", "bssid": "", "mqtt_status": "", "inverter_type": "", "inverter_serial": "", "uptime": "", "freeheapmem": ""}} + // Ausnahme: kontinuierliches Streaming der modbuswerte, hier wird kein response und nicht das ursprüngliche Command zurückgegeben + // example: {"data-id":{ "registername": "value", "registername": "value", ...}} + + String action(""), subaction(""), item(""); + bool newState = false; + JsonDocument json; + DeserializationError error = deserializeJson(json, msg.c_str()); + if (!error) { + if (json["cmd"]) { + if (json["cmd"]["action"]) {action = json["cmd"]["action"].as();} + if (json["cmd"]["subaction"]){subaction = json["cmd"]["subaction"].as();} + if (json["cmd"]["item"]) {item = json["cmd"]["item"].as();} + + newState = json["cmd"]["newState"].as(); + } + + if (action == "GetItemsAsStream") { + // add client id to the list of clients to broadcast if not already in the list + auto it = std::find_if(WsConnectedClientsForBroadcast.begin(), WsConnectedClientsForBroadcast.end(), + [client](const WsConnClient_t& c) { return c.id == client->id(); }); + if (it == WsConnectedClientsForBroadcast.end()) { + const WsConnClient_t w = {client->id(), msg}; + WsConnectedClientsForBroadcast.push_back(w); + } + // if this is the first client in the list, then set the callback for the modbus values + if (this->WsConnectedClientsForBroadcast.size() == 1) { + mb->setWebSocketCallback([this](String& message) { + this->sendWebSocketMessage(message); + }); + } + return; + } + + if (action && action == "reset") { + if (handleReset()) { + json["response"]["status"] = 1; + json["response"]["text"] = "all config files deleted successfully"; + } else { + json["response"]["status"] = 0; + json["response"]["text"] = "deletion of config files failed"; + } + } + + if(action && action == "reboot") { + this->DoReboot = true; + json["response"]["status"] = 1; + json["response"]["text"] = "reboot after 5sec..."; + } + + if(action && action == "GetInitData") { + if (subaction && subaction == "status") { + this->GetInitDataStatus(json); + } else if (subaction && subaction == "navi") { + this->GetInitDataNavi(json); + } else if (subaction && subaction == "baseconfig") { + Config->GetInitData(json); + } else if (subaction && subaction == "modbusconfig") { + mb->GetInitData(json); + } else if (subaction && subaction == "rawdata") { + mb->GetInitRawData(json); + } + } + + if(action && action == "ReloadConfig") { + if (subaction && subaction == "baseconfig") { + Config->LoadJsonConfig(); + } else if (subaction && subaction == "modbusconfig") { + mb->LoadJsonConfig(false); + } else if (subaction && subaction == "modbusitemconfig") { + mb->LoadJsonItemConfig(); + } + + json["response"]["status"] = 1; + json["response"]["text"] = "new config reloaded sucessfully"; + } + + if (action && action == "SetActiveStatus") { + if (newState) mb->SetItemActiveStatus(item, true); + else mb->SetItemActiveStatus(item, false); + + json["response"]["status"] = 1; + json["response"]["text"] = String("item successfully set to " + String(newState ? "active" : "inactive")); + } + + if(action && action == "handlefiles") { + fsfiles->HandleRequest(json); + } + + } else { + Config->log(1, "WebSocket data received but not a valid json string: %s -> %s", msg.c_str(), error.c_str()); + json["response"]["status"] = 0; + json["response"]["text"] = error.c_str(); + } + + ws->text(client->id(), json.as()); + + } +} void MyWebServer::onImprovWiFiConnectedCb(const char *ssid, const char *password) { server->begin(); Config->log(1, "WebServer has been started now ..."); } +void MyWebServer::sendWebSocketMessage(String& message) { + // send message to all connected clients in the list WsConnectedClientsForBroadcast + for (auto client : WsConnectedClientsForBroadcast) { + if (ws->client(client.id)) { + message = message.substring(0, message.length()-1) + "," + client.json.substring(1, client.json.length()-1); + + Config->log(4, "send WebSocket Message to client %u: %s", client.id, message.c_str()); + ws->text(client.id, message); + } + } + +} + void MyWebServer::loop() { //delay(1); // slow response Issue: https://github.com/espressif/arduino-esp32/issues/4348#issuecomment-695115885 if (this->DoReboot) { @@ -64,6 +212,7 @@ void MyWebServer::loop() { } } ElegantOTA.loop(); + ws->cleanupClients(); } void MyWebServer::handleNotFound(AsyncWebServerRequest *request) { @@ -80,12 +229,8 @@ void MyWebServer::handleFavIcon(AsyncWebServerRequest *request) { request->send(response); } -void MyWebServer::handleReboot(AsyncWebServerRequest *request) { - request->send(LittleFS, "/web/reboot.html", "text/html"); - this->DoReboot = true; -} - -void MyWebServer::handleReset(AsyncWebServerRequest *request) { +bool MyWebServer::handleReset() { + bool ret = true; Config->log(3, "deletion of all config files was requested ...."); //LittleFS.format(); // Werkszustand -> nur die config dateien loeschen, die register dateien muessen erhalten bleiben File root = LittleFS.open("/config/"); @@ -94,27 +239,23 @@ void MyWebServer::handleReset(AsyncWebServerRequest *request) { String path("/config/"); path.concat(file.name()); if (path.indexOf(".json") == -1) {file = root.openNextFile(); continue;} file.close(); - bool f = LittleFS.remove(path); - Config->log(3, "deletion of configuration file '%s' %s", file.name(), (f?"was successful":"has failed")); + + if (LittleFS.remove(path)) { + Config->log(4, "deletion of configuration file '%s' was successful", file.name()); + } else { + Config->log(2, "deletion of configuration file '%s' has failed", file.name()); + ret = false; + } file = root.openNextFile(); } root.close(); + this->DoReboot = true; - this->handleReboot(request); -} - -void MyWebServer::handleWiFiReset(AsyncWebServerRequest *request) { - #ifdef ESP32 - WiFi.disconnect(true,true); - #elif defined(ESP8266) - ESP.eraseConfig(); - #endif - - this->handleReboot(request); + return ret; } void MyWebServer::handleGetItemJson(AsyncWebServerRequest *request) { - mb->GetLiveDataAsJson(request); + mb->GetLiveDataAsJsonToWebServer(request); } void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { @@ -123,119 +264,11 @@ void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { response->addHeader("Pragma", "no-cache"); response->addHeader("Expires", "-1"); - mb->GetRegisterAsJson(response); + mb->GetRegisterAsJsonToWebServer(response); request->send(response); } -void MyWebServer::handleAjax(AsyncWebServerRequest *request) { - char buffer[100] = {0}; - memset(buffer, 0, sizeof(buffer)); - String ret; - bool RaiseError = false; - String action, subaction, item, newState; - String json = "{}"; - - if(request->hasArg("json")) { - json = request->arg("json"); - } - - JsonDocument jsonGet; // TODO Use computed size?? - DeserializationError error = deserializeJson(jsonGet, json.c_str()); - - Config->log(4, "Ajax Json Empfangen: "); - if (!error) { - Config->log(4, jsonGet); - - if (jsonGet["action"]) {action = jsonGet["action"].as();} - if (jsonGet["subaction"]){subaction = jsonGet["subaction"].as();} - if (jsonGet["item"]) {item = jsonGet["item"].as();} - if (jsonGet["newState"]) {newState = jsonGet["newState"].as();} - - } else { - snprintf(buffer, sizeof(buffer), "Ajax Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); - RaiseError = true; - } - - if (action && action == "RefreshLiveData") { - mb->GetLiveDataAsJson(request); - return; - } - - AsyncResponseStream *response = request->beginResponseStream("text/json"); - response->addHeader("Server","ESP Async Web Server"); - - JsonDocument jsonReturn; - jsonReturn["response"].to(); - - if (RaiseError) { - jsonReturn["response"]["status"] = 0; - jsonReturn["response"]["text"] = buffer; - serializeJson(jsonReturn, ret); - response->print(ret); - - Config->log(4, buffer); - - return; - - } else if(action && action == "GetInitData") { - if (subaction && subaction == "status") { - this->GetInitDataStatus(response); - } else if (subaction && subaction == "navi") { - this->GetInitDataNavi(response); - } else if (subaction && subaction == "baseconfig") { - Config->GetInitData(response); - } else if (subaction && subaction == "modbusconfig") { - mb->GetInitData(response); - } else if (subaction && subaction == "rawdata") { - mb->GetInitRawData(response); - } - - } else if(action && action == "ReloadConfig") { - if (subaction && subaction == "baseconfig") { - Config->LoadJsonConfig(); - } else if (subaction && subaction == "modbusconfig") { - mb->LoadJsonConfig(false); - } else if (subaction && subaction == "modbusitemconfig") { - mb->LoadJsonItemConfig(); - } - - jsonReturn["response"]["status"] = 1; - jsonReturn["response"]["text"] = "new config reloaded sucessfully"; - serializeJson(jsonReturn, ret); - response->print(ret); - - //} else if (action && action == "RefreshLiveData") { - //TODO - //mb->GetLiveDataAsJson(response, subaction); - - } else if (action && action == "SetActiveStatus") { - if (strcmp(newState.c_str(),"true")==0) mb->SetItemActiveStatus(item, true); - if (strcmp(newState.c_str(),"false")==0) mb->SetItemActiveStatus(item, false); - - jsonReturn["response"]["status"] = 1; - jsonReturn["response"]["text"] = "successful"; - serializeJson(jsonReturn, ret); - response->print(ret); - - } else if(action && action == "handlefiles") { - fsfiles->HandleAjaxRequest(jsonGet, response); - - } else { - snprintf(buffer, sizeof(buffer), "Ajax Command unknown: %s - %s", action.c_str(), subaction.c_str()); - jsonReturn["response"]["status"] = 0; - jsonReturn["response"]["text"] = buffer; - serializeJson(jsonReturn, ret); - response->print(ret); - - Config->log(1, buffer); - } - - Config->log(4, "Ajax Json Antwort: ", ret); - - request->send(response); -} - void MyWebServer::GetInitDataNavi(AsyncResponseStream *response){ String ret; JsonDocument json; @@ -280,4 +313,41 @@ void MyWebServer::GetInitDataStatus(AsyncResponseStream *response) { serializeJson(json, ret); response->print(ret); +} + +void MyWebServer::GetInitDataNavi(JsonDocument& json) { + json["data"].to(); + json["data"]["hostname"] = Config->GetMqttRoot(); + json["data"]["releasename"] = Config->GetReleaseName(); + json["data"]["releasedate"] = __DATE__; + json["data"]["releasetime"] = __TIME__; + + json["response"].to(); + json["response"]["status"] = 1; + json["response"]["text"] = "successful"; +} + +void MyWebServer::GetInitDataStatus(JsonDocument& json) { + String rssi = (String)(Config->GetUseETH()?ETH.linkSpeed():WiFi.RSSI()); + if (Config->GetUseETH()) rssi.concat(" Mbps"); + + json["data"].to(); + json["data"]["ipaddress"] = mqtt->GetIPAddress().toString(); + json["data"]["wifiname"] = (Config->GetUseETH()?"wired LAN":WiFi.SSID()); + json["data"]["macaddress"] = WiFi.macAddress(); + json["data"]["rssi"] = rssi; + json["data"]["bssid"] = (Config->GetUseETH()?"wired LAN":WiFi.BSSIDstr()); + json["data"]["mqtt_status"] = (mqtt->GetConnectStatusMqtt()?"Connected":"Not Connected"); + json["data"]["inverter_type"] = mb->GetInverterType(); + json["data"]["inverter_serial"] = mb->GetInverterSN(); + json["data"]["uptime"] = uptime_formatter::getUptime(); + json["data"]["freeheapmem"] = ESP.getFreeHeap(); + + #ifndef USE_WEBSERIAL + json["data"]["tr_webserial"]["className"] = "hide"; + #endif + + json["response"].to(); + json["response"]["status"] = 1; + json["response"]["text"] = "successful"; } \ No newline at end of file diff --git a/src/MyWebServer.h b/src/MyWebServer.h index b297a18b..4bab069b 100644 --- a/src/MyWebServer.h +++ b/src/MyWebServer.h @@ -19,45 +19,49 @@ #include "handleFiles.h" #include "mqtt.h" #include "favicon.h" -//#include "html_update.h" #include #include "_Release.h" class MyWebServer { - //enum page_t {ROOT, BASECONFIG, MODBUSCONFIG, MODBUSITEMCONFIG, MODBUSRAWDATA, FSFILES}; - + typedef struct { + uint32_t id; + String json; + } WsConnClient_t; + public: MyWebServer(AsyncWebServer *server, DNSServer* dns); void loop(); + void sendWebSocketMessage(String& message); private: bool DoReboot; - unsigned long RequestRebootTime; + uint64_t RequestRebootTime; + std::vector WsConnectedClientsForBroadcast = {}; + AsyncWebServer* server; DNSServer* dns; + AsyncWebSocket* ws; handleFiles* fsfiles; -// void handle_update_page(AsyncWebServerRequest *request); -// void handle_update_progress(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); -// void handle_update_response(AsyncWebServerRequest *request); void handleNotFound(AsyncWebServerRequest *request); - void handleReboot(AsyncWebServerRequest *request); - void handleReset(AsyncWebServerRequest *request); - void handleWiFiReset(AsyncWebServerRequest *request); + bool handleReset(); void handleRoot(AsyncWebServerRequest *request); void handleFavIcon(AsyncWebServerRequest *request); - void handleAjax(AsyncWebServerRequest *request); void handleGetItemJson(AsyncWebServerRequest *request); void handleGetRegisterJson(AsyncWebServerRequest *request); + void GetInitDataStatus(AsyncResponseStream *response); void GetInitDataNavi(AsyncResponseStream *response); - + void GetInitDataStatus(JsonDocument& json); + void GetInitDataNavi(JsonDocument& json); + void onImprovWiFiConnectedCb(const char *ssid, const char *password); + void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); }; #endif diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 38724d58..dbb9009e 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -92,9 +92,7 @@ const String BaseConfig::GetReleaseName() { return String(Release) + "(@" + String(GIT_BRANCH) + ")"; } -void BaseConfig::GetInitData(AsyncResponseStream *response) { - String ret; - JsonDocument json; +void BaseConfig::GetInitData(JsonDocument& json) { json["data"]["mqttroot"] = this->mqtt_root; json["data"]["mqttserver"] = this->mqtt_server; json["data"]["mqttport"] = this->mqtt_port; @@ -120,8 +118,6 @@ void BaseConfig::GetInitData(AsyncResponseStream *response) { json["response"]["status"] = 1; json["response"]["text"] = "successful"; - serializeJson(json, ret); - response->print(ret); } void BaseConfig::log(const int loglevel, const char* format, ...) { diff --git a/src/baseconfig.h b/src/baseconfig.h index ebede46e..2fccc361 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -11,7 +11,7 @@ class BaseConfig { public: BaseConfig(); void LoadJsonConfig(); - void GetInitData(AsyncResponseStream *response); + void GetInitData(JsonDocument& json); /** * @brief Wrapper function for logging like Serial.printf diff --git a/src/commonlibs.h b/src/commonlibs.h index ecd3a2b8..4ee0690a 100644 --- a/src/commonlibs.h +++ b/src/commonlibs.h @@ -34,5 +34,4 @@ #include #include -//#include #include diff --git a/src/handleFiles.cpp b/src/handleFiles.cpp index df0b5f15..e15330f1 100644 --- a/src/handleFiles.cpp +++ b/src/handleFiles.cpp @@ -15,7 +15,7 @@ handleFiles::handleFiles(AsyncWebServer *server) { //############################################################### // returns the complete folder structure //############################################################### -void handleFiles::getDirList(JsonArray* json, String path) { +void handleFiles::getDirList(JsonArray json, String path) { JsonDocument doc; JsonObject jsonRoot = doc.to(); @@ -45,47 +45,39 @@ void handleFiles::getDirList(JsonArray* json, String path) { file = FSroot.openNextFile(); } FSroot.close(); - json->add(jsonRoot); + json.add(jsonRoot); } //############################################################### -// returns the requested data via AJAX from Webserver.cpp +// returns the requested data from Webserver.cpp //############################################################### -void handleFiles::HandleAjaxRequest(JsonDocument& jsonGet, AsyncResponseStream* response) { +void handleFiles::HandleRequest(JsonDocument& json) { String subaction = ""; - if (jsonGet["subaction"]) {subaction = jsonGet["subaction"].as();} + if (json["cmd"]["subaction"]) {subaction = json["cmd"]["subaction"].as();} - Config->log(3, "handle Ajax Request in handleFiles.cpp: %s", subaction.c_str()); + Config->log(3, "handle Request in handleFiles.cpp: %s", subaction.c_str()); if (subaction == "listDir") { - JsonDocument doc; - JsonArray content = doc.add(); + JsonArray content = json["JS"]["listdir"].to(); - this->getDirList(&content, "/"); - String ret(""); - serializeJson(content, ret); - Config->log(5, content); + this->getDirList(content, "/"); + Config->log(5, json["content"].as().c_str()); - response->print(ret); } else if (subaction == "deleteFile") { - String filename(""), ret(""); - JsonDocument jsonReturn; + String filename(""); Config->log(3, "Request to delete file %s", filename.c_str()); - if (jsonGet["filename"]) {filename = jsonGet["filename"].as();} + if (json["cmd"]["filename"]) {filename = json["cmd"]["filename"].as();} if (LittleFS.remove(filename)) { - jsonReturn["response_status"] = 1; - jsonReturn["response_text"] = "deletion successful"; + json["response"]["status"] = 1; + json["response"]["text"] = "deletion successful"; } else { - jsonReturn["response_status"] = 0; - jsonReturn["response_text"] = "deletion failed"; + json["response"]["status"] = 0; + json["response"]["text"] = "deletion failed"; } - Config->log(3, jsonReturn); - - serializeJson(jsonReturn, ret); - response->print(ret); + Config->log(3, json.as().c_str()); } } diff --git a/src/handleFiles.h b/src/handleFiles.h index b7001706..194268a4 100644 --- a/src/handleFiles.h +++ b/src/handleFiles.h @@ -3,16 +3,17 @@ #include "commonlibs.h" #include "baseconfig.h" +#include class handleFiles { public: handleFiles(AsyncWebServer *server); - void HandleAjaxRequest(JsonDocument& jsonGet, AsyncResponseStream* response); + void HandleRequest(JsonDocument& json); void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); private: - void getDirList(JsonArray* json, String path); + void getDirList(JsonArray json, String path); }; #endif diff --git a/src/html_update.h b/src/html_update.h deleted file mode 100644 index b58a4ce3..00000000 --- a/src/html_update.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef HTMLUPDATE_H -#define HTMLUPDATE_H - -// https://jsfiddle.net/tobiasfaust/Lc1earnz/ -const char HTML_UPDATEPAGE[] PROGMEM = R"=====( - - - - - - - - Solar Inverter Modbus MQTT Gateway - - Update firmware:

-

- - -
- - - -

please select 'data' directory: - -

- - - - - - -)====="; - -#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e42bac8c..f698681a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,18 +3,18 @@ Solar Inverter Modbus-RTU Gateway to MQTT _________________________________________________________________ | | -| author : Tobias Faust +#include +#include +#include +#include AsyncWebServer server(80); DNSServer dns; @@ -29,11 +29,11 @@ void myMQTTCallBack(char* topic, byte* payload, unsigned int length) { Config->log(3, "Message arrived [%s]", topic); for (unsigned int i = 0; i < length; i++) { - msg.concat((char)payload[i]); + msg.concat(static_cast(payload[i])); } Config->log(3, "Message: %s", msg.c_str()); - + mb->ReceiveMQTT(topic, atoi(msg.c_str())); } @@ -42,7 +42,10 @@ void setup() { Config = new BaseConfig(); #ifndef USE_WEBSERIAL - Serial.begin(115200, SERIAL_8N1, Config->GetSerialRx(), Config->GetSerialTx()); // RX, TX, zb.: 33, 32 + Serial.begin(115200, + SERIAL_8N1, + Config->GetSerialRx(), + Config->GetSerialTx()); // RX, TX, zb.: 33, 32 Serial.println(""); Serial.println("ready"); #endif @@ -53,22 +56,19 @@ void setup() { WebSerial.setBuffer(100); #endif - Config->log(1, "Start of Modbus-RTU MQTT Gateway"); + Config->log(1, "Start of Modbus-RTU MQTT Gateway"); Config->log(1, "Starting BaseConfig"); - + Config->log(1, "Starting Wifi and MQTT"); - mqtt = new MQTT(Config->GetMqttServer().c_str(), - Config->GetMqttPort(), - Config->GetMqttBasePath().c_str(), - Config->GetMqttRoot().c_str(), - (char*)"AP_ModbusGateway", - (char*)"MbMQTTGtw" - ); + mqtt = new MQTT(Config->GetMqttServer().c_str(), + Config->GetMqttPort(), + Config->GetMqttBasePath().c_str(), + Config->GetMqttRoot().c_str()); mqtt->setCallback(myMQTTCallBack); mb = new modbus(); mb->enableMqtt(mqtt); - + Config->log(1, "attempting to start WebServer"); mywebserver = new MyWebServer(&server, &dns); } @@ -76,8 +76,8 @@ void setup() { void loop() { mqtt->loop(); mywebserver->loop(); - mb->loop(); - + mb->loop(); + #ifdef USE_WEBSERIAL WebSerial.loop(); #endif diff --git a/src/modbus.cpp b/src/modbus.cpp index 9ab71947..684a1d68 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -11,7 +11,7 @@ modbus::modbus(): enableRelays(false), LastTxIdData(0), LastTxInverter(0), Conf_OpenWBModulID(1), - Conf_OpenWBBatteryID(2) { + Conf_OpenWBBatteryID(2) { DataFrame = new std::vector{}; SaveIdDataframe = new std::vector{}; SaveLiveDataframe = new std::vector{}; @@ -79,6 +79,13 @@ void modbus::init(bool firstrun) { this->QueryIdData(); } +/******************************************************* +* set websocket callback +********************************************************/ +void modbus::setWebSocketCallback(std::function callback) { + webSocketCallback = callback; +} + /******************************************************* * Read configured pin states *******************************************************/ @@ -88,6 +95,11 @@ void modbus::ReadRelays() { this->state_Relay2 = digitalRead(this->pin_Relay2); this->mqtt->Publish_Int("relay2", this->state_Relay2, false); + + if (webSocketCallback) { + String message = "{\"data-id\": {\"relay1.value\":\"" + String(this->state_Relay1?"On":"Off") + "\",\"relay2.value\":\"" + String(this->state_Relay2?"On":"Off") + "\"}}"; + webSocketCallback(message); + } } /******************************************************* @@ -605,7 +617,7 @@ void modbus::ParseData() { //byte ReadBuffer[] = {0x01, 0x04, 0x80, 0x12, 0x34, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x09, 0x64, 0x09, 0x67, 0x09, 0x6B, 0x13, 0x8C, 0x13, 0x8D, 0x13, 0x8B, 0x00, 0x27, 0x00, 0x28, 0x00, 0x28, 0x00, 0x2C, 0x0B, 0x54, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xC3,0x02s}; //Solax X1 - byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02}; + //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02}; //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x02}; //byte ReadBuffer[] = {0x01,0x03,0x28,0x48,0x34,0x35,0x30,0x32,0x41,0x49,0x34,0x34,0x35,0x39,0x30,0x30,0x35,0x73,0x6F,0x6C,0x61,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x4A,0xA0, 0x01}; @@ -619,6 +631,9 @@ void modbus::ParseData() { // K8H //byte ReadBuffer[] = {0xF7,0x02,0x1E,0x0B,0x3E,0x00,0x39,0x05,0xE1,0x0A,0x68,0x00,0x39,0x05,0xFA,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xFC,0x00,0x7C,0x00,0x00,0x13,0x87,0x00,0x00,0x24,0x27, 0x02}; + // Solax-X3 + byte ReadBuffer[] = {0x01,0x04,0x42,0x17,0xB4,0x12,0x21,0x00,0x02,0x00,0x02,0x13,0x88,0x00,0x27,0x00,0x02,0x00,0x7A,0x00,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xDB,0x01,0x04,0x2C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x48,0xFF,0xFF,0x05,0x2F,0x00,0x02,0x7E,0xDD,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0x00,0x00,0x4D,0x6E,0x00,0x00,0x00,0x01,0x00,0x00,0xD3,0x0A,0x01,0x04,0x64,0x08,0x4A,0x00,0x09,0x00,0x64,0x13,0x88,0x08,0x81,0x00,0x09,0x00,0x3D,0x13,0x88,0x08,0x20,0x00,0x09,0x00,0x61,0x13,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x95,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0x2D,0xFF,0xFF,0x31,0xA8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x4A,0xC0,0x00,0x00,0x00,0x1B,0x00,0x00,0x00,0x47,0x00,0x00,0x01,0xD9,0x00,0x00,0x55,0x5B,0x01,0x04,0x04,0x00,0x00,0x00,0x00,0xFB,0x84,0x02}; + for (uint16_t i = 0; iDataFrame->push_back(ReadBuffer[i]); } @@ -794,6 +809,7 @@ void modbus::ParseData() { if (this->mqtt && IsActiveItem && d.Name != "") { this->mqtt->Publish_String(d.Name.c_str(), d.value, false); + if (openwbtopic.length() > 0) { const String newTopic(OpenWB->getOpenWbTopic(openwbtopic)); if (newTopic.length() > 0) { @@ -810,6 +826,12 @@ void modbus::ParseData() { Config->log(3, "Inverter ID Data found -> %s: %s ", d.Name.c_str(), d.value.c_str()); } + + //if (webSocketCallback) { + //const String ws("{\"data-id\":{\"" + d.Name + ".value\":\"" + d.value + " "+ d.unit +"\"}}"); + //const String ws("{\"" + d.Name + ".value\":\"" + d.value + " "+ d.unit +"\"}"); + //webSocketCallback(ws); + //} } while (regfile.findUntil(",","]")); @@ -827,9 +849,26 @@ void modbus::ParseData() { this->SaveIdDataframe->assign(this->DataFrame->begin(), this->DataFrame->end()); } + if (webSocketCallback) { + this->SendDataToWebSocket(RequestType == "livedata" ? this->InverterLiveData : this->InverterIdData); + } + this->DataFrame->clear(); } +void modbus::SendDataToWebSocket(std::vector* vector) { + if (webSocketCallback) { + String msg("{\"data-id\":{"); msg.reserve(vector->size() * 25); + + for (uint8_t i=0; i < vector->size(); i++) { + if (i > 0) msg += ","; + msg += "\"" + vector->at(i).Name + ".value\":\"" + vector->at(i).value + " "+ vector->at(i).unit +"\""; + } + msg += "}}"; + webSocketCallback(msg); + } +} + String modbus::ConvertIntToBinaryString(int n, int numBits) { String binaryString = ""; binaryString.reserve(numBits); @@ -977,8 +1016,10 @@ String modbus::GetInverterSN() { * Return all LiveData as jsonArray * {data: [{"name": "xx", "value": "xx", ...}, ...] } *******************************************************/ -void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { +void modbus::GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request) { std::shared_ptr counter = std::make_shared(0); + std::shared_ptr firstRow = std::make_shared(true); + String subaction(""), json("{}"); if(request->hasArg("json")) { @@ -987,26 +1028,28 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { JsonDocument jsonGet; DeserializationError error = deserializeJson(jsonGet, json.c_str()); - Config->log(4, "[GetLiveDataAsJson] Json command empfangen: "); + Config->log(4, "[GetLiveDataAsJsonToWebServer] Json command empfangen: "); if (!error) { Config->log(4, jsonGet); if (jsonGet["subaction"]){subaction = jsonGet["subaction"].as();} } else { - Config->log(2, "[GetLiveDataAsJson] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); + Config->log(2, "[GetLiveDataAsJsonToWebServer] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); } - AsyncWebServerResponse *response = request->beginChunkedResponse("application/json", [this, counter, subaction](uint8_t *buffer, size_t maxLen, size_t index) { + AsyncWebServerResponse *response = request->beginChunkedResponse("application/json", [this, firstRow, counter, subaction](uint8_t *buffer, size_t maxLen, size_t index) { String ret(""); ret.reserve(maxLen); maxLen -= 500; // use a puffer of 500 bytes, every item is assumed to be 200 bytes + if (*counter == 0) { // send start of JSON ret += "{\"data\": {\"items\": ["; (*counter)++; } + if (*counter <= this->InverterIdData->size() && ret.length() < maxLen) { // send IdData uint16_t i = *counter - 1; @@ -1014,10 +1057,10 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { // jedes JsonObject wird mit 200 bytes angenommen, + 100 bytes puffer am Ende while (i < this->InverterIdData->size() && ret.length() < maxLen) { if (!(subaction == "onlyactive" && !this->InverterIdData->at(i).active)) { - if(*counter > 1) ret += ","; + if(!(*firstRow)) ret += ","; ret += "{\"name\": \"" + this->InverterIdData->at(i).Name + "\","; ret += "\"realname\": \"" + this->InverterIdData->at(i).RealName + "\","; - ret += "\"value\": \"" + this->InverterIdData->at(i).value + " " + this->InverterIdData->at(i).unit + "\","; + ret += "\"value\": {\"innerHTML\": \"" + this->InverterIdData->at(i).value + " " + this->InverterIdData->at(i).unit + "\", \"data-id\": \"" + this->InverterIdData->at(i).Name + ".value" + "\"},"; ret += "\"active\": {\"checked\": " + String(this->InverterIdData->at(i).active ? 1 : 0) + ", \"name\": \"" + this->InverterIdData->at(i).Name + "\"},"; ret += "\"mqtttopic\": \"" + this->mqtt->getTopic(this->InverterIdData->at(i).Name, false) + "\""; @@ -1025,6 +1068,7 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { ret += ",\"openwb\": [{\"openwbtopic\": \"" + OpenWB->getOpenWbTopic(this->InverterIdData->at(i).openwb) + "\"}]"; } ret += "}"; + (*firstRow) = false; } (*counter)++; @@ -1038,10 +1082,10 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { while (i < this->InverterLiveData->size() && ret.length() < maxLen) { if (!(subaction == "onlyactive" && !this->InverterLiveData->at(i).active)) { - if(*counter > 1) ret += ","; + if(!(*firstRow)) ret += ","; ret += "{\"name\": \"" + this->InverterLiveData->at(i).Name + "\","; ret += "\"realname\": \"" + this->InverterLiveData->at(i).RealName + "\","; - ret += "\"value\": \"" + this->InverterLiveData->at(i).value + " " + this->InverterLiveData->at(i).unit + "\","; + ret += "\"value\": {\"innerHTML\": \"" + this->InverterLiveData->at(i).value + " " + this->InverterLiveData->at(i).unit + "\", \"data-id\": \"" + this->InverterLiveData->at(i).Name + ".value" + "\"},"; ret += "\"active\": {\"checked\": " + String(this->InverterLiveData->at(i).active ? 1 : 0) + ", \"name\": \"" + this->InverterLiveData->at(i).Name + "\"},"; ret += "\"mqtttopic\": \"" + this->mqtt->getTopic(this->InverterLiveData->at(i).Name, false) + "\""; @@ -1049,6 +1093,7 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { ret += ",\"openwb\": [{\"openwbtopic\": \"" + OpenWB->getOpenWbTopic(this->InverterLiveData->at(i).openwb) + "\"}]"; } ret += "}"; + (*firstRow) = false; } (*counter)++; @@ -1070,13 +1115,12 @@ void modbus::GetLiveDataAsJson(AsyncWebServerRequest *request) { request->send(response); } - /******************************************************* * Return all LiveData as jsonArray * {data: [{"name": "xx", "value": "xx"}], } * {"GridVoltage_R":"0.00 V","GridCurrent_R":"0.00 A","GridPower_R":"0 W","GridFrequency_R":"0.00 Hz","GridVoltage_S":"0.90 V","GridCurrent_S":"1715.40 A","GridPower_S":"-28671 W","GridFrequency_S":"174.08 Hz","GridVoltage_T":"0.00 V","GridCurrent_T":"0.00 A","GridPower_T":"0 W","GridFrequency_T":"1.30 Hz","PvVoltage1":"259.80 V","PvVoltage2":"0.00 V","PvCurrent1":"1.00 A","PvCurrent2":"0.00 A","Temperature":"28 °C","PowerPv1":"283 W","PowerPv2":"0 W","BatVoltage":"0.00 V","BatCurrent":"0.00 A","BatPower":"0 W","BatTemp":"0 °C","BatCapacity":"0 %","OutputEnergyChargeWh":"0 Wh","OutputEnergyChargeKWh":"0.00 KWh","OutputEnergyChargeToday":"0.00 KWh","InputEnergyChargeWh":"0 Wh","InputEnergyChargeKWh":"0.00 KWh"} *******************************************************/ -void modbus::GetRegisterAsJson(AsyncResponseStream *response) { +void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { int count = 0; File regfile = LittleFS.open("/regs/"+this->InverterType.filename); @@ -1441,14 +1485,12 @@ void modbus::LoadJsonItemConfig() { /******************************************************************************************************* * WebContent *******************************************************************************************************/ -void modbus::GetInitData(AsyncResponseStream *response) { - String ret; - JsonDocument json; +void modbus::GetInitData(JsonDocument &json){ json["data"].to(); json["data"]["GpioPin_RX"] = this->pin_RX; json["data"]["GpioPin_TX"] = this->pin_TX; json["data"]["GpioPin_RTS"] = this->pin_RTS; - json["data"]["clientid"] = this->ClientID; + json["data"]["clientid"] = String(this->ClientID, HEX); json["data"]["baudrate"] = this->Baudrate; json["data"]["txintervallive"] = this->TxIntervalLiveData; json["data"]["txintervalid"] = this->TxIntervalIdData; @@ -1483,15 +1525,11 @@ void modbus::GetInitData(AsyncResponseStream *response) { json["response"].to(); json["response"]["status"] = 1; json["response"]["text"] = "successful"; - serializeJson(json, ret); - response->print(ret); } -void modbus::GetInitRawData(AsyncResponseStream *response) { - String ret = ""; +void modbus::GetInitRawData(JsonDocument& json) { std::ostringstream id, live; - JsonDocument json; - + live << std::hex << std::uppercase; id << std::hex << std::uppercase; @@ -1510,7 +1548,4 @@ void modbus::GetInitRawData(AsyncResponseStream *response) { json["response"].to(); json["response"]["status"] = 1; json["response"]["text"] = "successful"; - serializeJson(json, ret); - - response->print(ret); } diff --git a/src/modbus.h b/src/modbus.h index 438911c2..e2e36bd6 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -13,7 +13,7 @@ #include #include -//#define DEBUGMODE +#define DEBUGMODE class modbus { @@ -52,17 +52,19 @@ class modbus { const String& GetInverterType() const {return InverterType.name;} const String GetOpenWbVersion() const {return Conf_OpenWBVersion;} - void enableMqtt(MQTT* object); - void GetInitData(AsyncResponseStream *response); - void GetInitRawData(AsyncResponseStream *response); + void GetInitData(JsonDocument& json); + void GetInitRawData(JsonDocument& json); String GetInverterSN(); - - void GetLiveDataAsJson(AsyncWebServerRequest *request); - void GetRegisterAsJson(AsyncResponseStream *response); + void GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request); + void GetRegisterAsJsonToWebServer(AsyncResponseStream *response); void SetItemActiveStatus(String item, bool newstate); void ReceiveMQTT(String topic, int msg); + // Callback setzen + void setWebSocketCallback(std::function callback); + void deleteWebSocketCallback() { webSocketCallback = nullptr; } + private: uint8_t pin_RX; // Serial Receive pin uint8_t pin_TX; // Serial Transmit pin @@ -122,6 +124,7 @@ class modbus { String MapBitwise(JsonArray map, String value); String ConvertIntToBinaryString(int n, int numBits); void ReadRelays(); + void SendDataToWebSocket(std::vector* vector); // inverter config, in sync with register.h ->config ArduinoQueue>* ReadQueue; @@ -129,6 +132,9 @@ class modbus { std::vector>* Conf_RequestLiveData; std::vector>* Conf_RequestIdData; + + std::function webSocketCallback; // Callback-Funktion + uint8_t Conf_ClientIdPos; //uint8_t Conf_LiveDataStartsAtPos; //uint8_t Conf_IdDataStartsAtPos; diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 2f1cc479..3f15bc45 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -1,25 +1,27 @@ -#include "mqtt.h" - -MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot, char* APName, char* APpassword): - improvSerial(&Serial), - mqtt_root(MqttRoot), - mqtt_basepath(MqttBasepath), - ConnectStatusWifi(false), - ConnectStatusMqtt(false) -{ - +/******************************************************** + * Copyright [2024] Tobias Faust + +MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot): + improvSerial(&Serial), + mqtt_root(MqttRoot), + mqtt_basepath(MqttBasepath), + ConnectStatusWifi(false), + ConnectStatusMqtt(false) { this->subscriptions = new std::vector{}; - + WiFi.setHostname(this->mqtt_root.c_str()); -#ifdef ESP32 +#ifdef ESP32 WiFi.onEvent(std::bind(&MQTT::WifiOnEvent, this, std::placeholders::_1)); #endif Config->log(3, "Go into %s Mode", (Config->GetUseETH()?"ETH":"Wifi")); ImprovTypes::ChipFamily variant; - + #ifdef ESP32 String variantString = ARDUINO_VARIANT; #else @@ -38,21 +40,26 @@ MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, Strin variant = ImprovTypes::ChipFamily::CF_ESP32; } - improvSerial.setDeviceInfo(variant, String(GIT_REPO).c_str(), Config->GetReleaseName().c_str(), Config->GetMqttRoot().c_str()); - improvSerial.onImprovError(std::bind(&MQTT::onImprovWiFiErrorCb, this, std::placeholders::_1)); - + improvSerial.setDeviceInfo(variant, + String(GIT_REPO).c_str(), + Config->GetReleaseName().c_str(), + Config->GetMqttRoot().c_str()); + improvSerial.onImprovError(std::bind(&MQTT::onImprovWiFiErrorCb, + this, + std::placeholders::_1)); + if (Config->GetUseETH()) { #ifdef ESP32 eth_shield_t* shield = this->GetEthShield(Config->GetLANBoard()); - - //ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); + + // ETH.begin(1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN); ETH.begin(shield->PHY_ADDR, shield->PHY_POWER, shield->PHY_MDC, shield->PHY_MDIO, shield->PHY_TYPE, shield->CLK_MODE); - + this->WaitForConnect(); #endif @@ -65,17 +72,16 @@ MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, Strin Config->log(1, "Initializing MQTT (%s:%d)", Config->GetMqttServer().c_str(), Config->GetMqttPort()); espClient = WiFiClient(); - + PubSubClient::setClient(espClient); PubSubClient::setServer(Config->GetMqttServer().c_str(), Config->GetMqttPort()); } -void MQTT::onImprovWiFiErrorCb(ImprovTypes::Error err) -{ - if(err == ImprovTypes::Error::ERROR_WIFI_DISCONNECTED) { +void MQTT::onImprovWiFiErrorCb(ImprovTypes::Error err) { + if (err == ImprovTypes::Error::ERROR_WIFI_DISCONNECTED) { this->disconnect(); } - if(err == ImprovTypes::Error::ERROR_WIFI_CONNECT_GIVEUP) { + if (err == ImprovTypes::Error::ERROR_WIFI_CONNECT_GIVEUP) { Serial.println("Giving up on connecting to WiFi, restart the device"); ESP.restart(); } @@ -86,7 +92,7 @@ void MQTT::WifiOnEvent(WiFiEvent_t event) { Config->log(4, "[WiFi-event] event: %d", event); switch (event) { - case ARDUINO_EVENT_WIFI_READY: + case ARDUINO_EVENT_WIFI_READY: Config->log(1, "WiFi interface ready"); break; case ARDUINO_EVENT_WIFI_SCAN_DONE: @@ -116,7 +122,7 @@ void MQTT::WifiOnEvent(WiFiEvent_t event) { case ARDUINO_EVENT_WIFI_STA_LOST_IP: Config->log(1, "Lost IP address and IP address is reset to 0"); this->ConnectStatusWifi = false; - this->ipadresse = (0,0,0,0); + this->ipadresse = (0, 0, 0, 0); break; case ARDUINO_EVENT_WPS_ER_SUCCESS: Config->log(1, "WiFi Protected Setup (WPS): succeeded in enrollee mode"); @@ -169,16 +175,15 @@ void MQTT::WifiOnEvent(WiFiEvent_t event) { case ARDUINO_EVENT_ETH_DISCONNECTED: Config->log(1, "Ethernet disconnected"); this->ConnectStatusWifi = false; - this->ipadresse = (0,0,0,0); + this->ipadresse = (0, 0, 0, 0); break; case ARDUINO_EVENT_ETH_GOT_IP: if (!this->ConnectStatusWifi) { - Config->log(1, "ETH MAC: %s, IPv4: %s, %s, Mbps: %d", - ETH.macAddress().c_str(), + Config->log(1, "ETH MAC: %s, IPv4: %s, %s, Mbps: %d", + ETH.macAddress().c_str(), ETH.localIP().toString().c_str(), (ETH.fullDuplex()?"FULL_DUPLEX":"HALF_DUPLEX"), - ETH.linkSpeed() - ); + ETH.linkSpeed()); this->ipadresse = ETH.localIP(); this->ConnectStatusWifi = true; } @@ -192,8 +197,8 @@ void MQTT::WifiOnEvent(WiFiEvent_t event) { return LanShield parameter tuple ########################################*/ eth_shield_t* MQTT::GetEthShield(String ShieldName) { - for(uint8_t i=0; ilan_shields.size(); i++) { - if(this->lan_shields.at(i).name == ShieldName) { + for (uint8_t i = 0; i < this->lan_shields.size(); i++) { + if (this->lan_shields.at(i).name == ShieldName) { return &this->lan_shields.at(i); break; } @@ -204,8 +209,7 @@ eth_shield_t* MQTT::GetEthShield(String ShieldName) { void MQTT::WaitForConnect() { while (!this->ConnectStatusWifi) delay(100); - Config->log(1, "Wait for connect"); - //yield(); + Config->log(1, "Wait for connect"); } void MQTT::reconnect() { @@ -213,27 +217,33 @@ void MQTT::reconnect() { char LWT[50]; memset(&LWT[0], 0, sizeof(LWT)); memset(&topic[0], 0, sizeof(topic)); - - if (Config->UseRandomMQTTClientID()) { + + if (Config->UseRandomMQTTClientID()) { snprintf (topic, sizeof(topic), "%s-%s", this->mqtt_root.c_str(), String(random(0xffff)).c_str()); } else { snprintf (topic, sizeof(topic), "%s-%08X", this->mqtt_root.c_str(), ESP_getChipId()); } snprintf(LWT, sizeof(LWT), "%s/state", this->mqtt_root.c_str()); - + Config->log(1, "Attempting MQTT connection as %s ", topic); - - if (PubSubClient::connect(topic, Config->GetMqttUsername().c_str(), Config->GetMqttPassword().c_str(), LWT, true, false, "Offline")) { + + if (PubSubClient::connect(topic, + Config->GetMqttUsername().c_str(), + Config->GetMqttPassword().c_str(), + LWT, + true, + false, + "Offline")) { Config->log(1, "connected... "); // Once connected, publish basics ... this->Publish_IP(); this->Publish_String("ssid", WiFi.SSID(), false); this->Publish_String("version", Config->GetReleaseName(), false); - this->Publish_String("state", "Online", false); //LWT reset - + this->Publish_String("state", "Online", false); // LWT reset + // ... and resubscribe if needed for (uint8_t i=0; i< this->subscriptions->size(); i++) { - PubSubClient::subscribe(this->subscriptions->at(i).c_str()); + PubSubClient::subscribe(this->subscriptions->at(i).c_str()); Config->log(1, "MQTT resubscribed to: %s", this->subscriptions->at(i).c_str()); } @@ -247,13 +257,13 @@ void MQTT::disconnect() { } void MQTT::Publish_Bool(const char* subtopic, bool b, bool fulltopic) { - String s; - if(b) {s = "1";} else {s = "0";}; + String s(""); + if (b) {s = "1";} else {s = "0";} Publish_String(subtopic, s, fulltopic); } void MQTT::Publish_Int(const char* subtopic, int number, bool fulltopic) { - char buffer[20] = {0}; + char buffer[20] = {0}; memset(buffer, 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "%d", number); Publish_String(subtopic, (String)buffer, fulltopic); @@ -272,7 +282,7 @@ void MQTT::Publish_String(const char* subtopic, String value, bool fulltopic) { if (PubSubClient::connected()) { PubSubClient::publish((const char*)topic.c_str(), value.c_str(), true); Config->log(3, "Publish %s: %s ", topic.c_str(), value.c_str()); - } else Config->log(2, "Request for MQTT Publish, but not connected to Broker"); + } else { Config->log(2, "Request for MQTT Publish, but not connected to Broker"); } } String MQTT::getTopic(String subtopic, bool fulltopic) { @@ -282,7 +292,7 @@ String MQTT::getTopic(String subtopic, bool fulltopic) { return std::move(subtopic); } -void MQTT::Publish_IP() { +void MQTT::Publish_IP() { char buffer[16] = {0}; memset(&buffer[0], 0, sizeof(buffer)); snprintf(buffer, sizeof(buffer), "%s", this->ipadresse.toString().c_str()); @@ -305,7 +315,7 @@ bool MQTT::UnSubscribe(String topic) { for (uint8_t i=0; i< this->subscriptions->size(); i++) { if (topic == this->subscriptions->at(i)) { if (PubSubClient::connected()) { - PubSubClient::unsubscribe(this->subscriptions->at(i).c_str()); + PubSubClient::unsubscribe(this->subscriptions->at(i).c_str()); } Config->log(3, "MQTT unsubscribed from: %s", this->subscriptions->at(i).c_str()); this->subscriptions->erase(this->subscriptions->begin()+i); @@ -317,16 +327,16 @@ bool MQTT::UnSubscribe(String topic) { } void MQTT::ClearSubscriptions() { - for ( uint8_t i=0; i< this->subscriptions->size(); i++) { - if (PubSubClient::connected()) { - PubSubClient::unsubscribe(this->subscriptions->at(i).c_str()); + for (uint8_t i = 0; i < this->subscriptions->size(); i++) { + if (PubSubClient::connected()) { + PubSubClient::unsubscribe(this->subscriptions->at(i).c_str()); } } this->subscriptions->clear(); this->subscriptions->shrink_to_fit(); } -void MQTT::loop() { +void MQTT::loop() { improvSerial.loop(); #ifdef ESP8266 @@ -335,12 +345,14 @@ void MQTT::loop() { this->ipadresse = WiFi.localIP(); } else { this->ConnectStatusWifi = false; - this->ipadresse = (0,0,0,0); + this->ipadresse = (0, 0, 0, 0); } #endif if (this->mqtt_root != Config->GetMqttRoot()) { - Config->log(3, "MQTT DeviceName has changed via Web Configuration from %s to %s ", this->mqtt_root.c_str(), Config->GetMqttRoot().c_str()); + Config->log(3, "MQTT DeviceName has changed via Web Configuration from %s to %s ", + this->mqtt_root.c_str(), + Config->GetMqttRoot().c_str()); Config->log(3, "Initiate Reconnect"); this->mqtt_root = Config->GetMqttRoot(); @@ -348,7 +360,9 @@ void MQTT::loop() { } if (this->mqtt_basepath != Config->GetMqttBasePath()) { - Config->log(3, "MQTT Basepath has changed via Web Configuration from %s to %s ", this->mqtt_basepath.c_str(), Config->GetMqttBasePath().c_str()); + Config->log(3, "MQTT Basepath has changed via Web Configuration from %s to %s ", + this->mqtt_basepath.c_str(), + Config->GetMqttBasePath().c_str()); Config->log(3, "Initiate Reconnect"); this->mqtt_basepath = Config->GetMqttBasePath(); @@ -356,12 +370,12 @@ void MQTT::loop() { } // WIFI ok, MQTT lost - if (!PubSubClient::connected() && this->ConnectStatusWifi) { + if (!PubSubClient::connected() && this->ConnectStatusWifi) { if (millis() - mqttreconnect_lasttry > 10000) { - this->reconnect(); + this->reconnect(); this->mqttreconnect_lasttry = millis(); } - } else if (this->ConnectStatusWifi) { + } else if (this->ConnectStatusWifi) { PubSubClient::loop(); } @@ -374,7 +388,7 @@ void MQTT::loop() { if (Config->GetDebugLevel() >=4 && millis() - this->last_keepalive > (30 * 1000)) { // send messages for debugging every 30 seconds this->last_keepalive = millis(); - + if (Config->GetDebugLevel() >=4) { char buffer[100] = {0}; memset(buffer, 0, sizeof(buffer)); @@ -384,10 +398,10 @@ void MQTT::loop() { snprintf(buffer, sizeof(buffer), "%d", WiFi.RSSI()); this->Publish_String("rssi", buffer, false); - + uint64_t uptimeMicroSeconds = esp_timer_get_time(); uint64_t uptimeSeconds = uptimeMicroSeconds / 1000000; - this->Publish_Int("uptime",uptimeSeconds,false); + this->Publish_Int("uptime", uptimeSeconds, false); } } } diff --git a/src/mqtt.h b/src/mqtt.h index ed991a99..41fefa86 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -1,53 +1,63 @@ -#ifndef MQTT_H -#define MQTT_H +/******************************************************** + * Copyright [2024] Tobias Faust #include #include -#include #include +#include +#include +#include + #ifdef ESP8266 - //#define SetHostName(x) wifi_station_set_hostname(x); - #define ESP_getChipId() ESP.getChipId() + // #define SetHostName(x) wifi_station_set_hostname(x); + #define ESP_getChipId() ESP.getChipId() #endif #ifdef ESP32 #include - //#define SetHostName(x) WiFi.getHostname(x); --> MQTT.cpp TODO - #define ESP_getChipId() (uint32_t)ESP.getEfuseMac() // Unterschied zu ESP.getFlashChipId() ??? + // #define SetHostName(x) WiFi.getHostname(x); --> MQTT.cpp TODO + #define ESP_getChipId() static_cast(ESP.getEfuseMac()) // Unterschied zu ESP.getFlashChipId() ??? #endif #ifdef ESP32 typedef struct { - String name; + String name; uint8_t PHY_ADDR; - int PHY_POWER; + int PHY_POWER; int PHY_MDC; - int PHY_MDIO; + int PHY_MDIO; eth_phy_type_t PHY_TYPE; eth_clock_mode_t CLK_MODE; } eth_shield_t; #elif defined(ESP8266) typedef struct { - String name; + String name; } eth_shield_t; #endif class MQTT: PubSubClient { - #ifdef ESP32 - std::vector lan_shields = {{"WT32-ETH01", 1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN}, - {"test", 1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN}}; + std::vector lan_shields = { + {"WT32-ETH01", 1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN}, + {"test", 1, 16, 23, 18, ETH_PHY_LAN8720, ETH_CLOCK_GPIO0_IN}}; #elif defined(ESP8266) - std::vector lan_shields = {{"test1"}, - {"test2"}}; + std::vector lan_shields = { + {"test1"}, + {"test2"}}; #endif - public: - - MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, String MqttRoot, char* APName, char* APpassword); + public: + MQTT(const char* MqttServer, + uint16_t MqttPort, + String MqttBasepath, + String MqttRoot); void loop(); void Publish_Bool(const char* subtopic, bool b, bool fulltopic); void Publish_Int(const char* subtopic, int number, bool fulltopic); @@ -56,12 +66,12 @@ class MQTT: PubSubClient { void Publish_IP(); String getTopic(String subtopic, bool fulltopic); void disconnect(); - const String& GetRoot() const {return mqtt_root;}; - const String& GetBasePath() const {return mqtt_basepath;}; + const String& GetRoot() const {return mqtt_root;} + const String& GetBasePath() const {return mqtt_basepath;} void Subscribe(String topic); bool UnSubscribe(String topic); void ClearSubscriptions(); - + const bool& GetConnectStatusWifi() const {return ConnectStatusWifi;} const bool& GetConnectStatusMqtt() const {return ConnectStatusMqtt;} const IPAddress& GetIPAddress() const {return ipadresse;} @@ -70,22 +80,22 @@ class MQTT: PubSubClient { ImprovWiFi improvSerial; - protected: + protected: void reconnect(); - private: + private: WiFiClient espClient; std::vector* subscriptions = NULL; String mqtt_root = ""; String mqtt_basepath = ""; - unsigned long mqttreconnect_lasttry = 0; - unsigned long last_keepalive = 0; + uint64_t mqttreconnect_lasttry = 0; + uint64_t last_keepalive = 0; bool ConnectStatusWifi; bool ConnectStatusMqtt; IPAddress ipadresse; - + #ifdef ESP32 void WifiOnEvent(WiFiEvent_t event); #endif @@ -97,4 +107,4 @@ class MQTT: PubSubClient { extern MQTT* mqtt; -#endif +#endif // MQTT_H_ From 08b2ed63b859e5739dc6489074da88b917976ace Mon Sep 17 00:00:00 2001 From: tobiasfaust Date: Wed, 22 Jan 2025 18:02:32 +0100 Subject: [PATCH 051/106] Visualisierung der Settopics (#127) * Visualisierung der Settopics (#124) * Update modbusitemconfig.html * Update modbus.cpp * Update modbus.cpp * Update modbus.h * Update modbus.h * Update modbus.cpp * Update modbus.cpp * Update modbus.h * Update modbus.h * Update modbus.h * Update modbus.cpp * Update modbus.cpp * Update MyWebServer.cpp * Update MyWebServer.h * Update modbus.cpp * Update modbus.h * Update modbus.h * Update MyWebServer.cpp * Update MyWebServer.cpp * Update MyWebServer.cpp * Update MyWebServer.cpp * Update MyWebServer.cpp * Update modbusitemconfig.js * Update modbusitemconfig.js * Update MyWebServer.cpp * Update modbus.cpp * Update modbus.h * Update modbus.h * Update modbusitemconfig.html * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbusitemconfig.html * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.h * Update modbus.cpp * Update modbus.h * Update modbusitemconfig.html * update * refactor: initialize subscription fields with default values and clean up comments * refactor: remove unnecessary headers from handleGetSetterJson response * fix per cpplint * fix: update WebSocket connection to use dynamic origin * change setter handling to regfile * refactor: update setter handling and improve JSON response structure * fix: debugmode with Solax-X1 data, fix SetActiveStatus handling * refactor: change Setters type to setter_t and tidy up code * add setter info-box, remove /getregister, add setter mapping info to WebUI * refactor: update info descriptions in Solax-X1 JSON and clean up unused server routes --------- Co-authored-by: Lazgar <34341913+Lazgar@users.noreply.github.com> --- data/regs/Solax-X1.json | 44 ++++- data/web/Javascript.js | 4 +- data/web/Style.css | 2 +- data/web/modbusitemconfig.html | 38 +++- data/web/modbusitemconfig.js | 70 ++++++- src/MyWebServer.cpp | 68 ++----- src/MyWebServer.h | 48 +++-- src/baseconfig.cpp | 26 +-- src/baseconfig.h | 25 +-- src/commonlibs.h | 4 + src/favicon.h | 4 + src/handleFiles.cpp | 4 + src/handleFiles.h | 4 + src/main.cpp | 2 +- src/modbus.cpp | 328 +++++++++++++++++++++++++++------ src/modbus.h | 21 ++- src/mqtt.cpp | 10 +- src/openwb.cpp | 15 +- src/openwb.h | 31 ++-- 19 files changed, 536 insertions(+), 212 deletions(-) diff --git a/data/regs/Solax-X1.json b/data/regs/Solax-X1.json index 65a8f3fc..70ae2b47 100644 --- a/data/regs/Solax-X1.json +++ b/data/regs/Solax-X1.json @@ -515,14 +515,46 @@ ] }, "set": [ + { + "name": "setUnlockSettings", + "realname": "Unlock Settings", + "info": "send the 4 digit advanced password", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x00" + ] + }, { - "name": "TargetBatSOC", - "request": [ - "#ClientID", - "0x06", - "0x00", - "0x83" + "name": "setTargetBatSOC", + "realname": "Target SoC", + "info": "set battery SOC: 0 - 100 in percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x83" ] + }, + { + "name": "setOperationMode", + "realname": "Operation Mode", + "info": "setting of 6 possible operation modes", + "mapping": [ + [ "SelfUse", 0 ], + [ "FeedInPriority", 1 ], + [ "BackupMode", 2 ], + [ "ManuelMode", 3 ], + [ "PeakShaving", 4 ], + [ "TUOMode", 5 ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x1f" + ] } ] } diff --git a/data/web/Javascript.js b/data/web/Javascript.js index cf47fc32..0b5a3e1a 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -122,8 +122,8 @@ export function connectWebSocket() { return; } - //ws = new WebSocket(location.origin.replace(/^http/, 'ws') + '/ajaxws'); - ws = new WebSocket('ws://10.0.2.150/ajaxws'); + ws = new WebSocket(location.origin.replace(/^http/, 'ws') + '/ajaxws'); + //ws = new WebSocket('ws://10.0.2.150/ajaxws'); var wsStatus = document.getElementById('ws-status'); ws.onopen = function() { diff --git a/data/web/Style.css b/data/web/Style.css index 91cfdff8..cf4ded95 100644 --- a/data/web/Style.css +++ b/data/web/Style.css @@ -238,7 +238,7 @@ body { } } -#needToSave { +.needToSave { width: 300px; margin: 0 auto; border: 1px solid red; diff --git a/data/web/modbusitemconfig.html b/data/web/modbusitemconfig.html index af0007dc..98c9ad52 100644 --- a/data/web/modbusitemconfig.html +++ b/data/web/modbusitemconfig.html @@ -62,6 +62,42 @@ +
+ +
+ + Set commands deactivated +
+ +
+ + no Set commands available +
+ + + + + + + + + + + + +
ActiveNameSubscription


@@ -74,4 +110,4 @@ x
- \ No newline at end of file + diff --git a/data/web/modbusitemconfig.js b/data/web/modbusitemconfig.js index 8ede60fb..d3e73ed9 100644 --- a/data/web/modbusitemconfig.js +++ b/data/web/modbusitemconfig.js @@ -2,7 +2,6 @@ import * as global from './Javascript.js'; // ************************************************ export function init1() { - // erstelle ein Beispiel json mit Beispielwerten welches die funktion modbus::GetLiveDataAsJsonToWebserver generieren würde und weise das json der variable data zu. var data = {"data": {"items": [ {"name": "InverterIdData", "realname": "InverterIdData", @@ -18,11 +17,22 @@ export function init1() { ]}, "response": {"status": 1, "text": "successful"}, "cmd": { - "callbackFn": "mbitemconfig_Callback" + "callbackFn": "mbitemconfig_ItemCallback" }} global.handleJsonItems(data); - datavalues = global.getFormData("DataForm"); + + + data = {"globalEnabled": "1", "data": {"setitems": [{"name": "setUnlockSettings","realname": "Unlock Settings","active": {"checked": 0, "name": "setUnlockSettings"},"subscription": "home/Solax-Test/set/setUnlockSettings","info": "send the 4 digit advanced password"}, + {"name": "setTargetBatSOC","realname": "Target SoC","active": {"checked": 0, "name": "setTargetBatSOC"},"subscription": "home/Solax-Test/set/setTargetBatSOC","info": "set 0 - 100 in percent"} , + {"name": "setOperationMode","realname": "Operation Mode","active": {"checked": 0, "name": "setOperationMode"},"subscription": "home/Solax-Test/set/setOperationMode","info": {"data-mapping": "[[ 'Self-Use', 0],['FeedInPriority', 1],['BackupMode',2],['ManuelMode',3],['PeakShaving',4],[ 'TUOMode', 5 ]]", "innerHTML": "set inverter operation mode"}} + ]}, + "object_id": "home/Solax-Test", "response": {"status": 1, "text": "successful"}, + "cmd": { + "callbackFn": "mbitemconfig_SetterCallback" + }} + global.handleJsonItems(data); + data = {"data-id": { "InverterIdData.value" : "684453556"}, "response": {"status": 1, "text": "successful"}, @@ -41,11 +51,20 @@ export function init() { .then(response => response.json()) .then(data => { data['cmd'] = {}; - data['cmd']['callbackFn'] = "mbitemconfig_Callback"; + data['cmd']['callbackFn'] = "mbitemconfig_ItemCallback"; global.handleJsonItems(data); }) .catch(error => console.error('Error fetching items:', error)); + fetch('/getsetter') + .then(response => response.json()) + .then(data => { + data['cmd'] = {}; + data['cmd']['callbackFn'] = "mbitemconfig_SetterCallback"; + global.handleJsonItems(data); + }) + .catch(error => console.error('Error fetching setters:', error)); + // Warte bis die WebSocket-Verbindung aufgebaut ist let checkWebSocketInterval = setInterval(() => { if (global.ws && global.ws.readyState === WebSocket.OPEN) { @@ -57,13 +76,14 @@ export function init() { // ************************************************ export const functionMap = { - mbitemconfig_Callback: MyCallback + mbitemconfig_ItemCallback: MyItemCallback, + mbitemconfig_SetterCallback: MySetterCallback }; // ************************************************ -function MyCallback(json) { - global.transformCheckboxes() - +function MyItemCallback(json) { + global.transformCheckboxes(); + document.querySelectorAll('#DataForm input:not([type=checkbox]):not([type=radio]), #DataForm select').forEach(element => { element.addEventListener('blur', global.showMustSaveDialog); }); @@ -78,6 +98,38 @@ function MyCallback(json) { document.querySelector("body").style.visibility = "visible"; } +// ************************************************ +function MySetterCallback(json) { + MyItemCallback(json); + + if ("data" in json && "setitems" in json["data"] && json["data"]["setitems"].length == 0) { + document.getElementById("setterNa").classList.remove("hide"); + } + else if ("globalEnabled" in json && json["globalEnabled"] == 0) { + document.getElementById("setterDeactive").classList.remove("hide"); + } + else { + document.getElementById("settable").classList.remove("hide"); + } + + // handle data-mapping and add them to info field + document.querySelectorAll('[data-mapping]').forEach(element => { + try { + const mapping = JSON.parse(element.getAttribute('data-mapping').replace(/'/g, '"')); + const obj = document.getElementById(element.id); + var info = ""; + + for (var i = 0; i < mapping.length; i++) { + if (info.length > 0) info += "
"; + info += mapping[i][1] + "->" + mapping[i][0]; + } + obj.innerHTML += "
" + info; + } catch (e) { + console.error('Invalid JSON:', e); + } + }); +} + // ************************************************ function RefreshLiveData() { var data = {}; @@ -87,7 +139,7 @@ function RefreshLiveData() { global.requestData(data); } - + // ************************************************ export function ChangeActiveStatus(id) { var obj = document.getElementById(id); diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index 9d1bff66..2861d585 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -1,3 +1,7 @@ +/******************************************************** + * Copyright [2024] Tobias Faust on("/", HTTP_GET, std::bind(&MyWebServer::handleRoot, this, std::placeholders::_1)); server->on("/favicon.ico", HTTP_GET, std::bind(&MyWebServer::handleFavIcon, this, std::placeholders::_1)); - server->on("/getitems", HTTP_GET, std::bind(&MyWebServer::handleGetItemJson, this, std::placeholders::_1)); - server->on("/getregister", HTTP_GET, std::bind(&MyWebServer::handleGetRegisterJson, this, std::placeholders::_1)); + server->on("/getitems", HTTP_GET, [&](AsyncWebServerRequest *request){ mb->GetLiveDataAsJsonToWebServer(request); }); + //server->on("/getregister", HTTP_GET, std::bind(&MyWebServer::handleGetRegisterJson, this, std::placeholders::_1)); // deprecated, not longer in use + server->on("/getsetter", HTTP_GET, [&](AsyncWebServerRequest *request){ mb->GetSettersAsJsonToWebServer(request); }); + ws->onEvent(std::bind(&MyWebServer::onWsEvent, this, std::placeholders::_1, std::placeholders::_2, @@ -24,7 +30,7 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): std::placeholders::_6 )); server->addHandler(ws); - + ElegantOTA.begin(server); // Start ElegantOTA ElegantOTA.setGitEnv(String(GIT_OWNER), String(GIT_REPO), String(GIT_BRANCH)); ElegantOTA.setFWVersion(String(Config->GetReleaseName() + " / Build: " + GITHUB_RUN )); @@ -94,8 +100,8 @@ void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * clie if (json["cmd"]["action"]) {action = json["cmd"]["action"].as();} if (json["cmd"]["subaction"]){subaction = json["cmd"]["subaction"].as();} if (json["cmd"]["item"]) {item = json["cmd"]["item"].as();} + if (json["cmd"]["newState"]) {newState = (json["cmd"]["newState"].as() == "true"?true:false);} - newState = json["cmd"]["newState"].as(); } if (action == "GetItemsAsStream") { @@ -159,8 +165,7 @@ void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * clie } if (action && action == "SetActiveStatus") { - if (newState) mb->SetItemActiveStatus(item, true); - else mb->SetItemActiveStatus(item, false); + mb->SetItemActiveStatus(item, newState); json["response"]["status"] = 1; json["response"]["text"] = String("item successfully set to " + String(newState ? "active" : "inactive")); @@ -254,10 +259,6 @@ bool MyWebServer::handleReset() { return ret; } -void MyWebServer::handleGetItemJson(AsyncWebServerRequest *request) { - mb->GetLiveDataAsJsonToWebServer(request); -} - void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); @@ -265,56 +266,9 @@ void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { response->addHeader("Expires", "-1"); mb->GetRegisterAsJsonToWebServer(response); - request->send(response); } -void MyWebServer::GetInitDataNavi(AsyncResponseStream *response){ - String ret; - JsonDocument json; - json["data"].to(); - json["data"]["hostname"] = Config->GetMqttRoot(); - json["data"]["releasename"] = Config->GetReleaseName(); - json["data"]["releasedate"] = __DATE__; - json["data"]["releasetime"] = __TIME__; - - json["response"].to(); - json["response"]["status"] = 1; - json["response"]["text"] = "successful"; - serializeJson(json, ret); - response->print(ret); -} - -void MyWebServer::GetInitDataStatus(AsyncResponseStream *response) { - String ret; - JsonDocument json; - String rssi = (String)(Config->GetUseETH()?ETH.linkSpeed():WiFi.RSSI()); - if (Config->GetUseETH()) rssi.concat(" Mbps"); - - json["data"].to(); - json["data"]["ipaddress"] = mqtt->GetIPAddress().toString(); - json["data"]["wifiname"] = (Config->GetUseETH()?"wired LAN":WiFi.SSID()); - json["data"]["macaddress"] = WiFi.macAddress(); - json["data"]["rssi"] = rssi; - json["data"]["bssid"] = (Config->GetUseETH()?"wired LAN":WiFi.BSSIDstr()); - json["data"]["mqtt_status"] = (mqtt->GetConnectStatusMqtt()?"Connected":"Not Connected"); - json["data"]["inverter_type"] = mb->GetInverterType(); - json["data"]["inverter_serial"] = mb->GetInverterSN(); - json["data"]["uptime"] = uptime_formatter::getUptime(); - json["data"]["freeheapmem"] = ESP.getFreeHeap(); - - #ifndef USE_WEBSERIAL - json["data"]["tr_webserial"]["className"] = "hide"; - #endif - - json["response"].to(); - json["response"]["status"] = 1; - json["response"]["text"] = "successful"; - - serializeJson(json, ret); - response->print(ret); -} - void MyWebServer::GetInitDataNavi(JsonDocument& json) { json["data"].to(); json["data"]["hostname"] = Config->GetMqttRoot(); diff --git a/src/MyWebServer.h b/src/MyWebServer.h index 4bab069b..149f30ee 100644 --- a/src/MyWebServer.h +++ b/src/MyWebServer.h @@ -1,26 +1,22 @@ -// https://github.com/esp8266/Arduino/issues/3205 -// https://github.com/Hieromon/PageBuilder -// https://www.mediaevent.de/tutorial/sonderzeichen.html -// -// https://byte-style.de/2018/01/automatische-updates-fuer-microcontroller-mit-gitlab-und-platformio/ -// https://community.blynk.cc/t/self-updating-from-web-server-http-ota-firmware-for-esp8266-and-esp32/18544 -// https://forum.fhem.de/index.php?topic=50628.0 +/******************************************************** + * Copyright [2024] Tobias Faust #include -#include "uptime.h" // https://github.com/YiannisBourkelis/Uptime-Library/ -#include "uptime_formatter.h" - -#include "baseconfig.h" -#include "modbus.h" -#include "handleFiles.h" -#include "mqtt.h" -#include "favicon.h" +#include // https://github.com/YiannisBourkelis/Uptime-Library/ +#include + +#include +#include +#include +#include +#include #include -#include "_Release.h" +#include <_Release.h> class MyWebServer { @@ -29,13 +25,13 @@ class MyWebServer { String json; } WsConnClient_t; - public: + public: MyWebServer(AsyncWebServer *server, DNSServer* dns); void loop(); void sendWebSocketMessage(String& message); - private: + private: bool DoReboot; uint64_t RequestRebootTime; @@ -52,11 +48,11 @@ class MyWebServer { bool handleReset(); void handleRoot(AsyncWebServerRequest *request); void handleFavIcon(AsyncWebServerRequest *request); - void handleGetItemJson(AsyncWebServerRequest *request); +// void handleGetItemJson(AsyncWebServerRequest *request); void handleGetRegisterJson(AsyncWebServerRequest *request); - - void GetInitDataStatus(AsyncResponseStream *response); - void GetInitDataNavi(AsyncResponseStream *response); +// void handleGetSetterJson(AsyncWebServerRequest *request); +// void GetInitDataStatus(AsyncResponseStream *response); +// void GetInitDataNavi(AsyncResponseStream *response); void GetInitDataStatus(JsonDocument& json); void GetInitDataNavi(JsonDocument& json); @@ -64,4 +60,4 @@ class MyWebServer { void onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); }; -#endif +#endif // MYWEBSERVER_H_ diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index dbb9009e..b1bf5c5c 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -1,14 +1,18 @@ -#include "baseconfig.h" +/******************************************************** + * Copyright [2024] Tobias Faust + +BaseConfig::BaseConfig(): debuglevel(2), + serial_rx(3), serial_tx(1), mqtt_UseRandomClientID(true), - useAuth(false) { + useAuth(false) { #ifdef ESP8266 LittleFS.begin(); #elif defined(ESP32) - if (LittleFS.begin(true)) { // true: format LittleFS/NVS if mount fails + if (LittleFS.begin(true)) { // true: format LittleFS/NVS if mount fails if (!LittleFS.exists("/config")) { LittleFS.mkdir("/config"); } @@ -16,29 +20,29 @@ BaseConfig::BaseConfig(): debuglevel(2), this->log(1, "LittleFS Mount Failed"); } #endif - + // Flash Write Issue // https://github.com/esp8266/Arduino/issues/4061#issuecomment-428007580 // LittleFS.format(); - + LoadJsonConfig(); } void BaseConfig::LoadJsonConfig() { bool loadDefaultConfig = false; if (LittleFS.exists("/config/baseconfig.json")) { - //file exists, reading and loading + // file exists, reading and loading this->log(2, "reading config file"); File configFile = LittleFS.open("/config/baseconfig.json", "r"); if (configFile) { this->log(2, "opened config file"); - + JsonDocument doc; DeserializationError error = deserializeJson(doc, configFile); - + if (!error && doc["data"]) { this->log(1, doc); - + if (doc["data"]["mqttroot"]) { this->mqtt_root = doc["data"]["mqttroot"].as();} else {this->mqtt_root = "solax";} if (doc["data"]["mqttserver"]) { this->mqtt_server = doc["data"]["mqttserver"].as();} else {this->mqtt_server = "test.mosquitto.org";} if (doc["data"]["mqttport"]) { this->mqtt_port = doc["data"]["mqttport"].as();} else {this->mqtt_port = 1883;} diff --git a/src/baseconfig.h b/src/baseconfig.h index 2fccc361..68aeb59d 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -1,14 +1,17 @@ -#ifndef BASECONFIG_H -#define BASECONFIG_H +/******************************************************** + * Copyright [2024] Tobias Faust +#include +#include <_Release.h> -class BaseConfig { - public: +class BaseConfig { + public: BaseConfig(); void LoadJsonConfig(); void GetInitData(JsonDocument& json); @@ -37,8 +40,9 @@ class BaseConfig { const String& GetAuthUser() const {return auth_user;} const String& GetAuthPass() const {return auth_pass;} - const String GetReleaseName(); - private: + const String GetReleaseName(); + + private: String mqtt_server; String mqtt_username; String mqtt_password; @@ -54,9 +58,8 @@ class BaseConfig { bool useAuth; String auth_user; String auth_pass; - }; extern BaseConfig* Config; -#endif +#endif // BASECONFIG_H_ diff --git a/src/commonlibs.h b/src/commonlibs.h index 4ee0690a..18147a93 100644 --- a/src/commonlibs.h +++ b/src/commonlibs.h @@ -1,3 +1,7 @@ +/******************************************************** + * Copyright [2024] Tobias Faust = 100 #include "Arduino.h" #else diff --git a/src/favicon.h b/src/favicon.h index 25971746..f8e5db90 100644 --- a/src/favicon.h +++ b/src/favicon.h @@ -1,3 +1,7 @@ +/******************************************************** + * Copyright [2024] Tobias Faust log(3, "Message: %s", msg.c_str()); - mb->ReceiveMQTT(topic, atoi(msg.c_str())); + mb->ReceiveMQTT(topic, msg); } void setup() { diff --git a/src/modbus.cpp b/src/modbus.cpp index 684a1d68..156c1b32 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1,3 +1,7 @@ +/******************************************************** + * Copyright [2024] Tobias Faust {}; SaveIdDataframe = new std::vector{}; SaveLiveDataframe = new std::vector{}; @@ -19,7 +24,7 @@ modbus::modbus(): enableRelays(false), InverterLiveData = new std::vector{}; InverterIdData = new std::vector{}; AvailableInverters = new std::vector{}; - Setters = new std::vector{}; + Setters = new std::vector{}; OpenWB = new openwb(); Conf_RequestLiveData= new std::vector>{}; @@ -44,7 +49,7 @@ modbus::modbus(): enableRelays(false), this->pin_Relay2 = this->default_pin_Relay2 = 19; } - this->LoadInvertersFromJson(); //needed for selecting default inverter + this->LoadInvertersFromJson(); //needed for selecting default inverter in LoadJsonConfig this->LoadJsonConfig(true); this->OpenWB->begin(this->Conf_OpenWBVersion); this->init(true); @@ -64,11 +69,11 @@ void modbus::init(bool firstrun) { pinMode(this->pin_Relay2, INPUT_PULLUP); } - this->LoadInvertersFromJson(); this->LoadInverterConfigFromJson(); - this->LoadRegItems(this->InverterIdData, "id"); - this->LoadRegItems(this->InverterLiveData, "livedata"); - this->LoadJsonItemConfig(); // loads InverterLiveData Items too + this->LoadRegItems(this->InverterIdData, "id"); // load item definitions from register file + this->LoadRegItems(this->InverterLiveData, "livedata"); // load item definitions from register file + this->LoadSettersFromRegFile(); // loads setter config from config file and updates Setters + this->LoadJsonItemConfig(); // loads item config (active/inactive) from config file and updates InverterIdData, InverterLiveData and Setters // https://forum.arduino.cc/t/creating-serial-objects-within-a-library/697780/11 @@ -114,9 +119,9 @@ String modbus::GetMqttSetTopic(String command) { } /******************************************************* - * subscribe to all possible "set" register (register.h) + * load all "set" register from regfile if setters are enabled globally *******************************************************/ -void modbus::GenerateMqttSubscriptions() { +void modbus::LoadSettersFromRegFile() { // clear vector this->Setters->clear(); @@ -127,6 +132,7 @@ void modbus::GenerateMqttSubscriptions() { regfile.find(streamString.c_str()); streamString = "\"set\": ["; regfile.find(streamString.c_str()); + do { JsonDocument elem; DeserializationError error = deserializeJson(elem, regfile); @@ -136,19 +142,10 @@ void modbus::GenerateMqttSubscriptions() { Config->log(5, elem); if(!elem["name"].isNull() && elem["request"].is()) { - subscription_t s = {}; - s.command = elem["name"].as(); - - JsonArray arr = elem["request"].as(); - std::vector t = {}; - for (String x : arr) { - byte e = this->String2Byte(x); - t.push_back(e); - } - s.request = t; + setter_t s = {}; + s.Name = elem["name"].as(); - this->mqtt->Subscribe(this->GetMqttSetTopic(s.command)); - Config->log(4, "Set command successfully parsed from JSON: %s with %s", s.command.c_str(), (this->PrintDataFrame(&(s.request))).c_str()); + Config->log(4, "Set command successfully parsed from JSON: %s", s.Name.c_str()); this->Setters->push_back(s); } else { @@ -163,31 +160,57 @@ void modbus::GenerateMqttSubscriptions() { regfile.close(); } - /******************************************************* * act on received mqtt command *******************************************************/ -void modbus::ReceiveMQTT(String topic, int msg) { +void modbus::ReceiveMQTT(String topic, String msg) { if (!this->Conf_EnableSetters) { - Config->log(2, "Set command <%s> received, but setters over mqtt are currently disabled", topic.c_str()); + Config->log(2, "Set command <%s> received, but setters over mqtt are currently disabled globally", topic.c_str()); return; } for (uint8_t i = 0; i < this->Setters->size(); i++ ) { - if (topic == this->GetMqttSetTopic(this->Setters->at(i).command)) { - std::vector request = this->Setters->at(i).request; + if (topic == this->GetMqttSetTopic(this->Setters->at(i).Name)) { + if (!this->Setters->at(i).active) { + Config->log(2, "Set command <%s> received, but setter %s is not active", topic.c_str(), this->Setters->at(i).Name.c_str()); + return; + } + + JsonDocument elem = this->GetSetterByName(this->Setters->at(i).Name); + if (elem.isNull()) { + Config->log(1, "Setter %s not found in JSON", this->Setters->at(i).Name.c_str()); + return; + } + + JsonArray arr = elem["request"].as(); + std::vector request = {}; + + for (String x : arr) { + byte e = this->String2Byte(x); + request.push_back(e); + } + + // map values if a mapping is specified + if(!elem["mapping"].isNull() && elem["mapping"].is() && msg != "") { + Config->log(4, "Map values for item %s", msg.c_str()); + + JsonArray map = elem["mapping"].as(); + msg = this->MapItem(map, msg); + } + + int msgInt = msg.toInt(); // atoi(msg.c_str()) byte bytes[4]; - bytes[0] = (msg >> 24) & 0xFF; - bytes[1] = (msg >> 16) & 0xFF; - bytes[2] = (msg >> 8) & 0xFF; - bytes[3] = (msg >> 0) & 0xFF; + bytes[0] = (msgInt >> 24) & 0xFF; + bytes[1] = (msgInt >> 16) & 0xFF; + bytes[2] = (msgInt >> 8) & 0xFF; + bytes[3] = (msgInt >> 0) & 0xFF; - // 16bit number + // 32bit number request.push_back(bytes[2]); request.push_back(bytes[3]); - Config->log(3, "MQTT Setter found: %s" ,this->Setters->at(i).command.c_str()); + Config->log(3, "MQTT Setter found: %s" ,this->Setters->at(i).Name.c_str()); Config->log(3, "Initiate Set Request to queue: %s" ,(this->PrintDataFrame(&request)).c_str()); this->SetQueue->enqueue(request); @@ -195,6 +218,48 @@ void modbus::ReceiveMQTT(String topic, int msg) { } } +/******************************************************* + * @brief get setter by name, read json file and return the json object for the setter + * @param name: name of the setter + * @return JsonDocument: json object for the setter + * ******************************************************/ +JsonDocument modbus::GetSetterByName(String name) { + File regfile = LittleFS.open("/regs/" + this->InverterType.filename); + if (!regfile) { + Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + return JsonDocument(); + } + + String streamString = ""; + streamString = "\""+ this->InverterType.name +"\": {"; + regfile.find(streamString.c_str()); + + streamString = "\"set\": ["; + regfile.find(streamString.c_str()); + do { + JsonDocument elem; + DeserializationError error = deserializeJson(elem, regfile); + + if (!error) { + // Print the result + Config->log(4, "parsing JSON ok"); + Config->log(5, elem); + } else { + Config->log(1, "(Function GetSetterByName) Failed to parse JSON Register Data: %s", error.c_str()); + } + + if (elem["name"] == name) { + regfile.close(); + return elem; + } + + } while (regfile.findUntil(",","]")); + + if (regfile) { regfile.close(); } + + return JsonDocument(); +} + /******************************************************* * get all defined inverters from json (register.h) *******************************************************/ @@ -321,7 +386,13 @@ byte modbus::String2Byte(String s){ *******************************************************/ void modbus::enableMqtt(MQTT* object) { this->mqtt = object; - this->GenerateMqttSubscriptions(); + + // subscribe to all active setters + for (uint8_t i = 0; i < this->Setters->size(); i++) { + if (this->Setters->at(i).active) { + this->mqtt->Subscribe(this->GetMqttSetTopic(this->Setters->at(i).Name)); + } + } } /******************************************************* @@ -350,7 +421,6 @@ void modbus::QueryIdData() { } } - /******************************************************* * Query Live Data to Inverter *******************************************************/ @@ -617,7 +687,7 @@ void modbus::ParseData() { //byte ReadBuffer[] = {0x01, 0x04, 0x80, 0x12, 0x34, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x09, 0x64, 0x09, 0x67, 0x09, 0x6B, 0x13, 0x8C, 0x13, 0x8D, 0x13, 0x8B, 0x00, 0x27, 0x00, 0x28, 0x00, 0x28, 0x00, 0x2C, 0x0B, 0x54, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xC3,0x02s}; //Solax X1 - //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02}; + byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x01,0x04,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x59,0x77,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x00,0x00,0x86,0x6B,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x00,0x00,0x03,0xF2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xB9,0x02}; //byte ReadBuffer[] = {0x01,0x04,0xEE,0x09,0x29,0x00,0x5E,0x08,0x9E,0x0B,0xFA,0x0B,0x44,0x00,0x16,0x00,0x39,0x13,0x8A,0x00,0x26,0x00,0x02,0x02,0xB8,0x06,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xB6,0x00,0x00,0x03,0xAC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0x00,0x00,0x6E,0xFB,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x33,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB9,0xA4,0x02}; //byte ReadBuffer[] = {0x01,0x03,0x28,0x48,0x34,0x35,0x30,0x32,0x41,0x49,0x34,0x34,0x35,0x39,0x30,0x30,0x35,0x73,0x6F,0x6C,0x61,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x4A,0xA0, 0x01}; @@ -632,7 +702,7 @@ void modbus::ParseData() { //byte ReadBuffer[] = {0xF7,0x02,0x1E,0x0B,0x3E,0x00,0x39,0x05,0xE1,0x0A,0x68,0x00,0x39,0x05,0xFA,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xFC,0x00,0x7C,0x00,0x00,0x13,0x87,0x00,0x00,0x24,0x27, 0x02}; // Solax-X3 - byte ReadBuffer[] = {0x01,0x04,0x42,0x17,0xB4,0x12,0x21,0x00,0x02,0x00,0x02,0x13,0x88,0x00,0x27,0x00,0x02,0x00,0x7A,0x00,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xDB,0x01,0x04,0x2C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x48,0xFF,0xFF,0x05,0x2F,0x00,0x02,0x7E,0xDD,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0x00,0x00,0x4D,0x6E,0x00,0x00,0x00,0x01,0x00,0x00,0xD3,0x0A,0x01,0x04,0x64,0x08,0x4A,0x00,0x09,0x00,0x64,0x13,0x88,0x08,0x81,0x00,0x09,0x00,0x3D,0x13,0x88,0x08,0x20,0x00,0x09,0x00,0x61,0x13,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x95,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0x2D,0xFF,0xFF,0x31,0xA8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x4A,0xC0,0x00,0x00,0x00,0x1B,0x00,0x00,0x00,0x47,0x00,0x00,0x01,0xD9,0x00,0x00,0x55,0x5B,0x01,0x04,0x04,0x00,0x00,0x00,0x00,0xFB,0x84,0x02}; + //byte ReadBuffer[] = {0x01,0x04,0x42,0x17,0xB4,0x12,0x21,0x00,0x02,0x00,0x02,0x13,0x88,0x00,0x27,0x00,0x02,0x00,0x7A,0x00,0x7A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xDB,0x01,0x04,0x2C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x48,0xFF,0xFF,0x05,0x2F,0x00,0x02,0x7E,0xDD,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1B,0x00,0x00,0x4D,0x6E,0x00,0x00,0x00,0x01,0x00,0x00,0xD3,0x0A,0x01,0x04,0x64,0x08,0x4A,0x00,0x09,0x00,0x64,0x13,0x88,0x08,0x81,0x00,0x09,0x00,0x3D,0x13,0x88,0x08,0x20,0x00,0x09,0x00,0x61,0x13,0x89,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x95,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0x2D,0xFF,0xFF,0x31,0xA8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x4A,0xC0,0x00,0x00,0x00,0x1B,0x00,0x00,0x00,0x47,0x00,0x00,0x01,0xD9,0x00,0x00,0x55,0x5B,0x01,0x04,0x04,0x00,0x00,0x00,0x00,0xFB,0x84,0x02}; for (uint16_t i = 0; iDataFrame->push_back(ReadBuffer[i]); @@ -1032,7 +1102,7 @@ void modbus::GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request) { if (!error) { Config->log(4, jsonGet); - if (jsonGet["subaction"]){subaction = jsonGet["subaction"].as();} + if (jsonGet["cmd"]["subaction"]) subaction = jsonGet["cmd"]["subaction"].as(); } else { Config->log(2, "[GetLiveDataAsJsonToWebServer] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); @@ -1115,6 +1185,109 @@ void modbus::GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request) { request->send(response); } +/******************************************************* + * Return all LiveData as jsonArray + * {data: [{"name": "xx", "value": "xx", ...}, ...] } +*******************************************************/ +void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { + std::shared_ptr counter = std::make_shared(0); + String subaction(""); + + if(request->hasArg("json")) { + const String json = request->arg("json"); + Config->log(4, "[GetSetterAsJson] Json command empfangen: %s", json.c_str()); + + JsonDocument jsonGet; + DeserializationError error = deserializeJson(jsonGet, json.c_str()); + + if (!error) { + if (jsonGet["cmd"]["subaction"]) subaction = jsonGet["cmd"]["subaction"].as(); + } else { + Config->log(2, "[GetSetterAsJson] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); + } + } + + AsyncWebServerResponse *response = request->beginChunkedResponse("application/json", [this, counter, subaction](uint8_t *buffer, size_t maxLen, size_t index) { + String ret(""); + ret.reserve(maxLen); + maxLen -= 500; // use a puffer of 500 bytes, every item is assumed to be 200 bytes + + if (*counter == 0) { + // send start of JSON + ret += "{\"globalEnabled\": \""+ String(this->Conf_EnableSetters) +"\", \"data\": {\"setitems\": ["; + (*counter)++; + } + + File regfile = LittleFS.open("/regs/"+this->InverterType.filename); + if (!regfile) { + Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + return 0; + } + + String streamString = ""; + uint16_t itemIterator = 0; + + streamString = "\""+ this->InverterType.name +"\": {"; + regfile.find(streamString.c_str()); + + streamString = "\"set\": ["; + regfile.find(streamString.c_str()); + do { + if (itemIterator == (*counter - 1)) { + bool isActive = false; // default + JsonDocument elem; + DeserializationError error = deserializeJson(elem, regfile); + + if (error) { + Config->log(1, "(Function GetSettersAsJsonToWebServer) Failed to parse JSON Register Data: %s", error.c_str()); + break; + } + + Config->log(4, "parsing JSON ok"); + Config->log(5, elem); + + //check if setter is active + for (uint8_t i = 0; i < this->Setters->size(); i++) { + if (this->Setters->at(i).Name == elem["name"].as()) { + isActive = this->Setters->at(i).active; + break; + } + } + + if ((subaction == "onlyactive" && isActive) || subaction != "onlyactive") { + if(*counter > 1) ret += ","; + String mapping = elem["mapping"].as(); mapping.replace("\"", "'"); + + ret += "{\"name\": \"" + elem["name"].as() + "\","; + ret += "\"realname\": \"" + elem["realname"].as() + "\","; + ret += "\"active\": {\"checked\": " + String(isActive ? 1 : 0) + ", \"name\": \"" + elem["name"].as() + "\"},"; + ret += "\"subscription\": \"" + this->GetMqttSetTopic(elem["name"].as()) + "\","; + ret += "\"info\": {\"data-mapping\": \""+ mapping + "\", \"innerHTML\": \"" + elem["info"].as() + "\"}"; + ret += "}"; + } + } + + (*counter)++; + itemIterator++; + + } while (regfile.findUntil(",","]")); + + if (regfile) { regfile.close(); } + + if (ret.length() > 0) { + // send end of JSON + ret += " ]}, \"object_id\": \"" + Config->GetMqttBasePath() + "/" + Config->GetMqttRoot() + "\"}"; + (*counter)++; + } + + int len = sprintf((char*)buffer, ret.c_str()); + return len; + + }); + + request->send(response); +} + /******************************************************* * Return all LiveData as jsonArray * {data: [{"name": "xx", "value": "xx"}], } @@ -1198,16 +1371,33 @@ void modbus::SetItemActiveStatus(String item, bool newstate) { if (this->InverterLiveData->at(j).Name == item) { Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); this->InverterLiveData->at(j).active = newstate; + return; } } - //Lazgar + for (uint16_t j=0; j < this->InverterIdData->size(); j++) { if (this->InverterIdData->at(j).Name == item) { Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); this->InverterIdData->at(j).active = newstate; + return; } } - //Lazgar + + for (uint16_t j=0; j < this->Setters->size(); j++) { + if (this->Setters->at(j).Name == item) { + Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); + if (this->mqtt && this->Setters->at(j).active != newstate) { + if (!newstate) { + this->mqtt->UnSubscribe(this->GetMqttSetTopic(this->Setters->at(j).Name)); + } else { + this->mqtt->Subscribe(this->GetMqttSetTopic(this->Setters->at(j).Name)); + } + } + this->Setters->at(j).active = newstate; + return; + } + } + } /******************************************************* @@ -1315,6 +1505,7 @@ void modbus::LoadJsonConfig(bool firstrun) { uint8_t pin_Relay1_old = this->pin_Relay1; uint8_t pin_Relay2_old = this->pin_Relay2; bool enableRelays_old = this->enableRelays; + bool enableSetters_old = this->Conf_EnableSetters; if (LittleFS.exists("/config/modbusconfig.json")) { //file exists, reading and loading @@ -1355,6 +1546,7 @@ void modbus::LoadJsonConfig(bool firstrun) { for (uint8_t i=0; iAvailableInverters->size(); i++) { if (this->AvailableInverters->at(i).name == (doc["data"]["invertertype"]).as()) { this->InverterType = this->AvailableInverters->at(i); + Config->log(3, "Invertertyp '%s' was found in register file '%s', set it as selected active Inverter", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); found = true; } @@ -1400,7 +1592,7 @@ void modbus::LoadJsonConfig(bool firstrun) { loadDefaultConfig = false; //set back } - // ReInit if Baudrate was changed, not at firstrun! + // ReInit if basics has been changed, not at firstrun! if(!firstrun && ( (Baudrate_old != this->Baudrate) || (pin_RX_old != this->pin_RX) || @@ -1408,16 +1600,15 @@ void modbus::LoadJsonConfig(bool firstrun) { (pin_RTS_old != this->pin_RTS) || (enableRelays_old != this->enableRelays) || (pin_Relay1_old != this->pin_Relay1) || - (pin_Relay2_old != this->pin_Relay2))) { + (pin_Relay2_old != this->pin_Relay2) || + (InverterType_old.name != this->InverterType.name))) { this->init(false); } - // ReInit if Invertertype was changed - if(!firstrun && ( - InverterType_old.name != this->InverterType.name) ) { - - this->init(false); + if (enableSetters_old != this->Conf_EnableSetters) { + this->LoadSettersFromRegFile(); + this->LoadJsonItemConfig(false, false, true); // load only Setters } } @@ -1425,7 +1616,11 @@ void modbus::LoadJsonConfig(bool firstrun) { /******************************************************* * load Modbus Item configuration from file *******************************************************/ -void modbus::LoadJsonItemConfig() { +void modbus::LoadJsonItemConfig() { + this->LoadJsonItemConfig(true, true, true); +} + +void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSetters) { if (LittleFS.exists("/config/modbusitemconfig.json")) { //file exists, reading and loading @@ -1450,26 +1645,43 @@ void modbus::LoadJsonItemConfig() { for (JsonPair kv : elem.as()) { const char* ItemName = kv.key().c_str(); + + /* handle LiveData */ + if (loadLiveData) { + for(uint16_t i=0; iInverterLiveData->size(); i++) { + if (this->InverterLiveData->at(i).Name == ItemName ) { + this->InverterLiveData->at(i).active = kv.value().as(); + + Config->log(3, "item %s -> %s", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); - for(uint16_t i=0; iInverterLiveData->size(); i++) { - if (this->InverterLiveData->at(i).Name == ItemName ) { - this->InverterLiveData->at(i).active = kv.value().as(); + break; + } + } + } - Config->log(3, "item %s -> %s", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); + if (loadIdData) { + /* handle IdData */ + for(uint16_t i=0; iInverterIdData->size(); i++) { + if (this->InverterIdData->at(i).Name == ItemName ) { + this->InverterIdData->at(i).active = kv.value().as(); - break; + Config->log(3, "item %s -> %s", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); + break; + } } } - //Lazgar - for(uint16_t i=0; iInverterIdData->size(); i++) { - if (this->InverterIdData->at(i).Name == ItemName ) { - this->InverterIdData->at(i).active = kv.value().as(); - Config->log(3, "item %s -> %s", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); - break; + if (loadSetters) { + /* handle Setters */ + for(uint16_t i=0; iSetters->size(); i++) { + if (this->Setters->at(i).Name == ItemName ) { + this->Setters->at(i).active = kv.value().as(); + Config->log(3, "setter %s -> %s", ItemName, (this->Setters->at(i).active?"enabled":"disabled")); + break; + } } } - //Lazgar + } } while (stream.findUntil(",","]")); diff --git a/src/modbus.h b/src/modbus.h index e2e36bd6..3ec5785b 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -1,3 +1,7 @@ +/******************************************************** + * Copyright [2024] Tobias Faust request; - } subscription_t; + String Name; + bool active = false; + } setter_t; // available inverter register json files typedef struct { @@ -47,6 +51,7 @@ class modbus { void init(bool firstrun); void LoadJsonConfig(bool firstrun); void LoadJsonItemConfig(); + void LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSetters); void loop(); @@ -57,9 +62,11 @@ class modbus { void GetInitRawData(JsonDocument& json); String GetInverterSN(); void GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request); + void GetSettersAsJsonToWebServer(AsyncWebServerRequest *request); void GetRegisterAsJsonToWebServer(AsyncResponseStream *response); void SetItemActiveStatus(String item, bool newstate); - void ReceiveMQTT(String topic, int msg); + void ReceiveMQTT(String topic, String msg); + JsonDocument GetSetterByName(String name); // Callback setzen void setWebSocketCallback(std::function callback); @@ -96,8 +103,8 @@ class modbus { std::vector* DataFrame; // storing read results as hexdata to parse std::vector* InverterIdData; // storing readable results std::vector* InverterLiveData; // storing readable results - std::vector*AvailableInverters; // available inverters from JSON - std::vector* Setters; // available set Options from JSON register + std::vector* AvailableInverters; // available inverters from JSON + std::vector* Setters; // available set Options from JSON register MQTT* mqtt = NULL; openwb* OpenWB = NULL; @@ -116,7 +123,7 @@ class modbus { void ParseData(); void LoadInvertersFromJson(); void LoadInverterConfigFromJson(); - void GenerateMqttSubscriptions(); + void LoadSettersFromRegFile(); String GetMqttSetTopic(String command); void ChangeRegItem(std::vector* vector, reg_t item); void LoadRegItems(std::vector* vector, String type); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 3f15bc45..9d677d98 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -258,7 +258,11 @@ void MQTT::disconnect() { void MQTT::Publish_Bool(const char* subtopic, bool b, bool fulltopic) { String s(""); - if (b) {s = "1";} else {s = "0";} + if (b) { + s = "1"; + } else { + s = "0"; + } Publish_String(subtopic, s, fulltopic); } @@ -282,7 +286,9 @@ void MQTT::Publish_String(const char* subtopic, String value, bool fulltopic) { if (PubSubClient::connected()) { PubSubClient::publish((const char*)topic.c_str(), value.c_str(), true); Config->log(3, "Publish %s: %s ", topic.c_str(), value.c_str()); - } else { Config->log(2, "Request for MQTT Publish, but not connected to Broker"); } + } else { + Config->log(2, "Request for MQTT Publish, but not connected to Broker"); + } } String MQTT::getTopic(String subtopic, bool fulltopic) { diff --git a/src/openwb.cpp b/src/openwb.cpp index 21afd554..b1f51cbf 100644 --- a/src/openwb.cpp +++ b/src/openwb.cpp @@ -1,4 +1,8 @@ -#include "openwb.h" +/******************************************************** + * Copyright [2024] Tobias Faust openwb::openwb(): _version("") { OpenWBTopics = new std::vector(); @@ -25,7 +29,7 @@ void openwb::LoadAvailableOpenWbVersions() { } OpenWBVersions->clear(); - + JsonDocument doc; DeserializationError error = deserializeJson(doc, file); if (error) { @@ -70,7 +74,6 @@ void openwb::LoadOpenWBTopicsFromJson() { this->OpenWBTopics->push_back(t); Config->log(3, "openWB topic loaded: %s", kv.value().as().c_str()); - } } break; @@ -80,7 +83,7 @@ void openwb::LoadOpenWBTopicsFromJson() { file.close(); } -String openwb::getOpenWbTopic(String& key) { +const String openwb::getOpenWbTopic(const String& key) { for (uint8_t i = 0; i < this->OpenWBTopics->size(); i++) { if (this->OpenWBTopics->at(i).key == key) { String topic = this->OpenWBTopics->at(i).value; @@ -91,7 +94,7 @@ String openwb::getOpenWbTopic(String& key) { } } return ""; -} +} void openwb::addMapping(String key, String value) { for (uint8_t i = 0; i < this->OpenWBMappings->size(); i++) { @@ -105,4 +108,4 @@ void openwb::addMapping(String key, String value) { t.key = key; t.value = value; this->OpenWBMappings->push_back(t); -} \ No newline at end of file +} diff --git a/src/openwb.h b/src/openwb.h index 42bcbce4..b8a07c3c 100644 --- a/src/openwb.h +++ b/src/openwb.h @@ -1,18 +1,22 @@ -#ifndef OPENWB_H -#define SOLAXMODBUS_H +/******************************************************** + * Copyright [2024] Tobias Faust +#include +#include class openwb { - //openwb mqtt topics + // openwb mqtt topics typedef struct { String key; String value; } openwb_t; - public: + public: openwb(); /******************************************************* @@ -24,21 +28,21 @@ class openwb { * @brief set the needed OpenWB API Version, used from getOpenWbVersions *******************************************************/ void setVersion(String version); - + /******************************************************* * get openWB topic from key * * @param key: key from openWB JSON * @return string: topic *******************************************************/ - String getOpenWbTopic(String& key); + const String getOpenWbTopic(const String& key); /******************************************************* * @brief Get all available openWB API Versions * * @return std::vector* ******************************************************/ - const std::vector* getOpenWbVersions() { return OpenWBVersions; } + const std::vector* getOpenWbVersions() { return OpenWBVersions; } /******************************************************* * @brief add a new Mapping for Topic Keys like #key#, @@ -56,10 +60,9 @@ class openwb { /******************************************************* * @brief clear all mappings ******************************************************/ - void clearMappings() { OpenWBMappings->clear(); } - - private: + void clearMappings() { OpenWBMappings->clear(); } + private: std::vector* OpenWBTopics; // openWB mqtt topics from JSON std::vector* OpenWBVersions; // openWB available versions from JSON std::vector* OpenWBMappings; // openWB mappings from JSON @@ -70,4 +73,4 @@ class openwb { void LoadAvailableOpenWbVersions(); }; -#endif \ No newline at end of file +#endif // OPENWB_H_ From 2eec165b5dee69f0d1d796010deee7ce18922696 Mon Sep 17 00:00:00 2001 From: tobiasfaust Date: Thu, 23 Jan 2025 18:01:48 +0100 Subject: [PATCH 052/106] add "newUpdate available" info in WebUI header (#129) --- ChangeLog.md | 2 ++ data/web/Javascript.js | 4 ---- data/web/Style.css | 16 +++++++++++++ data/web/handlefiles.html | 2 +- data/web/index.html | 2 +- data/web/modbusitemconfig.js | 14 +++++++++-- data/web/navi.html | 27 ++++++++++++++++------ data/web/navi.js | 45 +++++++++++++++++++++++++++++++++++- data/web/status.html | 2 +- src/MyWebServer.cpp | 4 +++- src/modbus.cpp | 2 ++ src/modbus.h | 10 ++++---- 12 files changed, 107 insertions(+), 23 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 690c7215..26699f70 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,8 @@ Release 3.3.2: - new feature: add confirmation dialog for ESP reset - migrate from old ajax communication to standard websocket communication + - new feature: show set topics in WebUI (thanks to @laszgar) + - new feature: add "newUpdate available" info in WebUI header Release 3.3.1: - new Feature: datatype "binary" now available for json register definitions (PR #115) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index 0b5a3e1a..e16dc104 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -154,8 +154,6 @@ export function connectWebSocket() { }; } - - /****************************************************************************************** * activate all radioselections after pageload to hide unnecessary elements * Works for all checkbox and radio elements with onclick="radioselection(show, hide)" @@ -225,7 +223,6 @@ function updateDataID(json, highlight) { } } - /***************************************************************************************** * * definition of applying jsondata to html templates @@ -556,7 +553,6 @@ export function onSubmit(DataForm, separator='') { }); } - /**************************************************************************************** * blendet Zeilen der Tabelle aus * @param {*} show: Array of shown IDs return true; diff --git a/data/web/Style.css b/data/web/Style.css index cf4ded95..dc43fbcb 100644 --- a/data/web/Style.css +++ b/data/web/Style.css @@ -238,6 +238,22 @@ body { } } +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.3; + } + 100% { + opacity: 1; + } +} + +.pulse { + animation: pulse 1.5s infinite; +} + .needToSave { width: 300px; margin: 0 auto; diff --git a/data/web/handlefiles.html b/data/web/handlefiles.html index d3467469..2bc657ca 100644 --- a/data/web/handlefiles.html +++ b/data/web/handlefiles.html @@ -68,7 +68,7 @@ -
+

Are you sure you want to delete this file?

diff --git a/data/web/index.html b/data/web/index.html index a236d11a..3955fdcc 100644 --- a/data/web/index.html +++ b/data/web/index.html @@ -3,7 +3,7 @@ Modbus MQTT Gateway - + diff --git a/data/web/modbusitemconfig.js b/data/web/modbusitemconfig.js index d3e73ed9..5b6be360 100644 --- a/data/web/modbusitemconfig.js +++ b/data/web/modbusitemconfig.js @@ -51,7 +51,7 @@ export function init() { .then(response => response.json()) .then(data => { data['cmd'] = {}; - data['cmd']['callbackFn'] = "mbitemconfig_ItemCallback"; + data['cmd']['callbackFn'] = "mbitemconfig_ItemItemCallback"; global.handleJsonItems(data); }) .catch(error => console.error('Error fetching items:', error)); @@ -65,6 +65,15 @@ export function init() { }) .catch(error => console.error('Error fetching setters:', error)); + fetch('/getsetter') + .then(response => response.json()) + .then(data => { + data['cmd'] = {}; + data['cmd']['callbackFn'] = "mbitemconfig_SetterCallback"; + global.handleJsonItems(data); + }) + .catch(error => console.error('Error fetching setters:', error)); + // Warte bis die WebSocket-Verbindung aufgebaut ist let checkWebSocketInterval = setInterval(() => { if (global.ws && global.ws.readyState === WebSocket.OPEN) { @@ -121,7 +130,7 @@ function MySetterCallback(json) { for (var i = 0; i < mapping.length; i++) { if (info.length > 0) info += "
"; - info += mapping[i][1] + "->" + mapping[i][0]; + info += mapping[i][0]; } obj.innerHTML += "
" + info; } catch (e) { @@ -140,6 +149,7 @@ function RefreshLiveData() { global.requestData(data); } + // ************************************************ export function ChangeActiveStatus(id) { var obj = document.getElementById(id); diff --git a/data/web/navi.html b/data/web/navi.html index b1dc73f8..d88746bf 100644 --- a/data/web/navi.html +++ b/data/web/navi.html @@ -4,12 +4,14 @@ + @@ -19,14 +21,15 @@ - - -
-

Configuration

+
+ Configuration of + - () - + + + + Release: @@ -60,5 +63,15 @@

Configuration

+ +
+ An update are available
+ You are using:
+ available version: +

+ + +
+ \ No newline at end of file diff --git a/data/web/navi.js b/data/web/navi.js index 90fb7bf2..a2b1abe2 100644 --- a/data/web/navi.js +++ b/data/web/navi.js @@ -1,10 +1,13 @@ import * as global from './Javascript.js'; +var currentVersion, newVersion = 0; + // ************************************************ export function init() { // Initiale Verbindung aufbauen global.connectWebSocket(); - + checkUpdate(); + // Warte bis die WebSocket-Verbindung aufgebaut ist let checkWebSocketInterval = setInterval(() => { if (global.ws && global.ws.readyState === WebSocket.OPEN) { @@ -24,6 +27,46 @@ function GetInitData() { global.requestData(data); } +// ************************************************ +function checkUpdate() { + // get deviceinfo the know where releases.json is + fetch("/getdeviceinfo") + .then(response => response.json()) + .then(data => { + fetch("https://"+ data.owner + ".github.io/" + data.repository + "/firmware/releases.json") + .then(response => response.json()) + .then(releases => { + var newBuild = 0; + for (let i = 0; i < releases.length; i++) { + if (releases[i].stage == "stable" && releases[i].chipFamilies.includes(data.chipfamily) && releases[i].build > newBuild) { + newBuild = releases[i].build; + newVersion = releases[i].version + " (@" + releases[i].stage + ") / Build: " + newBuild; + } + } + + currentVersion = data.FWVersion; + + if (newBuild > data.build && newBuild > 0) { + document.getElementById("updateInfoItem").classList.remove('hide'); + } + + }); + }); +} + +// ************************************************ +export function showUpdateInfoInMainFrame() { + document.getElementById("currentVersion").innerText = currentVersion; + document.getElementById("newVersion").innerText = newVersion; + const showUpdateInfo = document.getElementById("showUpdateInfo").cloneNode(true); + if (top.frames['frame_main'].document.getElementById("showUpdateInfo")) { + top.frames['frame_main'].document.getElementById("showUpdateInfo").classList.remove('hide'); + } else { + top.frames['frame_main'].document.body.appendChild(showUpdateInfo); + showUpdateInfo.classList.remove('hide'); + } +} + // ************************************************ export function highlightNavi(item) { const collection = document.getElementsByName('navi') diff --git a/data/web/status.html b/data/web/status.html index fbb8c156..ac57f33c 100644 --- a/data/web/status.html +++ b/data/web/status.html @@ -127,7 +127,7 @@ -
+

Are you sure you want to reset?

diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index 2861d585..347f2f83 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -32,7 +32,7 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): server->addHandler(ws); ElegantOTA.begin(server); // Start ElegantOTA - ElegantOTA.setGitEnv(String(GIT_OWNER), String(GIT_REPO), String(GIT_BRANCH)); + ElegantOTA.setGitEnv(String(GIT_OWNER), String(GIT_REPO), String(GIT_BRANCH), String(GITHUB_RUN).toInt()); ElegantOTA.setFWVersion(String(Config->GetReleaseName() + " / Build: " + GITHUB_RUN )); ElegantOTA.setBackupRestoreFS("/config"); ElegantOTA.setAutoReboot(true); @@ -259,6 +259,7 @@ bool MyWebServer::handleReset() { return ret; } +/* void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { AsyncResponseStream *response = request->beginResponseStream("application/json"); response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); @@ -268,6 +269,7 @@ void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { mb->GetRegisterAsJsonToWebServer(response); request->send(response); } +*/ void MyWebServer::GetInitDataNavi(JsonDocument& json) { json["data"].to(); diff --git a/src/modbus.cpp b/src/modbus.cpp index 156c1b32..77aa0e75 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1293,6 +1293,7 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { * {data: [{"name": "xx", "value": "xx"}], } * {"GridVoltage_R":"0.00 V","GridCurrent_R":"0.00 A","GridPower_R":"0 W","GridFrequency_R":"0.00 Hz","GridVoltage_S":"0.90 V","GridCurrent_S":"1715.40 A","GridPower_S":"-28671 W","GridFrequency_S":"174.08 Hz","GridVoltage_T":"0.00 V","GridCurrent_T":"0.00 A","GridPower_T":"0 W","GridFrequency_T":"1.30 Hz","PvVoltage1":"259.80 V","PvVoltage2":"0.00 V","PvCurrent1":"1.00 A","PvCurrent2":"0.00 A","Temperature":"28 °C","PowerPv1":"283 W","PowerPv2":"0 W","BatVoltage":"0.00 V","BatCurrent":"0.00 A","BatPower":"0 W","BatTemp":"0 °C","BatCapacity":"0 %","OutputEnergyChargeWh":"0 Wh","OutputEnergyChargeKWh":"0.00 KWh","OutputEnergyChargeToday":"0.00 KWh","InputEnergyChargeWh":"0 Wh","InputEnergyChargeKWh":"0.00 KWh"} *******************************************************/ +/* void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { int count = 0; @@ -1361,6 +1362,7 @@ void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { if (regfile) { regfile.close(); } response->print("]}"); } +*/ /******************************************************* * request for changing active Status of a certain item diff --git a/src/modbus.h b/src/modbus.h index 3ec5785b..4f5f850d 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -5,9 +5,9 @@ #ifndef SOLAXMODBUS_H #define SOLAXMODBUS_H -#include "commonlibs.h" -#include "baseconfig.h" -#include "mqtt.h" +#include +#include +#include #include #include #include @@ -17,7 +17,7 @@ #include #include -#define DEBUGMODE +//#define DEBUGMODE class modbus { @@ -63,7 +63,7 @@ class modbus { String GetInverterSN(); void GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request); void GetSettersAsJsonToWebServer(AsyncWebServerRequest *request); - void GetRegisterAsJsonToWebServer(AsyncResponseStream *response); + //void GetRegisterAsJsonToWebServer(AsyncResponseStream *response); void SetItemActiveStatus(String item, bool newstate); void ReceiveMQTT(String topic, String msg); JsonDocument GetSetterByName(String name); From e15fafb09bc38f70981a8860aaddfa28d93d80b9 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:03:22 +0100 Subject: [PATCH 053/106] Update Solax-X3.json (#128) --- data/regs/Solax-X3.json | 2617 ++++++++++++++++++++++++++------------- 1 file changed, 1747 insertions(+), 870 deletions(-) diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index 1ff20640..0a9cb884 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -1,874 +1,1751 @@ { - "Solax-X3": { - "config": { - "author": "Lazgar", - "RequestLiveData": [ - [ - "#ClientID", - "0x04", - "0x00", - "0x00", - "0x00", - "0x78" - ], - [ - "#ClientID", - "0x04", - "0x00", - "0x78", - "0x00", - "0x77" - ] - ], - "RequestIdData": [ - [ - "#ClientID", - "0x03", - "0x00", - "0x00", - "0x00", - "0x14" - ] - ], - "ClientIdPos": 0, - "LiveDataFunctionCodePos": 1, - "LiveDataFunctionCode": "0x04", - "IdDataFunctionCodePos": 1, - "IdDataFunctionCode": "0x03", - "LiveDataStartsAtPos": 3, - "IdDataStartsAtPos": 3, - "LiveDataErrorPos": 1, - "LiveDataErrorCode": "0x84", - "IdDataErrorPos": 1, - "IdDataErrorCode": "0x83", - "LiveDataSuccessPos": 1, - "LiveDataSuccessCode": "0x04", - "IdDataSuccessPos": 1, - "IdDataSuccessCode": "0x03" - }, - "data": { - "livedata": [ - { - "position": [ - 215, - 216 - ], - "name": "GridVoltage_R", - "realname": "Grid Voltage L1", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 217, - 218 - ], - "name": "GridCurrent_R", - "realname": "Grid Current L1", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 219, - 220 - ], - "name": "GridPower_R", - "realname": "Grid Power L1", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 221, - 222 - ], - "name": "GridFrequency_R", - "realname": "Grid Frequency L1", - "datatype": "float", - "factor": 0.01, - "unit": "Hz" - }, - { - "position": [ - 223, - 224 - ], - "name": "GridVoltage_S", - "realname": "Grid Voltage L2", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 225, - 226 - ], - "name": "GridCurrent_S", - "realname": "Grid Current L2", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 227, - 228 - ], - "name": "GridPower_S", - "realname": "Grid Power L2", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 229, - 230 - ], - "name": "GridFrequency_S", - "realname": "Grid Frequency L2", - "datatype": "float", - "factor": 0.01, - "unit": "Hz" - }, - { - "position": [ - 231, - 232 - ], - "name": "GridVoltage_T", - "realname": "Grid Voltage L3", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 233, - 234 - ], - "name": "GridCurrent_T", - "realname": "Grid Current L3", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 235, - 236 - ], - "name": "GridPower_T", - "realname": "Grid Power L3", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 237, - 238 - ], - "name": "GridFrequency_T", - "realname": "Grid Frequency L3", - "datatype": "float", - "factor": 0.01, - "unit": "Hz" - }, - { - "position": [ - 9, - 10 - ], - "name": "PvVoltage1", - "realname": "Pv Voltage 1", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 11, - 12 - ], - "name": "PvVoltage2", - "realname": "Pv Voltage 2", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 13, - 14 - ], - "name": "PvCurrent1", - "realname": "Pv Current 1", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 15, - 16 - ], - "name": "PvCurrent2", - "realname": "Pv Current 2", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 19, - 20 - ], - "name": "Temperature", - "realname": "Temperature", - "datatype": "integer", - "unit": "°C" - }, - { - "position": [ - 21, - 22 - ], - "name": "InverterStatus", - "realname": "Inverter Status", - "datatype": "integer", - "mapping": [ - [ - 0, - "WaitMode" - ], - [ - 1, - "CheckMode" - ], - [ - 2, - "NormalMode" - ], - [ - 3, - "FaultMode" - ], - [ - 4, - "PermanentFaultMode" - ], - [ - 5, - "UpdateMode" - ], - [ - 6, - "EPSCheckMode" - ], - [ - 7, - "EPSMode" - ], - [ - 8, - "SelfTest" - ], - [ - 9, - "IdleMode" - ] - ] - }, - { - "position": [ - 23, - 24 - ], - "name": "PowerPv1", - "realname": "Power PV 1", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 25, - 26 - ], - "name": "PowerPv2", - "realname": "Power PV 2", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 43, - 44 - ], - "name": "BatVoltage", - "realname": "Battery Voltage", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 45, - 46 - ], - "name": "BatCurrent", - "realname": "Battery Current", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 47, - 48 - ], - "name": "BatPower", - "realname": "Battery Power", - "datatype": "integer", - "openwbtopic": "setbatimpwh", - "unit": "W" - }, - { - "position": [ - 51, - 52 - ], - "name": "BatTemp", - "realname": "Battery Temperature", - "datatype": "integer", - "unit": "°C" - }, - { - "position": [ - 55, - 56 - ], - "name": "GridStatus", - "realname": "Grid Status", - "datatype": "integer", - "mapping": [ - [ - 0, - "OnGrid" - ], - [ - 1, - "OffGrid" - ] - ] - }, - { - "position": [ - 59, - 60 - ], - "name": "BatCapacity", - "realname": "Battery Capacity", - "datatype": "integer", - "openwbtopic": "setbatsoc", - "unit": "%" - }, - { - "position": [ - 63, - 64, - 61, - 62 - ], - "name": "OutputEnergyChargeWh", - "realname": "Output Energy Charge (Wh)", - "datatype": "integer", - "openwbtopic": "setbatexpwh", - "factor": 100, - "unit": "Wh" - }, - { - "position": [ - 63, - 64, - 61, - 62 - ], - "name": "OutputEnergyChargeKWh", - "realname": "Output Energy Charge (KWh)", - "datatype": "float", - "factor": 0.1, - "unit": "KWh" - }, - { - "position": [ - 67, - 68 - ], - "name": "OutputEnergyChargeToday", - "realname": "Output Energy Charge Today", - "datatype": "float", - "factor": 0.1, - "unit": "KWh" - }, - { - "position": [ - 71, - 72, - 69, - 70 - ], - "name": "InputEnergyChargeWh", - "realname": "Input Energy Charge (Wh)", - "datatype": "integer", - "openwbtopic": "setbatimpwhhImported", - "factor": 100, - "unit": "Wh" - }, - { - "position": [ - 71, - 72, - 69, - 70 - ], - "name": "InputEnergyChargeKWh", - "realname": "Input Energy Charge (KWh)", - "datatype": "float", - "factor": 0.1, - "unit": "KWh" - }, - { - "position": [ - 73, - 74 - ], - "name": "InputEnergyChargeToday", - "realname": "Input Energy Charge Today", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 145, - 146, - 143, - 144 - ], - "name": "feedinPower", - "realname": "FeedIn Energy Power to Grid", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 149, - 150, - 147, - 148 - ], - "name": "feedinEnergyTotal", - "realname": "FeedIn Energy Total", - "datatype": "float", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 153, - 154, - 151, - 152 - ], - "name": "consumedEnergyTotal", - "realname": "Consumed Energy Total", - "datatype": "float", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 163, - 164 - ], - "name": "EnergyTodayToGrid", - "realname": "Today Energy to Grid", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 169, - 170, - 167, - 168 - ], - "name": "EnergyTotalToGridKwh", - "realname": "Total Energy to Grid in KWh", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 169, - 170, - 167, - 168 - ], - "name": "EnergyTotalToGridWh", - "realname": "Total Energy to Grid in Wh", - "datatype": "integer", - "openwbtopic": "setcounterwh", - "factor": 100, - "unit": "Wh" - }, - { - "position": [ - 282, - 283, - 280, - 281 - ], - "name": "OnGridRunTime", - "realname": "OnGrid RunTime", - "datatype": "float", - "factor": 0.1, - "unit": "h" - }, - { - "position": [ - 286, - 287, - 284, - 285 - ], - "name": "OffGridRunTime", - "realname": "OffGrid RunTime", - "datatype": "float", - "factor": 0.1, - "unit": "h" - }, - { - "position": [ - 239, - 240 - ], - "name": "OffGridVoltage_R", - "realname": "Off Grid Voltage L1", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 241, - 242 - ], - "name": "OffGridCurrent_R", - "realname": "Off Grid Current L1", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 250, - 251 - ], - "name": "OffGridPowerActive_R", - "realname": "Off Grid Power L1", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 254, - 255 - ], - "name": "OffGridVoltage_S", - "realname": "Off Grid Voltage L2", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 256, - 257 - ], - "name": "OffGridCurrent_S", - "realname": "Off Grid Current L2", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 258, - 259 - ], - "name": "OffGridPowerActive_S", - "realname": "Off Grid Power L2", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 262, - 263 - ], - "name": "OffGridVoltage_T", - "realname": "Off Grid Voltage L3", - "datatype": "float", - "factor": 0.1, - "unit": "V" - }, - { - "position": [ - 264, - 265 - ], - "name": "OffGridCurrent_T", - "realname": "Off Grid Current L3", - "datatype": "float", - "factor": 0.1, - "unit": "A" - }, - { - "position": [ - 266, - 267 - ], - "name": "OffGridPowerActive_T", - "realname": "Off Grid Power L3", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 270, - 271, - 268, - 269 - ], - "name": "FeedInPowerPhase_R", - "realname": "FeedIn Power Phase L1", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 274, - 275, - 272, - 273 - ], - "name": "FeedInPowerPhase_S", - "realname": "FeedIn Power Phase L2", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 278, - 279, - 276, - 277 - ], - "name": "FeedInPowerPhase_T", - "realname": "FeedIn Power Phase L3", - "datatype": "integer", - "unit": "W" - }, - { - "position": [ - 294, - 295, - 292, - 293 - ], - "name": "OffGridYieldTotal", - "realname": "OffGrid Yield Total", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 296, - 297 - ], - "name": "OffGridYieldToday", - "realname": "OffGrid Yield Today", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 298, - 299 - ], - "name": "EChargeToday", - "realname": "ECharge Today", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 302, - 303, - 300, - 301 - ], - "name": "EChargeTotal", - "realname": "ECharge Total", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 306, - 307, - 304, - 305 - ], - "name": "SolarEnergyTotal", - "realname": "SolarEnergy Total", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 308, - 309 - ], - "name": "SolarEnergyToday", - "realname": "SolarEnergy Today", - "datatype": "float", - "factor": 0.1, - "unit": "kWh" - }, - { - "position": [ - 314, - 315, - 312, - 313 - ], - "name": "EnergyFeedin", - "realname": "EnergyFeedin Today", - "datatype": "float", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 318, - 319, - 316, - 317 - ], - "name": "EnergyConsum", - "realname": "EnergyConsum Today", - "datatype": "float", - "factor": 0.01, - "unit": "kWh" - }, - { - "position": [ - 384, - 385 - ], - "name": "CellVoltageHigh", - "realname": "Cell Voltage High", - "datatype": "float", - "factor": 0.001, - "unit": "V" - }, - { - "position": [ - 386, - 387 - ], - "name": "CellVoltageLow", - "realname": "Cell Voltage Low", - "datatype": "float", - "factor": 0.001, - "unit": "V" - } - ], - "id": [ - { - "position": [ - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16 - ], - "name": "InverterSN", - "realname": "Inverter SerialNumber", - "datatype": "string" - }, - { - "position": [ - 17, - 18, - 19, - 20, - 21, - 22, - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30 - ], - "name": "FactoryName", - "realname": "Factory Name", - "datatype": "string" - }, - { - "position": [ - 31, - 32, - 33, - 34, - 35, - 36, - 37, - 38, - 39, - 40, - 41, - 42 - ], - "name": "ModuleName", - "realname": "Module Name", - "datatype": "string" - } + "Solax-X3": { + "config": { + "author": "Lazgar", + "RequestLiveData": [ + [ + "#ClientID", + "0x04", + "0x00", + "0x00", + "0x00", + "0x79" + ], + [ + "#ClientID", + "0x04", + "0x00", + "0x79", + "0x00", + "0x79" + ], + [ + "#ClientID", + "0x04", + "0x00", + "0xf2", + "0x00", + "0x41" + ] + ], + "RequestIdData": [ + [ + "#ClientID", + "0x03", + "0x00", + "0x00", + "0x00", + "0x79" + ], + [ + "#ClientID", + "0x03", + "0x00", + "0x79", + "0x00", + "0x79" + ], + [ + "#ClientID", + "0x03", + "0x00", + "0xf2", + "0x00", + "0x79" + ], + [ + "#ClientID", + "0x03", + "0x01", + "0x6b", + "0x00", + "0x0c" + ] + ], + "ClientIdPos": 0, + "LiveDataFunctionCodePos": 1, + "LiveDataFunctionCode": "0x04", + "IdDataFunctionCodePos": 1, + "IdDataFunctionCode": "0x03", + "LiveDataStartsAtPos": 3, + "IdDataStartsAtPos": 3, + "LiveDataErrorPos": 1, + "LiveDataErrorCode": "0x84", + "IdDataErrorPos": 1, + "IdDataErrorCode": "0x83", + "LiveDataSuccessPos": 1, + "LiveDataSuccessCode": "0x04", + "IdDataSuccessPos": 1, + "IdDataSuccessCode": "0x03" + }, + "data": { + "livedata": [ + { + "position": [ + 9, + 10 + ], + "name": "PvVoltage1", + "realname": "Pv Voltage 1", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 11, + 12 + ], + "name": "PvVoltage2", + "realname": "Pv Voltage 2", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 13, + 14 + ], + "name": "PvCurrent1", + "realname": "Pv Current 1", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 15, + 16 + ], + "name": "PvCurrent2", + "realname": "Pv Current 2", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 23, + 24 + ], + "name": "PvPower1", + "realname": "Pv Power 1", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 25, + 26 + ], + "name": "PvPower2", + "realname": "Pv Power 2", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 19, + 20 + ], + "name": "Temperature", + "realname": "Temperature", + "datatype": "integer", + "unit": "°C" + }, + { + "position": [ + 21, + 22 + ], + "name": "InverterStatus", + "realname": "Inverter Status", + "datatype": "integer", + "mapping": [ + [ + 0, + "Waiting" + ], + [ + 1, + "Checking" + ], + [ + 2, + "Normal" + ], + [ + 3, + "Fault" + ], + [ + 4, + "Permanent Fault" + ], + [ + 5, + "Update" + ], + [ + 6, + "Off-Grid Waiting" + ], + [ + 7, + "Off-Grid" + ], + [ + 8, + "Self Testing" + ], + [ + 9, + "Idle" + ], + [ + 10, + "Standby" + ] + ] + }, + { + "position": [ + 133, + 134, + 131, + 132 + ], + "name": "InverterFaultMessage", + "realname": "Inverter Fault Message", + "datatype": "binary", + "mapping": [ + "TZ Protect Fault", + "Grid Lost Fault", + "Grid Volt Fault", + "Grid Freq Fault", + "PV Volt Fault", + "Bus Volt Fault", + "Bat Volt Fault", + "AC10mins Volt Fault", + "DCI OCP Fault", + "DCV OCP Fault", + "SW OCP Fault", + "RC OCP Fault", + "Isolation Fault", + "Temp Over Fault", + "BatConnDir Fault", + "Off-Grid Overload", + "Overload", + "Bat Power Low", + "BMS Lost", + "Fan Fault", + "Low Temp Fault", + "Parallel Fault", + "Hard Limit Fault", + "INV Volt Sample Fault", + "Inner Comm Fault", + "INV EEPROM Fault", + "RCD Fault", + "Grid Relay Fault", + "Off-grid Relay Fault", + "PV Conndir Fault", + "Charger Relay Fault", + "Earth Relay Fault", + "no Error" + ] + }, + { + "position": [ + 137, + 138 + ], + "name": "ManagerFaultMessage", + "realname": "Manager Fault Message", + "datatype": "binary", + "mapping": [ + "Power Type Fault", + "Port OC Warning", + "Mgr EEPROM Fault", + "Reserve3", + "NTC Sample Invalid", + "Bat Temp Low", + "Bat Temp High", + "Reserve7", + "Reserve8", + "Meter Fault", + "Bypass Relay Fault", + "Fan 2 Fault", + "Reserve12", + "Reserve13", + "Reserve14", + "Reserve15", + "no Error" + ] + }, + { + "position": [ + 141, + 142, + 139, + 140 + ], + "name": "BMSFaultMessage", + "realname": "BMS Fault Message", + "datatype": "binary", + "mapping": [ + "BMS_External_Err", + "BMS_Internal_Err", + "BMS_OverVoltage", + "BMS_LowerVoltage", + "BMS_ChargeOCP", + "BMS_DischargeOCP", + "BMS_TemHigh", + "BMS_TemLow", + "BMS_CellImbalance", + "BMS_Hardware_Protect", + "BMS_Circuit_Fault", + "BMS_ISO_Fault", + "BMS_VolSen_Fault", + "BMS_TempSen_Fault", + "BMS_CurSen_Fault", + "BMS_Relay_Fault", + "BMS_Type_Unmatch", + "BMS_Ver_Unmathch", + "BMS_MFR_Unmathch", + "BMS_SW_Unmathch", + "BMS_M&S_Unmatch", + "BMS_CR_NORespond", + "BMS_SW_Protect", + "BMS_536_Fault", + "BMS_SelfcheckErr", + "BMS_TempdiffErr", + "BMS_BreakFault", + "BMS_Flash_Fault", + "BMS_Precharge_Fault", + "BMS_AirSwitch_Break", + "Rev", + "Rev", + "no Error" + ] + }, + { + "position": [ + 171, + 172 + ], + "name": "InverterSettings", + "realname": "Inverter Settings", + "datatype": "integer", + "settopic": "UnlockSettings", + "mapping": [ + [ + 0, + "Locked" + ], + [ + 1, + "Unlocked" + ] + ] + }, + { + "position": [ + 525, + 526 + ], + "name": "ModbusPowerControl", + "realname": "Modbus Power Control", + "datatype": "integer", + "settopic": "ModbusPowerControl", + "mapping": [ + [ + 0, + "Off" + ], + [ + 1, + "PowerCtrl" + ], + [ + 2, + "ElectricQuantityCtrl" + ], + [ + 3, + "SoCTargetCtrl" + ] + ] + }, + { + "position": [ + 55, + 56 + ], + "name": "GridStatus", + "realname": "Grid Status", + "datatype": "integer", + "mapping": [ + [ + 0, + "OnGrid" + ], + [ + 1, + "OffGrid" + ] + ] + }, + { + "position": [ + 53, + 54 + ], + "name": "BatStatus", + "realname": "Battery Status", + "datatype": "integer", + "mapping": [ + [ + 0, + "Discharge" + ], + [ + 1, + "Charge" + ], + [ + 2, + "Stop" + ] + ] + }, + { + "position": [ + 43, + 44 + ], + "name": "BatVoltage", + "realname": "Battery Voltage", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 45, + 46 + ], + "name": "BatCurrent", + "realname": "Battery Current", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 47, + 48 + ], + "name": "BatPower", + "realname": "Battery Power", + "datatype": "integer", + "openwbtopic": "setbatimpwh", + "unit": "W" + }, + { + "position": [ + 51, + 52 + ], + "name": "BatTemp", + "realname": "Battery Temperature", + "datatype": "integer", + "unit": "°C" + }, + { + "position": [ + 59, + 60 + ], + "name": "BatCapacity", + "realname": "Battery Capacity", + "datatype": "integer", + "openwbtopic": "setbatsoc", + "unit": "%" + }, + { + "position": [ + 388, + 389 + ], + "name": "BatUserSoC", + "realname": "Battery User SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 390, + 391 + ], + "name": "BatUserSoH", + "realname": "Battery User SoH", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 579, + 580 + ], + "name": "BatTargetSoC", + "realname": "Battery Target SoC", + "datatype": "integer", + "settopic": "TargetSoC", + "unit": "%" + }, + { + "position": [ + 67, + 68 + ], + "name": "ChargeEnergyOutputToday", + "realname": "Charge Energy Output Today", + "datatype": "float", + "factor": 0.1, + "unit": "KWh" + }, + { + "position": [ + 63, + 64, + 61, + 62 + ], + "name": "ChargeEnergyOutputTotal", + "realname": "Charge Energy Output Total", + "datatype": "float", + "factor": 0.1, + "unit": "KWh" + }, + { + "position": [ + 63, + 64, + 61, + 62 + ], + "name": "ChargeEnergyOutputTotalWh", + "realname": "Charge Energy Output Total(Wh)", + "datatype": "integer", + "openwbtopic": "setbatexpwh", + "factor": 100, + "unit": "Wh" + }, + { + "position": [ + 73, + 74 + ], + "name": "ChargeEnergyInputToday", + "realname": "Charge Energy Input Today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 71, + 72, + 69, + 70 + ], + "name": "ChargeEnergyInputTotal", + "realname": "Charge Energy Input Total", + "datatype": "float", + "factor": 0.1, + "unit": "KWh" + }, + { + "position": [ + 71, + 72, + 69, + 70 + ], + "name": "ChargeEnergyInputTotalWh", + "realname": "Charge Energy Input Total (Wh)", + "datatype": "integer", + "openwbtopic": "setbatimpwh", + "factor": 100, + "unit": "Wh" + }, + { + "position": [ + 314, + 315, + 312, + 313 + ], + "name": "FeedInEnergyToday", + "realname": "FeedIn Energy Today", + "datatype": "float", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 149, + 150, + 147, + 148 + ], + "name": "FeedInEnergyTotal", + "realname": "FeedIn Energy Total", + "datatype": "float", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 318, + 319, + 316, + 317 + ], + "name": "ConsumedEnergyToday", + "realname": "Consumed Energy Today", + "datatype": "float", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 153, + 154, + 151, + 152 + ], + "name": "ConsumedEnergyTotal", + "realname": "Consumed Energy Total", + "datatype": "float", + "factor": 0.01, + "unit": "kWh" + }, + { + "position": [ + 163, + 164 + ], + "name": "EnergyTodayToGrid", + "realname": "Today Energy to Grid", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 169, + 170, + 167, + 168 + ], + "name": "EnergyTotalToGrid", + "realname": "Total Energy to Grid in KWh", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 169, + 170, + 167, + 168 + ], + "name": "EnergyTotalToGridWh", + "realname": "Total Energy to Grid in Wh", + "datatype": "float", + "openwbtopic": "setcounterwh", + "factor": 100, + "unit": "Wh" + }, + { + "position": [ + 215, + 216 + ], + "name": "GridVoltage_L1", + "realname": "Grid Voltage L1", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 223, + 224 + ], + "name": "GridVoltage_L2", + "realname": "Grid Voltage L2", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 231, + 232 + ], + "name": "GridVoltage_L3", + "realname": "Grid Voltage L3", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 217, + 218 + ], + "name": "GridCurrent_L1", + "realname": "Grid Current L1", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 225, + 226 + ], + "name": "GridCurrent_L2", + "realname": "Grid Current L2", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 233, + 234 + ], + "name": "GridCurrent_L3", + "realname": "Grid Current L3", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 219, + 220 + ], + "name": "GridPower_L1", + "realname": "Grid Power L1", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 227, + 228 + ], + "name": "GridPower_L2", + "realname": "Grid Power L2", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 235, + 236 + ], + "name": "GridPower_L3", + "realname": "Grid Power L3", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 221, + 222 + ], + "name": "GridFrequency_L1", + "realname": "Grid Frequency L1", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [ + 229, + 230 + ], + "name": "GridFrequency_L2", + "realname": "Grid Frequency L2", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [ + 237, + 238 + ], + "name": "GridFrequency_L3", + "realname": "Grid Frequency L3", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [ + 239, + 240 + ], + "name": "OffGridVoltage_L1", + "realname": "Off Grid Voltage L1", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 252, + 253 + ], + "name": "OffGridVoltage_L2", + "realname": "Off Grid Voltage L2", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 260, + 261 + ], + "name": "OffGridVoltage_L3", + "realname": "Off Grid Voltage L3", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [ + 241, + 242 + ], + "name": "OffGridCurrent_L1", + "realname": "Off Grid Current L1", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 254, + 255 + ], + "name": "OffGridCurrent_L2", + "realname": "Off Grid Current L2", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 262, + 263 + ], + "name": "OffGridCurrent_L3", + "realname": "Off Grid Current L3", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 243, + 244 + ], + "name": "OffGridPowerActive_L1", + "realname": "Off Grid Power L1", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 256, + 257 + ], + "name": "OffGridPowerActive_L2", + "realname": "Off Grid Power L2", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 264, + 265 + ], + "name": "OffGridPowerActive_L3", + "realname": "Off Grid Power L3", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 145, + 146, + 143, + 144 + ], + "name": "FeedInPower", + "realname": "FeedIn Power to Grid", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 270, + 271, + 268, + 269 + ], + "name": "FeedInPower_L1", + "realname": "FeedIn Power L1", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 274, + 275, + 272, + 273 + ], + "name": "FeedInPower_L2", + "realname": "FeedIn Power L2", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 278, + 279, + 276, + 277 + ], + "name": "FeedInPower_L3", + "realname": "FeedIn Power L3", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 282, + 283, + 280, + 281 + ], + "name": "OnGridRunTime", + "realname": "OnGrid RunTime", + "datatype": "float", + "factor": 0.1, + "unit": "h" + }, + { + "position": [ + 286, + 287, + 284, + 285 + ], + "name": "OffGridRunTime", + "realname": "OffGrid RunTime", + "datatype": "float", + "factor": 0.1, + "unit": "h" + }, + { + "position": [ + 296, + 297 + ], + "name": "OffGridYieldToday", + "realname": "OffGrid Yield Today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 294, + 295, + 292, + 293 + ], + "name": "OffGridYieldTotal", + "realname": "OffGrid Yield Total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 298, + 299 + ], + "name": "EChargeToday", + "realname": "ECharge Today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 302, + 303, + 300, + 301 + ], + "name": "EChargeTotal", + "realname": "ECharge Total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 308, + 309 + ], + "name": "SolarEnergyToday", + "realname": "SolarEnergy Today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 306, + 307, + 304, + 306 + ], + "name": "SolarEnergyTotal", + "realname": "SolarEnergy Total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [ + 384, + 385 + ], + "name": "CellVoltageHigh", + "realname": "Cell Voltage High", + "datatype": "float", + "factor": 0.001, + "unit": "V" + }, + { + "position": [ + 386, + 387 + ], + "name": "CellVoltageLow", + "realname": "Cell Voltage Low", + "datatype": "float", + "factor": 0.001, + "unit": "V" + } + ], + "id": [ + { + "position": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16 + ], + "name": "InverterSN", + "realname": "Inverter SerialNumber", + "datatype": "string" + }, + { + "position": [ + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30 + ], + "name": "InverterManufacturer", + "realname": "Inverter Manufacturer", + "datatype": "string" + }, + { + "position": [ + 535, + 536 + ], + "name": "InverterMachineType", + "realname": "Inverter Machine Type", + "datatype": "integer", + "mapping": [ + [ + 1, + "X1" + ], + [ + 3, + "X3" + ] + ] + }, + { + "position": [ + 539, + 540 + ], + "name": "InverterMachineStyle", + "realname": "Inverter Machine Style", + "datatype": "integer", + "mapping": [ + [ + 0, + "Hybrid" + ], + [ + 1, + "FIT" + ] + ] + }, + { + "position": [ + 380, + 381 + ], + "name": "InverterPowerType", + "realname": "Inverter Power Type", + "datatype": "integer", + "mapping": [ + [ + 15000, + "15k" + ], + [ + 12000, + "12k" + ], + [ + 10000, + "10k" + ], + [ + 8000, + "8k" + ], + [ + 6000, + "6k" + ], + [ + 5000, + "5k" + ] + ] + }, + { + "position": [ + 456, + 457 + ], + "name": "InverterUserPassword", + "realname": "Inverter User Password", + "datatype": "integer" + }, + { + "position": [ + 458, + 459 + ], + "name": "InverterAdminPassword", + "realname": "Inverter Admin Password", + "datatype": "integer" + }, + { + "position": [ + 286, + 287 + ], + "name": "OperationMode", + "realname": "Operation Mode", + "datatype": "integer", + "mapping": [ + [ + 0, + "SelfUseMode" + ], + [ + 1, + "FeedInPriority" + ], + [ + 2, + "BackupMode" + ], + [ + 3, + "ManuelMode" + ], + [ + 4, + "PeakShaving" + ], + [ + 5, + "TOUMode" + ] + ] + }, + { + "position": [ + 288, + 289 + ], + "name": "ManuelMode", + "realname": "Manuel Mode", + "datatype": "integer", + "mapping": [ + [ + 0, + "StopCharge&Discharge" + ], + [ + 1, + "ForceCharge" + ], + [ + 2, + "ForceDischarge" + ] + ] + }, + { + "position": [ + 372, + 373 + ], + "name": "ExportLimit", + "realname": "Export Limit", + "datatype": "integer", + "factor": 10, + "unit": "W" + }, + { + "position": [ + 374, + 375 + ], + "name": "OffGridMute", + "realname": "Off-Grid Mute", + "datatype": "integer", + "mapping": [ + [ + 0, + "Off" + ], + [ + 1, + "On" + ] + ] + }, + { + "position": [ + 376, + 377 + ], + "name": "OffGridMinimumSoC", + "realname": "Off-Grid Minimum SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 384, + 385 + ], + "name": "MPPT", + "realname": "MPPT", + "datatype": "integer", + "mapping": [ + [ + 0, + "Off" + ], + [ + 1, + "On" + ] + ] + }, + { + "position": [ + 529, + 530 + ], + "name": "DRMFunction", + "realname": "DRM Function", + "datatype": "integer", + "mapping": [ + [ + 0, + "Off" + ], + [ + 1, + "On" + ] + ] + }, + { + "position": [ + 537, + 538 + ], + "name": "PhasePowerBalance", + "realname": "Phase Power Balance", + "datatype": "integer", + "mapping": [ + [ + 1, + "On" + ], + [ + 0, + "Off" + ] + ] + }, + { + "position": [ + 364, + 365 + ], + "name": "PgridBias", + "realname": "Pgrid Bias", + "datatype": "integer", + "mapping": [ + [ + 2, + "INV" + ], + [ + 1, + "Grid" + ], + [ + 0, + "Off" ] + ] + }, + { + "position": [ + 296, + 297 + ], + "name": "BatChargeMaxCurrent", + "realname": "Battery Charge Max Current", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 298, + 299 + ], + "name": "BatDischargeMaxCurrent", + "realname": "Battery Discharge Max Current", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [ + 302 + ], + "name": "SelfUseMinSoC", + "realname": "Self-Use Minimum SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 307 + ], + "name": "FeedInMinSoC", + "realname": "FeedIn Minimum SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 306 + ], + "name": "FeedInNightChargeSoC", + "realname": "FeedIn NightCharge SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 309 + ], + "name": "BackupMinSoC", + "realname": "Backup Minimum SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 309 + ], + "name": "BackupNightChargeSoC", + "realname": "Backup NightCharge SoC", + "datatype": "integer", + "unit": "%" } - } + ] + }, + "set": [ + { + "name": "setUnlockSettings", + "realname": "Unlock Settings", + "info": "send the 4 digit advanced password", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x00" + ] + }, + { + "name": "setOperationMode", + "realname": "Operation Mode", + "info": "accepted values:", + "mapping": [ + [ + "SelfUseMode", + 0 + ], + [ + "FeedInPriority", + 1 + ], + [ + "BackupMode", + 2 + ], + [ + "ManuelMode", + 3 + ], + [ + "PeakShaving", + 4 + ], + [ + "TUOMode", + 5 + ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x1f" + ] + }, + { + "name": "setManuelMode", + "realname": "Manuel Mode", + "info": "accepted values:", + "mapping": [ + [ + "StopCharge&Discharge", + 0 + ], + [ + "ForceCharge", + 1 + ], + [ + "ForceDischarge", + 2 + ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x20" + ] + }, + { + "name": "setExportLimit", + "realname": "Export Limit in Watt", + "info": "can be set in steps of 10, if you send 100 the limit is set to 1000 W", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x42" + ] + }, + { + "name": "setOffGridMute", + "realname": "Off-Grid Mute", + "info": "accepted values:", + "mapping": [ + [ + "Off", + 0 + ], + [ + "On", + 1 + ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x43" + ] + }, + { + "name": "setOffGridMinimumSoC", + "realname": "Off-Grid Minimum SoC", + "info": "can be set between 10 and 25 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x44" + ] + }, + { + "name": "setPhasePowerBalance", + "realname": "Phase Power Balance", + "info": "has to be investigated", + "mapping": [ + [ + "Off", + 0 + ], + [ + "On", + 1 + ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x9e" + ] + }, + { + "name": "setModbusPowerControl", + "realname": "Modbus Power Control", + "info": "accepted values:", + "mapping": [ + [ + "Off", + 0 + ], + [ + "PowerCtrl", + 1 + ], + [ + "ElectricQuantityCtrl", + 2 + ], + [ + "SoCTargetCtrl", + 3 + ] + ], + "request": [ + "#ClientID", + "0x10", + "0x00", + "0x7c" + ] + }, + { + "name": "setTargetSetType", + "realname": "Target Set Type", + "info": "accepted values:", + "mapping": [ + [ + "Set", + 1 + ], + [ + "Update", + 2 + ] + ], + "request": [ + "#ClientID", + "0x10", + "0x00", + "0x7d" + ] + }, + { + "name": "setTargetSoC", + "realname": "Target SoC", + "info": "set 0 - 100 in percent", + "request": [ + "#ClientID", + "0x10", + "0x00", + "0x83" + ] + }, + { + "name": "setPgridBias", + "realname": "Pgrid Bias", + "info": "accepted values:", + "mapping": [ + [ + "Off", + 0 + ], + [ + "Grid", + 1 + ], + [ + "INV", + 2 + ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x8D" + ] + }, + { + "name": "setBatChargeMaxCurrent", + "realname": "Battery Charge Max Current", + "info": "can be set in steps of 0.1, if you send 300 the limit is set to 30A", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x24" + ] + }, + { + "name": "setBatDischargeMaxCurrent", + "realname": "Battery Charge Max Current", + "info": "can be set in steps of 0.1, if you send 300 the limit is set to 30A", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x25" + ] + }, + { + "name": "setSelfUseMinSoC", + "realname": "Self-Use Minimum SoC", + "info": "can be set between 10 - 100 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x61" + ] + }, + { + "name": "setFeedInMinSoC", + "realname": "FeedIn Minimum SoC", + "info": "can be set between 10 - 100 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x65" + ] + }, + { + "name": "setFeedInNightChargeSoC", + "realname": "FeedIn NightCharge SoC", + "info": "can be set between 10 - 100 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x64" + ] + }, + { + "name": "setBackupMinSoC", + "realname": "Backup Minimum SoC", + "info": "can be set between 15 - 100 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x67" + ] + }, + { + "name": "setBackupNightChargeSoC", + "realname": "Backup NightCharge SoC", + "info": "can be set between 30 - 100 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x66" + ] + } + ] + } } From eec827ebd4f5f7ad88ffa0bc1571874d2567f2dd Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Thu, 23 Jan 2025 18:08:43 +0100 Subject: [PATCH 054/106] fix: correct update availability message in WebUI --- data/web/navi.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/web/navi.html b/data/web/navi.html index d88746bf..95cec900 100644 --- a/data/web/navi.html +++ b/data/web/navi.html @@ -64,8 +64,8 @@ -
- An update are available
+
+ An update is available
You are using:
available version:

From 8712f1073f20b1ff1b5d9ef2886d0a2ed7d8457c Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Thu, 23 Jan 2025 20:23:00 +0100 Subject: [PATCH 055/106] refactor: update subscription field to MQTT topic and enhance tooltip handling --- data/web/modbusitemconfig.html | 9 +++--- data/web/modbusitemconfig.js | 50 +++++++++++++++++++++------------- src/modbus.cpp | 13 ++++++--- 3 files changed, 45 insertions(+), 27 deletions(-) diff --git a/data/web/modbusitemconfig.html b/data/web/modbusitemconfig.html index 98c9ad52..fcdf1dcf 100644 --- a/data/web/modbusitemconfig.html +++ b/data/web/modbusitemconfig.html @@ -79,7 +79,7 @@ Active Name - Subscription + MQTT Topic @@ -89,11 +89,12 @@ - - + + + + - diff --git a/data/web/modbusitemconfig.js b/data/web/modbusitemconfig.js index 5b6be360..67d11379 100644 --- a/data/web/modbusitemconfig.js +++ b/data/web/modbusitemconfig.js @@ -23,14 +23,11 @@ export function init1() { global.handleJsonItems(data); - data = {"globalEnabled": "1", "data": {"setitems": [{"name": "setUnlockSettings","realname": "Unlock Settings","active": {"checked": 0, "name": "setUnlockSettings"},"subscription": "home/Solax-Test/set/setUnlockSettings","info": "send the 4 digit advanced password"}, - {"name": "setTargetBatSOC","realname": "Target SoC","active": {"checked": 0, "name": "setTargetBatSOC"},"subscription": "home/Solax-Test/set/setTargetBatSOC","info": "set 0 - 100 in percent"} , - {"name": "setOperationMode","realname": "Operation Mode","active": {"checked": 0, "name": "setOperationMode"},"subscription": "home/Solax-Test/set/setOperationMode","info": {"data-mapping": "[[ 'Self-Use', 0],['FeedInPriority', 1],['BackupMode',2],['ManuelMode',3],['PeakShaving',4],[ 'TUOMode', 5 ]]", "innerHTML": "set inverter operation mode"}} - ]}, - "object_id": "home/Solax-Test", "response": {"status": 1, "text": "successful"}, + data = {"globalEnabled": "1", "data": {"setitems": [{"name": "setUnlockSettings","realname": "Unlock Settings","active": {"checked": 0, "name": "setUnlockSettings"},"info": "send the 4 digit advanced password","subscription": "home/Solax/set/setUnlockSettings"},{"name": "setTargetBatSOC","realname": "Target SoC","active": {"checked": 0, "name": "setTargetBatSOC"},"info": "set battery SOC: 0 - 100 in percent","subscription": "home/Solax/set/setTargetBatSOC"},{"name": "setOperationMode","realname": "Operation Mode","active": {"checked": 0, "name": "setOperationMode"},"info": "setting of 6 possible operation modes","subscription": "home/Solax/set/setOperationMode","mapping": {"data-mapping": "[['SelfUse',0],['FeedInPriority',1],['BackupMode',2],['ManuelMode',3],['PeakShaving',4],['TUOMode',5]]"}} ]}, + "object_id": "home/Solax", "cmd": { "callbackFn": "mbitemconfig_SetterCallback" - }} + }} global.handleJsonItems(data); @@ -65,15 +62,6 @@ export function init() { }) .catch(error => console.error('Error fetching setters:', error)); - fetch('/getsetter') - .then(response => response.json()) - .then(data => { - data['cmd'] = {}; - data['cmd']['callbackFn'] = "mbitemconfig_SetterCallback"; - global.handleJsonItems(data); - }) - .catch(error => console.error('Error fetching setters:', error)); - // Warte bis die WebSocket-Verbindung aufgebaut ist let checkWebSocketInterval = setInterval(() => { if (global.ws && global.ws.readyState === WebSocket.OPEN) { @@ -121,7 +109,7 @@ function MySetterCallback(json) { document.getElementById("settable").classList.remove("hide"); } - // handle data-mapping and add them to info field + // handle data-mapping and add them to topic as tooltip document.querySelectorAll('[data-mapping]').forEach(element => { try { const mapping = JSON.parse(element.getAttribute('data-mapping').replace(/'/g, '"')); @@ -130,15 +118,39 @@ function MySetterCallback(json) { for (var i = 0; i < mapping.length; i++) { if (info.length > 0) info += "
"; - info += mapping[i][0]; + info += "- " + mapping[i][0]; } - obj.innerHTML += "
" + info; + + info = "possible values:

" + info; + createTooltip(obj, info); + } catch (e) { console.error('Invalid JSON:', e); } }); + + // handle data-info and add them to realname as tooltip + document.querySelectorAll('[data-info]').forEach(element => { + const info = element.getAttribute('data-info'); + const obj = document.getElementById(element.id); + + createTooltip(obj, info); + }); } - + +// ************************************************ +function createTooltip(obj, tooltip) { + var dfn = document.createElement('dfn'); + dfn.classList.add('tooltip_simple'); + obj.parentNode.replaceChild(dfn, obj); + dfn.appendChild(obj); + + var span = document.createElement('span'); + span.setAttribute('role', 'tooltip_simple'); + span.innerHTML = tooltip; + dfn.appendChild(span); +} + // ************************************************ function RefreshLiveData() { var data = {}; diff --git a/src/modbus.cpp b/src/modbus.cpp index 77aa0e75..e5fd20f3 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1259,11 +1259,16 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { String mapping = elem["mapping"].as(); mapping.replace("\"", "'"); ret += "{\"name\": \"" + elem["name"].as() + "\","; - ret += "\"realname\": \"" + elem["realname"].as() + "\","; + + ret += "\"realname\": {\"innerHTML\": \"" + elem["realname"].as() + "\""; + if (elem["info"]) ret += ", \"data-info\": \"" + elem["info"].as() + "\""; + ret += "},"; + ret += "\"active\": {\"checked\": " + String(isActive ? 1 : 0) + ", \"name\": \"" + elem["name"].as() + "\"},"; - ret += "\"subscription\": \"" + this->GetMqttSetTopic(elem["name"].as()) + "\","; - ret += "\"info\": {\"data-mapping\": \""+ mapping + "\", \"innerHTML\": \"" + elem["info"].as() + "\"}"; - ret += "}"; + + ret += "\"subscription\": {\"innerHTML\": \"" + this->GetMqttSetTopic(elem["name"].as()) + "\""; + if (elem["mapping"]) ret += ", \"data-mapping\": \""+ mapping + "\""; + ret += "}}"; } } From 2afca2d9b759440f832e621dd416bc7eb8a87006 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Fri, 24 Jan 2025 11:54:02 +0100 Subject: [PATCH 056/106] fix: normalize case for mapped values in modbus mapping function --- src/modbus.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modbus.cpp b/src/modbus.cpp index e5fd20f3..091dd46e 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -980,6 +980,10 @@ String modbus::MapItem(JsonArray map, String value) { String v1 = mapItem[0].as(); String v2 = mapItem[1].as(); + v1.toLowerCase(); + v2.toLowerCase(); + value.toLowerCase(); + if (value == v1) { ret = v2; Config->log(4, "Mapped value: %s -> %s", v1.c_str(), v2.c_str()); From 8426a65d1d1316923b0643d6ea4aaf8f43770cbf Mon Sep 17 00:00:00 2001 From: Naomi Rennie-Waldock Date: Sat, 25 Jan 2025 20:44:46 +0000 Subject: [PATCH 057/106] Correct bootloader offsets as not all ESP32 variants use 0x1000 (#130) --- .github/scripts/createMergedFirmware.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/scripts/createMergedFirmware.py b/.github/scripts/createMergedFirmware.py index 301c66eb..b89ac3ad 100644 --- a/.github/scripts/createMergedFirmware.py +++ b/.github/scripts/createMergedFirmware.py @@ -31,18 +31,33 @@ args = parser.parse_args() result = None +# Bootloader offsets vary by chip +bootloader_offsets = { + "ESP32": "0x1000", + "ESP32-S2": "0x1000", + "ESP32-S3": "0x0", + "ESP32-C2": "0x0", + "ESP32-C3": "0x0", + "ESP32-C5": "0x2000", + "ESP32-C6": "0x0", + "ESP32-H2": "0x0", + "ESP32-P4": "0x2000", +} + if(not os.path.isfile(args.PathOfPartitionsCSV)): logging.error(f'File {args.PathOfPartitionsCSV} does not exist') exit(1) if args.BuildDir and os.path.isdir(args.BuildDir): if 'ESP32' in args.ChipFamily: + bootloader_offset = bootloader_offsets[args.ChipFamily] + result = f'esptool.py --chip {args.ChipFamily} merge_bin \ --output {args.BuildDir}/merged-firmware.bin \ --flash_mode dout \ --flash_freq 80m \ --flash_size 4MB \ - 0x1000 {args.BuildDir}/bootloader.bin \ + {bootloader_offset} {args.BuildDir}/bootloader.bin \ 0x8000 {args.BuildDir}/partitions.bin \ {readOffsetFromPartitionCSV("partitions.csv", "app0")} {args.BuildDir}/firmware.bin' From 2710049d38c6b7b48fafdc02f9a59a65ff624906 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Wed, 29 Jan 2025 09:31:39 +0100 Subject: [PATCH 058/106] add toggle buttons at ModbusItems WebUI to change all items at once (#96) --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index 26699f70..d0bce31a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,7 @@ Release 3.3.2: - migrate from old ajax communication to standard websocket communication - new feature: show set topics in WebUI (thanks to @laszgar) - new feature: add "newUpdate available" info in WebUI header + - new feature: add toggle buttons at ModbusItems WebUI to change all items at once (#96) Release 3.3.1: - new Feature: datatype "binary" now available for json register definitions (PR #115) From 3ea9a203c70f27a60135510b907032e58ce47667 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Wed, 29 Jan 2025 10:12:17 +0100 Subject: [PATCH 059/106] add Growatt-SPH-V124 register file (thanks to @StefanNouza) (#109) --- ChangeLog.md | 1 + data/regs/Growatt-SPH-V124.json | 659 ++++++++++++++++++++++++++++++++ 2 files changed, 660 insertions(+) create mode 100644 data/regs/Growatt-SPH-V124.json diff --git a/ChangeLog.md b/ChangeLog.md index d0bce31a..8c91a1e1 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,7 @@ Release 3.3.2: - new feature: show set topics in WebUI (thanks to @laszgar) - new feature: add "newUpdate available" info in WebUI header - new feature: add toggle buttons at ModbusItems WebUI to change all items at once (#96) + - new Inverter: add Growatt-SPH-V124 register file (thanks to @StefanNouza) (#109) Release 3.3.1: - new Feature: datatype "binary" now available for json register definitions (PR #115) diff --git a/data/regs/Growatt-SPH-V124.json b/data/regs/Growatt-SPH-V124.json new file mode 100644 index 00000000..8fbe97fb --- /dev/null +++ b/data/regs/Growatt-SPH-V124.json @@ -0,0 +1,659 @@ +{ + "Growatt-SPH-V124": { + "config": { + "author": "StefanNouza", + "RequestLiveData": [ + ["#ClientID", "0x04", "0x00", "0x00", "0x00", "0x77"], + ["#ClientID", "0x04", "0x03", "0xF1", "0x00", "0x7D"] + ], + "RequestIdData": [ + ["#ClientID", "0x03", "0x00", "0x00", "0x00", "0x1E"] + ], + "ClientIdPos": 0, + "LiveDataFunctionCodePos": 1, + "LiveDataFunctionCode": "0x04", + "IdDataFunctionCodePos": 1, + "IdDataFunctionCode": "0x03", + "LiveDataStartsAtPos": 3, + "IdDataStartsAtPos": 3, + "LiveDataErrorPos": 1, + "LiveDataErrorCode": "0x84", + "IdDataErrorPos": 1, + "IdDataErrorCode": "0x83", + "LiveDataSuccessPos": 1, + "LiveDataSuccessCode": "0x04", + "IdDataSuccessPos": 1, + "IdDataSuccessCode": "0x03" + }, + "data": { + "livedata": [ + { + "position": [3, 4], + "name": "Inverter_Status", + "realname": "Inverter status (number)", + "datatype": "integer", + "unit": "" + }, + { + "position": [3, 4], + "name": "Inverter_StatusText", + "realname": "Inverter status (text)", + "datatype": "integer", + "mapping": [[0,"0: waiting"], + [1,"1: normal"], + [2,"2:"], + [3,"3: fault"], + [4,"4:"], + [5,"5:"], + [6,"6:"], + [7,"7:"], + [8,"8:"], + [9,"9:"]], + "unit": "" + }, + { + "position": [5, 6, 7, 8], + "name": "Power_PV_Input_W", + "realname": "Power of PV input", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [9, 10], + "name": "PV1_InputVoltage_V", + "realname": "PV1 input Voltage", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [11, 12], + "name": "PV1_InputCurrent_A", + "realname": "PV1 input Current", + "datatype": "float", + "factor": 1.0, + "unit": "A" + }, + { + "position": [13, 14, 15, 16], + "name": "Power_PV1_Input_W", + "realname": "Power of PV1 input", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [17, 18], + "name": "PV2_InputVoltage_V", + "realname": "PV2 input Voltage", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [19, 20], + "name": "PV2_InputCurrent_A", + "realname": "PV2 input Current", + "datatype": "float", + "factor": 1.0, + "unit": "A" + }, + { + "position": [21, 22, 23, 24], + "name": "Power_PV2_Input_W", + "realname": "Power of PV2 input", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [73, 74, 75, 76], + "name": "AC_OutputPower_W", + "realname": "AC output Power", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [77, 78], + "name": "AC_GridFrequency_Hz", + "realname": "Grid Frequency", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [109, 110, 111, 112], + "name": "Energy_Generated_today_kWh", + "realname": "generated Energy today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [113, 114, 115, 116], + "name": "Energy_Generated_total_kWh", + "realname": "generated Energy total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [117, 118, 119, 120], + "name": "Inverter_WorkTime_total_s", + "realname": "Inverter work time total", + "datatype": "integer", + "factor": 0.5, + "unit": "s" + }, + { + "position": [121, 122, 123, 124], + "name": "Energy_Generated_PV1_today_kWh", + "realname": "generated Energy today PV1", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [125, 126, 127, 128], + "name": "Energy_Generated_PV1_total_kWh", + "realname": "generated Energy total PV1", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [129, 130, 131, 132], + "name": "Energy_Generated_PV2_today_kWh", + "realname": "generated Energy today PV2", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [133, 134, 135, 136], + "name": "Energy_Generated_PV2_total_kWh", + "realname": "generated Energy total PV2", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [185, 186, 187, 188], + "name": "Energy_Generated_PV_total_kWh", + "realname": "generated Energy total PV", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [211, 212], + "name": "Inverter_DeratingMode", + "realname": "Inverter derating mode", + "datatype": "integer", + "mapping": [[0,"no derate"], + [1,"PV derate"], + [2,"derate-2"], + [3,"Vac derate"], + [4,"Fac derate"], + [5,"Tboost derate"], + [6,"Tinv derate"], + [7,"Control derate"], + [8,"derate-8"], + [9,"OverBack By Time derate"]], + "unit": "" + }, + { + "position": [213, 214], + "name": "Inverter_FaultCode", + "realname": "Inverter fault code (number)", + "datatype": "integer", + "unit": "" + }, + { + "position": [215, 216, 217, 218], + "name": "Inverter_FaultCodeText", + "realname": "Inverter fault code bits (text)", + "datatype": "integer", + "mapping": [[ 1,"b00 "], + [ 2,"b01 communication error"], + [ 4,"b02 "], + [ 8,"b03 StrReverse or StrShort fault"], + [ 16,"b04 model init fault"], + [ 32,"b05 grid volt sample different"], + [ 64,"b06 ISO sample different"], + [ 128,"b07 GFCI sample different"], + [ 256,"b08 "], + [ 512,"b09 "], + [ 1024,"b10 "], + [ 2048,"b11 "], + [ 4096,"b12 AFCI fault"], + [ 8192,"b13 "], + [ 16384,"b14 AFCI module fault"], + [ 32768,"b15 "], + [ 65536,"b16 "], + [ 131072,"b17 relay check fault"], + [ 262144,"b18 "], + [ 524288,"b19 "], + [ 1048576,"b20 "], + [ 2097152,"b21 communication error"], + [ 4194304,"b22 bus voltage error"], + [ 8388608,"b23 auto-test fail"], + [ 16777216,"b24 no utility"], + [ 33554432,"b25 PV isolation low"], + [ 67108864,"b26 residual I high"], + [ 134217728,"b27 output high DCI"], + [ 268435456,"b28 PV voltage high"], + [ 536870912,"b29 AC V outrange"], + [ 1073741824,"b30 AC F outrange"], + [-2147483648,"b31 temperature high"]], + "unit": "" + }, + { + "position": [227, 228, 229, 230], + "name": "Energy_ChargeFromGrid_today_kWh", + "realname": "Charge Energy from Grid today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [231, 232, 233, 234], + "name": "Energy_ChargeFromGrid_total_kWh", + "realname": "Charge Energy from Grid total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [239, 240], + "name": "Inverter_Priority", + "realname": "Priority of power-distribution by inverter (number)", + "datatype": "integer", + "unit": "" + }, + { + "position": [239, 240], + "name": "Inverter_Priority_Text", + "realname": "Priority of power-distribution by inverter (text)", + "datatype": "integer", + "mapping": [[0,"0: Load first"], + [1,"1: Batt first"], + [2,"2: Grid first"]], + "unit": "" + }, + { + "position": [246, 247, 248, 249], + "name": "Power_Battery_Discharge_W", + "realname": "Discharge Power", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [250, 251, 252, 253], + "name": "Power_Battery_Charge_W", + "realname": "Charge Power", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [254, 255], + "name": "Battery_Voltage_V", + "realname": "Battery Voltage", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [256, 257], + "name": "Battery_SOC_Percent", + "realname": "Battery State Of Charge", + "datatype": "float", + "factor": 1.0, + "unit": "%" + }, + { + "position": [270, 271, 272, 273], + "name": "Power_AC_GridToUser_W", + "realname": "SmartMeter: AC Power Grid to User", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [286, 287, 288, 289], + "name": "Power_AC_UserToGrid_W", + "realname": "SmartMeter: AC Power User to Grid", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [302, 303, 304, 305], + "name": "Power_AC_toLocalLoad_W", + "realname": "Power to Local Load", + "datatype": "float", + "factor": 0.1, + "unit": "W" + }, + { + "position": [308, 309], + "name": "Battery_Temperature_degC", + "realname": "Battery Temperature", + "datatype": "float", + "factor": 0.1, + "unit": "°C" + }, + { + "position": [316, 317, 318, 319], + "name": "Energy_toUser_today_kWh", + "realname": "Energy to User today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [320, 321, 322, 323], + "name": "Energy_toUser_total_kWh", + "realname": "Energy to User total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [324, 325, 326, 327], + "name": "Energy_toGrid_today_kWh", + "realname": "Energy to Grid today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [328, 329, 330, 331], + "name": "Energy_toGrid_total_kWh", + "realname": "Energy to Grid total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [332, 333, 334, 335], + "name": "Energy_Discharge_today_kWh", + "realname": "Discharge Energy today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [336, 337, 338, 339], + "name": "Energy_Discharge_total_kWh", + "realname": "Discharge Energy total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [340, 341, 342, 343], + "name": "Energy_Charge_today_kWh", + "realname": "Charge Energy today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [344, 345, 346, 347], + "name": "Energy_Charge_total_kWh", + "realname": "Charge Energy total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [348, 349, 350, 351], + "name": "Energy_LocalLoad_today_kWh", + "realname": "Energy Local Load today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [352, 353, 354, 355], + "name": "Energy_LocalLoad_total_kWh", + "realname": "Energy Local Load total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [362, 363], + "name": "UPS_Frequency_Hz", + "realname": "UPS Frequency", + "datatype": "float", + "factor": 0.01, + "unit": "Hz" + }, + { + "position": [364, 365], + "name": "UPS_VoltageL1_V", + "realname": "UPS Voltage L1", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [366, 367], + "name": "UPS_CurrentL1_A", + "realname": "UPS Current L1", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [368, 369, 370, 371], + "name": "UPS_PowerL1_VA", + "realname": "UPS Power L1", + "datatype": "float", + "factor": 0.1, + "unit": "VA" + }, + { + "position": [372, 373], + "name": "UPS_VoltageL2_V", + "realname": "UPS Voltage L2", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [374, 375], + "name": "UPS_CurrentL2_A", + "realname": "UPS Current L2", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [376, 377, 378, 379], + "name": "UPS_PowerL2_VA", + "realname": "UPS Power L2", + "datatype": "float", + "factor": 0.1, + "unit": "VA" + }, + { + "position": [380, 381], + "name": "UPS_VoltageL3_V", + "realname": "UPS Voltage L3", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [382, 383], + "name": "UPS_CurrentL3_A", + "realname": "UPS Current L3", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [384, 385, 386, 387], + "name": "UPS_PowerL3_VA", + "realname": "UPS Power L3", + "datatype": "float", + "factor": 0.1, + "unit": "VA" + }, + { + "position": [388, 389], + "name": "UPS_Load_Percent", + "realname": "UPS Load Percent", + "datatype": "float", + "factor": 1.0, + "unit": "%" + }, + { + "position": [400, 401], + "name": "Battery_SOC_BMS_Percent", + "realname": "Battery State Of Charge from BMS", + "datatype": "float", + "factor": 1.0, + "unit": "%" + }, + { + "position": [402, 403], + "name": "Battery_Voltage_BMS_V", + "realname": "Battery Voltage from BMS", + "datatype": "float", + "factor": 0.1, + "unit": "V" + }, + { + "position": [404, 405], + "name": "Battery_Current_BMS_A", + "realname": "Battery Current from BMS", + "datatype": "float", + "factor": 0.1, + "unit": "A" + }, + { + "position": [406, 407], + "name": "Battery_Temperature_BMS_degC", + "realname": "Battery Temperature from BMS", + "datatype": "float", + "factor": 0.1, + "unit": "°C" + }, + { + "position": [418, 419], + "name": "Battery_CycleCount_BMS", + "realname": "Cycle count from BMS", + "datatype": "integer", + "unit": "" + }, + { + "position": [420, 421], + "name": "Battery_SOH_BMS_Percent", + "realname": "Battery State Of Health from BMS", + "datatype": "float", + "factor": 1.0, + "unit": "%" + }, + { + "position": [444, 445], + "name": "BMS_MaxCellVoltage_V", + "realname": "Highest cell voltage", + "datatype": "float", + "factor": 0.001, + "unit": "V" + }, + { + "position": [446, 447], + "name": "BMS_MinCellVoltage_V", + "realname": "Lowest cell voltage", + "datatype": "float", + "factor": 0.001, + "unit": "V" + }, + { + "position": [452, 453], + "name": "BMS_MaxVoltageCellNr", + "realname": "Number of cell with highest voltage", + "datatype": "integer", + "unit": "" + }, + { + "position": [454, 455], + "name": "BMS_MinVoltageCellNr", + "realname": "Number of cell with lowest voltage", + "datatype": "integer", + "unit": "" + }, + { + "position": [456, 457], + "name": "BMS_MaxCellTemperature_degC", + "realname": "Highest cell temperature", + "datatype": "float", + "factor": 0.1, + "unit": "°C" + }, + { + "position": [458, 459], + "name": "BMS_MinCellTemperature_degC", + "realname": "Lowest cell temperature", + "datatype": "float", + "factor": 0.1, + "unit": "°C" + }, + { + "position": [460, 461], + "name": "BMS_MaxTemperatureCellNr", + "realname": "Number of cell with highest temp.", + "datatype": "integer", + "unit": "" + }, + { + "position": [462, 463], + "name": "BMS_MinTemperatureCellNr", + "realname": "Number of cell with lowest temp.", + "datatype": "integer", + "unit": "" + }, + { + "position": [476, 477, 478, 479], + "name": "AC_ChargeEnergy_today_kWh", + "realname": "AC charging energy today", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [480, 481, 482, 483], + "name": "AC_ChargeEnergy_total_kWh", + "realname": "AC charging energy total", + "datatype": "float", + "factor": 0.1, + "unit": "kWh" + }, + { + "position": [484, 485, 486, 487], + "name": "AC_ChargePower_W", + "realname": "AC charging power", + "datatype": "float", + "factor": 1.0, + "unit": "W" + } + ], + "id": [ + { + "position": [49, 50, 51, 52, 53, 54, 55, 56, 57, 58], + "name": "InverterSN", + "realname": "Inverter SerialNumber", + "datatype": "string" + }] + } + } +} \ No newline at end of file From 278457995df87ab94c4b8e76d091947670649dce Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Wed, 29 Jan 2025 14:19:02 +0100 Subject: [PATCH 060/106] add support for additional file types in BuildAndDeploy workflow --- .github/workflows/BuildAndDeploy.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/BuildAndDeploy.yml b/.github/workflows/BuildAndDeploy.yml index aa6cdc1c..74417466 100644 --- a/.github/workflows/BuildAndDeploy.yml +++ b/.github/workflows/BuildAndDeploy.yml @@ -17,6 +17,10 @@ on: - '**.yml' - '**.sh' - '**.py' + - '**.json' + - '**.js' + - '**.css' + - '**.html' jobs: build: From 237ae47bb540fa992506405e6b69de2ea01f3833 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 3 Feb 2025 12:32:54 +0100 Subject: [PATCH 061/106] add toggle functionality for Modbus items --- data/web/Javascript.js | 5 +++-- data/web/modbusitemconfig.html | 18 ++++++++++++++---- data/web/modbusitemconfig.js | 30 +++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index e16dc104..b5297ec8 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -654,7 +654,7 @@ export function getFormData(formElement) { } /**************************************************************************************** - * Show a dialog if the form data has been changed + * Show a dialog if the values of items in formdata has been changed * ****************************************************************************************/ export function showMustSaveDialog() { if (document.getElementById('needToSave') && datavalues !== getFormData("DataForm")) { @@ -665,7 +665,8 @@ export function showMustSaveDialog() { } /**************************************************************************************** - * Initialize the data values in variable "datavalues" + * Save the current form data values in the variable "datavalues" to check on every change + * if the form data has been changed. If the form data has been changed, show a dialog. * ****************************************************************************************/ export function initDataValues() { datavalues = getFormData("DataForm"); diff --git a/data/web/modbusitemconfig.html b/data/web/modbusitemconfig.html index fcdf1dcf..3757f403 100644 --- a/data/web/modbusitemconfig.html +++ b/data/web/modbusitemconfig.html @@ -10,10 +10,12 @@ @@ -30,10 +32,14 @@
- +
- + @@ -77,7 +83,11 @@
Active + Active + + + Name OpenWB Wert
- + diff --git a/data/web/modbusitemconfig.js b/data/web/modbusitemconfig.js index 67d11379..56eace1d 100644 --- a/data/web/modbusitemconfig.js +++ b/data/web/modbusitemconfig.js @@ -48,7 +48,7 @@ export function init() { .then(response => response.json()) .then(data => { data['cmd'] = {}; - data['cmd']['callbackFn'] = "mbitemconfig_ItemItemCallback"; + data['cmd']['callbackFn'] = "mbitemconfig_ItemCallback"; global.handleJsonItems(data); }) .catch(error => console.error('Error fetching items:', error)); @@ -176,3 +176,31 @@ export function ChangeActiveStatus(id) { } // ************************************************ + +export function setToggleIcon(targetTable, element) { + if (element.classList.contains('fa-toggle-off')) { + element.classList.remove('fa-toggle-off'); + element.classList.add('fa-toggle-on'); + setCheckboxes(targetTable, "true"); + } else { + element.classList.remove('fa-toggle-on'); + element.classList.add('fa-toggle-off'); + setCheckboxes(targetTable, false); + } +} + +export function setCheckboxes(targetTable, state) { + document.querySelectorAll(`#${targetTable} input[type=checkbox]`).forEach(checkbox => { + if (typeof state == 'undefined') { + //checkbox.checked = !checkbox.checked; + checkbox.click(); + } else if ( typeof state !== 'undefined' && state && !checkbox.checked) { + //checkbox.checked = true; + checkbox.click(); + } else if (typeof state !== 'undefined' && !state && checkbox.checked) { + //checkbox.checked = false; + checkbox.click(); + } + }); + global.showMustSaveDialog(); +} \ No newline at end of file From a195e5ea9d41b774744ae7e739168fcadaaeab83 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Fri, 7 Feb 2025 14:20:42 +0100 Subject: [PATCH 062/106] add titles to toggle icons for better accessibility in Modbus item configuration --- data/web/modbusitemconfig.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/web/modbusitemconfig.html b/data/web/modbusitemconfig.html index 3757f403..a3386211 100644 --- a/data/web/modbusitemconfig.html +++ b/data/web/modbusitemconfig.html @@ -37,8 +37,8 @@ @@ -85,8 +85,8 @@ From 90e0f69db27d46d8253285797d59436be9f3578d Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sun, 9 Feb 2025 15:25:56 +0100 Subject: [PATCH 063/106] Update Solax-X3.json (#134) --- data/regs/Solax-X3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index 0a9cb884..42c7b44c 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -1033,7 +1033,7 @@ 306, 307, 304, - 306 + 305 ], "name": "SolarEnergyTotal", "realname": "SolarEnergy Total", From 1b11cb28ba766896f98e129b3a62ecf4c6d682eb Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Tue, 11 Feb 2025 18:06:49 +0100 Subject: [PATCH 064/106] refactor logging methods to use logN for consistency and improved readability --- ChangeLog.md | 3 + src/MyWebServer.cpp | 24 +++--- src/MyWebServer.h | 2 +- src/baseconfig.cpp | 27 ++++-- src/baseconfig.h | 1 + src/commonlibs.h | 3 - src/handleFiles.cpp | 16 ++-- src/main.cpp | 12 +-- src/modbus.cpp | 194 ++++++++++++++++++++++---------------------- src/mqtt.cpp | 88 ++++++++++---------- src/openwb.cpp | 12 +-- 11 files changed, 199 insertions(+), 183 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8c91a1e1..520fa349 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,6 @@ +Release 3.3.3: + - improve logging functionality methods + Release 3.3.2: - new feature: add confirmation dialog for ESP reset - migrate from old ajax communication to standard websocket communication diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index 347f2f83..9439c7a3 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -54,7 +54,7 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): // try to start the server if wifi is connected, otherwise wait for wifi connection if (mqtt->GetConnectStatusWifi()) { server->begin(); - Config->log(1, "WebServer has been started ..."); + Config->logN(1, "WebServer has been started ..."); } else { mqtt->improvSerial.onImprovConnected(std::bind(&MyWebServer::onImprovWiFiConnectedCb, this, std::placeholders::_1, std::placeholders::_2)); } @@ -62,10 +62,10 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) { if (type == WS_EVT_CONNECT) { - Config->log(2, "[Client: %u] WebSocket client connected", client->id()); + Config->logN(2, "[Client: %u] WebSocket client connected", client->id()); } else if (type == WS_EVT_DISCONNECT) { - Config->log(2, "[Client: %u] WebSocket client disconnected", client->id()); + Config->logN(2, "[Client: %u] WebSocket client disconnected", client->id()); // wenn client->id() in der Liste WsConnectedClientsForBroadcast vorhanden ist, dann entfernen @@ -83,7 +83,7 @@ void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * clie } else if (type == WS_EVT_DATA) { String msg(""); msg.reserve(len + 1); for (size_t i = 0; i < len; i++) { msg += (char)data[i]; } msg += '\0'; - Config->log(2, "[Client: %u] WebSocket data received: %s", client->id(), msg.c_str()); + Config->logN(2, "[Client: %u] WebSocket data received: %s", client->id(), msg.c_str()); // message json request format: {"cmd": {"action": "GetInitData", "subaction": "status"}} // subaction optional // message json response format: die Antwort wird im json ergänzt, so weiß der Requestor zu welchem Command die Antwort gehört: @@ -176,7 +176,7 @@ void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * clie } } else { - Config->log(1, "WebSocket data received but not a valid json string: %s -> %s", msg.c_str(), error.c_str()); + Config->logN(1, "WebSocket data received but not a valid json string: %s -> %s", msg.c_str(), error.c_str()); json["response"]["status"] = 0; json["response"]["text"] = error.c_str(); } @@ -188,7 +188,7 @@ void MyWebServer::onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * clie void MyWebServer::onImprovWiFiConnectedCb(const char *ssid, const char *password) { server->begin(); - Config->log(1, "WebServer has been started now ..."); + Config->logN(1, "WebServer has been started now ..."); } void MyWebServer::sendWebSocketMessage(String& message) { @@ -197,7 +197,7 @@ void MyWebServer::sendWebSocketMessage(String& message) { if (ws->client(client.id)) { message = message.substring(0, message.length()-1) + "," + client.json.substring(1, client.json.length()-1); - Config->log(4, "send WebSocket Message to client %u: %s", client.id, message.c_str()); + Config->logN(4, "send WebSocket Message to client %u: %s", client.id, message.c_str()); ws->text(client.id, message); } } @@ -209,10 +209,10 @@ void MyWebServer::loop() { if (this->DoReboot) { if (this->RequestRebootTime == 0) { this->RequestRebootTime = millis(); - Config->log(1, "Request to Reboot, wait 5sek ..."); + Config->logN(1, "Request to Reboot, wait 5sek ..."); } if (millis() - this->RequestRebootTime > 5000) { // wait 3sek until reboot - Config->log(1, "Rebooting..."); + Config->logN(1, "Rebooting..."); ESP.restart(); } } @@ -236,7 +236,7 @@ void MyWebServer::handleFavIcon(AsyncWebServerRequest *request) { bool MyWebServer::handleReset() { bool ret = true; - Config->log(3, "deletion of all config files was requested ...."); + Config->logN(3, "deletion of all config files was requested ...."); //LittleFS.format(); // Werkszustand -> nur die config dateien loeschen, die register dateien muessen erhalten bleiben File root = LittleFS.open("/config/"); File file = root.openNextFile(); @@ -246,9 +246,9 @@ bool MyWebServer::handleReset() { file.close(); if (LittleFS.remove(path)) { - Config->log(4, "deletion of configuration file '%s' was successful", file.name()); + Config->logN(4, "deletion of configuration file '%s' was successful", file.name()); } else { - Config->log(2, "deletion of configuration file '%s' has failed", file.name()); + Config->logN(2, "deletion of configuration file '%s' has failed", file.name()); ret = false; } file = root.openNextFile(); diff --git a/src/MyWebServer.h b/src/MyWebServer.h index 149f30ee..8f49fb59 100644 --- a/src/MyWebServer.h +++ b/src/MyWebServer.h @@ -49,7 +49,7 @@ class MyWebServer { void handleRoot(AsyncWebServerRequest *request); void handleFavIcon(AsyncWebServerRequest *request); // void handleGetItemJson(AsyncWebServerRequest *request); - void handleGetRegisterJson(AsyncWebServerRequest *request); +// void handleGetRegisterJson(AsyncWebServerRequest *request); // void handleGetSetterJson(AsyncWebServerRequest *request); // void GetInitDataStatus(AsyncResponseStream *response); // void GetInitDataNavi(AsyncResponseStream *response); diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index b1bf5c5c..48ecd97c 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -17,7 +17,7 @@ BaseConfig::BaseConfig(): debuglevel(2), LittleFS.mkdir("/config"); } } else { - this->log(1, "LittleFS Mount Failed"); + this->logN(1, "LittleFS Mount Failed"); } #endif @@ -32,10 +32,10 @@ void BaseConfig::LoadJsonConfig() { bool loadDefaultConfig = false; if (LittleFS.exists("/config/baseconfig.json")) { // file exists, reading and loading - this->log(2, "reading config file"); + this->logN(2, "reading config file"); File configFile = LittleFS.open("/config/baseconfig.json", "r"); if (configFile) { - this->log(2, "opened config file"); + this->logN(2, "opened config file"); JsonDocument doc; DeserializationError error = deserializeJson(doc, configFile); @@ -61,12 +61,12 @@ void BaseConfig::LoadJsonConfig() { this->mqtt_UseRandomClientID = doc["data"]["useRandomClientID"].as(); } else { - this->log(1, "failed to load json config, load default config"); + this->logN(1, "failed to load json config, load default config"); loadDefaultConfig = true; } } } else { - this->log(3, "baseconfig.json config File not exists, load default config"); + this->logN(3, "baseconfig.json config File not exists, load default config"); loadDefaultConfig = true; } @@ -124,7 +124,7 @@ void BaseConfig::GetInitData(JsonDocument& json) { json["response"]["text"] = "successful"; } -void BaseConfig::log(const int loglevel, const char* format, ...) { +void BaseConfig::logN(const int loglevel, const char* format, ...) { if (this->GetDebugLevel() < loglevel) return; va_list args; @@ -141,6 +141,21 @@ void BaseConfig::log(const int loglevel, const char* format, ...) { va_end(args); } +void BaseConfig::log(const int loglevel, const char* format, ...) { + if (this->GetDebugLevel() < loglevel) return; + + va_list args; + va_start(args, format); + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), format, args); + #ifdef USE_WEBSERIAL + WebSerial.print(buffer); + #else + Serial.print(buffer); + #endif + va_end(args); +} + void BaseConfig::log(const int loglevel, const JsonDocument& json) { if (this->GetDebugLevel() < loglevel) return; diff --git a/src/baseconfig.h b/src/baseconfig.h index 68aeb59d..9d28d622 100644 --- a/src/baseconfig.h +++ b/src/baseconfig.h @@ -22,6 +22,7 @@ class BaseConfig { * @param ... the arguments */ void log(const int loglevel, const char* format, ...); + void logN(const int loglevel, const char* format, ...); void log(const int loglevel, const JsonDocument& json); const String& GetMqttServer() const {return mqtt_server;} diff --git a/src/commonlibs.h b/src/commonlibs.h index 18147a93..8bca9654 100644 --- a/src/commonlibs.h +++ b/src/commonlibs.h @@ -19,9 +19,6 @@ #ifdef USE_WEBSERIAL #include - #define dbg WebSerial -#else - #define dbg Serial #endif #ifdef ESP8266 diff --git a/src/handleFiles.cpp b/src/handleFiles.cpp index 93b4027d..ebb67a2b 100644 --- a/src/handleFiles.cpp +++ b/src/handleFiles.cpp @@ -59,18 +59,18 @@ void handleFiles::HandleRequest(JsonDocument& json) { String subaction = ""; if (json["cmd"]["subaction"]) {subaction = json["cmd"]["subaction"].as();} - Config->log(3, "handle Request in handleFiles.cpp: %s", subaction.c_str()); + Config->logN(3, "handle Request in handleFiles.cpp: %s", subaction.c_str()); if (subaction == "listDir") { JsonArray content = json["JS"]["listdir"].to(); this->getDirList(content, "/"); - Config->log(5, json["content"].as().c_str()); + Config->logN(5, json["content"].as().c_str()); } else if (subaction == "deleteFile") { String filename(""); - Config->log(3, "Request to delete file %s", filename.c_str()); + Config->logN(3, "Request to delete file %s", filename.c_str()); if (json["cmd"]["filename"]) {filename = json["cmd"]["filename"].as();} @@ -81,7 +81,7 @@ void handleFiles::HandleRequest(JsonDocument& json) { json["response"]["status"] = 0; json["response"]["text"] = "deletion failed"; } - Config->log(3, json.as().c_str()); + Config->logN(3, json.as().c_str()); } } @@ -90,24 +90,24 @@ void handleFiles::HandleRequest(JsonDocument& json) { //############################################################### void handleFiles::handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - Config->log(5, "Client: %s %s", request->client()->remoteIP().toString().c_str(), request->url().c_str());; + Config->logN(5, "Client: %s %s", request->client()->remoteIP().toString().c_str(), request->url().c_str());; if (!index) { // open the file on first call and store the file handle in the request object request->_tempFile = LittleFS.open(filename, "w"); - Config->log(5, "Upload Start: %s", filename.c_str()); + Config->logN(5, "Upload Start: %s", filename.c_str()); } if (len) { // stream the incoming chunk to the opened file request->_tempFile.write(data, len); - Config->log(3, "Writing file: %s ,index=%d len=%d bytes, FreeMem: %d", filename.c_str(), index, len, ESP.getFreeHeap()); + Config->logN(3, "Writing file: %s ,index=%d len=%d bytes, FreeMem: %d", filename.c_str(), index, len, ESP.getFreeHeap()); } if (final) { // close the file handle as the upload is now done request->_tempFile.close(); - Config->log(3, "Upload Complete: %s ,size: %d Bytes", filename.c_str(), (index + len)); + Config->logN(3, "Upload Complete: %s ,size: %d Bytes", filename.c_str(), (index + len)); AsyncResponseStream *response = request->beginResponseStream("text/json"); response->addHeader("Server","ESP Async Web Server"); diff --git a/src/main.cpp b/src/main.cpp index 0d0cd9c1..cda8e5c9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,13 +26,13 @@ MyWebServer* mywebserver = NULL; void myMQTTCallBack(char* topic, byte* payload, unsigned int length) { String msg; - Config->log(3, "Message arrived [%s]", topic); + Config->logN(3, "Message arrived [%s]", topic); for (unsigned int i = 0; i < length; i++) { msg.concat(static_cast(payload[i])); } - Config->log(3, "Message: %s", msg.c_str()); + Config->logN(3, "Message: %s", msg.c_str()); mb->ReceiveMQTT(topic, msg); } @@ -56,10 +56,10 @@ void setup() { WebSerial.setBuffer(100); #endif - Config->log(1, "Start of Modbus-RTU MQTT Gateway"); - Config->log(1, "Starting BaseConfig"); + Config->logN(1, "Start of Modbus-RTU MQTT Gateway"); + Config->logN(1, "Starting BaseConfig"); - Config->log(1, "Starting Wifi and MQTT"); + Config->logN(1, "Starting Wifi and MQTT"); mqtt = new MQTT(Config->GetMqttServer().c_str(), Config->GetMqttPort(), Config->GetMqttBasePath().c_str(), @@ -69,7 +69,7 @@ void setup() { mb = new modbus(); mb->enableMqtt(mqtt); - Config->log(1, "attempting to start WebServer"); + Config->logN(1, "attempting to start WebServer"); mywebserver = new MyWebServer(&server, &dns); } diff --git a/src/modbus.cpp b/src/modbus.cpp index 091dd46e..f82b189b 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -59,8 +59,8 @@ modbus::modbus(): enableRelays(false), * initialize transmission *******************************************************/ void modbus::init(bool firstrun) { - Config->log(3, "Start Hardwareserial 1 on RX (%d), TX(%d), RTS(%d)", this->pin_RX, this->pin_TX, this->pin_RTS); - Config->log(3, "Init Modbus to Client 0x%02X with %d Baud", this->ClientID, this->Baudrate); + Config->logN(3, "Start Hardwareserial 1 on RX (%d), TX(%d), RTS(%d)", this->pin_RX, this->pin_TX, this->pin_RTS); + Config->logN(3, "Init Modbus to Client 0x%02X with %d Baud", this->ClientID, this->Baudrate); // Configure Direction Control pin pinMode(this->pin_RTS, OUTPUT); @@ -138,14 +138,14 @@ void modbus::LoadSettersFromRegFile() { DeserializationError error = deserializeJson(elem, regfile); if (!error) { // Print the result - Config->log(4, "parsing JSON for data ok"); + Config->logN(4, "parsing JSON for data ok"); Config->log(5, elem); if(!elem["name"].isNull() && elem["request"].is()) { setter_t s = {}; s.Name = elem["name"].as(); - Config->log(4, "Set command successfully parsed from JSON: %s", s.Name.c_str()); + Config->logN(4, "Set command successfully parsed from JSON: %s", s.Name.c_str()); this->Setters->push_back(s); } else { @@ -153,7 +153,7 @@ void modbus::LoadSettersFromRegFile() { } } else { - Config->log(1, "Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(1, "Failed to parse JSON Register Data: %s", error.c_str()); } } while (regfile.findUntil(",","]")); @@ -165,20 +165,20 @@ void modbus::LoadSettersFromRegFile() { *******************************************************/ void modbus::ReceiveMQTT(String topic, String msg) { if (!this->Conf_EnableSetters) { - Config->log(2, "Set command <%s> received, but setters over mqtt are currently disabled globally", topic.c_str()); + Config->logN(2, "Set command <%s> received, but setters over mqtt are currently disabled globally", topic.c_str()); return; } for (uint8_t i = 0; i < this->Setters->size(); i++ ) { if (topic == this->GetMqttSetTopic(this->Setters->at(i).Name)) { if (!this->Setters->at(i).active) { - Config->log(2, "Set command <%s> received, but setter %s is not active", topic.c_str(), this->Setters->at(i).Name.c_str()); + Config->logN(2, "Set command <%s> received, but setter %s is not active", topic.c_str(), this->Setters->at(i).Name.c_str()); return; } JsonDocument elem = this->GetSetterByName(this->Setters->at(i).Name); if (elem.isNull()) { - Config->log(1, "Setter %s not found in JSON", this->Setters->at(i).Name.c_str()); + Config->logN(1, "Setter %s not found in JSON", this->Setters->at(i).Name.c_str()); return; } @@ -192,7 +192,7 @@ void modbus::ReceiveMQTT(String topic, String msg) { // map values if a mapping is specified if(!elem["mapping"].isNull() && elem["mapping"].is() && msg != "") { - Config->log(4, "Map values for item %s", msg.c_str()); + Config->logN(4, "Map values for item %s", msg.c_str()); JsonArray map = elem["mapping"].as(); msg = this->MapItem(map, msg); @@ -210,8 +210,8 @@ void modbus::ReceiveMQTT(String topic, String msg) { request.push_back(bytes[2]); request.push_back(bytes[3]); - Config->log(3, "MQTT Setter found: %s" ,this->Setters->at(i).Name.c_str()); - Config->log(3, "Initiate Set Request to queue: %s" ,(this->PrintDataFrame(&request)).c_str()); + Config->logN(3, "MQTT Setter found: %s" ,this->Setters->at(i).Name.c_str()); + Config->logN(3, "Initiate Set Request to queue: %s" ,(this->PrintDataFrame(&request)).c_str()); this->SetQueue->enqueue(request); } @@ -226,7 +226,7 @@ void modbus::ReceiveMQTT(String topic, String msg) { JsonDocument modbus::GetSetterByName(String name) { File regfile = LittleFS.open("/regs/" + this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); return JsonDocument(); } @@ -242,10 +242,10 @@ JsonDocument modbus::GetSetterByName(String name) { if (!error) { // Print the result - Config->log(4, "parsing JSON ok"); + Config->logN(4, "parsing JSON ok"); Config->log(5, elem); } else { - Config->log(1, "(Function GetSetterByName) Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(1, "(Function GetSetterByName) Failed to parse JSON Register Data: %s", error.c_str()); } if (elem["name"] == name) { @@ -273,14 +273,14 @@ void modbus::LoadInvertersFromJson() { File root = LittleFS.open("/regs/"); File file = root.openNextFile(); while(file){ - Config->log(3, "open register file from Filesystem: %s", file.name()); + Config->logN(3, "open register file from Filesystem: %s", file.name()); DeserializationError error = deserializeJson(regjson, file, DeserializationOption::Filter(filter)); if (!error && regjson.size() > 0) { // https://arduinojson.org/v6/api/jsonobject/begin_end/ JsonObject root = regjson.as(); for (JsonPair kv : root) { - Config->log(3, "Inverter found: %s", kv.key().c_str()); + Config->logN(3, "Inverter found: %s", kv.key().c_str()); regfiles_t wr = {}; wr.filename = file.name(); @@ -288,7 +288,7 @@ void modbus::LoadInvertersFromJson() { AvailableInverters->push_back(wr); } } else{ - Config->log(1, "Error: unable to load inverters from File %s: %s", file.name(), error.c_str()); + Config->logN(1, "Error: unable to load inverters from File %s: %s", file.name(), error.c_str()); } file.close(); file = root.openNextFile(); @@ -296,8 +296,8 @@ void modbus::LoadInvertersFromJson() { root.close(); if (this->AvailableInverters->size() == 0) { - Config->log(1, "ALERT: No register definitions found. ESP cannot work properly"); - Config->log(1, "Please flash filesystem Image!"); + Config->logN(1, "ALERT: No register definitions found. ESP cannot work properly"); + Config->logN(1, "Please flash filesystem Image!"); } } @@ -310,7 +310,7 @@ void modbus::LoadInverterConfigFromJson() { File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); } filter[this->InverterType.name]["config"] = true; @@ -318,9 +318,9 @@ void modbus::LoadInverterConfigFromJson() { DeserializationError error = deserializeJson(doc, regfile, DeserializationOption::Filter(filter)); if (error) { - Config->log(1, "Error: unable to read configdata for inverter %s: %s", this->InverterType.name.c_str(), error.c_str()); + Config->logN(1, "Error: unable to read configdata for inverter %s: %s", this->InverterType.name.c_str(), error.c_str()); } else { - Config->log(4, "Read config data for inverter %s", this->InverterType.name.c_str()); + Config->logN(4, "Read config data for inverter %s", this->InverterType.name.c_str()); Config->log(4, doc); } @@ -399,7 +399,7 @@ void modbus::enableMqtt(MQTT* object) { * Query ID Data to Inverter *******************************************************/ void modbus::QueryIdData() { - Config->log(4, "Query ID Data into Queue:"); + Config->logN(4, "Query ID Data into Queue:"); /* byte message[] = {this->ClientID, 0x03, // FunctionCode @@ -414,7 +414,7 @@ void modbus::QueryIdData() { if (this->ReadQueue->isEmpty()) { for (uint8_t i = 0; i < this->Conf_RequestIdData->size(); i++) { - Config->log(4, this->PrintDataFrame(&this->Conf_RequestIdData->at(i)).c_str()); + Config->logN(4, this->PrintDataFrame(&this->Conf_RequestIdData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestIdData->at(i)); } this->LastTxIdData = millis(); //erst setzen wenn erfolgreich in die Queue geschickt wurde @@ -425,7 +425,7 @@ void modbus::QueryIdData() { * Query Live Data to Inverter *******************************************************/ void modbus::QueryLiveData() { - Config->log(4, "Query Live Data into Queue:"); + Config->logN(4, "Query Live Data into Queue:"); /* byte message[] = {this->ClientID, 0x04, // FunctionCode @@ -440,7 +440,7 @@ void modbus::QueryLiveData() { if (this->ReadQueue->isEmpty()) { for (uint8_t i = 0; i < this->Conf_RequestLiveData->size(); i++) { - Config->log(4, this->PrintDataFrame(&this->Conf_RequestLiveData->at(i)).c_str()); + Config->logN(4, this->PrintDataFrame(&this->Conf_RequestLiveData->at(i)).c_str()); this->ReadQueue->enqueue(this->Conf_RequestLiveData->at(i)); } this->LastTxLiveData = millis(); //erst setzen wenn erfolgreich in die Queue geschickt wurde @@ -469,7 +469,7 @@ void modbus::QueryQueueToInverter() { else { rwtype = NUL; } if (rwtype != NUL) { - Config->log(3, "Request queue data to inverter: "); + Config->logN(3, "Request queue data to inverter: "); digitalWrite(this->pin_RTS, RS485Receive); // init Receive while (RS485Serial->available() > 0) { // read serial if any old data is available @@ -488,7 +488,7 @@ void modbus::QueryQueueToInverter() { m.push_back(lowByte(crc)); m.push_back(highByte(crc)); - Config->log(3, this->PrintDataFrame(message, sizeof(message)).c_str()); + Config->logN(3, this->PrintDataFrame(message, sizeof(message)).c_str()); digitalWrite(this->pin_RTS, RS485Transmit); // init Transmit RS485Serial->write(message, sizeof(message)); @@ -521,7 +521,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { std::vector RecvHexframe = {}; bool ret = false; - Config->log(3, "Read Data from Queue: "); + Config->logN(3, "Read Data from Queue: "); digitalWrite(this->pin_RTS, RS485Receive); // init Receive if (RS485Serial->available()) { @@ -529,10 +529,10 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { while(RS485Serial->available()) { byte d = RS485Serial->read(); RecvHexframe.push_back(d); - if (Config->GetDebugLevel() >=4) {dbg.print(PrintHex(d)); dbg.print(" ");} + Config->log(4, "%s ", PrintHex(d)); delay(1); // keep this! Loosing bytes possible if too fast } - if (Config->GetDebugLevel() >=4) {dbg.println();} + Config->logN(4, ""); // TODO // compare Set and Received Answer, should be equal @@ -550,7 +550,7 @@ bool modbus::ReceiveSetData(std::vector* SendHexFrame) { void modbus::ReceiveReadData() { size_t dataFrameStartPos = this->DataFrame->size(); - Config->log(3, "Read Data from Queue: "); + Config->logN(3, "Read Data from Queue: "); digitalWrite(this->pin_RTS, RS485Receive); // init Receive if (RS485Serial->available()) { @@ -558,10 +558,10 @@ void modbus::ReceiveReadData() { while(RS485Serial->available()) { byte d = RS485Serial->read(); this->DataFrame->push_back(d); - if (Config->GetDebugLevel() >=4) {dbg.print(PrintHex(d)); dbg.print(" ");} + Config->log(4, "%s", PrintHex(d).c_str()); delay(1); // keep this! Loosing bytes possible if too fast } - if (Config->GetDebugLevel() >=4) {dbg.println();} + Config->logN(4, ""); bool valid = true; @@ -570,39 +570,39 @@ void modbus::ReceiveReadData() { this->DataFrame->at(dataFrameStartPos+this->Conf_IdDataErrorPos) != this->Conf_IdDataErrorCode && this->DataFrame->at(dataFrameStartPos+this->Conf_LiveDataErrorPos) != this->Conf_LiveDataErrorCode) { - Config->log(4, "ErrorCode passed, OK"); + Config->logN(4, "ErrorCode passed, OK"); if (this->enableCrcCheck) { //CRC Check uint16_t crc = this->Calc_CRC(this->DataFrame, dataFrameStartPos, this->DataFrame->size()-2); - Config->log(4, "Received CRC: 0x%02X 0x%02X", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1)); - Config->log(4, "Calculated CRC: 0x%02X 0x%02X", lowByte(crc), highByte(crc)); + Config->logN(4, "Received CRC: 0x%02X 0x%02X", this->DataFrame->at(this->DataFrame->size()-2), this->DataFrame->at(this->DataFrame->size()-1)); + Config->logN(4, "Calculated CRC: 0x%02X 0x%02X", lowByte(crc), highByte(crc)); if (this->DataFrame->at(this->DataFrame->size()-2) != lowByte(crc) || this->DataFrame->at(this->DataFrame->size()-1) != highByte(crc)) { valid = false; - Config->log(2, "CRC check failed!"); + Config->logN(2, "CRC check failed!"); } } if (this->enableLengthCheck) { // Check datalength - Config->log(4, "Dataframe length should be: %d, is: %d bytes", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); + Config->logN(4, "Dataframe length should be: %d, is: %d bytes", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); if (this->DataFrame->at(dataFrameStartPos+2) != this->DataFrame->size()-dataFrameStartPos-5) { valid = false; - Config->log(2, "data length check failed, should be %d but is %d bytes", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); + Config->logN(2, "data length check failed, should be %d but is %d bytes", this->DataFrame->at(dataFrameStartPos+2), this->DataFrame->size()-dataFrameStartPos-5); } } } else { valid = false; } if (valid) { // Dataframe valid - Config->log(3, "Dataframe valid, Dateframe size: %d bytes", this->DataFrame->size()); + Config->logN(3, "Dataframe valid, Dateframe size: %d bytes", this->DataFrame->size()); } else { - Config->log(2, "Dataframe invalid"); + Config->logN(2, "Dataframe invalid"); // clear dataframe, clear ReadQueue to start from fresh this->DataFrame->clear(); for (unsigned int n = 0; n < this->ReadQueue->itemCount(); n++) { @@ -611,7 +611,7 @@ void modbus::ReceiveReadData() { } } else { - Config->log(2, "no response from client"); + Config->logN(2, "no response from client"); } } @@ -630,7 +630,7 @@ int modbus::JsonPosArrayToInt(JsonArray posArray, JsonArray posArray2) { if (v < this->DataFrame->size()) { val_i = (val_i << 8) | this->DataFrame->at(v); val_max = (val_max << 8) | 0xFF; - } else Config->log(1, "Error: position %d out of dataframe range", v); + } else Config->logN(1, "Error: position %d out of dataframe range", v); } } @@ -639,7 +639,7 @@ int modbus::JsonPosArrayToInt(JsonArray posArray, JsonArray posArray2) { for(uint16_t w : posArray2) { if (w < this->DataFrame->size()) { val2_i = (val2_i << 8) | this->DataFrame->at(w); - } else Config->log(1, "Error: position %d out of dataframe range", w); + } else Config->logN(1, "Error: position %d out of dataframe range", w); } } @@ -678,7 +678,7 @@ void modbus::ParseData() { // *********************************************** #ifdef DEBUGMODE this->DataFrame->clear(); - Config->log(3, "Start parsing in testmode, use some testdata instead real live data :)"); + Config->logN(3, "Start parsing in testmode, use some testdata instead real live data :)"); // Solar-KTL //byte ReadBuffer[] = {0x01, 0x03, 0x60, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x34, 0x01, 0xF3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x13, 0x86, 0x09, 0x11, 0x01, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x4C, 0x00, 0x00, 0x02, 0x5F, 0x00, 0x8A, 0x01, 0x7D, 0x00, 0x28, 0x00, 0x33, 0x0E, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x60, 0x01, 0x42, 0x0A, 0x3B, 0x00, 0x0E, 0x00, 0x05, 0x00, 0x00, 0x00, 0x09, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x25,0x01}; @@ -707,7 +707,7 @@ void modbus::ParseData() { for (uint16_t i = 0; iDataFrame->push_back(ReadBuffer[i]); } - Config->log(4, "%s", this->PrintDataFrame(this->DataFrame).c_str()); + Config->logN(4, "%s", this->PrintDataFrame(this->DataFrame).c_str()); #endif // *********************************************** } @@ -724,12 +724,12 @@ void modbus::ParseData() { RequestType = "livedata"; } - Config->log(3, "parse %d bytes of data", this->DataFrame->size()); - Config->log(4, "identified datatype: %s", RequestType.c_str()); + Config->logN(3, "parse %d bytes of data", this->DataFrame->size()); + Config->logN(4, "identified datatype: %s", RequestType.c_str()); File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); } String streamString = ""; streamString = "\""+ this->InverterType.name +"\": {"; @@ -743,10 +743,10 @@ void modbus::ParseData() { if (!error) { // Print the result - Config->log(4, "parsing JSON ok"); + Config->logN(4, "parsing JSON ok"); Config->log(5, elem); } else { - Config->log(1, "(Function ParseData) Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(1, "(Function ParseData) Failed to parse JSON Register Data: %s", error.c_str()); } // setUp local variables @@ -776,7 +776,7 @@ void modbus::ParseData() { if (elem["position"].is()) { posArray = elem["position"].as(); } else { - Config->log(1, "Error: for Name '%s' no position array found", d.Name.c_str()); + Config->logN(1, "Error: for Name '%s' no position array found", d.Name.c_str()); continue; } @@ -854,7 +854,7 @@ void modbus::ParseData() { if (v < DataFrame->size()) { buffer[i] = static_cast(DataFrame->at(v)); i++; - } else Config->log(1, "Error: for item '%s' position %d out of dataframe range", d.Name.c_str(), v); + } else Config->logN(1, "Error: for item '%s' position %d out of dataframe range", d.Name.c_str(), v); } buffer[i] = '\0'; d.value = String(buffer); @@ -862,20 +862,20 @@ void modbus::ParseData() { } else { //****************** sonst, leer *******************// d.value = ""; - Config->log(2, "Error: for Name '%s' no valid datatype found", d.Name.c_str()); + Config->logN(2, "Error: for Name '%s' no valid datatype found", d.Name.c_str()); } // map values if a mapping is specified if(!elem["mapping"].isNull() && elem["mapping"].is() && d.value != "") { - Config->log(4, "Map values for item %s", d.Name.c_str()); + Config->logN(4, "Map values for item %s", d.Name.c_str()); JsonArray map = elem["mapping"].as(); if (datatype == "binary") d.value = this->MapBitwise(map, d.value); else d.value = this->MapItem(map, d.value); } - Config->log(4, "Data: %s -> %s %s", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); + Config->logN(4, "Data: %s -> %s %s", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); if (this->mqtt && IsActiveItem && d.Name != "") { this->mqtt->Publish_String(d.Name.c_str(), d.value, false); @@ -893,7 +893,7 @@ void modbus::ParseData() { } else if(RequestType == "id") { this->ChangeRegItem(this->InverterIdData, d); - Config->log(3, "Inverter ID Data found -> %s: %s ", d.Name.c_str(), d.value.c_str()); + Config->logN(3, "Inverter ID Data found -> %s: %s ", d.Name.c_str(), d.value.c_str()); } @@ -958,7 +958,7 @@ String modbus::MapBitwise(JsonArray map, String value) { for (uint8_t i=0; ilog(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[map.size() -2 -i].as().c_str()); + Config->logN(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[map.size() -2 -i].as().c_str()); if (ret.length() > 0) ret += ", "; if ((map.size() -2 -i) >=0 && map[map.size() -2 -i]) ret += map[map.size() -2 -i].as(); else ret += "undefined"; @@ -986,7 +986,7 @@ String modbus::MapItem(JsonArray map, String value) { if (value == v1) { ret = v2; - Config->log(4, "Mapped value: %s -> %s", v1.c_str(), v2.c_str()); + Config->logN(4, "Mapped value: %s -> %s", v1.c_str(), v2.c_str()); } } return ret; @@ -1102,14 +1102,14 @@ void modbus::GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request) { JsonDocument jsonGet; DeserializationError error = deserializeJson(jsonGet, json.c_str()); - Config->log(4, "[GetLiveDataAsJsonToWebServer] Json command empfangen: "); + Config->logN(4, "[GetLiveDataAsJsonToWebServer] Json command empfangen: "); if (!error) { Config->log(4, jsonGet); if (jsonGet["cmd"]["subaction"]) subaction = jsonGet["cmd"]["subaction"].as(); } else { - Config->log(2, "[GetLiveDataAsJsonToWebServer] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); + Config->logN(2, "[GetLiveDataAsJsonToWebServer] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); } AsyncWebServerResponse *response = request->beginChunkedResponse("application/json", [this, firstRow, counter, subaction](uint8_t *buffer, size_t maxLen, size_t index) { @@ -1199,7 +1199,7 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { if(request->hasArg("json")) { const String json = request->arg("json"); - Config->log(4, "[GetSetterAsJson] Json command empfangen: %s", json.c_str()); + Config->logN(4, "[GetSetterAsJson] Json command empfangen: %s", json.c_str()); JsonDocument jsonGet; DeserializationError error = deserializeJson(jsonGet, json.c_str()); @@ -1207,7 +1207,7 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { if (!error) { if (jsonGet["cmd"]["subaction"]) subaction = jsonGet["cmd"]["subaction"].as(); } else { - Config->log(2, "[GetSetterAsJson] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); + Config->logN(2, "[GetSetterAsJson] Json Command not parseable: %s -> %s", json.c_str(), error.c_str()); } } @@ -1224,7 +1224,7 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); return 0; } @@ -1243,11 +1243,11 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { DeserializationError error = deserializeJson(elem, regfile); if (error) { - Config->log(1, "(Function GetSettersAsJsonToWebServer) Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(1, "(Function GetSettersAsJsonToWebServer) Failed to parse JSON Register Data: %s", error.c_str()); break; } - Config->log(4, "parsing JSON ok"); + Config->logN(4, "parsing JSON ok"); Config->log(5, elem); //check if setter is active @@ -1308,7 +1308,7 @@ void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); return; } @@ -1327,10 +1327,10 @@ void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { if (!error) { // Print the result - Config->log(4, "parsing JSON ok"); - Config->log(5, elem); + Config->logN(4, "parsing JSON ok"); + Config->logN(5, elem); } else { - Config->log(4, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(4, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); } String s = ""; @@ -1354,10 +1354,10 @@ void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { if (!error) { // Print the result - Config->log(4, "parsing JSON ok"); - Config->log(5, elem); + Config->logN(4, "parsing JSON ok"); + Config->logN(5, elem); } else { - Config->log(1, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(1, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); } String s = ""; @@ -1380,7 +1380,7 @@ void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { void modbus::SetItemActiveStatus(String item, bool newstate) { for (uint16_t j=0; j < this->InverterLiveData->size(); j++) { if (this->InverterLiveData->at(j).Name == item) { - Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); + Config->logN(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); this->InverterLiveData->at(j).active = newstate; return; } @@ -1388,7 +1388,7 @@ void modbus::SetItemActiveStatus(String item, bool newstate) { for (uint16_t j=0; j < this->InverterIdData->size(); j++) { if (this->InverterIdData->at(j).Name == item) { - Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); + Config->logN(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); this->InverterIdData->at(j).active = newstate; return; } @@ -1396,7 +1396,7 @@ void modbus::SetItemActiveStatus(String item, bool newstate) { for (uint16_t j=0; j < this->Setters->size(); j++) { if (this->Setters->at(j).Name == item) { - Config->log(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); + Config->logN(3, "Set Item <%s> ActiveState to %s", item.c_str(), (newstate?"true":"false")); if (this->mqtt && this->Setters->at(j).active != newstate) { if (!newstate) { this->mqtt->UnSubscribe(this->GetMqttSetTopic(this->Setters->at(j).Name)); @@ -1446,11 +1446,11 @@ void modbus::loop() { void modbus::LoadRegItems(std::vector* vector, String type) { vector->clear(); - Config->log(4, "Load RegItems for Inverter %s and type <%s>", this->InverterType.name.c_str(), type.c_str()); + Config->logN(4, "Load RegItems for Inverter %s and type <%s>", this->InverterType.name.c_str(), type.c_str()); File regfile = LittleFS.open("/regs/"+this->InverterType.filename); if (!regfile) { - Config->log(1, "failed to open %s file", this->InverterType.filename.c_str()); + Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); return; } @@ -1466,10 +1466,10 @@ void modbus::LoadRegItems(std::vector* vector, String type) { if (!error) { // Print the result - Config->log(4, "parsing JSON ok"); + Config->logN(4, "parsing JSON ok"); Config->log(5, elem); } else { - Config->log(1, "(Function LoadRegItems) Failed to parse JSON Register Data for Inverter <%s> and type <%s>: %s", this->InverterType.name.c_str(), type.c_str(), error.c_str()); + Config->logN(1, "(Function LoadRegItems) Failed to parse JSON Register Data for Inverter <%s> and type <%s>: %s", this->InverterType.name.c_str(), type.c_str(), error.c_str()); } reg_t d = {}; @@ -1496,7 +1496,7 @@ void modbus::LoadRegItems(std::vector* vector, String type) { d.active = false; // set initial vector->push_back(d); - Config->log(4, "processed RegItem: %s", d.Name.c_str()); + Config->logN(4, "processed RegItem: %s", d.Name.c_str()); } while (regfile.findUntil(",","]")); @@ -1520,10 +1520,10 @@ void modbus::LoadJsonConfig(bool firstrun) { if (LittleFS.exists("/config/modbusconfig.json")) { //file exists, reading and loading - Config->log(3, "reading config file...."); + Config->logN(3, "reading config file...."); File configFile = LittleFS.open("/config/modbusconfig.json", "r"); if (configFile) { - Config->log(3, "config file is open:"); + Config->logN(3, "config file is open:"); //size_t size = configFile.size(); JsonDocument doc; @@ -1558,7 +1558,7 @@ void modbus::LoadJsonConfig(bool firstrun) { if (this->AvailableInverters->at(i).name == (doc["data"]["invertertype"]).as()) { this->InverterType = this->AvailableInverters->at(i); - Config->log(3, "Invertertyp '%s' was found in register file '%s', set it as selected active Inverter", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); + Config->logN(3, "Invertertyp '%s' was found in register file '%s', set it as selected active Inverter", this->InverterType.name.c_str(), this->InverterType.filename.c_str()); found = true; } } @@ -1566,18 +1566,18 @@ void modbus::LoadJsonConfig(bool firstrun) { if (this->AvailableInverters->size()>0) { this->InverterType = this->AvailableInverters->at(0); } - Config->log(3, "Invertertyp '%s' was not found, use default '%s' instead", (doc["data"]["invertertype"]).as().c_str(), this->InverterType.name.c_str()); + Config->logN(3, "Invertertyp '%s' was not found, use default '%s' instead", (doc["data"]["invertertype"]).as().c_str(), this->InverterType.name.c_str()); } } } else { - Config->log(1, "failed to load modbus json config, load default config"); + Config->logN(1, "failed to load modbus json config, load default config"); loadDefaultConfig = true; } configFile.close(); } } else { - Config->log(3, "modbusconfig.json config File not exists, load default config"); + Config->logN(3, "modbusconfig.json config File not exists, load default config"); loadDefaultConfig = true; } @@ -1635,10 +1635,10 @@ void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSet if (LittleFS.exists("/config/modbusitemconfig.json")) { //file exists, reading and loading - Config->log(3, "reading modbus item config file...."); + Config->logN(3, "reading modbus item config file...."); File configFile = LittleFS.open("/config/modbusitemconfig.json", "r"); if (configFile) { - Config->log(3, "modbus item config file is open:"); + Config->logN(3, "modbus item config file is open:"); ReadBufferingStream stream{configFile, 64}; stream.find("\"data\":["); @@ -1648,10 +1648,10 @@ void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSet if (!error) { // Print the result - Config->log(4, "parsing JSON ok"); + Config->logN(4, "parsing JSON ok"); Config->log(5, elem); } else { - Config->log(1, "(Function LoadJsonItemConfig) Failed to parse JSON Register Data: %s", error.c_str()); + Config->logN(1, "(Function LoadJsonItemConfig) Failed to parse JSON Register Data: %s", error.c_str()); } for (JsonPair kv : elem.as()) { @@ -1663,7 +1663,7 @@ void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSet if (this->InverterLiveData->at(i).Name == ItemName ) { this->InverterLiveData->at(i).active = kv.value().as(); - Config->log(3, "item %s -> %s", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); + Config->logN(3, "item %s -> %s", ItemName, (this->InverterLiveData->at(i).active?"enabled":"disabled")); break; } @@ -1676,7 +1676,7 @@ void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSet if (this->InverterIdData->at(i).Name == ItemName ) { this->InverterIdData->at(i).active = kv.value().as(); - Config->log(3, "item %s -> %s", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); + Config->logN(3, "item %s -> %s", ItemName, (this->InverterIdData->at(i).active?"enabled":"disabled")); break; } } @@ -1687,7 +1687,7 @@ void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSet for(uint16_t i=0; iSetters->size(); i++) { if (this->Setters->at(i).Name == ItemName ) { this->Setters->at(i).active = kv.value().as(); - Config->log(3, "setter %s -> %s", ItemName, (this->Setters->at(i).active?"enabled":"disabled")); + Config->logN(3, "setter %s -> %s", ItemName, (this->Setters->at(i).active?"enabled":"disabled")); break; } } @@ -1698,10 +1698,10 @@ void modbus::LoadJsonItemConfig(bool loadLiveData, bool loadIdData, bool loadSet } while (stream.findUntil(",","]")); configFile.close(); } else { - Config->log(1, "failed to load modbusitemconfig.json, load default item config"); + Config->logN(1, "failed to load modbusitemconfig.json, load default item config"); } } else { - Config->log(3, "modbusitemconfig.json config File not exists, all items are inactive as default"); + Config->logN(3, "modbusitemconfig.json config File not exists, all items are inactive as default"); } } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 9d677d98..f8972870 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -18,7 +18,7 @@ MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, Strin WiFi.onEvent(std::bind(&MQTT::WifiOnEvent, this, std::placeholders::_1)); #endif - Config->log(3, "Go into %s Mode", (Config->GetUseETH()?"ETH":"Wifi")); + Config->logN(3, "Go into %s Mode", (Config->GetUseETH()?"ETH":"Wifi")); ImprovTypes::ChipFamily variant; @@ -68,9 +68,9 @@ MQTT::MQTT(const char* MqttServer, uint16_t MqttPort, String MqttBasepath, Strin improvSerial.ConnectToWifi(); } - if (Config->GetDebugLevel() >=4) WiFi.printDiag(dbg); + //if (Config->GetDebugLevel() >=4) WiFi.printDiag(Serial); - Config->log(1, "Initializing MQTT (%s:%d)", Config->GetMqttServer().c_str(), Config->GetMqttPort()); + Config->logN(1, "Initializing MQTT (%s:%d)", Config->GetMqttServer().c_str(), Config->GetMqttPort()); espClient = WiFiClient(); PubSubClient::setClient(espClient); @@ -89,97 +89,97 @@ void MQTT::onImprovWiFiErrorCb(ImprovTypes::Error err) { #ifdef ESP32 void MQTT::WifiOnEvent(WiFiEvent_t event) { - Config->log(4, "[WiFi-event] event: %d", event); + Config->logN(4, "[WiFi-event] event: %d", event); switch (event) { case ARDUINO_EVENT_WIFI_READY: - Config->log(1, "WiFi interface ready"); + Config->logN(1, "WiFi interface ready"); break; case ARDUINO_EVENT_WIFI_SCAN_DONE: - Config->log(1, "Completed scan for access points"); + Config->logN(1, "Completed scan for access points"); break; case ARDUINO_EVENT_WIFI_STA_START: - Config->log(1, "WiFi client started"); + Config->logN(1, "WiFi client started"); break; case ARDUINO_EVENT_WIFI_STA_STOP: - Config->log(1, "WiFi clients stopped"); + Config->logN(1, "WiFi clients stopped"); break; case ARDUINO_EVENT_WIFI_STA_CONNECTED: - Config->log(1, "Connected to access point"); + Config->logN(1, "Connected to access point"); break; case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - Config->log(1, "Disconnected from WiFi access point"); + Config->logN(1, "Disconnected from WiFi access point"); this->ConnectStatusWifi = false; break; case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: - Config->log(1, "Authentication mode of access point has changed"); + Config->logN(1, "Authentication mode of access point has changed"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP: - Config->log(1, "WiFi connected with local IP: %s", WiFi.localIP().toString().c_str()); + Config->logN(1, "WiFi connected with local IP: %s", WiFi.localIP().toString().c_str()); this->ipadresse = WiFi.localIP(); this->ConnectStatusWifi = true; break; case ARDUINO_EVENT_WIFI_STA_LOST_IP: - Config->log(1, "Lost IP address and IP address is reset to 0"); + Config->logN(1, "Lost IP address and IP address is reset to 0"); this->ConnectStatusWifi = false; this->ipadresse = (0, 0, 0, 0); break; case ARDUINO_EVENT_WPS_ER_SUCCESS: - Config->log(1, "WiFi Protected Setup (WPS): succeeded in enrollee mode"); + Config->logN(1, "WiFi Protected Setup (WPS): succeeded in enrollee mode"); break; case ARDUINO_EVENT_WPS_ER_FAILED: - Config->log(1, "WiFi Protected Setup (WPS): failed in enrollee mode"); + Config->logN(1, "WiFi Protected Setup (WPS): failed in enrollee mode"); break; case ARDUINO_EVENT_WPS_ER_TIMEOUT: - Config->log(1, "WiFi Protected Setup (WPS): timeout in enrollee mode"); + Config->logN(1, "WiFi Protected Setup (WPS): timeout in enrollee mode"); break; case ARDUINO_EVENT_WPS_ER_PIN: - Config->log(1, "WiFi Protected Setup (WPS): pin code in enrollee mode"); + Config->logN(1, "WiFi Protected Setup (WPS): pin code in enrollee mode"); break; case ARDUINO_EVENT_WIFI_AP_START: - Config->log(1, "WiFi access point started"); + Config->logN(1, "WiFi access point started"); break; case ARDUINO_EVENT_WIFI_AP_STOP: - Config->log(1, "WiFi access point stopped"); + Config->logN(1, "WiFi access point stopped"); break; case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - Config->log(1, "Client connected"); + Config->logN(1, "Client connected"); break; case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - Config->log(1, "Client disconnected"); + Config->logN(1, "Client disconnected"); break; case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: - Config->log(1, "Assigned IP address to client"); + Config->logN(1, "Assigned IP address to client"); break; case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: - Config->log(1, "Received probe request"); + Config->logN(1, "Received probe request"); break; case ARDUINO_EVENT_WIFI_AP_GOT_IP6: - Config->log(1, "AP IPv6 is preferred"); + Config->logN(1, "AP IPv6 is preferred"); break; case ARDUINO_EVENT_WIFI_STA_GOT_IP6: - Config->log(1, "STA IPv6 is preferred"); + Config->logN(1, "STA IPv6 is preferred"); break; case ARDUINO_EVENT_ETH_GOT_IP6: - Config->log(1, "Ethernet IPv6 is preferred"); + Config->logN(1, "Ethernet IPv6 is preferred"); break; case ARDUINO_EVENT_ETH_START: - Config->log(1, "Ethernet started"); + Config->logN(1, "Ethernet started"); break; case ARDUINO_EVENT_ETH_STOP: - Config->log(1, "Ethernet stopped"); + Config->logN(1, "Ethernet stopped"); break; case ARDUINO_EVENT_ETH_CONNECTED: - Config->log(1, "Ethernet connected"); + Config->logN(1, "Ethernet connected"); break; case ARDUINO_EVENT_ETH_DISCONNECTED: - Config->log(1, "Ethernet disconnected"); + Config->logN(1, "Ethernet disconnected"); this->ConnectStatusWifi = false; this->ipadresse = (0, 0, 0, 0); break; case ARDUINO_EVENT_ETH_GOT_IP: if (!this->ConnectStatusWifi) { - Config->log(1, "ETH MAC: %s, IPv4: %s, %s, Mbps: %d", + Config->logN(1, "ETH MAC: %s, IPv4: %s, %s, Mbps: %d", ETH.macAddress().c_str(), ETH.localIP().toString().c_str(), (ETH.fullDuplex()?"FULL_DUPLEX":"HALF_DUPLEX"), @@ -209,7 +209,7 @@ eth_shield_t* MQTT::GetEthShield(String ShieldName) { void MQTT::WaitForConnect() { while (!this->ConnectStatusWifi) delay(100); - Config->log(1, "Wait for connect"); + Config->logN(1, "Wait for connect"); } void MQTT::reconnect() { @@ -225,7 +225,7 @@ void MQTT::reconnect() { } snprintf(LWT, sizeof(LWT), "%s/state", this->mqtt_root.c_str()); - Config->log(1, "Attempting MQTT connection as %s ", topic); + Config->logN(1, "Attempting MQTT connection as %s ", topic); if (PubSubClient::connect(topic, Config->GetMqttUsername().c_str(), @@ -234,7 +234,7 @@ void MQTT::reconnect() { true, false, "Offline")) { - Config->log(1, "connected... "); + Config->logN(1, "connected... "); // Once connected, publish basics ... this->Publish_IP(); this->Publish_String("ssid", WiFi.SSID(), false); @@ -244,11 +244,11 @@ void MQTT::reconnect() { // ... and resubscribe if needed for (uint8_t i=0; i< this->subscriptions->size(); i++) { PubSubClient::subscribe(this->subscriptions->at(i).c_str()); - Config->log(1, "MQTT resubscribed to: %s", this->subscriptions->at(i).c_str()); + Config->logN(1, "MQTT resubscribed to: %s", this->subscriptions->at(i).c_str()); } } else { - Config->log(1, "failed, rc=%d - Trying again in 5 seconds", PubSubClient::state()); + Config->logN(1, "failed, rc=%d - Trying again in 5 seconds", PubSubClient::state()); } } @@ -285,9 +285,9 @@ void MQTT::Publish_String(const char* subtopic, String value, bool fulltopic) { if (PubSubClient::connected()) { PubSubClient::publish((const char*)topic.c_str(), value.c_str(), true); - Config->log(3, "Publish %s: %s ", topic.c_str(), value.c_str()); + Config->logN(3, "Publish %s: %s ", topic.c_str(), value.c_str()); } else { - Config->log(2, "Request for MQTT Publish, but not connected to Broker"); + Config->logN(2, "Request for MQTT Publish, but not connected to Broker"); } } @@ -312,7 +312,7 @@ void MQTT::Subscribe(String topic) { this->subscriptions->push_back(topic); if (PubSubClient::connected()) { PubSubClient::subscribe(topic.c_str()); - Config->log(3, "MQTT now subscribed to: %s", topic.c_str()); + Config->logN(3, "MQTT now subscribed to: %s", topic.c_str()); } } @@ -323,7 +323,7 @@ bool MQTT::UnSubscribe(String topic) { if (PubSubClient::connected()) { PubSubClient::unsubscribe(this->subscriptions->at(i).c_str()); } - Config->log(3, "MQTT unsubscribed from: %s", this->subscriptions->at(i).c_str()); + Config->logN(3, "MQTT unsubscribed from: %s", this->subscriptions->at(i).c_str()); this->subscriptions->erase(this->subscriptions->begin()+i); ret = true; break; @@ -356,20 +356,20 @@ void MQTT::loop() { #endif if (this->mqtt_root != Config->GetMqttRoot()) { - Config->log(3, "MQTT DeviceName has changed via Web Configuration from %s to %s ", + Config->logN(3, "MQTT DeviceName has changed via Web Configuration from %s to %s ", this->mqtt_root.c_str(), Config->GetMqttRoot().c_str()); - Config->log(3, "Initiate Reconnect"); + Config->logN(3, "Initiate Reconnect"); this->mqtt_root = Config->GetMqttRoot(); if (PubSubClient::connected()) PubSubClient::disconnect(); } if (this->mqtt_basepath != Config->GetMqttBasePath()) { - Config->log(3, "MQTT Basepath has changed via Web Configuration from %s to %s ", + Config->logN(3, "MQTT Basepath has changed via Web Configuration from %s to %s ", this->mqtt_basepath.c_str(), Config->GetMqttBasePath().c_str()); - Config->log(3, "Initiate Reconnect"); + Config->logN(3, "Initiate Reconnect"); this->mqtt_basepath = Config->GetMqttBasePath(); if (PubSubClient::connected()) PubSubClient::disconnect(); diff --git a/src/openwb.cpp b/src/openwb.cpp index b1f51cbf..f2d4b2b7 100644 --- a/src/openwb.cpp +++ b/src/openwb.cpp @@ -24,7 +24,7 @@ void openwb::setVersion(String version) { void openwb::LoadAvailableOpenWbVersions() { File file = LittleFS.open("/misc/openwb.json", "r"); if (!file) { - Config->log(1, "Failed to open /misc/openwb.json"); + Config->logN(1, "Failed to open /misc/openwb.json"); return; } @@ -33,14 +33,14 @@ void openwb::LoadAvailableOpenWbVersions() { JsonDocument doc; DeserializationError error = deserializeJson(doc, file); if (error) { - Config->log(1, "Failed to parse /misc/openwb.json: %s", error.c_str()); + Config->logN(1, "Failed to parse /misc/openwb.json: %s", error.c_str()); file.close(); return; } for (JsonObject v : doc.as()) { OpenWBVersions->push_back(v["version"].as()); - Config->log(3, "OpenWB Version found: %s", v["version"].as().c_str()); + Config->logN(3, "OpenWB Version found: %s", v["version"].as().c_str()); } file.close(); @@ -49,7 +49,7 @@ void openwb::LoadAvailableOpenWbVersions() { void openwb::LoadOpenWBTopicsFromJson() { File file = LittleFS.open("/misc/openwb.json", "r"); if (!file) { - Config->log(1, "Failed to open /misc/openwb.json"); + Config->logN(1, "Failed to open /misc/openwb.json"); return; } @@ -57,7 +57,7 @@ void openwb::LoadOpenWBTopicsFromJson() { DeserializationError error = deserializeJson(doc, file); if (error) { - Config->log(1, "Failed to parse /misc/openwb.json: %s", error.c_str()); + Config->logN(1, "Failed to parse /misc/openwb.json: %s", error.c_str()); file.close(); return; } @@ -73,7 +73,7 @@ void openwb::LoadOpenWBTopicsFromJson() { t.value = kv.value().as(); this->OpenWBTopics->push_back(t); - Config->log(3, "openWB topic loaded: %s", kv.value().as().c_str()); + Config->logN(3, "openWB topic loaded: %s", kv.value().as().c_str()); } } break; From 0727df1812d7b4e0fb57780fb25cbd242b2cf673 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Tue, 11 Feb 2025 18:07:49 +0100 Subject: [PATCH 065/106] bump version to 3.3.3 --- include/_Release.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/_Release.h b/include/_Release.h index 8881175d..f363ab37 100644 --- a/include/_Release.h +++ b/include/_Release.h @@ -1 +1 @@ -#define Release "3.3.2" +#define Release "3.3.3" From 367ff9e74291f1e061ca349304d078d6c133c7b9 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sat, 15 Feb 2025 21:36:23 +0100 Subject: [PATCH 066/106] Update Solax-X3.json (#135) --- data/regs/Solax-X3.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index 42c7b44c..fc876e35 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -1483,7 +1483,7 @@ 4 ], [ - "TUOMode", + "TOUMode", 5 ] ], From 96116f2fe3eb3e4dcf346fd47bb3e0ac4f5e3740 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Sun, 23 Feb 2025 17:14:08 +0100 Subject: [PATCH 067/106] =?UTF-8?q?Lowercase=20nur=20f=C3=BCrs=20Mapping?= =?UTF-8?q?=20bei=20Set=20Befehlen=20(#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.h * Update modbus.cpp * Update modbus.cpp --- src/modbus.cpp | 14 ++++++++------ src/modbus.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index f82b189b..f8275459 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -195,7 +195,7 @@ void modbus::ReceiveMQTT(String topic, String msg) { Config->logN(4, "Map values for item %s", msg.c_str()); JsonArray map = elem["mapping"].as(); - msg = this->MapItem(map, msg); + msg = this->MapItem(map, msg, true); } int msgInt = msg.toInt(); // atoi(msg.c_str()) @@ -872,7 +872,7 @@ void modbus::ParseData() { JsonArray map = elem["mapping"].as(); if (datatype == "binary") d.value = this->MapBitwise(map, d.value); - else d.value = this->MapItem(map, d.value); + else d.value = this->MapItem(map, d.value, false); } Config->logN(4, "Data: %s -> %s %s", d.Name.c_str(), d.value.c_str(), d.unit.c_str()); @@ -973,16 +973,18 @@ String modbus::MapBitwise(JsonArray map, String value) { /******************************************************* * Map a value to a predefined constant string *******************************************************/ -String modbus::MapItem(JsonArray map, String value) { +String modbus::MapItem(JsonArray map, String value, bool isSetter) { String ret = value; for (JsonArray mapItem : map) { String v1 = mapItem[0].as(); String v2 = mapItem[1].as(); - v1.toLowerCase(); - v2.toLowerCase(); - value.toLowerCase(); + if (isSetter) { + v1.toLowerCase(); + v2.toLowerCase(); + value.toLowerCase(); + } if (value == v1) { ret = v2; diff --git a/src/modbus.h b/src/modbus.h index 4f5f850d..53b52883 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -127,7 +127,7 @@ class modbus { String GetMqttSetTopic(String command); void ChangeRegItem(std::vector* vector, reg_t item); void LoadRegItems(std::vector* vector, String type); - String MapItem(JsonArray map, String value); + String MapItem(JsonArray map, String value, bool isSetter); String MapBitwise(JsonArray map, String value); String ConvertIntToBinaryString(int n, int numBits); void ReadRelays(); From 49d3280882b41803483c2665acc0a8dee24cd2b6 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 24 Feb 2025 12:43:00 +0100 Subject: [PATCH 068/106] Add backup functionality to handlefiles for complete filesystem zipping --- data/web/handlefiles.html | 8 +++++++- data/web/handlefiles.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/data/web/handlefiles.html b/data/web/handlefiles.html index 2bc657ca..ad57e35b 100644 --- a/data/web/handlefiles.html +++ b/data/web/handlefiles.html @@ -8,13 +8,18 @@ + + + + @@ -63,6 +68,7 @@ + diff --git a/data/web/handlefiles.js b/data/web/handlefiles.js index e60fbe93..f7aa83bc 100644 --- a/data/web/handlefiles.js +++ b/data/web/handlefiles.js @@ -266,4 +266,43 @@ export function deleteFile() { global.requestData(data); GetInitData(pathOfFile); } else { global.setResponse(false, 'Filename is empty, Please define it.');} +} + +// *********************************** +// backup complete filesystem of ESP by zipfile +// +// https://gist.github.com/noelvo/4502eea719f83270c8e9 +// *********************************** +export function backup() { + var url = []; + + for(let i = 0; i < DirJson.length; i++) { + DirJson[i].content.forEach(function (file) { + if (file.isDir==0) { + //console.log(DirJson[i].path, file.name) + url.push(DirJson[i].path + "/" + file.name) + } + }) + } + compressed_img(url, "backup"); +} + +function compressed_img(urls, nombre) { + var zip = new JSZip(); + var count = 0; + var name = nombre + ".zip"; + urls.forEach(function(url){ + JSZipUtils.getBinaryContent(url, function (err, data) { + if(err) { + throw err; + } + zip.file(url, data, {binary:true}); + count++; + if (count == urls.length) { + zip.generateAsync({type:'blob'}).then(function(content) { + saveAs(content, name); + }); + } + }); + }); } \ No newline at end of file From bc20b6d8b307a97ed9b16107e2b21ac7fd54a8ad Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 24 Feb 2025 13:07:28 +0100 Subject: [PATCH 069/106] Add clipboard copy functionality for raw data in web interface --- data/web/rawdata.html | 13 ++++++++++--- data/web/rawdata.js | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/data/web/rawdata.html b/data/web/rawdata.html index 64b351c3..be0277a0 100644 --- a/data/web/rawdata.html +++ b/data/web/rawdata.html @@ -9,9 +9,10 @@ @@ -30,7 +31,10 @@ - + - +
Active + Active + + + Name MQTT Topic
Active - - + + Name OpenWB
Active - - + + Name MQTT Topic
RawData of ID-Data + RawData of ID-Data + + @@ -38,7 +42,10 @@
RawData of Live-Data + RawData of Live-Data + + diff --git a/data/web/rawdata.js b/data/web/rawdata.js index 1746c4ed..3da30820 100644 --- a/data/web/rawdata.js +++ b/data/web/rawdata.js @@ -47,6 +47,22 @@ function GetInitData() { global.requestData(data); } +export function cpRawdata2Clipboard(rawdatatype) { + const string_rawdata = document.getElementById(rawdatatype + '_org').innerHTML; + let bytes = chunk(string_rawdata,2) + + for( var i=0; i< bytes.length; i++) { + bytes[i] = "0x" + bytes[i]; + } + + // Copy the result to the clipboard + navigator.clipboard.writeText(bytes.join(' ')).then(() => { + alert('Raw data copied to clipboard. You can now paste it using CTRL-V.'); + }).catch(err => { + console.error('Failed to copy text: ', err); + }); +} + /******************************* * Callback function after receiving the data *******************************/ From 13fbe1286cd349a37c45b4ab9e8a8fd801511f35 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 24 Feb 2025 17:17:39 +0100 Subject: [PATCH 070/106] bugfix: Set Befehle sind limitiert auf 19 #137 --- src/modbus.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/modbus.cpp b/src/modbus.cpp index f8275459..7457a27e 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1198,7 +1198,7 @@ void modbus::GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request) { void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { std::shared_ptr counter = std::make_shared(0); String subaction(""); - + if(request->hasArg("json")) { const String json = request->arg("json"); Config->logN(4, "[GetSetterAsJson] Json command empfangen: %s", json.c_str()); @@ -1238,19 +1238,21 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { streamString = "\"set\": ["; regfile.find(streamString.c_str()); + do { - if (itemIterator == (*counter - 1)) { - bool isActive = false; // default - JsonDocument elem; - DeserializationError error = deserializeJson(elem, regfile); - - if (error) { - Config->logN(1, "(Function GetSettersAsJsonToWebServer) Failed to parse JSON Register Data: %s", error.c_str()); - break; - } + JsonDocument elem; + DeserializationError error = deserializeJson(elem, regfile); - Config->logN(4, "parsing JSON ok"); - Config->log(5, elem); + if (error) { + Config->logN(1, "(Function GetSettersAsJsonToWebServer) Failed to parse JSON Register Data: %s", error.c_str()); + break; + } + + Config->logN(4, "parsing JSON ok"); + Config->log(5, elem); + + if (itemIterator == (*counter - 1) && ret.length() < maxLen) { + bool isActive = false; // default //check if setter is active for (uint8_t i = 0; i < this->Setters->size(); i++) { @@ -1265,27 +1267,25 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { String mapping = elem["mapping"].as(); mapping.replace("\"", "'"); ret += "{\"name\": \"" + elem["name"].as() + "\","; - ret += "\"realname\": {\"innerHTML\": \"" + elem["realname"].as() + "\""; if (elem["info"]) ret += ", \"data-info\": \"" + elem["info"].as() + "\""; ret += "},"; - ret += "\"active\": {\"checked\": " + String(isActive ? 1 : 0) + ", \"name\": \"" + elem["name"].as() + "\"},"; - ret += "\"subscription\": {\"innerHTML\": \"" + this->GetMqttSetTopic(elem["name"].as()) + "\""; if (elem["mapping"]) ret += ", \"data-mapping\": \""+ mapping + "\""; ret += "}}"; } - } - (*counter)++; + (*counter)++; + } + itemIterator++; } while (regfile.findUntil(",","]")); if (regfile) { regfile.close(); } - if (ret.length() > 0) { + if (itemIterator == (*counter - 1)) { // send end of JSON ret += " ]}, \"object_id\": \"" + Config->GetMqttBasePath() + "/" + Config->GetMqttRoot() + "\"}"; (*counter)++; From 5b8caab9f6ea39362cfb9127749750e33707b3a6 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 24 Feb 2025 17:22:39 +0100 Subject: [PATCH 071/106] refactor: Remove deprecated JSON handling functions from MyWebServer and modbus --- src/MyWebServer.cpp | 13 -------- src/MyWebServer.h | 5 --- src/modbus.cpp | 76 --------------------------------------------- src/modbus.h | 1 - 4 files changed, 95 deletions(-) diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index 9439c7a3..0acf8a2e 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -18,7 +18,6 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): server->on("/favicon.ico", HTTP_GET, std::bind(&MyWebServer::handleFavIcon, this, std::placeholders::_1)); server->on("/getitems", HTTP_GET, [&](AsyncWebServerRequest *request){ mb->GetLiveDataAsJsonToWebServer(request); }); - //server->on("/getregister", HTTP_GET, std::bind(&MyWebServer::handleGetRegisterJson, this, std::placeholders::_1)); // deprecated, not longer in use server->on("/getsetter", HTTP_GET, [&](AsyncWebServerRequest *request){ mb->GetSettersAsJsonToWebServer(request); }); @@ -259,18 +258,6 @@ bool MyWebServer::handleReset() { return ret; } -/* -void MyWebServer::handleGetRegisterJson(AsyncWebServerRequest *request) { - AsyncResponseStream *response = request->beginResponseStream("application/json"); - response->addHeader("Cache-Control", "no-cache, no-store, must-revalidate"); - response->addHeader("Pragma", "no-cache"); - response->addHeader("Expires", "-1"); - - mb->GetRegisterAsJsonToWebServer(response); - request->send(response); -} -*/ - void MyWebServer::GetInitDataNavi(JsonDocument& json) { json["data"].to(); json["data"]["hostname"] = Config->GetMqttRoot(); diff --git a/src/MyWebServer.h b/src/MyWebServer.h index 8f49fb59..3d26742e 100644 --- a/src/MyWebServer.h +++ b/src/MyWebServer.h @@ -48,11 +48,6 @@ class MyWebServer { bool handleReset(); void handleRoot(AsyncWebServerRequest *request); void handleFavIcon(AsyncWebServerRequest *request); -// void handleGetItemJson(AsyncWebServerRequest *request); -// void handleGetRegisterJson(AsyncWebServerRequest *request); -// void handleGetSetterJson(AsyncWebServerRequest *request); -// void GetInitDataStatus(AsyncResponseStream *response); -// void GetInitDataNavi(AsyncResponseStream *response); void GetInitDataStatus(JsonDocument& json); void GetInitDataNavi(JsonDocument& json); diff --git a/src/modbus.cpp b/src/modbus.cpp index 7457a27e..134da741 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -1299,82 +1299,6 @@ void modbus::GetSettersAsJsonToWebServer(AsyncWebServerRequest *request) { request->send(response); } -/******************************************************* - * Return all LiveData as jsonArray - * {data: [{"name": "xx", "value": "xx"}], } - * {"GridVoltage_R":"0.00 V","GridCurrent_R":"0.00 A","GridPower_R":"0 W","GridFrequency_R":"0.00 Hz","GridVoltage_S":"0.90 V","GridCurrent_S":"1715.40 A","GridPower_S":"-28671 W","GridFrequency_S":"174.08 Hz","GridVoltage_T":"0.00 V","GridCurrent_T":"0.00 A","GridPower_T":"0 W","GridFrequency_T":"1.30 Hz","PvVoltage1":"259.80 V","PvVoltage2":"0.00 V","PvCurrent1":"1.00 A","PvCurrent2":"0.00 A","Temperature":"28 °C","PowerPv1":"283 W","PowerPv2":"0 W","BatVoltage":"0.00 V","BatCurrent":"0.00 A","BatPower":"0 W","BatTemp":"0 °C","BatCapacity":"0 %","OutputEnergyChargeWh":"0 Wh","OutputEnergyChargeKWh":"0.00 KWh","OutputEnergyChargeToday":"0.00 KWh","InputEnergyChargeWh":"0 Wh","InputEnergyChargeKWh":"0.00 KWh"} -*******************************************************/ -/* -void modbus::GetRegisterAsJsonToWebServer(AsyncResponseStream *response) { - int count = 0; - - File regfile = LittleFS.open("/regs/"+this->InverterType.filename); - if (!regfile) { - Config->logN(1, "failed to open %s file", this->InverterType.filename.c_str()); - return; - } - - response->print("{\"data\": ["); - - String streamString = ""; - streamString = "\""+ this->InverterType.name +"\": {"; - regfile.find(streamString.c_str()); - - streamString = "\"livedata\": ["; - regfile.find(streamString.c_str()); - - do { - JsonDocument elem; - DeserializationError error = deserializeJson(elem, regfile); - - if (!error) { - // Print the result - Config->logN(4, "parsing JSON ok"); - Config->logN(5, elem); - } else { - Config->logN(4, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); - } - - String s = ""; - serializeJson(elem, s); - if(count>0) response->print(", "); - response->print(s); - count++; - - } while (regfile.findUntil(",","]")); - //Lazgar - streamString = ""; - streamString = "\""+ this->InverterType.name +"\": {"; - regfile.find(streamString.c_str()); - - streamString = "\"id\": ["; - regfile.find(streamString.c_str()); - - do { - JsonDocument elem; - DeserializationError error = deserializeJson(elem, regfile); - - if (!error) { - // Print the result - Config->logN(4, "parsing JSON ok"); - Config->logN(5, elem); - } else { - Config->logN(1, "(Function GetRegisterAsJson) Failed to parse JSON Register Data: %s", error.c_str()); - } - - String s = ""; - serializeJson(elem, s); - if(count>0) response->print(", "); - response->print(s); - count++; - - } while (regfile.findUntil(",","]")); - //Lazgar - if (regfile) { regfile.close(); } - response->print("]}"); -} -*/ - /******************************************************* * request for changing active Status of a certain item * Used by handleAjax function diff --git a/src/modbus.h b/src/modbus.h index 53b52883..937d12fa 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -63,7 +63,6 @@ class modbus { String GetInverterSN(); void GetLiveDataAsJsonToWebServer(AsyncWebServerRequest *request); void GetSettersAsJsonToWebServer(AsyncWebServerRequest *request); - //void GetRegisterAsJsonToWebServer(AsyncResponseStream *response); void SetItemActiveStatus(String item, bool newstate); void ReceiveMQTT(String topic, String msg); JsonDocument GetSetterByName(String name); From 7f99fa4b5076b364fa1bfb96f50d17833217ff77 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:17:03 +0100 Subject: [PATCH 072/106] Update Solax-X3.json (#143) * Update Solax-X3.json --- data/regs/Solax-X3.json | 195 ++++++++++++++++++++++++++++++++++------ 1 file changed, 168 insertions(+), 27 deletions(-) diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index fc876e35..10e3cf25 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -337,7 +337,6 @@ "name": "InverterSettings", "realname": "Inverter Settings", "datatype": "integer", - "settopic": "UnlockSettings", "mapping": [ [ 0, @@ -357,7 +356,6 @@ "name": "ModbusPowerControl", "realname": "Modbus Power Control", "datatype": "integer", - "settopic": "ModbusPowerControl", "mapping": [ [ 0, @@ -501,7 +499,6 @@ "name": "BatTargetSoC", "realname": "Battery Target SoC", "datatype": "integer", - "settopic": "TargetSoC", "unit": "%" }, { @@ -1221,7 +1218,7 @@ ], [ 3, - "ManuelMode" + "ManualMode" ], [ 4, @@ -1238,8 +1235,8 @@ 288, 289 ], - "name": "ManuelMode", - "realname": "Manuel Mode", + "name": "ManualMode", + "realname": "Manual Mode", "datatype": "integer", "mapping": [ [ @@ -1339,17 +1336,17 @@ 537, 538 ], - "name": "PhasePowerBalance", - "realname": "Phase Power Balance", + "name": "PhaseUnbalancedPowerFeed", + "realname": "Phase Unbalanced Power Feed", "datatype": "integer", "mapping": [ [ - 1, - "On" + 0, + "Disabled" ], [ - 0, - "Off" + 1, + "Enabled" ] ] }, @@ -1409,10 +1406,29 @@ }, { "position": [ - 307 + 303 ], - "name": "FeedInMinSoC", - "realname": "FeedIn Minimum SoC", + "name": "SelfUseChargeEnable", + "realname": "Self-Use Charge from Grid Enable", + "datatype": "integer", + "mapping": [ + [ + 0, + "Disabled" + ], + [ + 1, + "Enabled" + ] + ] + }, + { + "position": [ + 304, + 305 + ], + "name": "SelfUseMaxGridSoC", + "realname": "Self-Use Maximum Grid SoC", "datatype": "integer", "unit": "%" }, @@ -1427,21 +1443,70 @@ }, { "position": [ - 309 + 307 ], - "name": "BackupMinSoC", - "realname": "Backup Minimum SoC", + "name": "FeedInMinSoC", + "realname": "FeedIn Minimum SoC", "datatype": "integer", "unit": "%" }, { "position": [ - 309 + 308 ], "name": "BackupNightChargeSoC", "realname": "Backup NightCharge SoC", "datatype": "integer", "unit": "%" + }, + { + "position": [ + 309 + ], + "name": "BackupMinSoC", + "realname": "Backup Minimum SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 311, + 310 + ], + "name": "ChargePeriod1_StartTime", + "realname": "Forced Charge Period Start Time", + "datatype": "integer", + "unit": "h:m" + }, + { + "position": [ + 313, + 312 + ], + "name": "ChargePeriod1_EndTime", + "realname": "Forced Charge Period End Time", + "datatype": "integer", + "unit": "h:m" + }, + { + "position": [ + 315, + 314 + ], + "name": "DischargePeriod1_StartTime", + "realname": "Allowed Disc Period Start Time", + "datatype": "integer", + "unit": "h:m" + }, + { + "position": [ + 317, + 316 + ], + "name": "DischargePeriod1_EndTime", + "realname": "Allowed Disc Period End Time", + "datatype": "integer", + "unit": "h:m" } ] }, @@ -1475,7 +1540,7 @@ 2 ], [ - "ManuelMode", + "ManualMode", 3 ], [ @@ -1495,8 +1560,8 @@ ] }, { - "name": "setManuelMode", - "realname": "Manuel Mode", + "name": "setManualMode", + "realname": "Manual Mode", "info": "accepted values:", "mapping": [ [ @@ -1563,16 +1628,16 @@ ] }, { - "name": "setPhasePowerBalance", - "realname": "Phase Power Balance", - "info": "has to be investigated", + "name": "setPhaseUnbalancedPowerFeed", + "realname": "Phase Unbalanced Power Feed", + "info": "Allow unbalanced power feed to the grid", "mapping": [ [ - "Off", + "Disable", 0 ], [ - "On", + "Enable", 1 ] ], @@ -1702,6 +1767,38 @@ "0x61" ] }, + { + "name": "setSelfUseChargeEnable", + "realname": "Self-Use Charge from Grid", + "info": "enable/disable charging from grid", + "mapping": [ + [ + "Disable", + 0 + ], + [ + "Enable", + 1 + ] + ], + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x62" + ] + }, + { + "name": "setSelfUseMaxGridSoC", + "realname": "Self-Use Maximum Grid SoC", + "info": "can be set between 10 - 100 percent", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x63" + ] + }, { "name": "setFeedInMinSoC", "realname": "FeedIn Minimum SoC", @@ -1745,6 +1842,50 @@ "0x00", "0x66" ] + }, + { + "name": "setChargePeriod1_StartTime", + "realname": "Forced Charge Period Start Time", + "info": "Forced Charge Period Start Time", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x68" + ] + }, + { + "name": "setChargePeriod1_EndTime", + "realname": "Forced Charge Period End Time", + "info": "Forced Charge Period End Time", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x69" + ] + }, + { + "name": "setDischargePeriod1_StartTime", + "realname": "Allowed Disc Period Start Time", + "info": "Allowed Discharge Period Start Time", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x6A" + ] + }, + { + "name": "setDischargePeriod1_EndTime", + "realname": "Allowed Disc Period End Time", + "info": "Allowed Discharge Period End Time", + "request": [ + "#ClientID", + "0x06", + "0x00", + "0x6B" + ] } ] } From b725b3056cc07e0f102ae25350ef74453f452dd2 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Tue, 4 Mar 2025 17:22:35 +0100 Subject: [PATCH 073/106] fix: Improve visibility check and correct IP address assignment in MQTT handling --- data/web/Javascript.js | 2 +- src/mqtt.cpp | 30 +++++++++++++++++++----------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/data/web/Javascript.js b/data/web/Javascript.js index b5297ec8..7482d66f 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -469,7 +469,7 @@ export function CreateSelectionListFromInputField(querySelector, jsonLists, blac ****************************************************************************************/ function isVisible(_obj) { var ret = true; - if (_obj && _obj.style.display == "none") { ret = false;} + if (_obj && (_obj.style.display == "none" || _obj.classList.contains("hide"))) { ret = false; } else if (_obj && _obj.parentNode && _obj.tagName != "HTML") ret = isVisible(_obj.parentNode); return ret; } diff --git a/src/mqtt.cpp b/src/mqtt.cpp index f8972870..d8fefd1d 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -122,7 +122,7 @@ void MQTT::WifiOnEvent(WiFiEvent_t event) { case ARDUINO_EVENT_WIFI_STA_LOST_IP: Config->logN(1, "Lost IP address and IP address is reset to 0"); this->ConnectStatusWifi = false; - this->ipadresse = (0, 0, 0, 0); + this->ipadresse = IPAddress(0, 0, 0, 0); break; case ARDUINO_EVENT_WPS_ER_SUCCESS: Config->logN(1, "WiFi Protected Setup (WPS): succeeded in enrollee mode"); @@ -175,7 +175,7 @@ void MQTT::WifiOnEvent(WiFiEvent_t event) { case ARDUINO_EVENT_ETH_DISCONNECTED: Config->logN(1, "Ethernet disconnected"); this->ConnectStatusWifi = false; - this->ipadresse = (0, 0, 0, 0); + this->ipadresse = IPAddress(0, 0, 0, 0); break; case ARDUINO_EVENT_ETH_GOT_IP: if (!this->ConnectStatusWifi) { @@ -207,9 +207,10 @@ eth_shield_t* MQTT::GetEthShield(String ShieldName) { } void MQTT::WaitForConnect() { - while (!this->ConnectStatusWifi) + while (!this->ConnectStatusWifi) { delay(100); Config->logN(1, "Wait for connect"); + } } void MQTT::reconnect() { @@ -344,12 +345,13 @@ void MQTT::ClearSubscriptions() { void MQTT::loop() { improvSerial.loop(); - + #ifdef ESP8266 - if (WiFi.status() == WL_CONNECTED) { + if (WiFi.status() == WL_CONNECTED && !this->ConnectStatusWifi) { this->ConnectStatusWifi = true; this->ipadresse = WiFi.localIP(); - } else { + } + if (WiFi.status() != WL_CONNECTED && this->ConnectStatusWifi) { this->ConnectStatusWifi = false; this->ipadresse = (0, 0, 0, 0); } @@ -385,9 +387,10 @@ void MQTT::loop() { PubSubClient::loop(); } - if (PubSubClient::connected()) { + if (PubSubClient::connected() && !this->ConnectStatusMqtt) { this->ConnectStatusMqtt = true; - } else { + } + if (!PubSubClient::connected() && this->ConnectStatusMqtt) { this->ConnectStatusMqtt = false; } @@ -405,9 +408,14 @@ void MQTT::loop() { snprintf(buffer, sizeof(buffer), "%d", WiFi.RSSI()); this->Publish_String("rssi", buffer, false); - uint64_t uptimeMicroSeconds = esp_timer_get_time(); - uint64_t uptimeSeconds = uptimeMicroSeconds / 1000000; - this->Publish_Int("uptime", uptimeSeconds, false); + unsigned long uptime = millis() / 1000; + unsigned int hours = uptime / 3600; + unsigned int minutes = (uptime % 3600) / 60; + unsigned int seconds = uptime % 60; + char uptimeStr[20]; + snprintf(uptimeStr, sizeof(uptimeStr), "%02d:%02d:%02d", hours, minutes, seconds); + + this->Publish_String("uptime", uptimeStr, false); } } } From e52c96fd1de8d9e74b182bf601f5e30b3d1fbee1 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Wed, 19 Mar 2025 16:57:46 +0100 Subject: [PATCH 074/106] chore: Update ChangeLog for release 3.3.3 with bug fixes and Solax-X3 JSON update --- ChangeLog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 520fa349..b7de6c63 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,7 @@ Release 3.3.3: - improve logging functionality methods + - fixing some bugs + - updated Solax-X3 json (thanks to @Lazgar) Release 3.3.2: - new feature: add confirmation dialog for ESP reset From bd35b479af6d852eb864b579264dfef585f89cc8 Mon Sep 17 00:00:00 2001 From: Christoph Niedermayr <130648348+Obihoernchen80@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:13:25 +0200 Subject: [PATCH 075/106] yml; warnings; ESPAsyncWebServer@3.6.0 (#150) * use ${{env.GITHUB_OWNER}} in yml * fix warning "... will be initialized after [-Wreorder]" * fix "warning: comparison of unsigned expression >= 0 is always true [-Wtype-limits]" * require ESPAsyncWebServer@3.6.0, not compatible with latest --- .github/workflows/BuildAndDeploy.yml | 12 ++++++------ platformio.ini | 2 +- src/baseconfig.cpp | 4 ++-- src/modbus.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/BuildAndDeploy.yml b/.github/workflows/BuildAndDeploy.yml index 74417466..e86bec8c 100644 --- a/.github/workflows/BuildAndDeploy.yml +++ b/.github/workflows/BuildAndDeploy.yml @@ -254,7 +254,7 @@ jobs: continue fi echo "Uploading $file" - gh release upload -R tobiasfaust/${{ env.GITHUB_REPOSITORY }} ${{ steps.set_env_var.outputs.tagname }} "$file" --clobber + gh release upload -R ${{env.GITHUB_OWNER}}/${{ env.GITHUB_REPOSITORY }} ${{ steps.set_env_var.outputs.tagname }} "$file" --clobber fi done done @@ -267,18 +267,18 @@ jobs: GH_TOKEN: ${{ github.token }} run: | mkdir -p manifests - tagNames=$(gh release ls -R tobiasfaust/${{ env.GITHUB_REPOSITORY }} --json tagName) + tagNames=$(gh release ls -R ${{env.GITHUB_OWNER}}/${{ env.GITHUB_REPOSITORY }} --json tagName) for tag in $(echo "$tagNames" | jq -r '.[].tagName'); do - for asset in $(gh release view $tag -R tobiasfaust/${{ env.GITHUB_REPOSITORY }} --json assets --jq '.assets[].name' | grep -E 'manifestAll|filesAll'); do - echo "Downloading file: gh release download $tag -R tobiasfaust/${{ env.GITHUB_REPOSITORY }} --pattern '$asset' --dir 'manifests/${tag}/'" + for asset in $(gh release view $tag -R ${{env.GITHUB_OWNER}}/${{ env.GITHUB_REPOSITORY }} --json assets --jq '.assets[].name' | grep -E 'manifestAll|filesAll'); do + echo "Downloading file: gh release download $tag -R ${{env.GITHUB_OWNER}}/${{ env.GITHUB_REPOSITORY }} --pattern '$asset' --dir 'manifests/${tag}/'" gh release download $tag \ - -R tobiasfaust/${{ env.GITHUB_REPOSITORY }} \ + -R ${{env.GITHUB_OWNER}}/${{ env.GITHUB_REPOSITORY }} \ --pattern "$asset" \ --dir "manifests/${tag}/" || true done mkdir -p manifests/${tag}/ - gh release view $tag -R tobiasfaust/${{ env.GITHUB_REPOSITORY }} --json assets > "manifests/${tag}/assets.json" + gh release view $tag -R ${{env.GITHUB_OWNER}}/${{ env.GITHUB_REPOSITORY }} --json assets > "manifests/${tag}/assets.json" done python app/.github/scripts/createReleaseVersions.py \ --ManifestDir manifests \ diff --git a/platformio.ini b/platformio.ini index 6125aa89..33935309 100644 --- a/platformio.ini +++ b/platformio.ini @@ -43,7 +43,7 @@ lib_deps = https://github.com/tobiasfaust/Improv-WiFi-Library.git https://github.com/tobiasfaust/ElegantOTA.git ;https://github.com/mathieucarbou/AsyncTCP - ;https://github.com/mathieucarbou/ESPAsyncWebServer // installing by ElegantOTA + https://github.com/mathieucarbou/ESPAsyncWebServer@3.6.0 ; installing by ElegantOTA custom_lib_webserial = https://github.com/ayushsharma82/WebSerial.git diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 48ecd97c..91994823 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -4,10 +4,10 @@ #include -BaseConfig::BaseConfig(): debuglevel(2), +BaseConfig::BaseConfig(): mqtt_UseRandomClientID(true), + debuglevel(2), serial_rx(3), serial_tx(1), - mqtt_UseRandomClientID(true), useAuth(false) { #ifdef ESP8266 LittleFS.begin(); diff --git a/src/modbus.cpp b/src/modbus.cpp index 134da741..c7adf764 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -960,7 +960,7 @@ String modbus::MapBitwise(JsonArray map, String value) { //note: 1 item less than map size, because last item is default value Config->logN(4, "Mapped value: %s -> %s\n", String(value[i]).c_str(), map[map.size() -2 -i].as().c_str()); if (ret.length() > 0) ret += ", "; - if ((map.size() -2 -i) >=0 && map[map.size() -2 -i]) ret += map[map.size() -2 -i].as(); + if (((signed)map.size() -2 -i) >=0 && map[map.size() -2 -i]) ret += map[map.size() -2 -i].as(); else ret += "undefined"; } } From fa83bdeaf51ce813610396eae1454a14c1af9940 Mon Sep 17 00:00:00 2001 From: Naomi Rennie-Waldock Date: Mon, 28 Apr 2025 12:48:12 +0100 Subject: [PATCH 076/106] Change default pins for ESP32-C3 (fixes #148) (#149) * Allow overriding the default modbus pins * Change default modbus pins for ESP32-C3 16/17 are used by the internal flash * Use the globally defined RX & TX vars for the default serial pins * Update ChangeLog for release 3.3.4 and increment version in _Release.h --------- Co-authored-by: Tobias Faust --- ChangeLog.md | 3 +++ include/_Release.h | 2 +- platformio.ini | 3 +++ src/baseconfig.cpp | 12 ++++++------ src/modbus.cpp | 4 ++-- src/modbus.h | 8 ++++++++ 6 files changed, 23 insertions(+), 9 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index b7de6c63..fae17cab 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,6 @@ +Release 3.3.4: + - + Release 3.3.3: - improve logging functionality methods - fixing some bugs diff --git a/include/_Release.h b/include/_Release.h index f363ab37..ad287562 100644 --- a/include/_Release.h +++ b/include/_Release.h @@ -1 +1 @@ -#define Release "3.3.3" +#define Release "3.3.4" diff --git a/platformio.ini b/platformio.ini index 33935309..47dadced 100644 --- a/platformio.ini +++ b/platformio.ini @@ -67,3 +67,6 @@ board = esp32-s3-devkitc-1 [env:firmware_ESP32-C3] board = esp32-c3-devkitm-1 +build_flags = ${env.build_flags} + -D DEFAULT_MODBUS_RX_PIN=6 + -D DEFAULT_MODBUS_TX_PIN=7 diff --git a/src/baseconfig.cpp b/src/baseconfig.cpp index 91994823..8260077f 100644 --- a/src/baseconfig.cpp +++ b/src/baseconfig.cpp @@ -4,10 +4,10 @@ #include -BaseConfig::BaseConfig(): mqtt_UseRandomClientID(true), - debuglevel(2), - serial_rx(3), - serial_tx(1), +BaseConfig::BaseConfig(): debuglevel(2), + serial_rx(RX), + serial_tx(TX), + mqtt_UseRandomClientID(true), useAuth(false) { #ifdef ESP8266 LittleFS.begin(); @@ -52,8 +52,8 @@ void BaseConfig::LoadJsonConfig() { if (doc["data"]["SelectConnectivity"]){if (strcmp(doc["data"]["SelectConnectivity"], "wifi")==0) { this->useETH=false;} else {this->useETH=true;}} else {this->useETH = false;} if (doc["data"]["debuglevel"]) { this->debuglevel = _max(doc["data"]["debuglevel"].as(), 0);} else {this->debuglevel = 0; } if (doc["data"]["SelectLAN"]) { this->LANBoard = doc["data"]["SelectLAN"].as();} else {this->LANBoard = "";} - if (doc["data"]["serial_rx"]) { this->serial_rx = doc["data"]["serial_rx"].as(); } else {this->serial_rx = 3;} - if (doc["data"]["serial_tx"]) { this->serial_tx = doc["data"]["serial_tx"].as(); } else {this->serial_tx = 1;} + if (doc["data"]["serial_rx"]) { this->serial_rx = doc["data"]["serial_rx"].as(); } else {this->serial_rx = RX;} + if (doc["data"]["serial_tx"]) { this->serial_tx = doc["data"]["serial_tx"].as(); } else {this->serial_tx = TX;} if (doc["data"]["auth_user"]) { this->auth_user = doc["data"]["auth_user"].as();} else {this->auth_user = "admin";} if (doc["data"]["auth_pass"]) { this->auth_pass = doc["data"]["auth_pass"].as();} else {this->auth_pass = "password";} diff --git a/src/modbus.cpp b/src/modbus.cpp index c7adf764..f4679832 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -42,8 +42,8 @@ modbus::modbus(): enableRelays(false), this->pin_Relay1 = this->default_pin_Relay1 = 12; this->pin_Relay2 = this->default_pin_Relay2 = 14; } else { - this->pin_RX = this->default_pin_RX = 16; - this->pin_TX = this->default_pin_TX = 17; + this->pin_RX = this->default_pin_RX = DEFAULT_MODBUS_RX_PIN; + this->pin_TX = this->default_pin_TX = DEFAULT_MODBUS_TX_PIN; this->pin_RTS = this->default_pin_RTS = 5; this->pin_Relay1 = this->default_pin_Relay1 = 18; this->pin_Relay2 = this->default_pin_Relay2 = 19; diff --git a/src/modbus.h b/src/modbus.h index 937d12fa..afb30af1 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -17,6 +17,14 @@ #include #include +#ifndef DEFAULT_MODBUS_RX_PIN +#define DEFAULT_MODBUS_RX_PIN 16 +#endif + +#ifndef DEFAULT_MODBUS_TX_PIN +#define DEFAULT_MODBUS_TX_PIN 17 +#endif + //#define DEBUGMODE class modbus { From 8c690d3d89bb7fa0050bc198843f4715e318bb79 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 28 Apr 2025 13:51:21 +0200 Subject: [PATCH 077/106] fix: Update ChangeLog for release 3.3.4 to include default pin changes for ESP32-C3 --- ChangeLog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index fae17cab..831f6354 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,5 @@ Release 3.3.4: - - + - Change default pins for ESP32-C3 (#149), thanks to @NHellFire Release 3.3.3: - improve logging functionality methods From 7e5c2654154e05c768cabe2342e8891ab31e7c48 Mon Sep 17 00:00:00 2001 From: Lazgar <34341913+Lazgar@users.noreply.github.com> Date: Tue, 24 Jun 2025 19:39:53 +0200 Subject: [PATCH 078/106] Write multiregister (#154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Solax-X3.json neue Werte aus den Livedaten: BatChargeableEnergy BatDischargeableEnergy Set Befehle entfernt die nicht geschrieben werden können: setTargetSoC (+ der Wert aus den Live) setTargetSetType setModbusPowerControl (+ der Wert aus den Live) * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.h * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.h * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update Solax-X3.json * Update modbus.cpp * Update modbus.cpp * Update modbus.cpp * Update Solax-X3.json * Update Solax-X3.json * Update modbus.cpp * Update modbus.h * Update modbus.cpp * Update modbus.cpp --- data/regs/Solax-X3.json | 271 +++++++++++++++++++++++++--------------- src/modbus.cpp | 94 +++++++++++--- src/modbus.h | 1 + 3 files changed, 250 insertions(+), 116 deletions(-) diff --git a/data/regs/Solax-X3.json b/data/regs/Solax-X3.json index 10e3cf25..89a64a68 100644 --- a/data/regs/Solax-X3.json +++ b/data/regs/Solax-X3.json @@ -348,33 +348,6 @@ ] ] }, - { - "position": [ - 525, - 526 - ], - "name": "ModbusPowerControl", - "realname": "Modbus Power Control", - "datatype": "integer", - "mapping": [ - [ - 0, - "Off" - ], - [ - 1, - "PowerCtrl" - ], - [ - 2, - "ElectricQuantityCtrl" - ], - [ - 3, - "SoCTargetCtrl" - ] - ] - }, { "position": [ 55, @@ -471,6 +444,30 @@ "openwbtopic": "setbatsoc", "unit": "%" }, + { + "position": [ + 575, + 576, + 573, + 574 + ], + "name": "BatDischargeableEnergy", + "realname": "Battery Dischargeable Energy", + "datatype": "integer", + "unit": "Wh" + }, + { + "position": [ + 571, + 572, + 569, + 570 + ], + "name": "BatChargeableEnergy", + "realname": "Battery Chargeable Energy", + "datatype": "integer", + "unit": "Wh" + }, { "position": [ 388, @@ -491,16 +488,6 @@ "datatype": "integer", "unit": "%" }, - { - "position": [ - 579, - 580 - ], - "name": "BatTargetSoC", - "realname": "Battery Target SoC", - "datatype": "integer", - "unit": "%" - }, { "position": [ 67, @@ -1059,6 +1046,130 @@ "datatype": "float", "factor": 0.001, "unit": "V" + }, + { + "position": [ + 525, + 526 + ], + "name": "VPPControlMode", + "realname": "VPP Control Mode", + "datatype": "integer", + "mapping": [ + [ + 0, + "Disabled" + ], + [ + 1, + "PowerControl" + ], + [ + 2, + "ElectricQuantityControl" + ], + [ + 3, + "SoCTargetControl" + ] + ] + }, + { + "position": [ + 527, + 528 + ], + "name": "VPPControlStatus", + "realname": "VPP Control Status", + "datatype": "integer", + "mapping": [ + [ + 0, + "Unfinished" + ], + [ + 1, + "Finished" + ] + ] + }, + { + "position": [ + 531, + 532, + 529, + 530 + ], + "name": "VPPActivePowerTarget", + "realname": "VPP Active Power Target", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 535, + 536, + 533, + 534 + ], + "name": "VPPReactivePowerTarget", + "realname": "VPP Reactive Power Target", + "datatype": "integer", + "unit": "VA" + }, + { + "position": [ + 563, + 564, + 561, + 562 + ], + "name": "VPPTargetEnergy", + "realname": "VPP Target Energy", + "datatype": "integer", + "unit": "Wh" + }, + { + "position": [ + 579, + 580 + ], + "name": "VPPTargetSoC", + "realname": "VPP Target SoC", + "datatype": "integer", + "unit": "%" + }, + { + "position": [ + 567, + 568, + 565, + 566 + ], + "name": "VPPChargeDischargePower", + "realname": "VPP Charge Discharge Power", + "datatype": "integer", + "unit": "W" + }, + { + "position": [ + 577, + 578 + ], + "name": "VPPDuration", + "realname": "VPP Duration", + "datatype": "integer", + "unit": "s" + }, + { + "position": [ + 585, + 586 + ], + "name": "VPPCtrlTimeout", + "realname": "VPP Ctrl Timeout", + "datatype": "integer", + "unit": "s" } ], "id": [ @@ -1648,67 +1759,6 @@ "0x9e" ] }, - { - "name": "setModbusPowerControl", - "realname": "Modbus Power Control", - "info": "accepted values:", - "mapping": [ - [ - "Off", - 0 - ], - [ - "PowerCtrl", - 1 - ], - [ - "ElectricQuantityCtrl", - 2 - ], - [ - "SoCTargetCtrl", - 3 - ] - ], - "request": [ - "#ClientID", - "0x10", - "0x00", - "0x7c" - ] - }, - { - "name": "setTargetSetType", - "realname": "Target Set Type", - "info": "accepted values:", - "mapping": [ - [ - "Set", - 1 - ], - [ - "Update", - 2 - ] - ], - "request": [ - "#ClientID", - "0x10", - "0x00", - "0x7d" - ] - }, - { - "name": "setTargetSoC", - "realname": "Target SoC", - "info": "set 0 - 100 in percent", - "request": [ - "#ClientID", - "0x10", - "0x00", - "0x83" - ] - }, { "name": "setPgridBias", "realname": "Pgrid Bias", @@ -1843,6 +1893,31 @@ "0x66" ] }, + { + "name": "setVPPControl", + "realname": "VPP Control", + "info": "VPP Control Function, a comma separated List of Values. Check Solax Documentation for Details.", + "intsize": [ + "int16", + "int16", + "int32", + "int32", + "int16", + "int16", + "int32", + "int32", + "int16", + "int32" + ], + "request": [ + "#ClientID", + "0x10", + "0x00", + "0x7C", + "0x00", + "0x0F" + ] + }, { "name": "setChargePeriod1_StartTime", "realname": "Forced Charge Period Start Time", diff --git a/src/modbus.cpp b/src/modbus.cpp index f4679832..4a9ecfa0 100644 --- a/src/modbus.cpp +++ b/src/modbus.cpp @@ -189,27 +189,69 @@ void modbus::ReceiveMQTT(String topic, String msg) { byte e = this->String2Byte(x); request.push_back(e); } + //added by Lazgar + if (!elem["intsize"].isNull()) { // wenn "intsize" gefunden wird, muss es als multiregister beschrieben werden + + byte bn = this->String2Byte(arr[5]); // der letzte Value aus "arr" auslesen und in Byte umwandeln (byte number) + request.push_back(bn*2); //Byte verdoppeln und dem "request" anhängen + + JsonArray sizearr = elem["intsize"].as(); //intsize als Array verfügbar machen + + std::vector mparts = splitStringToVector(msg); // Spliten der "msg" in einzelne Strings und in einen Vector laden + + if (mparts.size() != sizearr.size()) { + Config->logN(1, "The correct number of values ​​was not passed (%s)", sizearr.size()); + return; + } + + for (uint8_t z = 0; z < mparts.size(); z++ ) { //Schleife zum umwandeln der Strings und anhängen an den "request" + + int msgInt = mparts.at(z).toInt(); // atoi(msg.c_str()) + byte bytes[4]; + + bytes[0] = (msgInt >> 24) & 0xFF; + bytes[1] = (msgInt >> 16) & 0xFF; + bytes[2] = (msgInt >> 8) & 0xFF; + bytes[3] = (msgInt >> 0) & 0xFF; + + if (sizearr[z] == "int32") { // bei int32 werden 4 byte dem "request" angehängt + + request.push_back(bytes[2]); // LSB zuerst + request.push_back(bytes[3]); + request.push_back(bytes[0]); // MSB danach + request.push_back(bytes[1]); + + } else { // bei int16 werden 2 byte dem "request" angehängt + + request.push_back(bytes[2]); + request.push_back(bytes[3]); + + } + + } + + } else { // übliche abarbeitung normaler Set Befehle + // map values if a mapping is specified + if(!elem["mapping"].isNull() && elem["mapping"].is() && msg != "") { + Config->logN(4, "Map values for item %s", msg.c_str()); + + JsonArray map = elem["mapping"].as(); + msg = this->MapItem(map, msg, true); + } - // map values if a mapping is specified - if(!elem["mapping"].isNull() && elem["mapping"].is() && msg != "") { - Config->logN(4, "Map values for item %s", msg.c_str()); - - JsonArray map = elem["mapping"].as(); - msg = this->MapItem(map, msg, true); - } - - int msgInt = msg.toInt(); // atoi(msg.c_str()) - byte bytes[4]; - - bytes[0] = (msgInt >> 24) & 0xFF; - bytes[1] = (msgInt >> 16) & 0xFF; - bytes[2] = (msgInt >> 8) & 0xFF; - bytes[3] = (msgInt >> 0) & 0xFF; + int msgInt = msg.toInt(); // atoi(msg.c_str()) + byte bytes[4]; - // 32bit number - request.push_back(bytes[2]); - request.push_back(bytes[3]); + bytes[0] = (msgInt >> 24) & 0xFF; + bytes[1] = (msgInt >> 16) & 0xFF; + bytes[2] = (msgInt >> 8) & 0xFF; + bytes[3] = (msgInt >> 0) & 0xFF; + // 32bit number + request.push_back(bytes[2]); + request.push_back(bytes[3]); + } + Config->logN(3, "MQTT Setter found: %s" ,this->Setters->at(i).Name.c_str()); Config->logN(3, "Initiate Set Request to queue: %s" ,(this->PrintDataFrame(&request)).c_str()); @@ -381,6 +423,22 @@ byte modbus::String2Byte(String s){ return ret; } +/******************************************************* + * split a String into Vector with comma as separator +*******************************************************/ +std::vector modbus::splitStringToVector(String msg){ + std::vector subStrings; + int j=0; + for(int i =0; i < msg.length(); i++){ + if(msg.charAt(i) == ','){ + subStrings.push_back(msg.substring(j,i)); + j = i+1; + } + } + subStrings.push_back(msg.substring(j,msg.length())); //to grab the last value of the string + return subStrings; +} + /******************************************************* * Enable MQTT Transmission *******************************************************/ diff --git a/src/modbus.h b/src/modbus.h index afb30af1..425e2cdb 100644 --- a/src/modbus.h +++ b/src/modbus.h @@ -139,6 +139,7 @@ class modbus { String ConvertIntToBinaryString(int n, int numBits); void ReadRelays(); void SendDataToWebSocket(std::vector* vector); + std::vector splitStringToVector(String msg); // inverter config, in sync with register.h ->config ArduinoQueue>* ReadQueue; From d1c2718800a47496d63d24b4560560c88fbfe606 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 18 Aug 2025 15:15:07 +0200 Subject: [PATCH 079/106] remove deprecated backup functionality from Elegant-OTA --- ChangeLog.md | 1 + src/MyWebServer.cpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 831f6354..b060324e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,6 @@ Release 3.3.4: - Change default pins for ESP32-C3 (#149), thanks to @NHellFire + - remove deprecated backup functionality from Elegant-OTA Release 3.3.3: - improve logging functionality methods diff --git a/src/MyWebServer.cpp b/src/MyWebServer.cpp index 0acf8a2e..ad367350 100644 --- a/src/MyWebServer.cpp +++ b/src/MyWebServer.cpp @@ -33,7 +33,6 @@ MyWebServer::MyWebServer(AsyncWebServer *server, DNSServer* dns): ElegantOTA.begin(server); // Start ElegantOTA ElegantOTA.setGitEnv(String(GIT_OWNER), String(GIT_REPO), String(GIT_BRANCH), String(GITHUB_RUN).toInt()); ElegantOTA.setFWVersion(String(Config->GetReleaseName() + " / Build: " + GITHUB_RUN )); - ElegantOTA.setBackupRestoreFS("/config"); ElegantOTA.setAutoReboot(true); //ElegantOTA callbacks From a96d3b67f62fe1d8c98df4319448e1cb44c03bdf Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 18 Aug 2025 17:16:37 +0200 Subject: [PATCH 080/106] add python.env file with HTML_DIR variable for handlefiles script --- .gitignore | 1 + ChangeLog.md | 5 + data/web/handlefiles.html | 90 ----------- data/web/handlefiles.js | 308 -------------------------------------- include/_Release.h | 2 +- platformio.ini | 1 + python.env | 1 + 7 files changed, 9 insertions(+), 399 deletions(-) delete mode 100644 data/web/handlefiles.html delete mode 100644 data/web/handlefiles.js create mode 100644 python.env diff --git a/.gitignore b/.gitignore index 84b3952b..b112f02f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .pio/ .vscode/ .git/ +data/web/handlefiles* diff --git a/ChangeLog.md b/ChangeLog.md index b060324e..1f87127e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,8 @@ +Release 3.4.0: + +++++++ This version is not OTA compatible with older versions +++++++ + +++++++ Please do a fresh installation by web-installer +++++++ + - add python.env file with HTML_DIR variable + Release 3.3.4: - Change default pins for ESP32-C3 (#149), thanks to @NHellFire - remove deprecated backup functionality from Elegant-OTA diff --git a/data/web/handlefiles.html b/data/web/handlefiles.html deleted file mode 100644 index ad57e35b..00000000 --- a/data/web/handlefiles.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - HandleFiles - - -
- - - - - - - - - - - - - - - - - - - -
DateienInhalt
- - - - - -
- path: - -
{path}
- -
-
- -
-
filename: - - - - - - -
-
-

Are you sure you want to delete this file?

- - -
-

-
-
- Connection Status: - -
-
- - \ No newline at end of file diff --git a/data/web/handlefiles.js b/data/web/handlefiles.js deleted file mode 100644 index f7aa83bc..00000000 --- a/data/web/handlefiles.js +++ /dev/null @@ -1,308 +0,0 @@ -// https://jsfiddle.net/tobiasfaust/uc1jfpgb/ - -import * as global from './Javascript.js'; - -var DirJson; - -// ************************************************ -export const functionMap = { - files_Callback: MyCallback -}; - -// ************************************************ -export function init1() { - var data = {"JS": {"listdir": [ - {"path": "/", "content": [{"name": "file1.txt", "isDir": 0}, {"name": "file2.txt", "isDir": 0}, {"name": "dir1", "isDir": 1}]}, - {"path": "/dir1", "content": [{"name": "file3.txt", "isDir": 0}, {"name": "file4.txt", "isDir": 0}]} - ]}, - "response": {"status": 1, "text": "successful"}, - "cmd": {"callbackFn": "files_Callback", "startpath": "/"} - }; - - global.handleJsonItems(data); - - document.getElementById('fullpath').innerHTML = ''; // div - document.getElementById('filename').value = ''; // input field - document.getElementById('content').value = ''; - - document.querySelector("#loader").style.visibility = "hidden"; - document.querySelector("body").style.visibility = "visible"; -} - -export function init() { - // Initiale Verbindung aufbauen - global.connectWebSocket(); - - // Warte bis die WebSocket-Verbindung aufgebaut ist - let checkWebSocketInterval = setInterval(() => { - if (global.ws && global.ws.readyState === WebSocket.OPEN) { - clearInterval(checkWebSocketInterval); - GetInitData("/"); - } - }, 100); -} - -// ************************************************ -export function GetInitData(startpath) { - var data = {}; - data['cmd'] = {}; - data['cmd']['action'] = "handlefiles"; - data['cmd']['subaction'] = "listDir" - data['cmd']['startpath'] = startpath; - data['cmd']['callbackFn'] = "files_Callback"; - - global.requestData(data); - - document.getElementById('fullpath').innerHTML = ''; // div - document.getElementById('filename').value = ''; // input field - document.getElementById('content').value = ''; - - document.querySelector("#loader").style.visibility = "hidden"; - document.querySelector("body").style.visibility = "visible"; -} - -// ************************************************ -function MyCallback(json) { - DirJson = json["JS"].listdir; - listFiles(json["cmd"]["startpath"]); -} - -// ************************************************ -// show content of fetched file -// ************************************************ -function setContent(string, file) { - document.getElementById('fullpath').innerHTML = file; // div - document.getElementById('filename').value = basename(file); // input field - - if (file.endsWith("json")) { - document.getElementById('content').value = JSON.stringify(JSON.parse(string), null, 2); - } else { - document.getElementById('content').value = string; - } -} - -// *********************************** -// fetch file from host -// *********************************** -export function fetchFile(file) { - document.getElementById('content').value = "loading "+file+"..."; - - fetch(file) - .then(response => response.text()) - .then(textString => setContent(textString, file)); -} - -// *********************************** -// show directory structure -// *********************************** -export function listFiles(path) { - var table = document.querySelector('#files'), - row = document.querySelector('#NewRow'), - cells, tr_tpl, DirJsonLocal; - - // cleanup table - table.replaceChildren(); - - // get the right part - for(let i = 0; i < DirJson.length; i++) { - if (DirJson[i].path == path) { - DirJsonLocal = DirJson[i] - } - } - - // show path information - document.getElementById('path').innerHTML = path; - - // set "back" item if not root - if (path != '/') { - tr_tpl = document.importNode(row.content, true); - cells = tr_tpl.querySelectorAll("td"); - cells.forEach(function (item, index) { - var text = item.innerHTML; - var oc = "listFiles('" + getParentPath(path) + "')" - text = text.replaceAll("{file}", '..'); - item.innerHTML = text; - item.setAttribute('onClick', oc); - }); - table.appendChild(tr_tpl); - } - - // show files - DirJsonLocal.content.forEach(function (file) { - // template "laden" (lies: klonen) - tr_tpl = document.importNode(row.content, true); - cells = tr_tpl.querySelectorAll("td"); - cells.forEach(function (item, index) { - var text = item.innerHTML; - var oc; - - if(file.isDir == 0) { - oc = item.getAttribute('onClick'); - var newPath = DirJsonLocal.path + "/" + file.name; - if (newPath.startsWith("//")) {newPath = newPath.substring(1)} - oc = oc.replaceAll("{fullpath}", newPath); - text = text.replaceAll("{file}", file.name); - } else if(file.isDir == 1) { - var newPath = DirJsonLocal.path + "/" + file.name; - if (newPath.startsWith("//")) {newPath = newPath.substring(1)} - oc = "listFiles('" + newPath + "')" - text = text.replaceAll("{file}", file.name + "/"); - } - - - item.innerHTML = text; - item.setAttribute('onClick', oc); - }); - table.appendChild(tr_tpl); - }) - } - -// *********************************** -// returns parent path: '/regs/web' -> '/regs' -// *********************************** -function getParentPath(path) { - var ParentPath, PathArray; - - PathArray = path.split('/') - PathArray.pop() - if (PathArray.length == 1) { ParentPath = '/' } - else { ParentPath = PathArray.join('/')} - return ParentPath -} - -// *********************************** -// extract the filename from path -// *********************************** -function basename(str) { - return str.split('\\').pop().split('/').pop(); -} - -// *********************************** -// return true if valid json, otherwise false -// *********************************** -function validateJson(json) { - try { - JSON.parse(json); - return true; - } catch { - return false; - } -} - -// *********************************** -// download content of textarea as filename on local pc -// *********************************** -export function downloadFile() { - var textToSave = document.getElementById("content").value; - var textToSaveAsBlob = new Blob([textToSave], {type:"text/plain"}); - var textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob); - var fileNameToSaveAs = document.getElementById("filename").value; - - if (fileNameToSaveAs != '') { - var downloadLink = document.createElement("a"); - downloadLink.download = fileNameToSaveAs; - downloadLink.innerHTML = "Download File"; - downloadLink.href = textToSaveAsURL; - - downloadLink.onclick = destroyClickedElement; - downloadLink.style.display = "none"; - document.body.appendChild(downloadLink); - downloadLink.click(); - } else { global.setResponse(false, 'Filename is empty, Please define it.');} -} - -function destroyClickedElement(event) -{ - document.body.removeChild(event.target); -} - -// *********************************** -// store content of textarea -// *********************************** -export function uploadFile() { - var textToSave = document.getElementById("content").value; - var textToSaveAsBlob = new Blob([textToSave], {type:"text/plain"}); - var fileNameToSaveAs = document.getElementById("filename").value; - var pathOfFile = document.getElementById('path').innerHTML; - - if (fileNameToSaveAs != '') { - if (fileNameToSaveAs.toLowerCase().endsWith('.json')) { - if (!validateJson(textToSave)) { - global.setResponse(false, 'Json invalid') - return; - } - } - - global.setResponse(true, 'Please wait for saving ...'); - - const formData = new FormData(); - formData.append(fileNameToSaveAs, textToSaveAsBlob, pathOfFile + '/' + fileNameToSaveAs); - - fetch('/doUpload', { - method: 'POST', - body: formData, - }) - .then (response => response.json()) - .then (json => { - global.setResponse(true, json.text) - }); - - } else { global.setResponse(false, 'Filename is empty, Please define it.');} -} - -// ************************************************ -export function deleteFile() { - var pathOfFile = document.getElementById('path').innerHTML; - var fileName = document.getElementById("filename").value; - - if (fileName != '') { - var data = {}; - data['cmd'] = {}; - data['cmd']['action'] = 'handlefiles'; - data['cmd']['subaction'] = "deleteFile"; - data['cmd']['filename'] = pathOfFile + '/' + fileName; - - global.setResponse(true, 'Please wait for deleting ...'); - global.requestData(data); - GetInitData(pathOfFile); - } else { global.setResponse(false, 'Filename is empty, Please define it.');} -} - -// *********************************** -// backup complete filesystem of ESP by zipfile -// -// https://gist.github.com/noelvo/4502eea719f83270c8e9 -// *********************************** -export function backup() { - var url = []; - - for(let i = 0; i < DirJson.length; i++) { - DirJson[i].content.forEach(function (file) { - if (file.isDir==0) { - //console.log(DirJson[i].path, file.name) - url.push(DirJson[i].path + "/" + file.name) - } - }) - } - compressed_img(url, "backup"); -} - -function compressed_img(urls, nombre) { - var zip = new JSZip(); - var count = 0; - var name = nombre + ".zip"; - urls.forEach(function(url){ - JSZipUtils.getBinaryContent(url, function (err, data) { - if(err) { - throw err; - } - zip.file(url, data, {binary:true}); - count++; - if (count == urls.length) { - zip.generateAsync({type:'blob'}).then(function(content) { - saveAs(content, name); - }); - } - }); - }); -} \ No newline at end of file diff --git a/include/_Release.h b/include/_Release.h index ad287562..5c1196df 100644 --- a/include/_Release.h +++ b/include/_Release.h @@ -1 +1 @@ -#define Release "3.3.4" +#define Release "3.4.0" diff --git a/platformio.ini b/platformio.ini index 47dadced..ef994b36 100644 --- a/platformio.ini +++ b/platformio.ini @@ -42,6 +42,7 @@ lib_deps = https://github.com/YiannisBourkelis/Uptime-Library.git https://github.com/tobiasfaust/Improv-WiFi-Library.git https://github.com/tobiasfaust/ElegantOTA.git + https://github.com/tobiasfaust/esp-handlefiles.git ;https://github.com/mathieucarbou/AsyncTCP https://github.com/mathieucarbou/ESPAsyncWebServer@3.6.0 ; installing by ElegantOTA diff --git a/python.env b/python.env new file mode 100644 index 00000000..64772c40 --- /dev/null +++ b/python.env @@ -0,0 +1 @@ +HTML_DIR=data/web # spezifisches unterverzeichnis für HTML Dateien, gebraucht fuer handlefiles script From 2e9788e1fb0aac735ce04f72038fcb6f4bc738c3 Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Mon, 18 Aug 2025 17:19:31 +0200 Subject: [PATCH 081/106] remove: delete handleFiles copy --- src/handleFiles.cpp | 129 -------------------------------------------- src/handleFiles.h | 23 -------- 2 files changed, 152 deletions(-) delete mode 100644 src/handleFiles.cpp delete mode 100644 src/handleFiles.h diff --git a/src/handleFiles.cpp b/src/handleFiles.cpp deleted file mode 100644 index ebb67a2b..00000000 --- a/src/handleFiles.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/******************************************************** - * Copyright [2024] Tobias Faust on("/doUpload", HTTP_POST, [](AsyncWebServerRequest *request) {}, - std::bind(&handleFiles::handleUpload, this, std::placeholders::_1, - std::placeholders::_2, - std::placeholders::_3, - std::placeholders::_4, - std::placeholders::_5, - std::placeholders::_6)); - -} - -//############################################################### -// returns the complete folder structure -//############################################################### -void handleFiles::getDirList(JsonArray json, String path) { - JsonDocument doc; - JsonObject jsonRoot = doc.to(); - - jsonRoot["path"] = path; - JsonArray content = jsonRoot["content"].to(); - - File FSroot = LittleFS.open(path); - File file = FSroot.openNextFile(); - - while (file) { - JsonDocument doc1; - JsonObject jsonObj = doc1.to(); - String fname(file.name()); - jsonObj["name"] = fname; - - if(file.isDirectory()){ - jsonObj["isDir"] = 1; - String p = path + "/" + fname; - if (p.startsWith("//")) { p = p.substring(1); } - this->getDirList(json, p); // recursive call - } else { - jsonObj["isDir"] = 0; - } - - content.add(jsonObj); - file.close(); - file = FSroot.openNextFile(); - } - FSroot.close(); - json.add(jsonRoot); -} - -//############################################################### -// returns the requested data from Webserver.cpp -//############################################################### -void handleFiles::HandleRequest(JsonDocument& json) { - String subaction = ""; - if (json["cmd"]["subaction"]) {subaction = json["cmd"]["subaction"].as();} - - Config->logN(3, "handle Request in handleFiles.cpp: %s", subaction.c_str()); - - if (subaction == "listDir") { - JsonArray content = json["JS"]["listdir"].to(); - - this->getDirList(content, "/"); - Config->logN(5, json["content"].as().c_str()); - - } else if (subaction == "deleteFile") { - String filename(""); - - Config->logN(3, "Request to delete file %s", filename.c_str()); - - if (json["cmd"]["filename"]) {filename = json["cmd"]["filename"].as();} - - if (LittleFS.remove(filename)) { - json["response"]["status"] = 1; - json["response"]["text"] = "deletion successful"; - } else { - json["response"]["status"] = 0; - json["response"]["text"] = "deletion failed"; - } - Config->logN(3, json.as().c_str()); - } -} - -//############################################################### -// store a file at Filesystem -//############################################################### -void handleFiles::handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - - Config->logN(5, "Client: %s %s", request->client()->remoteIP().toString().c_str(), request->url().c_str());; - - if (!index) { - // open the file on first call and store the file handle in the request object - request->_tempFile = LittleFS.open(filename, "w"); - Config->logN(5, "Upload Start: %s", filename.c_str()); - } - - if (len) { - // stream the incoming chunk to the opened file - request->_tempFile.write(data, len); - Config->logN(3, "Writing file: %s ,index=%d len=%d bytes, FreeMem: %d", filename.c_str(), index, len, ESP.getFreeHeap()); - } - - if (final) { - // close the file handle as the upload is now done - request->_tempFile.close(); - Config->logN(3, "Upload Complete: %s ,size: %d Bytes", filename.c_str(), (index + len)); - - AsyncResponseStream *response = request->beginResponseStream("text/json"); - response->addHeader("Server","ESP Async Web Server"); - - JsonDocument jsonReturn; - String ret; - - jsonReturn["status"] = 1; - jsonReturn["text"] = "OK"; - - serializeJson(jsonReturn, ret); - response->print(ret); - request->send(response); - - if (Config->GetDebugLevel() >=5) { - serializeJson(jsonReturn, Serial); - } - } -} diff --git a/src/handleFiles.h b/src/handleFiles.h deleted file mode 100644 index 75a8b133..00000000 --- a/src/handleFiles.h +++ /dev/null @@ -1,23 +0,0 @@ -/******************************************************** - * Copyright [2024] Tobias Faust - -class handleFiles { - public: - handleFiles(AsyncWebServer *server); - - void HandleRequest(JsonDocument& json); - void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); - - private: - void getDirList(JsonArray json, String path); -}; - -#endif From 2cf9fb0987acaad7bbe18ed0c0aeca3ec599425d Mon Sep 17 00:00:00 2001 From: Tobias Faust Date: Tue, 19 Aug 2025 11:37:21 +0200 Subject: [PATCH 082/106] splitting LittleFS partition into SystemFS and ConfigFS --- .github/scripts/createManifest.py | 2 +- .github/scripts/createMergedFirmware.py | 22 +++++----- .github/scripts/myUtils.py | 12 +++--- ChangeLog.md | 1 + data/web/Javascript.js | 14 +++++++ data/web/baseconfig.html | 6 +-- data/web/index.html | 4 +- data/web/modbusconfig.html | 6 +-- data/web/modbusitemconfig.html | 6 +-- data/web/navi.html | 32 ++++++++++++--- data/web/navi.js | 32 +++++++++++++++ data/web/rawdata.html | 6 +-- data/web/reboot.html | 2 +- data/web/status.html | 8 ++-- partitions.csv | 5 ++- src/MyWebServer.cpp | 32 ++++++++------- src/MyWebServer.h | 22 +++++----- src/baseconfig.cpp | 34 +++++----------- src/baseconfig.h | 3 +- src/main.cpp | 29 ++++++++++++-- src/modbus.cpp | 53 ++++++++++++++----------- src/modbus.h | 4 +- src/openwb.cpp | 8 ++-- src/openwb.h | 10 +++-- 24 files changed, 224 insertions(+), 129 deletions(-) diff --git a/.github/scripts/createManifest.py b/.github/scripts/createManifest.py index 9d79caf5..130c9e8e 100644 --- a/.github/scripts/createManifest.py +++ b/.github/scripts/createManifest.py @@ -124,7 +124,7 @@ if os.path.isfile(os.path.join(args.binarypath, "littlefs.bin")): files_data["parts"].append({ "path": f"https://tobiasfaust.github.io/{args.repository}/firmware/{SubDir}/{FIRMWARENAME}/littlefs.{FileExtension}.bin", - "offset": int(readOffsetFromPartitionCSV("partitions.csv", "spiffs"), 16), + "offset": int(readOffsetFromPartitionCSV("partitions.csv", "webdata"), 16), "filetype": "filesystem" }) diff --git a/.github/scripts/createMergedFirmware.py b/.github/scripts/createMergedFirmware.py index b89ac3ad..d34f4c18 100644 --- a/.github/scripts/createMergedFirmware.py +++ b/.github/scripts/createMergedFirmware.py @@ -9,7 +9,7 @@ -b, --BuildDir (str): The build directory containing the built firmware binaries. Required. -p, --PathOfPartitionsCSV (str): The path to the partitions.csv file. Default is 'partitions.csv'. Required. Functions: - readOffsetFromPartitionCSV(path: str, name: str) -> int: + readOffsetFromPartitionCSV(path: str, partitionLabel: str) -> int: Args: Returns: Example: @@ -52,25 +52,25 @@ if 'ESP32' in args.ChipFamily: bootloader_offset = bootloader_offsets[args.ChipFamily] - result = f'esptool.py --chip {args.ChipFamily} merge_bin \ + result = f'esptool --chip {args.ChipFamily} merge-bin \ --output {args.BuildDir}/merged-firmware.bin \ - --flash_mode dout \ - --flash_freq 80m \ - --flash_size 4MB \ + --flash-mode dout \ + --flash-freq 80m \ + --flash-size 4MB \ {bootloader_offset} {args.BuildDir}/bootloader.bin \ 0x8000 {args.BuildDir}/partitions.bin \ {readOffsetFromPartitionCSV("partitions.csv", "app0")} {args.BuildDir}/firmware.bin' if os.path.isfile(f'{args.BuildDir}/littlefs.bin'): - result += f' {readOffsetFromPartitionCSV("partitions.csv", "spiffs")} {args.BuildDir}/littlefs.bin' + result += f' {readOffsetFromPartitionCSV("partitions.csv", "webdata")} {args.BuildDir}/littlefs.bin' elif 'ESP8266' in args.ChipFamily and os.path.isfile(f'{args.BuildDir}/littlefs.bin'): - result = f'esptool.py --chip {args.ChipFamily} merge_bin \ + result = f'esptool --chip {args.ChipFamily} merge-bin \ --output {args.BuildDir}/merged-firmware.bin \ - --flash_mode dout \ - --flash_freq 40m \ - --flash_size 4MB \ + --flash-mode dout \ + --flash-freq 40m \ + --flash-size 4MB \ 0x0000 {args.BuildDir}/firmware.bin \ - {readOffsetFromPartitionCSV("partitions.csv", "spiffs")} {args.BuildDir}/littlefs.bin' + {readOffsetFromPartitionCSV("partitions.csv", "webdata")} {args.BuildDir}/littlefs.bin' print(f'command={result}') \ No newline at end of file diff --git a/.github/scripts/myUtils.py b/.github/scripts/myUtils.py index 445eebc9..c2d4b957 100644 --- a/.github/scripts/myUtils.py +++ b/.github/scripts/myUtils.py @@ -358,18 +358,18 @@ def changeURL(root: str, url: str) -> None: except Exception as e: logging.error(f"Fehler beim Verarbeiten der Datei {manifest_path}: {e}") -def readOffsetFromPartitionCSV(path: str, name: str) -> int: +def readOffsetFromPartitionCSV(path: str, partitionLabel: str) -> int: """ - Reads a CSV file and calculates the offset for a given name. + Reads a CSV file and calculates the offset for a given partition label. This function reads the specified CSV file with a delimiter of ','. It fills in the Offset column if it is empty by adding the previous offset and the value from the Size column of the previous row. It returns the calculated - offset from the row where the value in the first column matches the given name as a hexadecimal number. + offset from the row where the value in the first column matches the given partition label as a hexadecimal number. If the file does not exist or cannot be read, it returns None. Args: path (str): The path to the CSV file. - name (str): The name to search for in the first column. + partitionLabel (str): The partition label to search for in the first column. Returns: int: The calculated offset as a hexadecimal number, or None if the file cannot be read. @@ -379,7 +379,7 @@ def readOffsetFromPartitionCSV(path: str, name: str) -> int: with open(path, 'r') as file: lines = file.readlines() headers = lines[0].strip().split(',') - name_index = 0 + partition_label_index = 0 offset_index = 3 size_index = 4 @@ -391,7 +391,7 @@ def readOffsetFromPartitionCSV(path: str, name: str) -> int: columns[offset_index] = str(previous_offset + previous_size) previous_size = int(columns[size_index], 0) previous_offset = int(columns[offset_index], 0) - if columns[name_index] == name: + if columns[partition_label_index] == partitionLabel: return hex(previous_offset) except (FileNotFoundError, IndexError, ValueError): return None \ No newline at end of file diff --git a/ChangeLog.md b/ChangeLog.md index 1f87127e..a226b093 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,7 @@ Release 3.4.0: +++++++ This version is not OTA compatible with older versions +++++++ +++++++ Please do a fresh installation by web-installer +++++++ - add python.env file with HTML_DIR variable + - splitting LittleFS partitions: Separating system files (web UI, static assets) and user configuration/data (settings, user uploads) into distinct partitions (sysFS/configFS) improves reliability, allows safe firmware updates without overwriting user data, and enables easier backup/restore of user settings independently from system files. Release 3.3.4: - Change default pins for ESP32-C3 (#149), thanks to @NHellFire diff --git a/data/web/Javascript.js b/data/web/Javascript.js index 7482d66f..2126961c 100644 --- a/data/web/Javascript.js +++ b/data/web/Javascript.js @@ -675,5 +675,19 @@ export function initDataValues() { document.getElementById('needToSave').classList.add('hide'); } } + +/**************************************************************************************** + * Show or hide an object + * @returns {*} void + * ****************************************************************************************/ +export function toggleView() { + const logView = document.getElementById('logView'); + if (logView.style.display === 'none') { + logView.style.display = 'block'; + } else { + logView.style.display = 'none'; + } +} + /**************************************************************************************** ****************************************************************************************/ diff --git a/data/web/baseconfig.html b/data/web/baseconfig.html index 7bb5b7cc..1c6443f8 100644 --- a/data/web/baseconfig.html +++ b/data/web/baseconfig.html @@ -3,9 +3,9 @@ - - - + + + - + + + - + + + - + + + - + + +