Skip to content

Instantly share code, notes, and snippets.

@noone2k
Last active December 25, 2023 21:45
Show Gist options
  • Save noone2k/0b3a116a6f35286abef7199b62a0777a to your computer and use it in GitHub Desktop.
Save noone2k/0b3a116a6f35286abef7199b62a0777a to your computer and use it in GitHub Desktop.
quick and dirty hoymles grid profile parser
#!/usr/bin/python3
#
# hoymiles grid profile parser
# quick and dirty
# dyn table/version
#
# param 1: grid profile
# f.ex. python3 _hgp_qd.py 0a002001000c08fc07a3000f09e2001e064a00140a5500140ac8000a09e21003138812c0001413ec0014128e000514500005200000013003025809e207a3139c1356400007d0001050010001139c0190001000006000000109e20a5a021580010000085b012c08b70941099d012c006490000000005fb000000001f4005f700200012710a00200000000cbfe00
#
import sys
hex_string = sys.argv[1]
table_types={ 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)" }
### country / regulations or simple internal DB ID ? ;)
# 02 00 - austria ???? ( empty list ) / IEEE 1547 240V (?)
# 03 00 - germany - DE_VDE4105_2018 (2.0.x)
# 03 01 - unknown
# 0a 00 - European - EN 50549-1:2019
# 0c 00 - Austria - 2.0.x EU_EN50438
# 0d 04 - france - ???
# 12 00 - Poland - 2.0.x (EU_EN50438)
# 37 00 - swiss - 2.0.x (CH_NA EEA-NE7–CH2020)
table_regs={
0x02: { 0x00: "US - IEEE 1547 240V (?)"},
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"},
}
### { table_nr : { table_ver: [["name","unit",divider]] }}
tables_struct = {
0x00 : {
# Germany - DE_VDE4105_2018
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]
],
# AT Tor - EU_EN50438
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]
],
# Poland - EU_EN50438
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],
],
# Swiss - CH_NA EEA-NE7-CH2020
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],
],
# European - EN 50549-1:2019
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],
],
# unknown 03 01
0x08: [
["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],
["? High Voltage 2 (HV2)","V",10],
],
# IEEE 1547 240V (?)
0x35: [
["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],
["?","",1],
["?","",1],
["?","",1],
["?","",1],
]
},
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]
],
0x07: [
["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],
[" ??? ","?",1],
[" ??? ","?",1],
],
},
0x40: { 0x00: [
["Normal Ramp up Rate(RUR_NM)","Rated%/s",100],
["Soft Start Ramp up Rate (RUR_SS)","Rated%/s",100]
],
},
0x50: { 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],
["Recovery Low Frequency (RVLF)","Hz",100]
],
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],
],
# ieee
0x11: [
["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],
],
},
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: { 0x02: [
["APC Function Activated","bool",1],
["Power Ramp Rate (PRR)","Pn%/s",100]
],
0x00: [
["APC Function Activated","bool",1]
]
},
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]
]
}
}
def modbusCrc(msg:str) -> int:
crc = 0xFFFF
for n in range(len(msg)):
crc ^= msg[n]
for i in range(8):
if crc & 1:
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return crc
binary_string = bytes.fromhex(hex_string)
binary_length = len(binary_string)
### internal DB ID ( country/regulations ) ???
str_header1 = binary_string[0]
str_header2 = binary_string[1]
### version ( major.minor / rev ) ???
str_version1 = binary_string[2]
str_version2 = binary_string[3]
try:
print("Grid Profile: %s " % (table_regs[str_header1][str_header2]))
except:
print("Grid Profile: unknown ( plz report to https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser )" )
print ("Version: %s.%s.%s" % (((str_version1 >> 4) & 0x0F),(str_version1 & 0x0F),str_version2))
position=4
while (position < binary_length):
str_table_n = binary_string[position]
str_table_v = binary_string[position+1]
try:
print("Table Type: " , table_types[str_table_n])
except:
pass
try:
tables_diz=tables_struct[str_table_n][str_table_v]
table_length = len(tables_diz)
except:
crc=bytearray(binary_string[position:position+2]).hex()
crc2 = modbusCrc(binary_string[0:position])
crcc = crc2.to_bytes(2, byteorder='big').hex()
if crc == crcc:
print("CRC (ok): ",crcc)
else:
print("CRC (?): ",crc)
print("CRC calced: %s " % (crcc))
print(" - possible unknown table (module), plz report to https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser")
print("end")
break
table_pos=0
#print("str_table_n: %s, str_table_v: %s, table_length: %s" % (str_table_n,str_table_v,table_length))
position += 2
for x in range(table_length):
try:
table_diz=tables_diz[table_pos]
except:
table_diz=["","",1]
table_pos += 1
str_work = binary_string[position:position+2]
str_hex = str_work.hex()
str_int = int(str_hex,16)
str_val = str_int / table_diz[2]
print("position: %s \t: %s \t %s \t %s\t[%s]\t\t[%s]" % (position,str_hex,str_int,str_val,table_diz[1],table_diz[0]))
position += 2
@noone2k
Copy link
Author

noone2k commented Dec 25, 2023

thx ... i'll try to put everything together , once i find some time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment