* split register map into multiple maps * add base support reg mapping 0x01900000 * fix shadowed builtin * detect reg mapping for sensor automatically * add device and test regs for MS-3000 * add more register mappings * fix unit tests * add more MS-3000 registers * build modell string for TSUN MS-3000 * add MS3000 unit test * remove obsolete method __set_config_parms * fix start addr of modbus scans - in server mode the start addr must be reduced by mb_step * add tests for sensor_list of ms-3000 inverters * MS-3000: add integer test register * DCU-1000: add Out Status register * add integer test and batterie out register * fix Sonar Qube finding * DCU-1000: add temp sensors
296 lines
12 KiB
Python
296 lines
12 KiB
Python
|
|
import struct
|
|
import logging
|
|
from typing import Generator
|
|
from itertools import chain
|
|
|
|
from infos import Infos, Register
|
|
|
|
|
|
class RegisterMap:
|
|
__slots__ = ()
|
|
map = {
|
|
0xffffff00: {'reg': Register.INVERTER_CNT},
|
|
0xffffff01: {'reg': Register.UNKNOWN_SNR},
|
|
0xffffff02: {'reg': Register.UNKNOWN_MSG},
|
|
0xffffff03: {'reg': Register.INVALID_DATA_TYPE},
|
|
0xffffff04: {'reg': Register.INTERNAL_ERROR},
|
|
0xffffff05: {'reg': Register.UNKNOWN_CTRL},
|
|
0xffffff06: {'reg': Register.OTA_START_MSG},
|
|
0xffffff07: {'reg': Register.SW_EXCEPTION},
|
|
0xffffff08: {'reg': Register.POLLING_INTERVAL},
|
|
0xfffffffe: {'reg': Register.TEST_REG1},
|
|
0xffffffff: {'reg': Register.TEST_REG2},
|
|
}
|
|
map_0e100000 = {
|
|
0x00092ba8: {'reg': Register.COLLECTOR_FW_VERSION},
|
|
0x000927c0: {'reg': Register.CHIP_TYPE},
|
|
0x00092f90: {'reg': Register.CHIP_MODEL},
|
|
0x00094ae8: {'reg': Register.MAC_ADDR},
|
|
0x00095a88: {'reg': Register.TRACE_URL},
|
|
0x00095aec: {'reg': Register.LOGGER_URL},
|
|
0x000cfc38: {'reg': Register.CONNECT_COUNT},
|
|
0x000c3500: {'reg': Register.SIGNAL_STRENGTH},
|
|
0x000c96a8: {'reg': Register.POWER_ON_TIME},
|
|
0x000d0020: {'reg': Register.COLLECT_INTERVAL},
|
|
0x000cf850: {'reg': Register.DATA_UP_INTERVAL},
|
|
0x000c7f38: {'reg': Register.COMMUNICATION_TYPE},
|
|
}
|
|
map_01900000 = {
|
|
0x0000000a: {'reg': Register.PRODUCT_NAME},
|
|
0x00000014: {'reg': Register.MANUFACTURER},
|
|
0x0000001e: {'reg': Register.VERSION},
|
|
0x00000046: {'reg': Register.SERIAL_NUMBER},
|
|
0x0000005A: {'reg': Register.EQUIPMENT_MODEL},
|
|
0x00000064: {'reg': Register.INVERTER_STATUS},
|
|
0x00000190: {'reg': Register.EVENT_ALARM},
|
|
0x000001f4: {'reg': Register.EVENT_FAULT},
|
|
0x00000258: {'reg': Register.EVENT_BF1},
|
|
0x000002bc: {'reg': Register.EVENT_BF2},
|
|
0x00000320: {'reg': Register.TEST_IVAL_1},
|
|
0x000003e8: {'reg': Register.TEST_VAL_0},
|
|
0x0000044c: {'reg': Register.TEST_VAL_1}, # DC 1 Inpput Voltage *10
|
|
0x000004b0: {'reg': Register.TEST_VAL_2},
|
|
0x00000514: {'reg': Register.GRID_VOLTAGE}, # Grid Voltage
|
|
0x00000578: {'reg': Register.GRID_CURRENT}, # Grid Current
|
|
0x000005dc: {'reg': Register.TEST_VAL_3},
|
|
0x00000640: {'reg': Register.GRID_FREQUENCY},
|
|
0x000006a4: {'reg': Register.TEST_IVAL_2},
|
|
0x00000708: {'reg': Register.TEST_IVAL_3},
|
|
0x0000076c: {'reg': Register.TEST_IVAL_4},
|
|
0x000007d0: {'reg': Register.TEST_VAL_4}, # DC 2 Input Voltage *10
|
|
0x00000834: {'reg': Register.MAX_DESIGNED_POWER},
|
|
0x00000898: {'reg': Register.OUTPUT_POWER}, # Grid Power
|
|
0x000008fc: {'reg': Register.DAILY_GENERATION}, # Daily Generation
|
|
0x00000960: {'reg': Register.TOTAL_GENERATION}, # Total Genration
|
|
0x000009c4: {'reg': Register.TEST_IVAL_5},
|
|
0x00000a28: {'reg': Register.TEST_VAL_10}, # Isolationsimpedanz Rx
|
|
0x00000a8c: {'reg': Register.TEST_VAL_11}, # Isolationsimpedanz Ry
|
|
0x00000af0: {'reg': Register.TEST_IVAL_6},
|
|
0x000001324: {'reg': Register.PV1_VOLTAGE}, # PV1 Voltage
|
|
0x000001388: {'reg': Register.PV1_CURRENT}, # PV1 Current
|
|
0x0000013ec: {'reg': Register.PV1_POWER}, # PV1 Power
|
|
0x000001450: {'reg': Register.TEST_VAL_5},
|
|
0x0000015e0: {'reg': Register.PV2_VOLTAGE}, # PV2 Voltage
|
|
0x000001644: {'reg': Register.PV2_CURRENT}, # PV2 Current
|
|
0x0000016a8: {'reg': Register.PV2_POWER}, # PV2 Power
|
|
0x00000170c: {'reg': Register.TEST_VAL_6},
|
|
0x00000189c: {'reg': Register.PV3_VOLTAGE},
|
|
0x000001900: {'reg': Register.PV3_CURRENT},
|
|
0x000001964: {'reg': Register.PV3_POWER},
|
|
0x0000019c8: {'reg': Register.TEST_VAL_7},
|
|
0x000001c20: {'reg': Register.TEST_VAL_14},
|
|
0x000001c84: {'reg': Register.TEST_VAL_15},
|
|
0x000001ce8: {'reg': Register.TEST_VAL_16}, # DC 1 Voltage
|
|
0x000001d4c: {'reg': Register.TEST_VAL_17},
|
|
0x000001db0: {'reg': Register.TEST_VAL_18},
|
|
0x000001e14: {'reg': Register.TEST_IVAL_8},
|
|
0x000001e78: {'reg': Register.PV4_VOLTAGE},
|
|
0x000001edc: {'reg': Register.PV4_CURRENT},
|
|
0x000001f40: {'reg': Register.PV4_POWER},
|
|
0x000001fa4: {'reg': Register.TEST_VAL_8},
|
|
0x0000020c9: {'reg': Register.TEST_IVAL_9},
|
|
0x0000020db: {'reg': Register.TEST_IVAL_10},
|
|
|
|
0x000002134: {'reg': Register.PV5_VOLTAGE},
|
|
0x000002198: {'reg': Register.PV5_CURRENT},
|
|
0x0000021fc: {'reg': Register.PV5_POWER},
|
|
# 0x000002260: {'reg': Register.TEST_VAL_13},
|
|
0x0000023f0: {'reg': Register.PV6_VOLTAGE},
|
|
0x000002454: {'reg': Register.PV6_CURRENT},
|
|
0x0000024b8: {'reg': Register.PV6_POWER},
|
|
# 0x00000251c: {'reg': Register.TEST_VAL_14},
|
|
0x000002774: {'reg': Register.TEST_VAL_24},
|
|
0x0000027d8: {'reg': Register.TEST_VAL_25},
|
|
0x00000283c: {'reg': Register.TEST_VAL_26}, # DC 2 Voltage
|
|
0x0000028a0: {'reg': Register.TEST_VAL_27},
|
|
0x000002904: {'reg': Register.TEST_VAL_28},
|
|
0x000002968: {'reg': Register.TEST_IVAL_11},
|
|
0x0000029cc: {'reg': Register.TEST_IVAL_12},
|
|
}
|
|
map_01900001 = {
|
|
0x0000000a: {'reg': Register.PRODUCT_NAME},
|
|
0x00000014: {'reg': Register.MANUFACTURER},
|
|
0x0000001e: {'reg': Register.VERSION},
|
|
0x00000028: {'reg': Register.SERIAL_NUMBER},
|
|
0x00000032: {'reg': Register.EQUIPMENT_MODEL},
|
|
0x00013880: {'reg': Register.NO_INPUTS},
|
|
0x00000640: {'reg': Register.OUTPUT_POWER},
|
|
0x000005dc: {'reg': Register.RATED_POWER},
|
|
0x00000514: {'reg': Register.INVERTER_TEMP},
|
|
0x000006a4: {'reg': Register.PV1_VOLTAGE},
|
|
0x00000708: {'reg': Register.PV1_CURRENT},
|
|
0x0000076c: {'reg': Register.PV1_POWER},
|
|
0x000007d0: {'reg': Register.PV2_VOLTAGE},
|
|
0x00000834: {'reg': Register.PV2_CURRENT},
|
|
0x00000898: {'reg': Register.PV2_POWER},
|
|
0x000008fc: {'reg': Register.PV3_VOLTAGE},
|
|
0x00000960: {'reg': Register.PV3_CURRENT},
|
|
0x000009c4: {'reg': Register.PV3_POWER},
|
|
0x00000a28: {'reg': Register.PV4_VOLTAGE},
|
|
0x00000a8c: {'reg': Register.PV4_CURRENT},
|
|
0x00000af0: {'reg': Register.PV4_POWER},
|
|
0x00000c1c: {'reg': Register.PV1_DAILY_GENERATION},
|
|
0x00000c80: {'reg': Register.PV1_TOTAL_GENERATION},
|
|
0x00000ce4: {'reg': Register.PV2_DAILY_GENERATION},
|
|
0x00000d48: {'reg': Register.PV2_TOTAL_GENERATION},
|
|
0x00000dac: {'reg': Register.PV3_DAILY_GENERATION},
|
|
0x00000e10: {'reg': Register.PV3_TOTAL_GENERATION},
|
|
0x00000e74: {'reg': Register.PV4_DAILY_GENERATION},
|
|
0x00000ed8: {'reg': Register.PV4_TOTAL_GENERATION},
|
|
0x00000b54: {'reg': Register.DAILY_GENERATION},
|
|
0x00000bb8: {'reg': Register.TOTAL_GENERATION},
|
|
0x000003e8: {'reg': Register.GRID_VOLTAGE},
|
|
0x0000044c: {'reg': Register.GRID_CURRENT},
|
|
0x000004b0: {'reg': Register.GRID_FREQUENCY},
|
|
|
|
0x00000190: {'reg': Register.EVENT_ALARM},
|
|
0x000001f4: {'reg': Register.EVENT_FAULT},
|
|
0x00000258: {'reg': Register.EVENT_BF1},
|
|
0x000002bc: {'reg': Register.EVENT_BF2},
|
|
0x00000064: {'reg': Register.INVERTER_STATUS},
|
|
|
|
0x00000fa0: {'reg': Register.BOOT_STATUS},
|
|
0x00001004: {'reg': Register.DSP_STATUS},
|
|
0x000010cc: {'reg': Register.WORK_MODE},
|
|
0x000011f8: {'reg': Register.OUTPUT_SHUTDOWN},
|
|
0x0000125c: {'reg': Register.MAX_DESIGNED_POWER},
|
|
0x000012c0: {'reg': Register.RATED_LEVEL},
|
|
0x00001324: {'reg': Register.INPUT_COEFFICIENT, 'ratio': 100/1024},
|
|
0x00001388: {'reg': Register.GRID_VOLT_CAL_COEF},
|
|
0x00002710: {'reg': Register.PROD_COMPL_TYPE},
|
|
0x00003200: {'reg': Register.OUTPUT_COEFFICIENT, 'ratio': 100/1024},
|
|
}
|
|
|
|
|
|
class RegisterSel:
|
|
__sensor_map = {
|
|
0x0e100000: RegisterMap.map_0e100000,
|
|
0x01900000: RegisterMap.map_01900000,
|
|
0x01900001: RegisterMap.map_01900001,
|
|
}
|
|
|
|
@classmethod
|
|
def get(cls, sensor: int):
|
|
return cls.__sensor_map.get(sensor, RegisterMap.map)
|
|
|
|
|
|
class InfosG3(Infos):
|
|
__slots__ = ()
|
|
|
|
def ha_confs(self, ha_prfx: str, node_id: str, snr: str,
|
|
sug_area: str = '') \
|
|
-> Generator[tuple[dict, str], None, None]:
|
|
'''Generator function yields a json register struct for home-assistant
|
|
auto configuration and a unique entity string
|
|
|
|
arguments:
|
|
prfx:str ==> MQTT prefix for the home assistant 'stat_t string
|
|
snr:str ==> serial number of the inverter, used to build unique
|
|
entity strings
|
|
sug_area:str ==> suggested area string from the config file'''
|
|
# iterate over RegisterMap.map and get the register values
|
|
sensor = self.get_db_value(Register.SENSOR_LIST)
|
|
if "01900000" == sensor:
|
|
items = RegisterMap.map_01900000.items()
|
|
elif "01900001" == sensor:
|
|
items = RegisterMap.map_01900001.items()
|
|
else:
|
|
items = {}
|
|
|
|
for _, row in chain(RegisterMap.map_0e100000.items(), items):
|
|
reg = row['reg']
|
|
res = self.ha_conf(reg, ha_prfx, node_id, snr, False, sug_area) # noqa: E501
|
|
if res:
|
|
yield res
|
|
|
|
def parse(self, buf, ind=0, sensor: int = 0, node_id: str = '') -> \
|
|
Generator[tuple[str, bool], None, None]:
|
|
'''parse a data sequence received from the inverter and
|
|
stores the values in Infos.db
|
|
|
|
buf: buffer of the sequence to parse'''
|
|
reg_map = RegisterSel.get(sensor)
|
|
result = struct.unpack_from('!l', buf, ind)
|
|
elms = result[0]
|
|
i = 0
|
|
ind += 4
|
|
while i < elms:
|
|
result = struct.unpack_from('!lB', buf, ind)
|
|
addr = result[0]
|
|
if addr not in reg_map:
|
|
row = None
|
|
info_id = -1
|
|
else:
|
|
row = reg_map[addr]
|
|
info_id = row['reg']
|
|
data_type = result[1]
|
|
ind += 5
|
|
|
|
if data_type == 0x54: # 'T' -> Pascal-String
|
|
str_len = buf[ind]
|
|
result = struct.unpack_from(f'!{str_len+1}p', buf,
|
|
ind)[0].decode(encoding='ascii',
|
|
errors='replace')
|
|
ind += str_len+1
|
|
|
|
elif data_type == 0x00: # 'Nul' -> end
|
|
i = elms # abort the loop
|
|
|
|
elif data_type == 0x41: # 'A' -> Nop ??
|
|
ind += 0
|
|
i += 1
|
|
continue
|
|
|
|
elif data_type == 0x42: # 'B' -> byte, int8
|
|
result = struct.unpack_from('!B', buf, ind)[0]
|
|
ind += 1
|
|
|
|
elif data_type == 0x49: # 'I' -> int32
|
|
result = struct.unpack_from('!l', buf, ind)[0]
|
|
ind += 4
|
|
|
|
elif data_type == 0x53: # 'S' -> short, int16
|
|
result = struct.unpack_from('!h', buf, ind)[0]
|
|
ind += 2
|
|
|
|
elif data_type == 0x46: # 'F' -> float32
|
|
result = round(struct.unpack_from('!f', buf, ind)[0], 2)
|
|
ind += 4
|
|
|
|
elif data_type == 0x4c: # 'L' -> long, int64
|
|
result = struct.unpack_from('!q', buf, ind)[0]
|
|
ind += 8
|
|
|
|
else:
|
|
self.inc_counter('Invalid_Data_Type')
|
|
logging.error(f"Infos.parse: data_type: {data_type}"
|
|
f" @0x{addr:04x} No:{i}"
|
|
" not supported")
|
|
return
|
|
|
|
result = self.__modify_val(row, result)
|
|
|
|
yield from self.__store_result(addr, result, info_id, node_id)
|
|
i += 1
|
|
|
|
def __modify_val(self, row, result):
|
|
if row and 'ratio' in row:
|
|
result = round(result * row['ratio'], 2)
|
|
return result
|
|
|
|
def __store_result(self, addr, result, info_id, node_id):
|
|
keys, level, unit, must_incr = self._key_obj(info_id)
|
|
if keys:
|
|
name, update = self.update_db(keys, must_incr, result)
|
|
yield keys[0], update
|
|
else:
|
|
update = False
|
|
name = str(f'info-id.0x{addr:x}')
|
|
if update:
|
|
self.tracer.log(level, f'[{node_id}] GEN3: {name} :'
|
|
f' {result}{unit}')
|
|
|
|
logging.log(level, f'[{node_id}] GEN3: {name} :'
|
|
f' {result}{unit}')
|