add initial DCU support
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
|
||||
from typing import Generator
|
||||
from itertools import chain
|
||||
|
||||
from infos import Infos, Register, ProxyMode, Fmt
|
||||
|
||||
@@ -32,7 +33,8 @@ class RegisterMap:
|
||||
0x4102008e: {'reg': None, 'fmt': '<B'}, # noqa: E501 Encryption Certificate File Status
|
||||
0x4102008f: {'reg': None, 'fmt': '!40s'}, # noqa: E501
|
||||
0x410200b7: {'reg': Register.SSID, 'fmt': '!40s'}, # noqa: E501
|
||||
|
||||
}
|
||||
map_02b0 = {
|
||||
0x4201000c: {'reg': Register.SENSOR_LIST, 'fmt': '<H', 'func': Fmt.hex4}, # noqa: E501
|
||||
0x4201001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1, 'dep': ProxyMode.SERVER}, # noqa: E501, or packet number
|
||||
0x42010020: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
|
||||
@@ -110,6 +112,22 @@ class RegisterMap:
|
||||
0xffffff02: {'reg': Register.POLLING_INTERVAL},
|
||||
# 0x4281001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1}, # noqa: E501
|
||||
}
|
||||
map_3026 = {
|
||||
0x4201000c: {'reg': Register.SENSOR_LIST, 'fmt': '<H', 'func': Fmt.hex4}, # noqa: E501
|
||||
0x4201001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1, 'dep': ProxyMode.SERVER}, # noqa: E501, or packet number
|
||||
0x42010020: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
|
||||
}
|
||||
|
||||
|
||||
class RegisterSel:
|
||||
__sensor_map = {
|
||||
0x02b0: RegisterMap.map_02b0,
|
||||
0x3026: RegisterMap.map_3026,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get(cls, sensor: int):
|
||||
return cls.__sensor_map.get(sensor, RegisterMap.map)
|
||||
|
||||
|
||||
class InfosG3P(Infos):
|
||||
@@ -144,7 +162,9 @@ class InfosG3P(Infos):
|
||||
entity strings
|
||||
sug_area:str ==> suggested area string from the config file'''
|
||||
# iterate over RegisterMap.map and get the register values
|
||||
for row in RegisterMap.map.values():
|
||||
for _, row in chain(RegisterMap.map.items(),
|
||||
RegisterMap.map_02b0.items(),
|
||||
RegisterMap.map_3026.items()):
|
||||
info_id = row['reg']
|
||||
if self.__hide_topic(row):
|
||||
res = self.ha_remove(info_id, node_id, snr) # noqa: E501
|
||||
@@ -153,13 +173,14 @@ class InfosG3P(Infos):
|
||||
if res:
|
||||
yield res
|
||||
|
||||
def parse(self, buf, msg_type: int, rcv_ftype: int, node_id: str = '') \
|
||||
def parse(self, buf, msg_type: int, rcv_ftype: int,
|
||||
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'''
|
||||
for idx, row in RegisterMap.map.items():
|
||||
for idx, row in RegisterSel.get(sensor).items():
|
||||
addr = idx & 0xffff
|
||||
ftype = (idx >> 16) & 0xff
|
||||
mtype = (idx >> 24) & 0xff
|
||||
@@ -183,9 +204,9 @@ class InfosG3P(Infos):
|
||||
self.tracer.log(level, f'[{node_id}] GEN3PLUS: {name}'
|
||||
f' : {result}{unit}')
|
||||
|
||||
def build(self, len, msg_type: int, rcv_ftype: int):
|
||||
def build(self, len, msg_type: int, rcv_ftype: int, sensor: int = 0):
|
||||
buf = bytearray(len)
|
||||
for idx, row in RegisterMap.map.items():
|
||||
for idx, row in RegisterSel.get(sensor).items():
|
||||
addr = idx & 0xffff
|
||||
ftype = (idx >> 16) & 0xff
|
||||
mtype = (idx >> 24) & 0xff
|
||||
|
||||
@@ -103,7 +103,7 @@ class SolarmanEmu(SolarmanBase):
|
||||
self.data_timer.start(self.data_up_inv)
|
||||
_len = 420
|
||||
ftype = 1
|
||||
build_msg = self.db.build(_len, 0x42, ftype)
|
||||
build_msg = self.db.build(_len, 0x42, ftype, 0x02b0)
|
||||
|
||||
self._build_header(0x4210)
|
||||
self.ifc.tx_add(
|
||||
|
||||
@@ -516,11 +516,11 @@ class SolarmanV5(SolarmanBase):
|
||||
logger.info(f'Model: {model}')
|
||||
self.db.set_db_def_value(Register.EQUIPMENT_MODEL, model)
|
||||
|
||||
def __process_data(self, ftype, ts):
|
||||
def __process_data(self, ftype, ts, sensor=0):
|
||||
inv_update = False
|
||||
msg_type = self.control >> 8
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), msg_type, ftype,
|
||||
self.node_id):
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), msg_type,
|
||||
ftype, sensor, self.node_id):
|
||||
if update:
|
||||
if key == 'inverter':
|
||||
inv_update = True
|
||||
@@ -581,7 +581,7 @@ class SolarmanV5(SolarmanBase):
|
||||
else:
|
||||
ts = None
|
||||
|
||||
self.__process_data(ftype, ts)
|
||||
self.__process_data(ftype, ts, sensor)
|
||||
self.__forward_msg()
|
||||
self.__send_ack_rsp(0x1210, ftype)
|
||||
self.new_state_up()
|
||||
|
||||
@@ -105,7 +105,7 @@ def test_parse_4210(inverter_data: bytes):
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps({
|
||||
@@ -127,10 +127,10 @@ def test_build_4210(inverter_data: bytes):
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
for i in range(11, 31):
|
||||
build_msg[i] = inverter_data[i]
|
||||
assert inverter_data == build_msg
|
||||
@@ -286,53 +286,53 @@ def test_build_ha_conf4():
|
||||
def test_exception_and_calc(inverter_data: bytes):
|
||||
|
||||
# patch table to convert temperature from °F to °C
|
||||
ofs = RegisterMap.map[0x420100d8]['offset']
|
||||
RegisterMap.map[0x420100d8]['quotient'] = 1.8
|
||||
RegisterMap.map[0x420100d8]['offset'] = -32/1.8
|
||||
ofs = RegisterMap.map_02b0[0x420100d8]['offset']
|
||||
RegisterMap.map_02b0[0x420100d8]['quotient'] = 1.8
|
||||
RegisterMap.map_02b0[0x420100d8]['offset'] = -32/1.8
|
||||
# map PV1_VOLTAGE to invalid register
|
||||
RegisterMap.map[0x420100e0]['reg'] = Register.TEST_REG2
|
||||
RegisterMap.map_02b0[0x420100e0]['reg'] = Register.TEST_REG2
|
||||
# set invalid maping entry for OUTPUT_POWER (string instead of dict type)
|
||||
backup = RegisterMap.map[0x420100de]
|
||||
RegisterMap.map[0x420100de] = 'invalid_entry'
|
||||
backup = RegisterMap.map_02b0[0x420100de]
|
||||
RegisterMap.map_02b0[0x420100de] = 'invalid_entry'
|
||||
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
assert math.isclose(12.2222, round (i.get_db_value(Register.INVERTER_TEMP, 0),4), rel_tol=1e-09, abs_tol=1e-09)
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
assert build_msg[32:0xde] == inverter_data[32:0xde]
|
||||
assert build_msg[0xde:0xe2] == b'\x00\x00\x00\x00'
|
||||
assert build_msg[0xe2:-1] == inverter_data[0xe2:-1]
|
||||
|
||||
|
||||
# remove a table entry and test parsing and building
|
||||
del RegisterMap.map[0x420100d8]['quotient']
|
||||
del RegisterMap.map[0x420100d8]['offset']
|
||||
del RegisterMap.map_02b0[0x420100d8]['quotient']
|
||||
del RegisterMap.map_02b0[0x420100d8]['offset']
|
||||
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
assert 54 == i.get_db_value(Register.INVERTER_TEMP, 0)
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
assert build_msg[32:0xd8] == inverter_data[32:0xd8]
|
||||
assert build_msg[0xd8:0xe2] == b'\x006\x00\x00\x02X\x00\x00\x00\x00'
|
||||
assert build_msg[0xe2:-1] == inverter_data[0xe2:-1]
|
||||
|
||||
# test restore table
|
||||
RegisterMap.map[0x420100d8]['offset'] = ofs
|
||||
RegisterMap.map[0x420100e0]['reg'] = Register.PV1_VOLTAGE # reset mapping
|
||||
RegisterMap.map[0x420100de] = backup # reset mapping
|
||||
RegisterMap.map_02b0[0x420100d8]['offset'] = ofs
|
||||
RegisterMap.map_02b0[0x420100e0]['reg'] = Register.PV1_VOLTAGE # reset mapping
|
||||
RegisterMap.map_02b0[0x420100de] = backup # reset mapping
|
||||
|
||||
# test orginial table
|
||||
i.db.clear()
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
assert 14 == i.get_db_value(Register.INVERTER_TEMP, 0)
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
assert build_msg[32:-1] == inverter_data[32:-1]
|
||||
|
||||
Reference in New Issue
Block a user