Merge pull request #39 from s-allius/refactor-Infos-class

Refactor infos class
This commit is contained in:
Stefan Allius
2024-03-30 22:22:16 +01:00
committed by GitHub
8 changed files with 568 additions and 505 deletions

View File

@@ -1,17 +1,94 @@
import struct
import json
import logging
from typing import Generator
if __name__ == "app.src.gen3.infos_g3":
from app.src.infos import Infos
from app.src.infos import Infos, Register
else: # pragma: no cover
from infos import Infos
from infos import Infos, Register
class RegisterMap:
map = {
0x00092ba8: Register.COLLECTOR_FW_VERSION,
0x000927c0: Register.CHIP_TYPE,
0x00092f90: Register.CHIP_MODEL,
0x00095a88: Register.TRACE_URL,
0x00095aec: Register.LOGGER_URL,
0x0000000a: Register.PRODUCT_NAME,
0x00000014: Register.MANUFACTURER,
0x0000001e: Register.VERSION,
0x00000028: Register.SERIAL_NUMBER,
0x00000032: Register.EQUIPMENT_MODEL,
0x00013880: Register.NO_INPUTS,
0xffffff00: Register.INVERTER_CNT,
0xffffff01: Register.UNKNOWN_SNR,
0xffffff02: Register.UNKNOWN_MSG,
0xffffff03: Register.INVALID_DATA_TYPE,
0xffffff04: Register.INTERNAL_ERROR,
0xffffff05: Register.UNKNOWN_CTRL,
0xffffff06: Register.OTA_START_MSG,
0xffffff07: Register.SW_EXCEPTION,
0xfffffffe: Register.TEST_REG1,
0xffffffff: Register.TEST_REG2,
0x00000640: Register.OUTPUT_POWER,
0x000005dc: Register.RATED_POWER,
0x00000514: Register.INVERTER_TEMP,
0x000006a4: Register.PV1_VOLTAGE,
0x00000708: Register.PV1_CURRENT,
0x0000076c: Register.PV1_POWER,
0x000007d0: Register.PV2_VOLTAGE,
0x00000834: Register.PV2_CURRENT,
0x00000898: Register.PV2_POWER,
0x000008fc: Register.PV3_VOLTAGE,
0x00000960: Register.PV3_CURRENT,
0x000009c4: Register.PV3_POWER,
0x00000a28: Register.PV4_VOLTAGE,
0x00000a8c: Register.PV4_CURRENT,
0x00000af0: Register.PV4_POWER,
0x00000c1c: Register.PV1_DAILY_GENERATION,
0x00000c80: Register.PV1_TOTAL_GENERATION,
0x00000ce4: Register.PV2_DAILY_GENERATION,
0x00000d48: Register.PV2_TOTAL_GENERATION,
0x00000dac: Register.PV3_DAILY_GENERATION,
0x00000e10: Register.PV3_TOTAL_GENERATION,
0x00000e74: Register.PV4_DAILY_GENERATION,
0x00000ed8: Register.PV4_TOTAL_GENERATION,
0x00000b54: Register.DAILY_GENERATION,
0x00000bb8: Register.TOTAL_GENERATION,
0x000003e8: Register.GRID_VOLTAGE,
0x0000044c: Register.GRID_CURRENT,
0x000004b0: Register.GRID_FREQUENCY,
0x000cfc38: Register.CONNECT_COUNT,
0x000c3500: Register.SIGNAL_STRENGTH,
0x000c96a8: Register.POWER_ON_TIME,
0x000d0020: Register.COLLECT_INTERVAL,
0x000cf850: Register.DATA_UP_INTERVAL,
0x000c7f38: Register.COMMUNICATION_TYPE,
0x00000191: Register.EVENT_401,
0x00000192: Register.EVENT_402,
0x00000193: Register.EVENT_403,
0x00000194: Register.EVENT_404,
0x00000195: Register.EVENT_405,
0x00000196: Register.EVENT_406,
0x00000197: Register.EVENT_407,
0x00000198: Register.EVENT_408,
0x00000199: Register.EVENT_409,
0x0000019a: Register.EVENT_410,
0x0000019b: Register.EVENT_411,
0x0000019c: Register.EVENT_412,
0x0000019d: Register.EVENT_413,
0x0000019e: Register.EVENT_414,
0x0000019f: Register.EVENT_415,
0x000001a0: Register.EVENT_416,
}
class InfosG3(Infos):
def ha_confs(self, ha_prfx, node_id, snr, singleton: bool, sug_area='') \
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
@@ -21,119 +98,11 @@ class InfosG3(Infos):
snr:str ==> serial number of the inverter, used to build unique
entity strings
sug_area:str ==> suggested area string from the config file'''
tab = self.info_defs
for key in tab:
row = tab[key]
if 'singleton' in row:
if singleton != row['singleton']:
continue
elif singleton:
continue
prfx = ha_prfx + node_id
# check if we have details for home assistant
if 'ha' in row:
ha = row['ha']
if 'comp' in ha:
component = ha['comp']
else:
component = 'sensor'
attr = {}
if 'name' in ha:
attr['name'] = ha['name']
else:
attr['name'] = row['name'][-1]
attr['stat_t'] = prfx + row['name'][0]
attr['dev_cla'] = ha['dev_cla']
attr['stat_cla'] = ha['stat_cla']
attr['uniq_id'] = ha['id']+snr
if 'val_tpl' in ha:
attr['val_tpl'] = ha['val_tpl']
elif 'fmt' in ha:
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos._info_defs: the row for {key} do"
" not have a 'val_tpl' nor a 'fmt' value")
# add unit_of_meas only, if status_class isn't none. If
# status_cla is None we want a number format and not line
# graph in home assistant. A unit will change the number
# format to a line graph
if 'unit' in row and attr['stat_cla'] is not None:
attr['unit_of_meas'] = row['unit'] # 'unit_of_meas'
if 'icon' in ha:
attr['ic'] = ha['icon'] # icon for the entity
if 'nat_prc' in ha:
attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats
if 'ent_cat' in ha:
attr['ent_cat'] = ha['ent_cat'] # diagnostic, config
# enabled_by_default is deactivated, since it avoid the via
# setup of the devices. It seems, that there is a bug in home
# assistant. tested with 'Home Assistant 2023.10.4'
# if 'en' in ha: # enabled_by_default
# attr['en'] = ha['en']
if 'dev' in ha:
device = self.info_devs[ha['dev']]
if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501
continue
dev = {}
# the same name for 'name' and 'suggested area', so we get
# dedicated devices in home assistant with short value
# name and headline
if (sug_area == '' or
('singleton' in device and device['singleton'])):
dev['name'] = device['name']
dev['sa'] = device['name']
else:
dev['name'] = device['name']+' - '+sug_area
dev['sa'] = device['name']+' - '+sug_area
if 'via' in device: # add the link to the parent device
via = device['via']
if via in self.info_devs:
via_dev = self.info_devs[via]
if 'singleton' in via_dev and via_dev['singleton']:
dev['via_device'] = via
else:
dev['via_device'] = f"{via}_{snr}"
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos._info_defs: the row for "
f"{key} has an invalid via value: "
f"{via}")
for key in ('mdl', 'mf', 'sw', 'hw'): # add optional
# values fpr 'modell', 'manufacturer', 'sw version' and
# 'hw version'
if key in device:
data = self.dev_value(device[key])
if data is not None:
dev[key] = data
if 'singleton' in device and device['singleton']:
dev['ids'] = [f"{ha['dev']}"]
else:
dev['ids'] = [f"{ha['dev']}_{snr}"]
attr['dev'] = dev
origin = {}
origin['name'] = self.app_name
origin['sw'] = self.version
attr['o'] = origin
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos._info_defs: the row for {key} "
"missing 'dev' value for ha register")
yield json.dumps(attr), component, node_id, attr['uniq_id']
# iterate over RegisterMap.map and get the register values
for reg in RegisterMap.map.values():
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) -> Generator[tuple[str, bool], None, None]:
'''parse a data sequence received from the inverter and
@@ -146,10 +115,13 @@ class InfosG3(Infos):
ind += 4
while i < elms:
result = struct.unpack_from('!lB', buf, ind)
info_id = result[0]
addr = result[0]
if addr not in RegisterMap.map:
info_id = -1
else:
info_id = RegisterMap.map[addr]
data_type = result[1]
ind += 5
keys, level, unit, must_incr, new_val = self._key_obj(info_id)
if data_type == 0x54: # 'T' -> Pascal-String
str_len = buf[ind]
@@ -159,26 +131,18 @@ class InfosG3(Infos):
ind += str_len+1
elif data_type == 0x49: # 'I' -> int32
# if new_val:
# struct.pack_into('!l', buf, ind, new_val)
result = struct.unpack_from('!l', buf, ind)[0]
ind += 4
elif data_type == 0x53: # 'S' -> short
# if new_val:
# struct.pack_into('!h', buf, ind, new_val)
result = struct.unpack_from('!h', buf, ind)[0]
ind += 2
elif data_type == 0x46: # 'F' -> float32
# if new_val:
# struct.pack_into('!f', buf, ind, new_val)
result = round(struct.unpack_from('!f', buf, ind)[0], 2)
ind += 4
elif data_type == 0x4c: # 'L' -> int64
# if new_val:
# struct.pack_into('!q', buf, ind, new_val)
result = struct.unpack_from('!q', buf, ind)[0]
ind += 8
@@ -188,31 +152,14 @@ class InfosG3(Infos):
" not supported")
return
keys, level, unit, must_incr, new_val = self._key_obj(info_id)
if keys:
dict = self.db
name = ''
for key in keys[:-1]:
if key not in dict:
dict[key] = {}
dict = dict[key]
name += key + '.'
if keys[-1] not in dict:
update = (not must_incr or result > 0)
else:
if must_incr:
update = dict[keys[-1]] < result
else:
update = dict[keys[-1]] != result
if update:
dict[keys[-1]] = result
name += keys[-1]
name, update = self.update_db(keys, must_incr, result)
yield keys[0], update
else:
update = False
name = str(f'info-id.0x{info_id:x}')
name = str(f'info-id.0x{addr:x}')
self.tracer.log(level, f'{name} : {result}{unit}'
f' update: {update}')

