diff --git a/lib/Hoymiles/src/parser/GridProfileParser.cpp b/lib/Hoymiles/src/parser/GridProfileParser.cpp index 35f7689d5..6b8663c5a 100644 --- a/lib/Hoymiles/src/parser/GridProfileParser.cpp +++ b/lib/Hoymiles/src/parser/GridProfileParser.cpp @@ -5,6 +5,7 @@ #include "GridProfileParser.h" #include "../Hoymiles.h" #include +#include GridProfileParser::GridProfileParser() : Parser() @@ -38,3 +39,88 @@ std::vector GridProfileParser::getRawData() HOY_SEMAPHORE_GIVE(); return ret; } + + +int GridProfileParser::modbusCrc(std::string msg) { + int crc = 0xFFFF; + for (int n = 0; n < msg.length(); n++) { + crc ^= msg[n]; + for (int i = 0; i < 8; i++) { + if (crc & 1) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} + +std::string GridProfileParser::parseGridProfile(std::string hex_string) { + + std::vector binary_string; + for (int i = 0; i < hex_string.length(); i += 2) { + std::string byte_string = hex_string.substr(i, 2); + unsigned char byte = std::stoi(byte_string, nullptr, 16); + binary_string.push_back(byte); + } + int binary_length = binary_string.size(); + + int str_header1 = binary_string[0]; + int str_header2 = binary_string[1]; + int str_version1 = binary_string[2]; + int str_version2 = binary_string[3]; + + try { + std::cout << "Grid Profile: " << profile_types[str_header1][str_header2] << std::endl; + } catch (...) { + std::cout << "Grid Profile: unknown" << std::endl; + } + + std::cout << "Version: " << ((str_version1 >> 4) & 0x0F) << "." << (str_version1 & 0x0F) << "." << str_version2 << std::endl; + + int position = 4; + while (position < binary_length) { + int str_table_n = binary_string[position]; + int str_table_v = binary_string[position + 1]; + + try { + std::cout << "Table Type: " << profile_structs[str_table_n] << std::endl; + } catch (...) { + // pass + } + + try { + std::vector> tables_diz = profile_details[str_table_n][str_table_v]; + int table_length = tables_diz.size(); + + position += 2; + for (int x = 0; x < table_length; x++) { + std::vector table_diz = tables_diz[x]; + int str_int = (binary_string[position] << 8) | binary_string[position + 1]; + float str_val = str_int / std::stof(table_diz[2]); + + std::cout << "position: " << position << "\t: " << std::hex << str_int << "\t" << std::dec << str_val << "\t[" << table_diz[1] << "]\t\t[" << table_diz[0] << "]" << std::endl; + position += 2; + } + } catch (...) { + std::string crc = std::to_string(binary_string[position]) + std::to_string(binary_string[position + 1]); + int crc2 = modbusCrc(hex_string.substr(0, position)); + std::string crcc = std::to_string(crc2); + + if (crc == crcc) { + std::cout << "CRC (ok): " << crcc << std::endl; + } else { + std::cout << "CRC (?): " << crc << std::endl; + std::cout << "CRC calced: " << crcc << std::endl; + std::cout << " - possible unknown table (module)" << std::endl; + } + + std::cout << "end" << std::endl; + break; + } + } + + return 0; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/parser/GridProfileParser.h b/lib/Hoymiles/src/parser/GridProfileParser.h index c2af52f87..36acee31e 100644 --- a/lib/Hoymiles/src/parser/GridProfileParser.h +++ b/lib/Hoymiles/src/parser/GridProfileParser.h @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include "Parser.h" +#include +#include #define GRID_PROFILE_SIZE 141 @@ -11,8 +13,220 @@ class GridProfileParser : public Parser { void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len); std::vector getRawData(); + int GridProfileParser::modbusCrc(std::string msg); + std::string parseGridProfile(std::string hex_string); private: uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {}; uint8_t _gridProfileLength = 0; -}; \ No newline at end of file + + std::map> profile_types = { + {0x02, {{0x00, "no data (yet)"}}}, + {0x03, {{0x00, "Germany - DE_VDE4105_2018"}}}, + {0x0a, {{0x00, "European - EN 50549-1:2019"}}}, + {0x0c, {{0x00, "AT Tor - EU_EN50438"}}}, + {0x0d, {{0x04, "France"}}}, + {0x12, {{0x00, "Poland - EU_EN50438"}}}, + {0x37, {{0x00, "Swiss - CH_NA EEA-NE7-CH2020"}}} + }; + + std::map profile_sections = { + {0x00, "Voltage (H/LVRT)"}, + {0x10, "Frequency (H/LFRT)"}, + {0x20, "Island Detection (ID)"}, + {0x30, "Reconnection (RT)"}, + {0x40, "Ramp Rates (RR)"}, + {0x50, "Frequency Watt (FW)"}, + {0x60, "Volt Watt (VW)"}, + {0x70, "Active Power Control (APC)"}, + {0x80, "Volt Var (VV)"}, + {0x90, "Specified Power Factor (SPF)"}, + {0xA0, "Reactive Power Control (RPC)"}, + {0xB0, "Watt Power Factor (WPF)"} + }; + + std::map>>> profile_details = { + {0x00, { + {0x00, { + {"Nominale Voltage (NV)", "V", "10"}, + {"Low Voltage 1 (LV1)", "V", "10"}, + {"LV1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 1 (HV1)", "V", "10"}, + {"HV1 Maximum Trip Time (MTT)", "s", "10"} + }}, + {0x03, { + {"Nominale Voltage (NV)", "V", "10"}, + {"Low Voltage 1 (LV1)", "V", "10"}, + {"LV1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 1 (HV1)", "V", "10"}, + {"HV1 Maximum Trip Time (MTT)", "s", "10"}, + {"Low Voltage 2 (LV2)", "V", "10"}, + {"LV2 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 2 (HV2)", "V", "10"}, + {"HV2 Maximum Trip Time (MTT)", "s", "10"} + }}, + {0x0A, { + {"Nominale Voltage (NV)", "V", "10"}, + {"Low Voltage 1 (LV1)", "V", "10"}, + {"LV1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 1 (HV1)", "V", "10"}, + {"HV1 Maximum Trip Time (MTT)", "s", "10"}, + {"Low Voltage 2 (LV2)", "V", "10"}, + {"LV2 Maximum Trip Time (MTT)", "s", "10"}, + {"10mins Average High Voltage (AHV)", "V", "10"} + }}, + {0x0B, { + {"Nominale Voltage (NV)", "V", "10"}, + {"Low Voltage 1 (LV1)", "V", "10"}, + {"LV1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 1 (HV1)", "V", "10"}, + {"HV1 Maximum Trip Time (MTT)", "s", "10"}, + {"Low Voltage 2 (LV2)", "V", "10"}, + {"LV2 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 2 (HV2)", "V", "10"}, + {"HV2 Maximum Trip Time (MTT)", "s", "10"}, + {"10mins Average High Voltage (AHV)", "V", "10"} + }}, + {0x0C, { + {"Nominale Voltage (NV)", "V", "10"}, + {"Low Voltage 1 (LV1)", "V", "10"}, + {"LV1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 1 (HV1)", "V", "10"}, + {"HV1 Maximum Trip Time (MTT)", "s", "10"}, + {"Low Voltage 2 (LV2)", "V", "10"}, + {"LV2 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 2 (HV2)", "V", "10"}, + {"HV2 Maximum Trip Time (MTT)", "s", "10"}, + {"High Voltage 3 (HV3)", "V", "10"}, + {"HV3 Maximum Trip Time (MTT)", "s", "10"}, + {"10mins Average High Voltage (AHV)", "V", "10"} + }} + }}, + {0x10, { + {0x00, { + {"Nominal Frequency", "Hz", "100"}, + {"Low Frequency 1 (LF1)", "Hz", "100"}, + {"LF1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Frequency 1 (HF1)", "Hz", "100"}, + {"HF1 Maximum Trip time (MTT)", "s", "10"} + }}, + {0x03, { + {"Nominal Frequency", "Hz", "100"}, + {"Low Frequency 1 (LF1)", "Hz", "100"}, + {"LF1 Maximum Trip Time (MTT)", "s", "10"}, + {"High Frequency 1 (HF1)", "Hz", "100"}, + {"HF1 Maximum Trip time (MTT)", "s", "10"}, + {"Low Frequency 2 (LF2)", "Hz", "100"}, + {"LF2 Maximum Trip Time (MTT)", "s", "10"}, + {"High Frequency 2 (HF2)", "Hz", "100"}, + {"HF2 Maximum Trip time (MTT)", "s", "10"} + }} + }}, + {0x20, { + {0x00, { + {"ID Function Activated", "bool", "1"} + }} + }}, + {0x30, { + {0x03, { + {"Reconnect Time (RT)", "s", "10"}, + {"Reconnect High Voltage (RHV)", "V", "10"}, + {"Reconnect Low Voltage (RLV)", "V", "10"}, + {"Reconnect High Frequency (RHF)", "Hz", "100"}, + {"Reconnect Low Frequency (RLF)", "Hz", "100"} + }} + }}, + {0x40, { + {0x00, { + {"Normal Ramp up Rate(RUR_NM)", "Rated%/s", "100"}, + {"Soft Start Ramp up Rate (RUR_SS)", "Rated%/s", "100"} + }} + }}, + {0x50, { + {0x00, { + {"FW Function Activated", "bool", "1"}, + {"Start of Frequency Watt Droop (Fstart)", "Hz", "100"}, + {"FW Droop Slope (Kpower_Freq)", "Pn%/Hz", "10"}, + {"Recovery Ramp Rate (RRR)", "Pn%/s", "100"} + }}, + {0x01, { + {"FW Function Activated", "bool", "1"}, + {"Start of Frequency Watt Droop (Fstart)", "Hz", "100"}, + {"FW Droop Slope (Kpower_Freq)", "Pn%/Hz", "10"}, + {"Recovery Ramp Rate (RRR)", "Pn%/s", "100"}, + {"Recovery High Frequency (RVHF)", "Hz", "100"} // may need to be div 10 + }}, + {0x08, { + {"FW Function Activated", "bool", "1"}, + {"Start of Frequency Watt Droop (Fstart)", "Hz", "100"}, + {"FW Droop Slope (Kpower_Freq)", "Pn%/Hz", "10"}, + {"Recovery Ramp Rate (RRR)", "Pn%/s", "100"}, + {"Recovery High Frequency (RVHF)", "Hz", "100"}, // may need to be div 10 + {"Recovery Low Frequency (RVLF)", "Hz", "100"} + }} + }}, + {0x60, { + {0x00, { + {"VW Function Activated", "bool", "1"}, + {"Start of Voltage Watt Droop (Vstart)", "V", "10"}, + {"End of Voltage Watt Droop (Vend)", "V", "10"}, + {"Droop Slope (Kpower_Volt)", "Pn%/V", "100"} + }}, + {0x04, { + {"VW Function Activated", "bool", "1"}, + {"Start of Voltage Watt Droop (Vstart)", "V", "10"}, + {"End of Voltage Watt Droop (Vend)", "V", "10"}, + {"Droop Slope (Kpower_Volt)", "Pn%/V", "100"} + }} + }}, + {0x70, { + {0x00, { + {"APC Function Activated", "bool", "1"} + }}, + {0x02, { + {"APC Function Activated", "bool", "1"}, + {"Power Ramp Rate (PRR)", "Pn%/s", "100"} + }} + }}, + {0x80, { + {0x00, { + {"VV Function Activated", "bool", "1"}, + {"Voltage Set Point V1", "V", "10"}, + {"Reactive Set Point Q1", "%Pn", "10"}, + {"Voltage Set Point V2", "V", "10"}, + {"Voltage Set Point V3", "V", "10"}, + {"Voltage Set Point V4", "V", "10"}, + {"Reactive Set Point Q4", "%Pn", "10"} + }}, + {0x01, { + {"VV Function Activated", "bool", "1"}, + {"Voltage Set Point V1", "V", "10"}, + {"Reactive Set Point Q1", "%Pn", "10"}, + {"Voltage Set Point V2", "V", "10"}, + {"Voltage Set Point V3", "V", "10"}, + {"Voltage Set Point V4", "V", "10"}, + {"Reactive Set Point Q4", "%Pn", "10"}, + {"Setting Time (Tr)", "s", "10"} + }} + }}, + {0x90, { + {0x00, { + {"SPF Function Activated", "bool", "1"}, + {"Power Factor (PF)", "", "100"} + }} + }}, + {0xA0, { + {0x02, { + {"RPC Function Activated", "bool", "1"}, + {"Reactive Power (VAR)", "%Sn", "1"} + }} + }}, + {0xB0, { + {0x00, { + {"WPF Function Activated", "bool", "1"}, + {"Start of Power of WPF (Pstart)", "%Pn", "10"}, + {"Power Factor ar Rated Power (PFRP)", "", "100"} + }} + }} + }; +};