View File

@@ -110,7 +110,7 @@ class InverterG3(Inverter, ConnectionG3):
'''register all our topics at home assistant'''
for data_json, component, node_id, id in self.db.ha_confs(
self.entity_prfx, self.node_id, self.unique_id,
False, self.sug_area):
self.sug_area):
logger_mqtt.debug(f"MQTT Register: cmp:'{component}'"
f" node_id:'{node_id}' {data_json}")
await self.mqtt.publish(f"{self.discovery_prfx}{component}"

View File

@@ -1,17 +1,80 @@
import struct
import json
import logging
from typing import Generator
if __name__ == "app.src.gen3plus.infos_g3p":
from app.src.infos import Infos
from app.src.infos import Infos, Register
else: # pragma: no cover
from infos import Infos
from infos import Infos, Register
class RegisterMap:
# make the class read/only by using __slots__
__slots__ = ()
map = {
# 0x41020007: {'reg': Register.DEVICE_SNR, 'fmt': '<L'}, # noqa: E501
0x41020018: {'reg': Register.DATA_UP_INTERVAL, 'fmt': '!B', 'ratio': 60}, # noqa: E501
0x41020019: {'reg': Register.COLLECT_INTERVAL, 'fmt': '!B', 'ratio': 1}, # noqa: E501
0x4102001e: {'reg': Register.COLLECTOR_FW_VERSION, 'fmt': '!40s'}, # noqa: E501
0x41020064: {'reg': Register.VERSION, 'fmt': '!40s'}, # noqa: E501
0x42010020: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
0x420100d2: {'reg': Register.GRID_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100d4: {'reg': Register.GRID_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100d6: {'reg': Register.GRID_FREQUENCY, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100d8: {'reg': Register.INVERTER_TEMP, 'fmt': '!H', 'eval': '(result-32)/1.8'}, # noqa: E501
0x420100dc: {'reg': Register.RATED_POWER, 'fmt': '!H', 'ratio': 1}, # noqa: E501
0x420100de: {'reg': Register.OUTPUT_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100e0: {'reg': Register.PV1_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100e2: {'reg': Register.PV1_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100e4: {'reg': Register.PV1_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100e6: {'reg': Register.PV2_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100e8: {'reg': Register.PV2_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100ea: {'reg': Register.PV2_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100ec: {'reg': Register.PV3_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100ee: {'reg': Register.PV3_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100f0: {'reg': Register.PV3_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100f2: {'reg': Register.PV4_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100f4: {'reg': Register.PV4_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100f6: {'reg': Register.PV4_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x420100f8: {'reg': Register.DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x420100fa: {'reg': Register.TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x420100fe: {'reg': Register.PV1_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x42010100: {'reg': Register.PV1_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x42010104: {'reg': Register.PV2_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x42010106: {'reg': Register.PV2_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x4201010a: {'reg': Register.PV3_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x4201010c: {'reg': Register.PV3_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x42010110: {'reg': Register.PV4_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x42010112: {'reg': Register.PV4_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x42010170: {'reg': Register.NO_INPUTS, 'fmt': '!B'}, # noqa: E501
}
'''
COMMUNICATION_TYPE = 400
SIGNAL_STRENGTH = 401
POWER_ON_TIME = 402
CONNECT_COUNT = 405
CHIP_TYPE = 2
CHIP_MODEL = 3
TRACE_URL = 4
LOGGER_URL = 5
PRODUCT_NAME = 20
MANUFACTURER = 21
EQUIPMENT_MODEL = 24
'''
class InfosG3P(Infos):
def ha_confs(self, ha_prfx, node_id, snr, singleton: bool, sug_area='') \
def __init__(self):
super().__init__()
self.set_db_def_value(Register.MANUFACTURER, 'TSUN')
self.set_db_def_value(Register.EQUIPMENT_MODEL, 'TSOL-MS2000')
self.set_db_def_value(Register.CHIP_TYPE, 'IGEN TECH')
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
@@ -21,204 +84,49 @@ class InfosG3P(Infos):
snr:str ==> serial number of the inverter, used to build unique
entity strings
sug_area:str ==> suggested area string from the config file'''
tab = self.info_defs
for key in tab:
row = tab[key]
if 'singleton' in row:
if singleton != row['singleton']:
continue
elif singleton:
continue
prfx = ha_prfx + node_id
# iterate over RegisterMap.map and get the register values
for row in RegisterMap.map.values():
info_id = row['reg']
res = self.ha_conf(info_id, ha_prfx, node_id, snr, False, sug_area) # noqa: E501
if res:
yield res
# check if we have details for home assistant
if 'ha' in row:
ha = row['ha']
if 'comp' in ha:
component = ha['comp']
else:
component = 'sensor'
attr = {}
if 'name' in ha:
attr['name'] = ha['name']
else:
attr['name'] = row['name'][-1]
attr['stat_t'] = prfx + row['name'][0]
attr['dev_cla'] = ha['dev_cla']
attr['stat_cla'] = ha['stat_cla']
attr['uniq_id'] = ha['id']+snr
if 'val_tpl' in ha:
attr['val_tpl'] = ha['val_tpl']
elif 'fmt' in ha:
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos._info_defs: the row for {key} do"
" not have a 'val_tpl' nor a 'fmt' value")
# add unit_of_meas only, if status_class isn't none. If
# status_cla is None we want a number format and not line
# graph in home assistant. A unit will change the number
# format to a line graph
if 'unit' in row and attr['stat_cla'] is not None:
attr['unit_of_meas'] = row['unit'] # 'unit_of_meas'
if 'icon' in ha:
attr['ic'] = ha['icon'] # icon for the entity
if 'nat_prc' in ha:
attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats
if 'ent_cat' in ha:
attr['ent_cat'] = ha['ent_cat'] # diagnostic, config
# enabled_by_default is deactivated, since it avoid the via
# setup of the devices. It seems, that there is a bug in home
# assistant. tested with 'Home Assistant 2023.10.4'
# if 'en' in ha: # enabled_by_default
# attr['en'] = ha['en']
if 'dev' in ha:
device = self.info_devs[ha['dev']]
if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501
continue
dev = {}
# the same name for 'name' and 'suggested area', so we get
# dedicated devices in home assistant with short value
# name and headline
if (sug_area == '' or
('singleton' in device and device['singleton'])):
dev['name'] = device['name']
dev['sa'] = device['name']
else:
dev['name'] = device['name']+' - '+sug_area
dev['sa'] = device['name']+' - '+sug_area
if 'via' in device: # add the link to the parent device
via = device['via']
if via in self.info_devs:
via_dev = self.info_devs[via]
if 'singleton' in via_dev and via_dev['singleton']:
dev['via_device'] = via
else:
dev['via_device'] = f"{via}_{snr}"
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos._info_defs: the row for "
f"{key} has an invalid via value: "
f"{via}")
for key in ('mdl', 'mf', 'sw', 'hw'): # add optional
# values fpr 'modell', 'manufacturer', 'sw version' and
# 'hw version'
if key in device:
data = self.dev_value(device[key])
if data is not None:
dev[key] = data
if 'singleton' in device and device['singleton']:
dev['ids'] = [f"{ha['dev']}"]
else:
dev['ids'] = [f"{ha['dev']}_{snr}"]
attr['dev'] = dev
origin = {}
origin['name'] = self.app_name
origin['sw'] = self.version
attr['o'] = origin
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos._info_defs: the row for {key} "
"missing 'dev' value for ha register")
yield json.dumps(attr), component, node_id, attr['uniq_id']
def parse(self, buf, ind=0) -> Generator[tuple[str, bool], None, None]:
def parse(self, buf, msg_type: int, rcv_ftype: int) \
-> 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'''
result = struct.unpack_from('!l', buf, ind)
elms = result[0]
i = 0
ind += 4
while i < elms:
result = struct.unpack_from('!lB', buf, ind)
info_id = result[0]
data_type = result[1]
ind += 5
for idx, row in RegisterMap.map.items():
addr = idx & 0xffff
ftype = (idx >> 16) & 0xff
mtype = (idx >> 24) & 0xff
if ftype != rcv_ftype or mtype != msg_type:
continue
if isinstance(row, dict):
info_id = row['reg']
fmt = row['fmt']
res = struct.unpack_from(fmt, buf, addr)
result = res[0]
if isinstance(result, (bytearray, bytes)):
result = result.decode('utf-8')
if 'eval' in row:
result = eval(row['eval'])
if 'ratio' in row:
result = round(result * row['ratio'], 2)
keys, level, unit, must_incr, new_val = self._key_obj(info_id)
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 == 0x49: # 'I' -> int32
# if new_val:
# struct.pack_into('!l', buf, ind, new_val)
result = struct.unpack_from('!l', buf, ind)[0]
ind += 4
elif data_type == 0x53: # 'S' -> short
# if new_val:
# struct.pack_into('!h', buf, ind, new_val)
result = struct.unpack_from('!h', buf, ind)[0]
ind += 2
elif data_type == 0x46: # 'F' -> float32
# if new_val:
# struct.pack_into('!f', buf, ind, new_val)
result = round(struct.unpack_from('!f', buf, ind)[0], 2)
ind += 4
elif data_type == 0x4c: # 'L' -> int64
# if new_val:
# struct.pack_into('!q', buf, ind, new_val)
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}"
" not supported")
return
if keys:
dict = self.db
name = ''
for key in keys[:-1]:
if key not in dict:
dict[key] = {}
dict = dict[key]
name += key + '.'
if keys[-1] not in dict:
update = (not must_incr or result > 0)
else:
if must_incr:
update = dict[keys[-1]] < result
else:
update = dict[keys[-1]] != result
if update:
dict[keys[-1]] = result
name += keys[-1]
name, update = self.update_db(keys, must_incr, result)
yield keys[0], update
else:
name = str(f'info-id.0x{addr:x}')
update = False
name = str(f'info-id.0x{info_id:x}')
self.tracer.log(level, f'{name} : {result}{unit}'
f' update: {update}')
i += 1
def ignore_this_device(self, dep: dict) -> bool:
'''Checks the equation in the dep dict

View File

@@ -110,7 +110,7 @@ class InverterG3P(Inverter, ConnectionG3P):
'''register all our topics at home assistant'''
for data_json, component, node_id, id in self.db.ha_confs(
self.entity_prfx, self.node_id, self.unique_id,
False, self.sug_area):
self.sug_area):
logger_mqtt.debug(f"MQTT Register: cmp:'{component}'"
f" node_id:'{node_id}' {data_json}")
await self.mqtt.publish(f"{self.discovery_prfx}{component}"

View File

@@ -236,26 +236,12 @@ class SolarmanV5(Message):
total = result[1]
tim = result[2]
res = result[3] # always zero
logger.info(f'frame type:{ftype} total:{total}s'
logger.info(f'frame type:{ftype:02x} total:{total}s'
f' timer:{tim:08x}s null:{res}')
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
if (ftype == 2):
result = struct.unpack_from('!BBBBBB40s', data, 13)
upload_period = result[0]
data_acq_period = result[1]
heart_beat = result[2]
res = result[3]
wifi = result[4]
ver = result[6]
# res2 = result[5]
logger.info(f'upload:{upload_period}min '
f'data collect:{data_acq_period}s '
f'heartbeat:{heart_beat}s '
f'wifi:{wifi}%')
logger.info(f'ver:{ver}')
self.__process_data(ftype)
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
def msg_dev_rsp(self):
@@ -270,36 +256,21 @@ class SolarmanV5(Message):
offset = result[3]
unkn = result[4]
cnt = result[5]
logger.info(f'ftype:{ftype} total:{total}s'
logger.info(f'ftype:{ftype:02x} total:{total}s'
f' timer:{tim:08x}s ofs:{offset}'
f' ??: {unkn:08x} cnt:{cnt}')
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
if (ftype == 1):
result = struct.unpack_from('!HH', data, 0xdc)
rated = result[0]/1
actual = result[1]/10
logger.info(f'Rated Power:{rated}W, Actual Power:{actual}W')
result = struct.unpack_from('!HLHLHLHLHL', data, 0xf8)
daily = result[0]/100
total = result[1]/100
pv1_daily = result[2]/100
pv1_total = result[3]/100
pv2_daily = result[4]/100
pv2_total = result[5]/100
pv3_daily = result[6]/100
pv3_total = result[7]/100
pv4_daily = result[8]/100
pv4_total = result[9]/100
logger.info(f'daily:{daily}kWh '
f'tolal:{total}kWh\n'
f'pv daily:{pv1_daily}kWh, {pv2_daily}kWh, {pv3_daily}kWh, {pv4_daily}kWh\n' # noqa: E501
f'pv total:{pv1_total}kWh, {pv2_total}kWh, {pv3_total}kWh, {pv4_total}kWh') # noqa: E501
self.__process_data(ftype & 0x7f)
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
def __process_data(self, ftype):
msg_type = self.control >> 8
for key, update in self.db.parse(self._recv_buffer, msg_type, ftype):
if update:
self.new_data[key] = True
def msg_data_rsp(self):
self.msg_response()

View File

@@ -1,5 +1,92 @@
import logging
import json
import os
from enum import Enum
from typing import Generator
class Register(Enum):
COLLECTOR_FW_VERSION = 1
CHIP_TYPE = 2
CHIP_MODEL = 3
TRACE_URL = 4
LOGGER_URL = 5
PRODUCT_NAME = 20
MANUFACTURER = 21
VERSION = 22
SERIAL_NUMBER = 23
EQUIPMENT_MODEL = 24
NO_INPUTS = 25
INVERTER_CNT = 50
UNKNOWN_SNR = 51
UNKNOWN_MSG = 52
INVALID_DATA_TYPE = 53
INTERNAL_ERROR = 54
UNKNOWN_CTRL = 55
OTA_START_MSG = 56
SW_EXCEPTION = 57
OUTPUT_POWER = 83
RATED_POWER = 84
INVERTER_TEMP = 85
PV1_VOLTAGE = 100
PV1_CURRENT = 101
PV1_POWER = 102
PV2_VOLTAGE = 110
PV2_CURRENT = 111
PV2_POWER = 112
PV3_VOLTAGE = 120
PV3_CURRENT = 121
PV3_POWER = 122
PV4_VOLTAGE = 130
PV4_CURRENT = 131
PV4_POWER = 132
PV5_VOLTAGE = 140
PV5_CURRENT = 141
PV5_POWER = 142
PV6_VOLTAGE = 150
PV6_CURRENT = 151
PV6_POWER = 152
PV1_DAILY_GENERATION = 200
PV1_TOTAL_GENERATION = 201
PV2_DAILY_GENERATION = 210
PV2_TOTAL_GENERATION = 211
PV3_DAILY_GENERATION = 220
PV3_TOTAL_GENERATION = 221
PV4_DAILY_GENERATION = 230
PV4_TOTAL_GENERATION = 231
PV5_DAILY_GENERATION = 240
PV5_TOTAL_GENERATION = 241
PV6_DAILY_GENERATION = 250
PV6_TOTAL_GENERATION = 251
GRID_VOLTAGE = 300
GRID_CURRENT = 301
GRID_FREQUENCY = 302
DAILY_GENERATION = 303
TOTAL_GENERATION = 304
COMMUNICATION_TYPE = 400
SIGNAL_STRENGTH = 401
POWER_ON_TIME = 402
COLLECT_INTERVAL = 403
DATA_UP_INTERVAL = 404
CONNECT_COUNT = 405
EVENT_401 = 500
EVENT_402 = 501
EVENT_403 = 502
EVENT_404 = 503
EVENT_405 = 504
EVENT_406 = 505
EVENT_407 = 506
EVENT_408 = 507
EVENT_409 = 508
EVENT_410 = 509
EVENT_411 = 510
EVENT_412 = 511
EVENT_413 = 512
EVENT_414 = 513
EVENT_415 = 514
EVENT_416 = 515
TEST_REG1 = 10000
TEST_REG2 = 10001
class Infos:
@@ -14,13 +101,13 @@ class Infos:
logging.info('Initialize proxy statistics')
# init proxy counter in the class.stat dictionary
cls.stat['proxy'] = {}
for key in cls._info_defs:
name = cls._info_defs[key]['name']
for key in cls.__info_defs:
name = cls.__info_defs[key]['name']
if name[0] == 'proxy':
cls.stat['proxy'][name[1]] = 0
# add values from the environment to the device definition table
prxy = cls._info_devs['proxy']
prxy = cls.__info_devs['proxy']
prxy['sw'] = cls.version
prxy['mdl'] = cls.app_name
@@ -28,122 +115,122 @@ class Infos:
self.db = {}
self.tracer = logging.getLogger('data')
_info_devs = {
__info_devs = {
'proxy': {'singleton': True, 'name': 'Proxy', 'mf': 'Stefan Allius'}, # noqa: E501
'controller': {'via': 'proxy', 'name': 'Controller', 'mdl': 0x00092f90, 'mf': 0x000927c0, 'sw': 0x00092ba8}, # noqa: E501
'inverter': {'via': 'controller', 'name': 'Micro Inverter', 'mdl': 0x00000032, 'mf': 0x00000014, 'sw': 0x0000001e}, # noqa: E501
'controller': {'via': 'proxy', 'name': 'Controller', 'mdl': Register.CHIP_MODEL, 'mf': Register.CHIP_TYPE, 'sw': Register.COLLECTOR_FW_VERSION}, # noqa: E501
'inverter': {'via': 'controller', 'name': 'Micro Inverter', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'sw': Register.VERSION}, # noqa: E501
'input_pv1': {'via': 'inverter', 'name': 'Module PV1'},
'input_pv2': {'via': 'inverter', 'name': 'Module PV2', 'dep': {'reg': 0x00013880, 'gte': 2}}, # noqa: E501
'input_pv3': {'via': 'inverter', 'name': 'Module PV3', 'dep': {'reg': 0x00013880, 'gte': 3}}, # noqa: E501
'input_pv4': {'via': 'inverter', 'name': 'Module PV4', 'dep': {'reg': 0x00013880, 'gte': 4}}, # noqa: E501
'input_pv2': {'via': 'inverter', 'name': 'Module PV2', 'dep': {'reg': Register.NO_INPUTS, 'gte': 2}}, # noqa: E501
'input_pv3': {'via': 'inverter', 'name': 'Module PV3', 'dep': {'reg': Register.NO_INPUTS, 'gte': 3}}, # noqa: E501
'input_pv4': {'via': 'inverter', 'name': 'Module PV4', 'dep': {'reg': Register.NO_INPUTS, 'gte': 4}}, # noqa: E501
}
__comm_type_val_tpl = "{%set com_types = ['n/a','Wi-Fi', 'G4', 'G5', 'GPRS'] %}{{com_types[value_json['Communication_Type']|int(0)]|default(value_json['Communication_Type'])}}" # noqa: E501
_info_defs = {
__info_defs = {
# collector values used for device registration:
0x00092ba8: {'name': ['collector', 'Collector_Fw_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
0x000927c0: {'name': ['collector', 'Chip_Type'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00092f90: {'name': ['collector', 'Chip_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00095a88: {'name': ['collector', 'Trace_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00095aec: {'name': ['collector', 'Logger_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.COLLECTOR_FW_VERSION: {'name': ['collector', 'Collector_Fw_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
Register.CHIP_TYPE: {'name': ['collector', 'Chip_Type'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.CHIP_MODEL: {'name': ['collector', 'Chip_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.TRACE_URL: {'name': ['collector', 'Trace_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.LOGGER_URL: {'name': ['collector', 'Logger_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
# inverter values used for device registration:
0x0000000a: {'name': ['inverter', 'Product_Name'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000014: {'name': ['inverter', 'Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000001e: {'name': ['inverter', 'Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
0x00000028: {'name': ['inverter', 'Serial_Number'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000032: {'name': ['inverter', 'Equipment_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00013880: {'name': ['inverter', 'No_Inputs'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.PRODUCT_NAME: {'name': ['inverter', 'Product_Name'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.MANUFACTURER: {'name': ['inverter', 'Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.VERSION: {'name': ['inverter', 'Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
Register.SERIAL_NUMBER: {'name': ['inverter', 'Serial_Number'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EQUIPMENT_MODEL: {'name': ['inverter', 'Equipment_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.NO_INPUTS: {'name': ['inverter', 'No_Inputs'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
# proxy:
0xffffff00: {'name': ['proxy', 'Inverter_Cnt'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_count_', 'fmt': '| int', 'name': 'Active Inverter Connections', 'icon': 'mdi:counter'}}, # noqa: E501
0xffffff01: {'name': ['proxy', 'Unknown_SNR'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_snr_', 'fmt': '| int', 'name': 'Unknown Serial No', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
0xffffff02: {'name': ['proxy', 'Unknown_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_msg_', 'fmt': '| int', 'name': 'Unknown Msg Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
0xffffff03: {'name': ['proxy', 'Invalid_Data_Type'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_data_type_', 'fmt': '| int', 'name': 'Invalid Data Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
0xffffff04: {'name': ['proxy', 'Internal_Error'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'intern_err_', 'fmt': '| int', 'name': 'Internal Error', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic', 'en': False}}, # noqa: E501
0xffffff05: {'name': ['proxy', 'Unknown_Ctrl'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_ctrl_', 'fmt': '| int', 'name': 'Unknown Control Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
0xffffff06: {'name': ['proxy', 'OTA_Start_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'ota_start_cmd_', 'fmt': '| int', 'name': 'OTA Start Cmd', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
0xffffff07: {'name': ['proxy', 'SW_Exception'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'sw_exception_', 'fmt': '| int', 'name': 'Internal SW Exception', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.INVERTER_CNT: {'name': ['proxy', 'Inverter_Cnt'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_count_', 'fmt': '| int', 'name': 'Active Inverter Connections', 'icon': 'mdi:counter'}}, # noqa: E501
Register.UNKNOWN_SNR: {'name': ['proxy', 'Unknown_SNR'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_snr_', 'fmt': '| int', 'name': 'Unknown Serial No', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.UNKNOWN_MSG: {'name': ['proxy', 'Unknown_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_msg_', 'fmt': '| int', 'name': 'Unknown Msg Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.INVALID_DATA_TYPE: {'name': ['proxy', 'Invalid_Data_Type'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_data_type_', 'fmt': '| int', 'name': 'Invalid Data Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.INTERNAL_ERROR: {'name': ['proxy', 'Internal_Error'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'intern_err_', 'fmt': '| int', 'name': 'Internal Error', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic', 'en': False}}, # noqa: E501
Register.UNKNOWN_CTRL: {'name': ['proxy', 'Unknown_Ctrl'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_ctrl_', 'fmt': '| int', 'name': 'Unknown Control Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.OTA_START_MSG: {'name': ['proxy', 'OTA_Start_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'ota_start_cmd_', 'fmt': '| int', 'name': 'OTA Start Cmd', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.SW_EXCEPTION: {'name': ['proxy', 'SW_Exception'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'sw_exception_', 'fmt': '| int', 'name': 'Internal SW Exception', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
# 0xffffff03: {'name':['proxy', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'proxy', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'proxy_volt_', 'fmt':'| float','name': 'Grid Voltage'}}, # noqa: E501
# events
0x00000191: {'name': ['events', '401_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000192: {'name': ['events', '402_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000193: {'name': ['events', '403_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000194: {'name': ['events', '404_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000195: {'name': ['events', '405_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000196: {'name': ['events', '406_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000197: {'name': ['events', '407_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000198: {'name': ['events', '408_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x00000199: {'name': ['events', '409_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000019a: {'name': ['events', '410_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000019b: {'name': ['events', '411_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000019c: {'name': ['events', '412_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000019d: {'name': ['events', '413_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000019e: {'name': ['events', '414_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x0000019f: {'name': ['events', '415_GridFreqOverRating'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
0x000001a0: {'name': ['events', '416_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_401: {'name': ['events', '401_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_402: {'name': ['events', '402_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_403: {'name': ['events', '403_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_404: {'name': ['events', '404_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_405: {'name': ['events', '405_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_406: {'name': ['events', '406_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_407: {'name': ['events', '407_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_408: {'name': ['events', '408_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_409: {'name': ['events', '409_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_410: {'name': ['events', '410_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_411: {'name': ['events', '411_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_412: {'name': ['events', '412_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_413: {'name': ['events', '413_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_414: {'name': ['events', '414_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_415: {'name': ['events', '415_GridFreqOverRating'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
Register.EVENT_416: {'name': ['events', '416_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
# grid measures:
0x000003e8: {'name': ['grid', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'inverter', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'out_volt_', 'fmt': '| float', 'name': 'Grid Voltage', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x0000044c: {'name': ['grid', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'inverter', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'out_cur_', 'fmt': '| float', 'name': 'Grid Current', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000004b0: {'name': ['grid', 'Frequency'], 'level': logging.DEBUG, 'unit': 'Hz', 'ha': {'dev': 'inverter', 'dev_cla': 'frequency', 'stat_cla': 'measurement', 'id': 'out_freq_', 'fmt': '| float', 'name': 'Grid Frequency', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000640: {'name': ['grid', 'Output_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'out_power_', 'fmt': '| float', 'name': 'Power'}}, # noqa: E501
0x000005dc: {'name': ['env', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'rated_power_', 'fmt': '| string + " W"', 'name': 'Rated Power', 'icon': 'mdi:lightning-bolt', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000514: {'name': ['env', 'Inverter_Temp'], 'level': logging.DEBUG, 'unit': '°C', 'ha': {'dev': 'inverter', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_', 'fmt': '| int', 'name': 'Temperature'}}, # noqa: E501
Register.GRID_VOLTAGE: {'name': ['grid', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'inverter', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'out_volt_', 'fmt': '| float', 'name': 'Grid Voltage', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.GRID_CURRENT: {'name': ['grid', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'inverter', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'out_cur_', 'fmt': '| float', 'name': 'Grid Current', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.GRID_FREQUENCY: {'name': ['grid', 'Frequency'], 'level': logging.DEBUG, 'unit': 'Hz', 'ha': {'dev': 'inverter', 'dev_cla': 'frequency', 'stat_cla': 'measurement', 'id': 'out_freq_', 'fmt': '| float', 'name': 'Grid Frequency', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.OUTPUT_POWER: {'name': ['grid', 'Output_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'out_power_', 'fmt': '| float', 'name': 'Power'}}, # noqa: E501
Register.RATED_POWER: {'name': ['env', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'rated_power_', 'fmt': '| string + " W"', 'name': 'Rated Power', 'icon': 'mdi:lightning-bolt', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.INVERTER_TEMP: {'name': ['env', 'Inverter_Temp'], 'level': logging.DEBUG, 'unit': '°C', 'ha': {'dev': 'inverter', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_', 'fmt': '| int', 'name': 'Temperature'}}, # noqa: E501
# input measures:
0x000006a4: {'name': ['input', 'pv1', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv1_', 'val_tpl': "{{ (value_json['pv1']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000708: {'name': ['input', 'pv1', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv1_', 'val_tpl': "{{ (value_json['pv1']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x0000076c: {'name': ['input', 'pv1', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv1', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv1_', 'val_tpl': "{{ (value_json['pv1']['Power'] | float)}}"}}, # noqa: E501
0x000007d0: {'name': ['input', 'pv2', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv2_', 'val_tpl': "{{ (value_json['pv2']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000834: {'name': ['input', 'pv2', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv2_', 'val_tpl': "{{ (value_json['pv2']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000898: {'name': ['input', 'pv2', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv2', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv2_', 'val_tpl': "{{ (value_json['pv2']['Power'] | float)}}"}}, # noqa: E501
0x000008fc: {'name': ['input', 'pv3', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv3', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv3_', 'val_tpl': "{{ (value_json['pv3']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000960: {'name': ['input', 'pv3', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv3', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv3_', 'val_tpl': "{{ (value_json['pv3']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000009c4: {'name': ['input', 'pv3', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv3', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv3_', 'val_tpl': "{{ (value_json['pv3']['Power'] | float)}}"}}, # noqa: E501
0x00000a28: {'name': ['input', 'pv4', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv4', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv4_', 'val_tpl': "{{ (value_json['pv4']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000a8c: {'name': ['input', 'pv4', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv4', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv4_', 'val_tpl': "{{ (value_json['pv4']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x00000af0: {'name': ['input', 'pv4', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv4', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv4_', 'val_tpl': "{{ (value_json['pv4']['Power'] | float)}}"}}, # noqa: E501
0x00000c1c: {'name': ['input', 'pv1', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv1_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv1']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
0x00000c80: {'name': ['input', 'pv1', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv1_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv1']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
0x00000ce4: {'name': ['input', 'pv2', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv2_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv2']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
0x00000d48: {'name': ['input', 'pv2', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv2_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv2']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
0x00000dac: {'name': ['input', 'pv3', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv3_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv3']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
0x00000e10: {'name': ['input', 'pv3', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv3_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv3']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
0x00000e74: {'name': ['input', 'pv4', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv4_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv4']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
0x00000ed8: {'name': ['input', 'pv4', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv4_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv4']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
Register.PV1_VOLTAGE: {'name': ['input', 'pv1', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv1_', 'val_tpl': "{{ (value_json['pv1']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV1_CURRENT: {'name': ['input', 'pv1', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv1_', 'val_tpl': "{{ (value_json['pv1']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV1_POWER: {'name': ['input', 'pv1', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv1', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv1_', 'val_tpl': "{{ (value_json['pv1']['Power'] | float)}}"}}, # noqa: E501
Register.PV2_VOLTAGE: {'name': ['input', 'pv2', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv2_', 'val_tpl': "{{ (value_json['pv2']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV2_CURRENT: {'name': ['input', 'pv2', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv2_', 'val_tpl': "{{ (value_json['pv2']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV2_POWER: {'name': ['input', 'pv2', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv2', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv2_', 'val_tpl': "{{ (value_json['pv2']['Power'] | float)}}"}}, # noqa: E501
Register.PV3_VOLTAGE: {'name': ['input', 'pv3', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv3', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv3_', 'val_tpl': "{{ (value_json['pv3']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV3_CURRENT: {'name': ['input', 'pv3', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv3', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv3_', 'val_tpl': "{{ (value_json['pv3']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV3_POWER: {'name': ['input', 'pv3', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv3', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv3_', 'val_tpl': "{{ (value_json['pv3']['Power'] | float)}}"}}, # noqa: E501
Register.PV4_VOLTAGE: {'name': ['input', 'pv4', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv4', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv4_', 'val_tpl': "{{ (value_json['pv4']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV4_CURRENT: {'name': ['input', 'pv4', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv4', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv4_', 'val_tpl': "{{ (value_json['pv4']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.PV4_POWER: {'name': ['input', 'pv4', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv4', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv4_', 'val_tpl': "{{ (value_json['pv4']['Power'] | float)}}"}}, # noqa: E501
Register.PV1_DAILY_GENERATION: {'name': ['input', 'pv1', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv1_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv1']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
Register.PV1_TOTAL_GENERATION: {'name': ['input', 'pv1', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv1_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv1']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
Register.PV2_DAILY_GENERATION: {'name': ['input', 'pv2', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv2_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv2']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
Register.PV2_TOTAL_GENERATION: {'name': ['input', 'pv2', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv2_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv2']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
Register.PV3_DAILY_GENERATION: {'name': ['input', 'pv3', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv3_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv3']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
Register.PV3_TOTAL_GENERATION: {'name': ['input', 'pv3', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv3_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv3']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
Register.PV4_DAILY_GENERATION: {'name': ['input', 'pv4', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv4_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv4']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
Register.PV4_TOTAL_GENERATION: {'name': ['input', 'pv4', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv4_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv4']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
# total:
0x00000b54: {'name': ['total', 'Daily_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_', 'fmt': '| float', 'name': 'Daily Generation', 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
0x00000bb8: {'name': ['total', 'Total_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_', 'fmt': '| float', 'name': 'Total Generation', 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
Register.DAILY_GENERATION: {'name': ['total', 'Daily_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_', 'fmt': '| float', 'name': 'Daily Generation', 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501
Register.TOTAL_GENERATION: {'name': ['total', 'Total_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_', 'fmt': '| float', 'name': 'Total Generation', 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501
# controller:
0x000c3500: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': '| int', 'name': 'Signal Strength', 'icon': 'mdi:wifi'}}, # noqa: E501
0x000c96a8: {'name': ['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': '| float', 'name': 'Power on Time', 'nat_prc': '3', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000d0020: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " s"', 'name': 'Data Collect Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000cfc38: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': '| int', 'name': 'Connect Count', 'icon': 'mdi:counter', 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000c7f38: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'comp': 'sensor', 'icon': 'mdi:wifi'}}, # noqa: E501
# 0x000c7f38: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': 's', 'new_value': 5}, # noqa: E501
0x000cf850: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': '| string + " s"', 'name': 'Data Up Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.SIGNAL_STRENGTH: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': '| int', 'name': 'Signal Strength', 'icon': 'mdi:wifi'}}, # noqa: E501
Register.POWER_ON_TIME: {'name': ['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': '| float', 'name': 'Power on Time', 'nat_prc': '3', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.COLLECT_INTERVAL: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " s"', 'name': 'Data Collect Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.CONNECT_COUNT: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': '| int', 'name': 'Connect Count', 'icon': 'mdi:counter', 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.COMMUNICATION_TYPE: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'comp': 'sensor', 'icon': 'mdi:wifi'}}, # noqa: E501
Register.DATA_UP_INTERVAL: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': '| string + " s"', 'name': 'Data Up Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501
}
@property
def info_devs(self) -> dict:
return self._info_devs
@info_devs.setter
def info_devs(self, value: dict) -> None:
self._info_devs = value
return self.__info_devs
@property
def info_defs(self) -> dict:
return self._info_defs
return self.__info_defs
'''
if __name__ == "app.src.messages":
@info_defs.setter
def info_defs(self, value: dict) -> None:
self.__info_defs = value
@info_defs.setter
def info_defs(self, value: dict) -> None:
self._info_defs = value
@info_devs.setter
def info_devs(self, value: dict) -> None:
self.__info_devs = value
'''
def dev_value(self, idx: str | int) -> str | int | float | None:
'''returns the stored device value from our database
@@ -155,8 +242,8 @@ class Infos:
'''
if type(idx) is str:
return idx # return idx as a fixed value
elif idx in self._info_defs:
row = self._info_defs[idx]
elif idx in self.info_defs:
row = self.info_defs[idx]
if 'singleton' in row and row['singleton']:
dict = self.stat
else:
@@ -170,7 +257,7 @@ class Infos:
dict = dict[key]
return dict # value of the reqeusted entry
return None # unknwon idx, not in __info_defs
return None # unknwon idx, not in info_defs
def inc_counter(self, counter: str) -> None:
'''inc proxy statistic counter'''
@@ -182,9 +269,132 @@ class Infos:
dict = self.stat['proxy']
dict[counter] -= 1
def ha_proxy_confs(self, ha_prfx: str, node_id: str, snr: str) \
-> Generator[tuple[dict, str], None, None]:
'''Generator function yields json register struct for home-assistant
auto configuration and the unique entity string, for all proxy
registers
arguments:
ha_prfx:str ==> MQTT prefix for the home assistant 'stat_t string
node_id:str ==> node id of the inverter, used to build unique entity
snr:str ==> serial number of the inverter, used to build unique
entity strings
'''
# iterate over RegisterMap.map and get the register values for entries
# with Singleton=True, which means that this is a proxy register
for reg in self.info_defs.keys():
res = self.ha_conf(reg, ha_prfx, node_id, snr, True) # noqa: E501
if res:
yield res
def ha_conf(self, key, ha_prfx, node_id, snr, singleton: bool, sug_area: str = '') -> tuple[str, str, str, str]: # noqa: E501
if key not in self.info_defs:
return None
row = self.info_defs[key]
if 'singleton' in row:
if singleton != row['singleton']:
return None
elif singleton:
return None
prfx = ha_prfx + node_id
# check if we have details for home assistant
if 'ha' in row:
ha = row['ha']
if 'comp' in ha:
component = ha['comp']
else:
component = 'sensor'
attr = {}
if 'name' in ha:
attr['name'] = ha['name']
else:
attr['name'] = row['name'][-1]
attr['stat_t'] = prfx + row['name'][0]
attr['dev_cla'] = ha['dev_cla']
attr['stat_cla'] = ha['stat_cla']
attr['uniq_id'] = ha['id']+snr
if 'val_tpl' in ha:
attr['val_tpl'] = ha['val_tpl']
elif 'fmt' in ha:
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for {key} do"
" not have a 'val_tpl' nor a 'fmt' value")
# add unit_of_meas only, if status_class isn't none. If
# status_cla is None we want a number format and not line
# graph in home assistant. A unit will change the number
# format to a line graph
if 'unit' in row and attr['stat_cla'] is not None:
attr['unit_of_meas'] = row['unit'] # 'unit_of_meas'
if 'icon' in ha:
attr['ic'] = ha['icon'] # icon for the entity
if 'nat_prc' in ha:
attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats
if 'ent_cat' in ha:
attr['ent_cat'] = ha['ent_cat'] # diagnostic, config
# enabled_by_default is deactivated, since it avoid the via
# setup of the devices. It seems, that there is a bug in home
# assistant. tested with 'Home Assistant 2023.10.4'
# if 'en' in ha: # enabled_by_default
# attr['en'] = ha['en']
if 'dev' in ha:
device = self.info_devs[ha['dev']]
if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501
return None
dev = {}
# the same name for 'name' and 'suggested area', so we get
# dedicated devices in home assistant with short value
# name and headline
if (sug_area == '' or
('singleton' in device and device['singleton'])):
dev['name'] = device['name']
dev['sa'] = device['name']
else:
dev['name'] = device['name']+' - '+sug_area
dev['sa'] = device['name']+' - '+sug_area
if 'via' in device: # add the link to the parent device
via = device['via']
if via in self.info_devs:
via_dev = self.info_devs[via]
if 'singleton' in via_dev and via_dev['singleton']:
dev['via_device'] = via
else:
dev['via_device'] = f"{via}_{snr}"
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for "
f"{key} has an invalid via value: "
f"{via}")
for key in ('mdl', 'mf', 'sw', 'hw'): # add optional
# values fpr 'modell', 'manufacturer', 'sw version' and
# 'hw version'
if key in device:
data = self.dev_value(device[key])
if data is not None:
dev[key] = data
if 'singleton' in device and device['singleton']:
dev['ids'] = [f"{ha['dev']}"]
else:
dev['ids'] = [f"{ha['dev']}_{snr}"]
attr['dev'] = dev
origin = {}
origin['name'] = self.app_name
origin['sw'] = self.version
attr['o'] = origin
else:
self.inc_counter('Internal_Error')
logging.error(f"Infos.info_defs: the row for {key} "
"missing 'dev' value for ha register")
return json.dumps(attr), component, node_id, attr['uniq_id']
return None
def _key_obj(self, id) -> list:
d = self._info_defs.get(id, {'name': None, 'level': logging.DEBUG,
'unit': ''})
d = self.info_defs.get(id, {'name': None, 'level': logging.DEBUG,
'unit': ''})
if 'ha' in d and 'must_incr' in d['ha']:
must_incr = d['ha']['must_incr']
else:
@@ -194,3 +404,30 @@ class Infos:
# new_val = d['new_value']
return d['name'], d['level'], d['unit'], must_incr, new_val
def update_db(self, keys, must_incr, result):
name = ''
dict = self.db
for key in keys[:-1]:
if key not in dict:
dict[key] = {}
dict = dict[key]
name += key + '.'
if keys[-1] not in dict:
update = (not must_incr or result > 0)
else:
if must_incr:
update = dict[keys[-1]] < result
else:
update = dict[keys[-1]] != result
if update:
dict[keys[-1]] = result
name += keys[-1]
return name, update
def set_db_def_value(self, id, value):
'''set default value'''
row = self.info_defs[id]
if isinstance(row, dict):
keys = row['name']
self.update_db(keys, False, value)

View File

@@ -40,9 +40,8 @@ class Inverter():
@classmethod
async def _register_proxy_stat_home_assistant(cls) -> None:
'''register all our topics at home assistant'''
for data_json, component, node_id, id in cls.db_stat.ha_confs(
cls.entity_prfx, cls.proxy_node_id,
cls.proxy_unique_id, True):
for data_json, component, node_id, id in cls.db_stat.ha_proxy_confs(
cls.entity_prfx, cls.proxy_node_id, cls.proxy_unique_id):
logger_mqtt.debug(f"MQTT Register: cmp:'{component}' node_id:'{node_id}' {data_json}") # noqa: E501
await cls.mqtt.publish(f'{cls.discovery_prfx}{component}/{node_id}{id}/config', data_json) # noqa: E501

View File

@@ -1,5 +1,6 @@
# test_with_pytest.py
import pytest, json
from app.src.infos import Register
from app.src.gen3.infos_g3 import InfosG3
@pytest.fixture
@@ -218,7 +219,7 @@ def test_build_ha_conf1(ContrDataSeq):
i.static_init() # initialize counter
tests = 0
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', singleton=False):
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
if id == 'out_power_123':
assert comp == 'sensor'
@@ -249,7 +250,7 @@ def test_build_ha_conf1(ContrDataSeq):
assert tests==4
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True):
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'out_power_123':
assert False
@@ -279,7 +280,7 @@ def test_build_ha_conf2(ContrDataSeq, InvDataSeq, InvDataSeq2):
pass
tests = 0
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', singleton=False, sug_area = 'roof'):
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', sug_area = 'roof'):
if id == 'out_power_123':
assert comp == 'sensor'
@@ -405,22 +406,22 @@ def test_statistic_counter():
val = i.dev_value(0xffffffff) # invalid addr
assert val == None
val = i.dev_value(0xffffff00) # valid addr but not initiliazed
val = i.dev_value(Register.INVERTER_CNT) # valid addr but not initiliazed
assert val == None or val == 0
i.static_init() # initialize counter
assert json.dumps(i.stat) == json.dumps({"proxy": {"Inverter_Cnt": 0, "Unknown_SNR": 0, "Unknown_Msg": 0, "Invalid_Data_Type": 0, "Internal_Error": 0,"Unknown_Ctrl": 0, "OTA_Start_Msg": 0, "SW_Exception": 0}})
val = i.dev_value(0xffffff00) # valid and initiliazed addr
val = i.dev_value(Register.INVERTER_CNT) # valid and initiliazed addr
assert val == 0
i.inc_counter('Inverter_Cnt')
assert json.dumps(i.stat) == json.dumps({"proxy": {"Inverter_Cnt": 1, "Unknown_SNR": 0, "Unknown_Msg": 0, "Invalid_Data_Type": 0, "Internal_Error": 0,"Unknown_Ctrl": 0, "OTA_Start_Msg": 0, "SW_Exception": 0}})
val = i.dev_value(0xffffff00)
val = i.dev_value(Register.INVERTER_CNT)
assert val == 1
i.dec_counter('Inverter_Cnt')
val = i.dev_value(0xffffff00)
val = i.dev_value(Register.INVERTER_CNT)
assert val == 0
def test_dep_rules():
@@ -434,66 +435,66 @@ def test_dep_rules():
assert res == True
i.inc_counter('Inverter_Cnt') # is 1
val = i.dev_value(0xffffff00)
val = i.dev_value(Register.INVERTER_CNT)
assert val == 1
res = i.ignore_this_device({'reg':0xffffff00})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT})
assert res == True
res = i.ignore_this_device({'reg':0xffffff00, 'less_eq': 2})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'less_eq': 2})
assert res == False
res = i.ignore_this_device({'reg':0xffffff00, 'gte': 2})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'gte': 2})
assert res == True
i.inc_counter('Inverter_Cnt') # is 2
res = i.ignore_this_device({'reg':0xffffff00, 'less_eq': 2})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'less_eq': 2})
assert res == False
res = i.ignore_this_device({'reg':0xffffff00, 'gte': 2})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'gte': 2})
assert res == False
i.inc_counter('Inverter_Cnt') # is 3
res = i.ignore_this_device({'reg':0xffffff00, 'less_eq': 2})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'less_eq': 2})
assert res == True
res = i.ignore_this_device({'reg':0xffffff00, 'gte': 2})
res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'gte': 2})
assert res == False
def test_table_definition():
i = InfosG3()
i.static_init() # initialize counter
val = i.dev_value(0xffffff04) # check internal error counter
val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter
assert val == 0
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', singleton=False, sug_area = 'roof'):
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', sug_area = 'roof'):
pass
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'):
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
pass
val = i.dev_value(0xffffff04) # check internal error counter
val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter
assert val == 0
# test missing 'fmt' value
i.info_defs[0xfffffffe] = {'name':['proxy', 'Internal_Test1'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test1_'}}
i.info_defs[Register.TEST_REG1] = {'name':['proxy', 'Internal_Test1'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test1_'}}
tests = 0
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'):
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'intern_test1_456':
tests +=1
assert tests == 1
val = i.dev_value(0xffffff04) # check internal error counter
val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter
assert val == 1
# test missing 'dev' value
i.info_defs[0xfffffffe] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}}
i.info_defs[Register.TEST_REG1] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}}
tests = 0
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'):
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'intern_test2_456':
tests +=1
assert tests == 1
val = i.dev_value(0xffffff04) # check internal error counter
val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter
assert val == 2
@@ -501,15 +502,15 @@ def test_table_definition():
# test invalid 'via' value
i.info_devs['test_dev'] = {'via':'xyz', 'name':'Module PV1'}
i.info_defs[0xfffffffe] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev':'test_dev', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}}
i.info_defs[Register.TEST_REG1] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev':'test_dev', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}}
tests = 0
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'):
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'intern_test2_456':
tests +=1
assert tests == 1
val = i.dev_value(0xffffff04) # check internal error counter
val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter
assert val == 3
@@ -517,7 +518,7 @@ def test_invalid_data_type(InvalidDataSeq):
i = InfosG3()
i.static_init() # initialize counter
val = i.dev_value(0xffffff03) # check invalid data type counter
val = i.dev_value(Register.INVALID_DATA_TYPE) # check invalid data type counter
assert val == 0
@@ -525,6 +526,6 @@ def test_invalid_data_type(InvalidDataSeq):
pass
assert json.dumps(i.db) == json.dumps({"inverter": {"Product_Name": "Microinv"}})
val = i.dev_value(0xffffff03) # check invalid data type counter
val = i.dev_value(Register.INVALID_DATA_TYPE) # check invalid data type counter
assert val == 1