1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,5 @@ __pycache__
|
|||||||
mosquitto/**
|
mosquitto/**
|
||||||
homeassistant/**
|
homeassistant/**
|
||||||
tsun_proxy/**
|
tsun_proxy/**
|
||||||
system_tests/**
|
|
||||||
Doku/**
|
Doku/**
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- fix issue [#8](https://github.com/s-allius/tsun-gen3-proxy/issues/8)
|
- fix issue [#8](https://github.com/s-allius/tsun-gen3-proxy/issues/8)
|
||||||
|
- implement [#10](https://github.com/s-allius/tsun-gen3-proxy/issues/10)
|
||||||
|
- fix: don't dispatch ignored messages so that they are not forwarded
|
||||||
|
- add systemtests
|
||||||
|
- fix unit tests, which were broken since version 0.3.0
|
||||||
|
- add proxy device to home assistant
|
||||||
|
- add statistic counter to proxy device
|
||||||
|
- support multiple inverter registration at home assistant
|
||||||
|
|
||||||
## [0.3.0] - 2023-10-10
|
## [0.3.0] - 2023-10-10
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ mqtt.passwd = ''
|
|||||||
ha.auto_conf_prefix = 'homeassistant' # MQTT prefix for subscribing for homeassistant status updates
|
ha.auto_conf_prefix = 'homeassistant' # MQTT prefix for subscribing for homeassistant status updates
|
||||||
ha.discovery_prefix = 'homeassistant' # MQTT prefix for discovery topic
|
ha.discovery_prefix = 'homeassistant' # MQTT prefix for discovery topic
|
||||||
ha.entity_prefix = 'tsun' # MQTT topic prefix for publishing inverter values
|
ha.entity_prefix = 'tsun' # MQTT topic prefix for publishing inverter values
|
||||||
|
ha.proxy_node_id = 'proxy' # MQTT node id, for the proxy_node_id
|
||||||
|
ha.proxy_unique_id = 'P170000000000001' # MQTT unique id, to identify a proxy instance
|
||||||
|
|
||||||
# microinverters
|
# microinverters
|
||||||
inverters.allow_all = true # allow inverters, even if we have no inverter mapping
|
inverters.allow_all = true # allow inverters, even if we have no inverter mapping
|
||||||
|
|||||||
@@ -14,50 +14,16 @@ class AsyncStream(Message):
|
|||||||
self.remoteStream = remote_stream
|
self.remoteStream = remote_stream
|
||||||
self.server_side = server_side
|
self.server_side = server_side
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.unique_id = 0
|
|
||||||
self.node_id = ''
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Our puplic methods
|
Our puplic methods
|
||||||
'''
|
'''
|
||||||
def set_serial_no(self, serial_no : str):
|
|
||||||
logger.debug(f'SerialNo: {serial_no}')
|
|
||||||
|
|
||||||
if self.unique_id != serial_no:
|
|
||||||
|
|
||||||
inverters = Config.get('inverters')
|
|
||||||
#logger.debug(f'Inverters: {inverters}')
|
|
||||||
|
|
||||||
if serial_no in inverters:
|
|
||||||
logger.debug(f'SerialNo {serial_no} allowed!')
|
|
||||||
inv = inverters[serial_no]
|
|
||||||
self.node_id = inv['node_id']
|
|
||||||
self.sug_area = inv['suggested_area']
|
|
||||||
else:
|
|
||||||
logger.debug(f'SerialNo {serial_no} not known!')
|
|
||||||
self.node_id = ''
|
|
||||||
self.sug_area = ''
|
|
||||||
if not inverters['allow_all']:
|
|
||||||
self.unique_id = None
|
|
||||||
|
|
||||||
logger.warning(f'ignore message from unknow inverter! (SerialNo: {serial_no})')
|
|
||||||
return
|
|
||||||
|
|
||||||
self.unique_id = serial_no
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def loop(self) -> None:
|
async def loop(self) -> None:
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
await self.__async_read()
|
await self.__async_read()
|
||||||
|
|
||||||
if self.id_str:
|
|
||||||
self.set_serial_no(self.id_str.decode("utf-8"))
|
|
||||||
|
|
||||||
if self.unique_id:
|
if self.unique_id:
|
||||||
await self.__async_write()
|
await self.__async_write()
|
||||||
await self.__async_forward()
|
await self.__async_forward()
|
||||||
@@ -114,7 +80,7 @@ class AsyncStream(Message):
|
|||||||
await self.async_create_remote() # only implmeneted for server side => syncServerStream
|
await self.async_create_remote() # only implmeneted for server side => syncServerStream
|
||||||
|
|
||||||
if self.remoteStream:
|
if self.remoteStream:
|
||||||
hex_dump_memory(logging.DEBUG, f'Forward to {self.remoteStream.addr}:', self._forward_buffer, len(self._forward_buffer))
|
hex_dump_memory(logging.INFO, f'Forward to {self.remoteStream.addr}:', self._forward_buffer, len(self._forward_buffer))
|
||||||
self.remoteStream.writer.write (self._forward_buffer)
|
self.remoteStream.writer.write (self._forward_buffer)
|
||||||
await self.remoteStream.writer.drain()
|
await self.remoteStream.writer.drain()
|
||||||
self._forward_buffer = bytearray(0)
|
self._forward_buffer = bytearray(0)
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ class Config():
|
|||||||
'ha': {
|
'ha': {
|
||||||
'auto_conf_prefix': Use(str),
|
'auto_conf_prefix': Use(str),
|
||||||
'discovery_prefix': Use(str),
|
'discovery_prefix': Use(str),
|
||||||
'entity_prefix': Use(str)},
|
'entity_prefix': Use(str),
|
||||||
|
'proxy_node_id': Use(str),
|
||||||
|
'proxy_unique_id': Use(str)},
|
||||||
|
|
||||||
'inverters': {
|
'inverters': {
|
||||||
'allow_all' : Use(bool),
|
'allow_all' : Use(bool),
|
||||||
|
|||||||
111
app/src/infos.py
111
app/src/infos.py
@@ -3,14 +3,19 @@ import struct, json, logging, os
|
|||||||
|
|
||||||
|
|
||||||
class Infos:
|
class Infos:
|
||||||
|
stat = {}
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.db = {}
|
self.db = {}
|
||||||
self.app_name = os.getenv('SERVICE_NAME', 'proxy')
|
self.app_name = os.getenv('SERVICE_NAME', 'proxy')
|
||||||
self.version = os.getenv('VERSION', 'unknown')
|
self.version = os.getenv('VERSION', 'unknown')
|
||||||
self.tracer = logging.getLogger('data')
|
self.tracer = logging.getLogger('data')
|
||||||
|
prxy = self.__info_devs['proxy']
|
||||||
|
prxy['sw'] = self.version
|
||||||
|
prxy['mdl'] = self.app_name
|
||||||
|
|
||||||
__info_devs={
|
__info_devs={
|
||||||
'controller':{ 'name':'Controller', 'mdl':0x00092f90, 'mf':0x000927c0, 'sw':0x00092ba8},
|
'proxy': {'singleton': True, 'name':'Proxy', 'mf':'Stefan Allius'},
|
||||||
|
'controller':{'via':'proxy', 'name':'Controller', 'mdl':0x00092f90, 'mf':0x000927c0, 'sw':0x00092ba8},
|
||||||
'inverter': {'via':'controller', 'name':'Micro Inverter', 'mdl':0x00000032, 'mf':0x00000014, 'sw':0x0000001e},
|
'inverter': {'via':'controller', 'name':'Micro Inverter', 'mdl':0x00000032, 'mf':0x00000014, 'sw':0x0000001e},
|
||||||
'input_pv1': {'via':'inverter', 'name':'Module PV1'},
|
'input_pv1': {'via':'inverter', 'name':'Module PV1'},
|
||||||
'input_pv2': {'via':'inverter', 'name':'Module PV2', 'dep':{'reg':0x00095b50, 'gte': 2}},
|
'input_pv2': {'via':'inverter', 'name':'Module PV2', 'dep':{'reg':0x00095b50, 'gte': 2}},
|
||||||
@@ -33,7 +38,13 @@ class Infos:
|
|||||||
0x0000001e: {'name':['inverter', 'Version'], 'level': logging.INFO, 'unit': ''},
|
0x0000001e: {'name':['inverter', 'Version'], 'level': logging.INFO, 'unit': ''},
|
||||||
0x00000028: {'name':['inverter', 'Serial_Number'], 'level': logging.DEBUG, 'unit': ''},
|
0x00000028: {'name':['inverter', 'Serial_Number'], 'level': logging.DEBUG, 'unit': ''},
|
||||||
0x00000032: {'name':['inverter', 'Equipment_Model'], 'level': logging.DEBUG, 'unit': ''},
|
0x00000032: {'name':['inverter', 'Equipment_Model'], 'level': logging.DEBUG, 'unit': ''},
|
||||||
|
|
||||||
|
# proxy:
|
||||||
|
0xffffff00: {'name':['proxy', 'Inverter_Cnt'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'inv_count_', 'fmt':'| int', 'name': 'Inverter Connection Count', 'icon':'mdi:counter'}},
|
||||||
|
0xffffff01: {'name':['proxy', 'Unknown_SNR'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'unknown_snr_', 'fmt':'| int', 'name': 'Unknown Serial No', 'icon':'mdi:counter', 'ent_cat':'diagnostic'}},
|
||||||
|
0xffffff02: {'name':['proxy', 'Unknown_Msg'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'unknown_msg_', 'fmt':'| int', 'name': 'Unknown Msg Type', 'icon':'mdi:counter', 'ent_cat':'diagnostic'}},
|
||||||
|
# 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'}},
|
||||||
|
|
||||||
# events
|
# events
|
||||||
0x00000191: {'name':['events', '401_'], 'level': logging.DEBUG, 'unit': ''},
|
0x00000191: {'name':['events', '401_'], 'level': logging.DEBUG, 'unit': ''},
|
||||||
0x00000192: {'name':['events', '402_'], 'level': logging.DEBUG, 'unit': ''},
|
0x00000192: {'name':['events', '402_'], 'level': logging.DEBUG, 'unit': ''},
|
||||||
@@ -53,26 +64,26 @@ class Infos:
|
|||||||
0x000001a0: {'name':['events', '416_'], 'level': logging.DEBUG, 'unit': ''},
|
0x000001a0: {'name':['events', '416_'], 'level': logging.DEBUG, 'unit': ''},
|
||||||
|
|
||||||
# grid measures:
|
# 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'}},
|
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'}},
|
||||||
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'}},
|
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'}},
|
||||||
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'}},
|
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'}},
|
||||||
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'}},
|
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'}},
|
||||||
0x000005dc: {'name':['env', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha':{'dev':'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'rated_power_','fmt':'| int', 'name': 'Rated Power','ent_cat':'diagnostic'}},
|
0x000005dc: {'name':['env', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha':{'dev':'inverter', 'dev_cla': None, 'stat_cla': None, 'id':'rated_power_', 'fmt':'| int', 'name': 'Rated Power','ent_cat':'diagnostic'}},
|
||||||
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'}},
|
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'}},
|
||||||
|
|
||||||
# input measures:
|
# 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_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv1']['Voltage'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x000006a4: {'name':['input', 'pv1', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv1_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv1']['Voltage'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x00000708: {'name':['input', 'pv1', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv1_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv1']['Current'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x00000708: {'name':['input', 'pv1', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv1_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv1']['Current'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x0000076c: {'name':['input', 'pv1', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha':{'dev':'input_pv1', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv1_','name': 'Power', 'val_tpl' :"{{ (value_json['pv1']['Power'] | float)}}", 'icon':'mdi:gauge'}},
|
0x0000076c: {'name':['input', 'pv1', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha':{'dev':'input_pv1', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv1_','name': 'Power', 'val_tpl' :"{{ (value_json['pv1']['Power'] | float)}}"}},
|
||||||
0x000007d0: {'name':['input', 'pv2', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv2_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv2']['Voltage'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x000007d0: {'name':['input', 'pv2', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv2_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv2']['Voltage'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x00000834: {'name':['input', 'pv2', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv2_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv2']['Current'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x00000834: {'name':['input', 'pv2', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv2_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv2']['Current'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x00000898: {'name':['input', 'pv2', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha':{'dev':'input_pv2', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv2_','name': 'Power', 'val_tpl' :"{{ (value_json['pv2']['Power'] | float)}}", 'icon':'mdi:gauge'}},
|
0x00000898: {'name':['input', 'pv2', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha':{'dev':'input_pv2', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv2_','name': 'Power', 'val_tpl' :"{{ (value_json['pv2']['Power'] | float)}}"}},
|
||||||
0x000008fc: {'name':['input', 'pv3', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv3', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv3_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv3']['Voltage'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x000008fc: {'name':['input', 'pv3', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv3', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv3_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv3']['Voltage'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x00000960: {'name':['input', 'pv3', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv3', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv3_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv3']['Current'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x00000960: {'name':['input', 'pv3', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv3', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv3_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv3']['Current'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x000009c4: {'name':['input', 'pv3', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha':{'dev':'input_pv3', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv3_','name': 'Power', 'val_tpl' :"{{ (value_json['pv3']['Power'] | float)}}", 'icon':'mdi:gauge'}},
|
0x000009c4: {'name':['input', 'pv3', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha':{'dev':'input_pv3', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv3_','name': 'Power', 'val_tpl' :"{{ (value_json['pv3']['Power'] | float)}}"}},
|
||||||
0x00000a28: {'name':['input', 'pv4', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv4', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv4_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv4']['Voltage'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x00000a28: {'name':['input', 'pv4', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'input_pv4', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'volt_pv4_', 'name': 'Voltage', 'val_tpl' :"{{ (value_json['pv4']['Voltage'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x00000a8c: {'name':['input', 'pv4', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv4', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv4_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv4']['Current'] | float)}}", 'unvisible':1, 'icon':'mdi:gauge'}},
|
0x00000a8c: {'name':['input', 'pv4', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha':{'dev':'input_pv4', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id':'cur_pv4_', 'name': 'Current', 'val_tpl' :"{{ (value_json['pv4']['Current'] | float)}}", 'icon':'mdi:gauge','ent_cat':'diagnostic'}},
|
||||||
0x00000af0: {'name':['input', 'pv4', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha':{'dev':'input_pv4', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv4_','name': 'Power', 'val_tpl' :"{{ (value_json['pv4']['Power'] | float)}}", 'icon':'mdi:gauge'}},
|
0x00000af0: {'name':['input', 'pv4', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha':{'dev':'input_pv4', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id':'power_pv4_','name': 'Power', 'val_tpl' :"{{ (value_json['pv4']['Power'] | float)}}"}},
|
||||||
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'}},
|
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'}},
|
||||||
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'}},
|
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'}},
|
||||||
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'}},
|
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'}},
|
||||||
@@ -86,7 +97,7 @@ class Infos:
|
|||||||
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'}},
|
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'}},
|
||||||
|
|
||||||
# controller:
|
# 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','ent_cat':'diagnostic'}},
|
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'}},
|
||||||
0x000c96a8: {'name':['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha':{'dev':'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id':'power_on_time_', 'name': 'Power on Time', 'val_tpl':"{{ (value_json['Power_On_Time'] | float)}}", 'nat_prc':'3','ent_cat':'diagnostic'}},
|
0x000c96a8: {'name':['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha':{'dev':'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id':'power_on_time_', 'name': 'Power on Time', 'val_tpl':"{{ (value_json['Power_On_Time'] | float)}}", 'nat_prc':'3','ent_cat':'diagnostic'}},
|
||||||
0x000cf850: {'name':['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha':{'dev':'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id':'data_up_intval_', 'fmt':'| int', 'name': 'Data Up Interval', 'icon':'mdi:update','ent_cat':'diagnostic'}},
|
0x000cf850: {'name':['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha':{'dev':'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id':'data_up_intval_', 'fmt':'| int', 'name': 'Data Up Interval', 'icon':'mdi:update','ent_cat':'diagnostic'}},
|
||||||
|
|
||||||
@@ -127,7 +138,7 @@ class Infos:
|
|||||||
return not value <= dep['less_eq']
|
return not value <= dep['less_eq']
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def ha_confs(self, prfx="tsun/garagendach/", snr='123', sug_area =''):
|
def ha_confs(self, ha_prfx, inv_node_id, inv_snr, proxy_node_id, proxy_unique_id, sug_area =''):
|
||||||
'''Generator function yields a json register struct for home-assistant auto configuration and a unique entity string
|
'''Generator function yields a json register struct for home-assistant auto configuration and a unique entity string
|
||||||
|
|
||||||
arguments:
|
arguments:
|
||||||
@@ -137,6 +148,13 @@ class Infos:
|
|||||||
tab = self.__info_defs
|
tab = self.__info_defs
|
||||||
for key in tab:
|
for key in tab:
|
||||||
row = tab[key]
|
row = tab[key]
|
||||||
|
if 'singleton' in row and row['singleton']:
|
||||||
|
node_id = proxy_node_id
|
||||||
|
snr = proxy_unique_id
|
||||||
|
else:
|
||||||
|
node_id = inv_node_id
|
||||||
|
snr = inv_snr
|
||||||
|
prfx = ha_prfx + node_id
|
||||||
|
|
||||||
#check if we have details for home assistant
|
#check if we have details for home assistant
|
||||||
if 'ha' in row:
|
if 'ha' in row:
|
||||||
@@ -160,7 +178,9 @@ class Infos:
|
|||||||
elif 'fmt' in ha:
|
elif 'fmt' in ha:
|
||||||
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }}"
|
attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }}"
|
||||||
|
|
||||||
if 'unit' in row:
|
# 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'] != None:
|
||||||
attr['unit_of_meas'] = row['unit'] # optional add a 'unit_of_meas' e.g. 'W'
|
attr['unit_of_meas'] = row['unit'] # optional add a 'unit_of_meas' e.g. 'W'
|
||||||
if 'icon' in ha:
|
if 'icon' in ha:
|
||||||
attr['ic'] = ha['icon'] # optional add an icon for the entity
|
attr['ic'] = ha['icon'] # optional add an icon for the entity
|
||||||
@@ -179,24 +199,34 @@ class Infos:
|
|||||||
|
|
||||||
dev = {}
|
dev = {}
|
||||||
|
|
||||||
# the same name fpr 'name' and 'suggested area', so we get dedicated devices in home assistant with short value name and headline
|
# the same name for 'name' and 'suggested area', so we get dedicated devices in home assistant with short value name and headline
|
||||||
if 'name' in device:
|
if sug_area == '' or ('singleton' in device and device['singleton']):
|
||||||
dev['name'] = device['name']
|
dev['name'] = device['name']
|
||||||
dev['sa'] = device['name']
|
dev['sa'] = device['name']
|
||||||
# fixme: we ignore the suggested area, since one area make no sense for multiple devices
|
else:
|
||||||
#else:
|
dev['name'] = device['name']+' - '+sug_area
|
||||||
# dev['name'] = sug_area
|
dev['sa'] = device['name']+' - '+sug_area
|
||||||
# dev['sa'] = sug_area
|
|
||||||
|
|
||||||
if 'via' in device: # add the link to the parent device
|
if 'via' in device: # add the link to the parent device
|
||||||
dev['via_device'] = f"{device['via']}_{snr}"
|
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}"
|
||||||
|
|
||||||
|
|
||||||
for key in ('mdl','mf', 'sw', 'hw'): # add optional values fpr 'modell', 'manufaturer', 'sw version' and 'hw version'
|
for key in ('mdl','mf', 'sw', 'hw'): # add optional values fpr 'modell', 'manufaturer', 'sw version' and 'hw version'
|
||||||
if key in device:
|
if key in device:
|
||||||
data = self.dev_value(device[key])
|
data = self.dev_value(device[key])
|
||||||
if data is not None: dev[key] = data
|
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}"]
|
||||||
|
|
||||||
dev['ids'] = [f"{ha['dev']}_{snr}"]
|
|
||||||
attr['dev'] = dev
|
attr['dev'] = dev
|
||||||
|
|
||||||
origin = {}
|
origin = {}
|
||||||
@@ -205,11 +235,26 @@ class Infos:
|
|||||||
attr['o'] = origin
|
attr['o'] = origin
|
||||||
|
|
||||||
|
|
||||||
yield json.dumps (attr), component, attr['uniq_id']
|
yield json.dumps (attr), component, node_id, attr['uniq_id']
|
||||||
|
|
||||||
|
def __init_counter (self, counter:str) -> dict:
|
||||||
|
'''init proxy statistic counter, when its missing'''
|
||||||
|
if not 'proxy' in self.stat:
|
||||||
|
self.stat['proxy'] = {}
|
||||||
|
dict = self.stat['proxy']
|
||||||
|
if not counter in dict:
|
||||||
|
dict[counter] = 0
|
||||||
|
return dict
|
||||||
|
|
||||||
|
def inc_counter (self, counter:str) -> None:
|
||||||
|
'''inc proxy statistic counter'''
|
||||||
|
dict = self.__init_counter (counter)
|
||||||
|
dict[counter] += 1
|
||||||
|
|
||||||
|
def dec_counter (self, counter:str) -> None:
|
||||||
|
'''dec proxy statistic counter'''
|
||||||
|
dict = self.__init_counter (counter)
|
||||||
|
dict[counter] -= 1
|
||||||
|
|
||||||
|
|
||||||
def __key_obj(self, id) -> list:
|
def __key_obj(self, id) -> list:
|
||||||
|
|||||||
@@ -14,22 +14,28 @@ class Inverter(AsyncStream):
|
|||||||
def __init__ (self, reader, writer, addr):
|
def __init__ (self, reader, writer, addr):
|
||||||
super().__init__(reader, writer, addr, None, True)
|
super().__init__(reader, writer, addr, None, True)
|
||||||
self.mqtt = Mqtt()
|
self.mqtt = Mqtt()
|
||||||
self.ha_restarts = 0
|
self.ha_restarts = -1
|
||||||
ha = Config.get('ha')
|
ha = Config.get('ha')
|
||||||
self.entitiy_prfx = ha['entity_prefix'] + '/'
|
self.entity_prfx = ha['entity_prefix'] + '/'
|
||||||
self.discovery_prfx = ha['discovery_prefix'] + '/'
|
self.discovery_prfx = ha['discovery_prefix'] + '/'
|
||||||
|
self.proxy_node_id = ha['proxy_node_id'] + '/'
|
||||||
|
self.proxy_unique_id = ha['proxy_unique_id']
|
||||||
|
|
||||||
|
|
||||||
async def server_loop(self, addr):
|
async def server_loop(self, addr):
|
||||||
'''Loop for receiving messages from the inverter (server-side)'''
|
'''Loop for receiving messages from the inverter (server-side)'''
|
||||||
logging.info(f'Accept connection from {addr}')
|
logging.info(f'Accept connection from {addr}')
|
||||||
|
self.inc_counter ('Inverter_Cnt')
|
||||||
await self.loop()
|
await self.loop()
|
||||||
|
self.dec_counter ('Inverter_Cnt')
|
||||||
logging.info(f'Server loop stopped for {addr}')
|
logging.info(f'Server loop stopped for {addr}')
|
||||||
|
|
||||||
# if the server connection closes, we also have to disconnect the connection to te TSUN cloud
|
# if the server connection closes, we also have to disconnect the connection to te TSUN cloud
|
||||||
if self.remoteStream:
|
if self.remoteStream:
|
||||||
logging.debug ("disconnect client connection")
|
logging.debug ("disconnect client connection")
|
||||||
self.remoteStream.disc()
|
self.remoteStream.disc()
|
||||||
|
|
||||||
|
# await self.async_publ_mqtt()
|
||||||
|
|
||||||
async def client_loop(self, addr):
|
async def client_loop(self, addr):
|
||||||
'''Loop for receiving messages from the TSUN cloud (client-side)'''
|
'''Loop for receiving messages from the TSUN cloud (client-side)'''
|
||||||
@@ -68,6 +74,7 @@ class Inverter(AsyncStream):
|
|||||||
async def async_publ_mqtt(self) -> None:
|
async def async_publ_mqtt(self) -> None:
|
||||||
'''puplish data to MQTT broker'''
|
'''puplish data to MQTT broker'''
|
||||||
db = self.db.db
|
db = self.db.db
|
||||||
|
stat = self.db.stat
|
||||||
# check if new inverter or collector infos are available or when the home assistant has changed the status back to online
|
# check if new inverter or collector infos are available or when the home assistant has changed the status back to online
|
||||||
if (('inverter' in self.new_data and self.new_data['inverter']) or
|
if (('inverter' in self.new_data and self.new_data['inverter']) or
|
||||||
('collector' in self.new_data and self.new_data['collector']) or
|
('collector' in self.new_data and self.new_data['collector']) or
|
||||||
@@ -76,18 +83,25 @@ class Inverter(AsyncStream):
|
|||||||
self.ha_restarts = self.mqtt.ha_restarts
|
self.ha_restarts = self.mqtt.ha_restarts
|
||||||
|
|
||||||
for key in self.new_data:
|
for key in self.new_data:
|
||||||
if self.new_data[key] and key in db:
|
if self.new_data[key]:
|
||||||
data_json = json.dumps(db[key])
|
if key in db:
|
||||||
|
data_json = json.dumps(db[key])
|
||||||
|
node_id = self.node_id
|
||||||
|
elif key in stat:
|
||||||
|
data_json = json.dumps(stat[key])
|
||||||
|
node_id = self.proxy_node_id
|
||||||
|
else:
|
||||||
|
continue
|
||||||
logger_mqtt.debug(f'{key}: {data_json}')
|
logger_mqtt.debug(f'{key}: {data_json}')
|
||||||
await self.mqtt.publish(f"{self.entitiy_prfx}{self.node_id}{key}", data_json)
|
await self.mqtt.publish(f"{self.entity_prfx}{node_id}{key}", data_json)
|
||||||
self.new_data[key] = False
|
self.new_data[key] = False
|
||||||
|
|
||||||
async def __register_home_assistant(self) -> None:
|
async def __register_home_assistant(self) -> None:
|
||||||
'''register all our topics at home assistant'''
|
'''register all our topics at home assistant'''
|
||||||
try:
|
try:
|
||||||
for data_json, component, id in self.db.ha_confs(self.entitiy_prfx + self.node_id, self.unique_id, self.sug_area):
|
for data_json, component, node_id, id in self.db.ha_confs(self.entity_prfx, self.node_id, self.unique_id, self.proxy_node_id, self.proxy_unique_id, self.sug_area):
|
||||||
logger_mqtt.debug(f'MQTT Register: {data_json}')
|
logger_mqtt.debug(f"MQTT Register: cmp:'{component}' node_id:'{node_id}' {data_json}")
|
||||||
await self.mqtt.publish(f"{self.discovery_prfx}{component}/{self.node_id}{id}/config", data_json)
|
await self.mqtt.publish(f"{self.discovery_prfx}{component}/{node_id}{id}/config", data_json)
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Inverter: Exception:\n"
|
f"Inverter: Exception:\n"
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ class Message(metaclass=IterRegistry):
|
|||||||
self.header_valid = False
|
self.header_valid = False
|
||||||
self.header_len = 0
|
self.header_len = 0
|
||||||
self.data_len = 0
|
self.data_len = 0
|
||||||
|
self.unique_id = 0
|
||||||
|
self.node_id = ''
|
||||||
|
self.sug_area = ''
|
||||||
self._recv_buffer = b''
|
self._recv_buffer = b''
|
||||||
self._send_buffer = bytearray(0)
|
self._send_buffer = bytearray(0)
|
||||||
self._forward_buffer = bytearray(0)
|
self._forward_buffer = bytearray(0)
|
||||||
@@ -106,7 +109,40 @@ class Message(metaclass=IterRegistry):
|
|||||||
# so we have to erase self.switch, otherwise this instance can't be
|
# so we have to erase self.switch, otherwise this instance can't be
|
||||||
# deallocated by the garbage collector ==> we get a memory leak
|
# deallocated by the garbage collector ==> we get a memory leak
|
||||||
del self.switch
|
del self.switch
|
||||||
|
|
||||||
|
def inc_counter(self, counter:str) -> None:
|
||||||
|
self.db.inc_counter(counter)
|
||||||
|
self.new_data['proxy'] = True
|
||||||
|
|
||||||
|
def dec_counter(self, counter:str) -> None:
|
||||||
|
self.db.dec_counter(counter)
|
||||||
|
self.new_data['proxy'] = True
|
||||||
|
|
||||||
|
def set_serial_no(self, serial_no : str):
|
||||||
|
|
||||||
|
if self.unique_id == serial_no:
|
||||||
|
logger.debug(f'SerialNo: {serial_no}')
|
||||||
|
else:
|
||||||
|
inverters = Config.get('inverters')
|
||||||
|
#logger.debug(f'Inverters: {inverters}')
|
||||||
|
|
||||||
|
if serial_no in inverters:
|
||||||
|
inv = inverters[serial_no]
|
||||||
|
self.node_id = inv['node_id']
|
||||||
|
self.sug_area = inv['suggested_area']
|
||||||
|
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}')
|
||||||
|
else:
|
||||||
|
self.node_id = ''
|
||||||
|
self.sug_area = ''
|
||||||
|
if 'allow_all' not in inverters or not inverters['allow_all']:
|
||||||
|
self.inc_counter('Unknown_SNR')
|
||||||
|
self.unique_id = None
|
||||||
|
logger.warning(f'ignore message from unknow inverter! (SerialNo: {serial_no})')
|
||||||
|
return
|
||||||
|
logger.debug(f'SerialNo {serial_no} not known but accepted!')
|
||||||
|
|
||||||
|
self.unique_id = serial_no
|
||||||
|
|
||||||
|
|
||||||
def read(self) -> None:
|
def read(self) -> None:
|
||||||
self._read()
|
self._read()
|
||||||
@@ -115,6 +151,11 @@ class Message(metaclass=IterRegistry):
|
|||||||
self.__parse_header(self._recv_buffer, len(self._recv_buffer))
|
self.__parse_header(self._recv_buffer, len(self._recv_buffer))
|
||||||
|
|
||||||
if self.header_valid and len(self._recv_buffer) >= (self.header_len+self.data_len):
|
if self.header_valid and len(self._recv_buffer) >= (self.header_len+self.data_len):
|
||||||
|
hex_dump_memory(logging.INFO, f'Received from {self.addr}:', self._recv_buffer, self.header_len+self.data_len)
|
||||||
|
|
||||||
|
if self.id_str:
|
||||||
|
self.set_serial_no(self.id_str.decode("utf-8"))
|
||||||
|
|
||||||
self.__dispatch_msg()
|
self.__dispatch_msg()
|
||||||
self.__flush_recv_msg()
|
self.__flush_recv_msg()
|
||||||
return
|
return
|
||||||
@@ -203,11 +244,12 @@ class Message(metaclass=IterRegistry):
|
|||||||
|
|
||||||
|
|
||||||
def __dispatch_msg(self) -> None:
|
def __dispatch_msg(self) -> None:
|
||||||
hex_dump_memory(logging.INFO, f'Received from {self.addr}:', self._recv_buffer, self.header_len+self.data_len)
|
|
||||||
|
|
||||||
fnc = self.switch.get(self.msg_id, self.msg_unknown)
|
fnc = self.switch.get(self.msg_id, self.msg_unknown)
|
||||||
logger.info(self.__flow_str(self.server_side, 'rx') + f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}' )
|
if self.unique_id:
|
||||||
fnc()
|
logger.info(self.__flow_str(self.server_side, 'rx') + f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}' )
|
||||||
|
fnc()
|
||||||
|
else:
|
||||||
|
logger.info(self.__flow_str(self.server_side, 'drop') + f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}' )
|
||||||
|
|
||||||
|
|
||||||
def __flush_recv_msg(self) -> None:
|
def __flush_recv_msg(self) -> None:
|
||||||
@@ -296,6 +338,7 @@ class Message(metaclass=IterRegistry):
|
|||||||
|
|
||||||
def msg_unknown(self):
|
def msg_unknown(self):
|
||||||
logger.warning (f"Unknow Msg: ID:{self.msg_id}")
|
logger.warning (f"Unknow Msg: ID:{self.msg_id}")
|
||||||
|
self.inc_counter('Unknown_Msg')
|
||||||
self.forward(self._recv_buffer, self.header_len+self.data_len)
|
self.forward(self._recv_buffer, self.header_len+self.data_len)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def test_parse_control(ContrDataSeq):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
assert json.dumps(i.db) == json.dumps(
|
assert json.dumps(i.db) == json.dumps(
|
||||||
{"collector": {"Collector_Fw_Version": "RSW_400_V1.00.06", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com"}, "controller": {"Signal_Strength": 100, "Power_On_Time": 29, "Data_Up_Interval": 300}})
|
{"collector": {"Collector_Fw_Version": "RSW_400_V1.00.06", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com", "No_Inputs": 2}, "controller": {"Signal_Strength": 100, "Power_On_Time": 29, "Data_Up_Interval": 300}})
|
||||||
|
|
||||||
def test_parse_inverter(InvDataSeq):
|
def test_parse_inverter(InvDataSeq):
|
||||||
i = Infos()
|
i = Infos()
|
||||||
@@ -45,14 +45,14 @@ def test_parse_cont_and_invert(ContrDataSeq, InvDataSeq):
|
|||||||
|
|
||||||
assert json.dumps(i.db) == json.dumps(
|
assert json.dumps(i.db) == json.dumps(
|
||||||
{
|
{
|
||||||
"collector": {"Collector_Fw_Version": "RSW_400_V1.00.06", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com"}, "controller": {"Signal_Strength": 100, "Power_On_Time": 29, "Data_Up_Interval": 300},
|
"collector": {"Collector_Fw_Version": "RSW_400_V1.00.06", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com", "No_Inputs": 2}, "controller": {"Signal_Strength": 100, "Power_On_Time": 29, "Data_Up_Interval": 300},
|
||||||
"inverter": {"Product_Name": "Microinv", "Manufacturer": "TSUN", "Version": "V5.0.11", "Serial_Number": "T17E7307021D006A", "Equipment_Model": "TSOL-MS600"}})
|
"inverter": {"Product_Name": "Microinv", "Manufacturer": "TSUN", "Version": "V5.0.11", "Serial_Number": "T17E7307021D006A", "Equipment_Model": "TSOL-MS600"}})
|
||||||
|
|
||||||
|
|
||||||
def test_build_ha_conf1(ContrDataSeq):
|
def test_build_ha_conf1(ContrDataSeq):
|
||||||
i = Infos()
|
i = Infos()
|
||||||
tests = 0
|
tests = 0
|
||||||
for d_json, comp, id in i.ha_confs(prfx="tsun/garagendach/", snr='123'):
|
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", inv_snr='123', inv_node_id="garagendach/",proxy_node_id = 'proxy/', proxy_unique_id = '456'):
|
||||||
|
|
||||||
if id == 'out_power_123':
|
if id == 'out_power_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
@@ -66,19 +66,18 @@ def test_build_ha_conf1(ContrDataSeq):
|
|||||||
|
|
||||||
elif id == 'power_pv1_123':
|
elif id == 'power_pv1_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv1_123", "val_tpl": "{{ (value_json['pv1']['Power'] | float)}}", "unit_of_meas": "W", "ic": "mdi:gauge", "dev": {"name": "Module PV1", "sa": "Module PV1", "via_device": "inverter_123", "ids": ["input_pv1_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv1_123", "val_tpl": "{{ (value_json['pv1']['Power'] | float)}}", "unit_of_meas": "W", "dev": {"name": "Module PV1", "sa": "Module PV1", "via_device": "inverter_123", "ids": ["input_pv1_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||||
tests +=1
|
tests +=1
|
||||||
|
|
||||||
elif id == 'power_pv2_123':
|
elif id == 'power_pv2_123':
|
||||||
assert comp == 'sensor'
|
assert False # if we haven't received and parsed a control data msg, we don't know the number of inputs. In this case we only register the first one!!
|
||||||
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv2_123", "val_tpl": "{{ (value_json['pv2']['Power'] | float)}}", "unit_of_meas": "W", "ic": "mdi:gauge", "dev": {"name": "Module PV2", "sa": "Module PV2", "via_device": "inverter_123", "ids": ["input_pv2_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
|
||||||
tests +=1
|
|
||||||
|
|
||||||
elif id == 'signal_123':
|
elif id == 'signal_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
assert d_json == json.dumps({"name": "Signal Strength", "stat_t": "tsun/garagendach/controller", "dev_cla": None, "stat_cla": "measurement", "uniq_id": "signal_123", "val_tpl": "{{value_json[\'Signal_Strength\'] | int}}", "unit_of_meas": "%", "ic": "mdi:wifi", "dev": {"name": "Controller", "sa": "Controller", "ids": ["controller_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
assert d_json == json.dumps({"name": "Signal Strength", "stat_t": "tsun/garagendach/controller", "dev_cla": None, "stat_cla": "measurement", "uniq_id": "signal_123", "val_tpl": "{{value_json[\'Signal_Strength\'] | int}}", "unit_of_meas": "%", "ic": "mdi:wifi", "dev": {"name": "Controller", "sa": "Controller", "via_device": "proxy", "ids": ["controller_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||||
tests +=1
|
tests +=1
|
||||||
assert tests==5
|
assert tests==4
|
||||||
|
|
||||||
def test_build_ha_conf2(ContrDataSeq, InvDataSeq):
|
def test_build_ha_conf2(ContrDataSeq, InvDataSeq):
|
||||||
i = Infos()
|
i = Infos()
|
||||||
@@ -89,7 +88,7 @@ def test_build_ha_conf2(ContrDataSeq, InvDataSeq):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
tests = 0
|
tests = 0
|
||||||
for d_json, comp, id in i.ha_confs(prfx="tsun/garagendach/", snr='123'):
|
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", inv_snr='123', inv_node_id="garagendach/",proxy_node_id = 'proxy/', proxy_unique_id = '456'):
|
||||||
|
|
||||||
if id == 'out_power_123':
|
if id == 'out_power_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
@@ -103,16 +102,16 @@ def test_build_ha_conf2(ContrDataSeq, InvDataSeq):
|
|||||||
|
|
||||||
elif id == 'power_pv1_123':
|
elif id == 'power_pv1_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv1_123", "val_tpl": "{{ (value_json['pv1']['Power'] | float)}}", "unit_of_meas": "W", "ic": "mdi:gauge", "dev": {"name": "Module PV1", "sa": "Module PV1", "via_device": "inverter_123", "ids": ["input_pv1_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv1_123", "val_tpl": "{{ (value_json['pv1']['Power'] | float)}}", "unit_of_meas": "W", "dev": {"name": "Module PV1", "sa": "Module PV1", "via_device": "inverter_123", "ids": ["input_pv1_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||||
tests +=1
|
tests +=1
|
||||||
|
|
||||||
elif id == 'power_pv2_123':
|
elif id == 'power_pv2_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv2_123", "val_tpl": "{{ (value_json['pv2']['Power'] | float)}}", "unit_of_meas": "W", "ic": "mdi:gauge", "dev": {"name": "Module PV2", "sa": "Module PV2", "via_device": "inverter_123", "ids": ["input_pv2_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
assert d_json == json.dumps({"name": "Power", "stat_t": "tsun/garagendach/input", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "power_pv2_123", "val_tpl": "{{ (value_json['pv2']['Power'] | float)}}", "unit_of_meas": "W", "dev": {"name": "Module PV2", "sa": "Module PV2", "via_device": "inverter_123", "ids": ["input_pv2_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||||
tests +=1
|
tests +=1
|
||||||
|
|
||||||
elif id == 'signal_123':
|
elif id == 'signal_123':
|
||||||
assert comp == 'sensor'
|
assert comp == 'sensor'
|
||||||
assert d_json == json.dumps({"name": "Signal Strength", "stat_t": "tsun/garagendach/controller", "dev_cla": None, "stat_cla": "measurement", "uniq_id": "signal_123", "val_tpl": "{{value_json[\'Signal_Strength\'] | int}}", "unit_of_meas": "%", "ic": "mdi:wifi", "dev": {"name": "Controller", "sa": "Controller", "mdl": "RSW-1-10001", "mf": "Raymon", "sw": "RSW_400_V1.00.06", "ids": ["controller_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
assert d_json == json.dumps({"name": "Signal Strength", "stat_t": "tsun/garagendach/controller", "dev_cla": None, "stat_cla": "measurement", "uniq_id": "signal_123", "val_tpl": "{{value_json[\'Signal_Strength\'] | int}}", "unit_of_meas": "%", "ic": "mdi:wifi", "dev": {"name": "Controller", "sa": "Controller", "via_device": "proxy", "mdl": "RSW-1-10001", "mf": "Raymon", "sw": "RSW_400_V1.00.06", "ids": ["controller_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||||
tests +=1
|
tests +=1
|
||||||
assert tests==5
|
assert tests==5
|
||||||
|
|||||||
199
system_tests/test_tcp_socket.py
Normal file
199
system_tests/test_tcp_socket.py
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
# test_with_pytest.py and scapy
|
||||||
|
#
|
||||||
|
import pytest, socket, time
|
||||||
|
#from scapy.all import *
|
||||||
|
#from scapy.layers.inet import IP, TCP, TCP_client
|
||||||
|
|
||||||
|
def get_sn() -> bytes:
|
||||||
|
return b'R170000000000001'
|
||||||
|
|
||||||
|
def get_inv_no() -> bytes:
|
||||||
|
return b'T170000000000001'
|
||||||
|
|
||||||
|
def get_invalid_sn():
|
||||||
|
return b'R170000000000002'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgContactInfo(): # Contact Info message
|
||||||
|
return b'\x00\x00\x00\x2c\x10'+get_sn()+b'\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgContactResp(): # Contact Response message
|
||||||
|
return b'\x00\x00\x00\x14\x10'+get_sn()+b'\x99\x00\x01'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgContactInfo2(): # Contact Info message
|
||||||
|
return b'\x00\x00\x00\x2c\x10'+get_invalid_sn()+b'\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgContactResp2(): # Contact Response message
|
||||||
|
return b'\x00\x00\x00\x14\x10'+get_invalid_sn()+b'\x99\x00\x01'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgTimeStampReq(): # Get Time Request message
|
||||||
|
return b'\x00\x00\x00\x13\x10'+get_sn()+b'\x91\x22'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgTimeStampResp(): # Get Time Resonse message
|
||||||
|
return b'\x00\x00\x00\x1b\x10'+get_sn()+b'\x99\x22\x00\x00\x01\x89\xc6\x63\x4d\x80'
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgContollerInd(): # Data indication from the controller
|
||||||
|
msg = b'\x00\x00\x01\x2f\x10'+ get_sn() + b'\x91\x71\x0e\x10\x00\x00\x10'+get_sn()
|
||||||
|
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x55\x50'
|
||||||
|
msg += b'\x00\x00\x00\x15\x00\x09\x2b\xa8\x54\x10\x52\x53\x57\x5f\x34\x30\x30\x5f\x56\x31\x2e\x30\x30\x2e\x30\x36\x00\x09\x27\xc0\x54\x06\x52\x61\x79\x6d\x6f'
|
||||||
|
msg += b'\x6e\x00\x09\x2f\x90\x54\x0b\x52\x53\x57\x2d\x31\x2d\x31\x30\x30\x30\x31\x00\x09\x5a\x88\x54\x0f\x74\x2e\x72\x61\x79\x6d\x6f\x6e\x69\x6f\x74\x2e\x63\x6f\x6d\x00\x09\x5a\xec\x54'
|
||||||
|
msg += b'\x1c\x6c\x6f\x67\x67\x65\x72\x2e\x74\x61\x6c\x65\x6e\x74\x2d\x6d\x6f\x6e\x69\x74\x6f\x72\x69\x6e\x67\x2e\x63\x6f\x6d\x00\x0d\x00\x20\x49\x00\x00\x00\x01\x00\x0c\x35\x00\x49\x00'
|
||||||
|
msg += b'\x00\x00\x64\x00\x0c\x96\xa8\x49\x00\x00\x00\x1d\x00\x0c\x7f\x38\x49\x00\x00\x00\x01\x00\x0c\xfc\x38\x49\x00\x00\x00\x01\x00\x0c\xf8\x50\x49\x00\x00\x01\x2c\x00\x0c\x63\xe0\x49'
|
||||||
|
msg += b'\x00\x00\x00\x00\x00\x0c\x67\xc8\x49\x00\x00\x00\x00\x00\x0c\x50\x58\x49\x00\x00\x00\x01\x00\x09\x5e\x70\x49\x00\x00\x13\x8d\x00\x09\x5e\xd4\x49\x00\x00\x13\x8d\x00\x09\x5b\x50'
|
||||||
|
msg += b'\x49\x00\x00\x00\x02\x00\x0d\x04\x08\x49\x00\x00\x00\x00\x00\x07\xa1\x84\x49\x00\x00\x00\x01\x00\x0c\x50\x59\x49\x00\x00\x00\x4c\x00\x0d\x1f\x60\x49\x00\x00\x00\x00'
|
||||||
|
return msg
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgInvData(): # Data indication from the controller
|
||||||
|
msg = b'\x00\x00\x00\x8b\x10'+ get_sn() + b'\x91\x04\x01\x90\x00\x01\x10'+get_inv_no()
|
||||||
|
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x61\x08'
|
||||||
|
msg += b'\x00\x00\x00\x06\x00\x00\x00\x0a\x54\x08\x4d\x69\x63\x72\x6f\x69\x6e\x76\x00\x00\x00\x14\x54\x04\x54\x53\x55\x4e\x00\x00\x00\x1E\x54\x07\x56\x35\x2e\x30\x2e\x31\x31\x00\x00\x00\x28'
|
||||||
|
msg += b'\x54\x10\x54\x31\x37\x45\x37\x33\x30\x37\x30\x32\x31\x44\x30\x30\x36\x41\x00\x00\x00\x32\x54\x0a\x54\x53\x4f\x4c\x2d\x4d\x53\x36\x30\x30\x00\x00\x00\x3c\x54\x05\x41\x2c\x42\x2c\x43'
|
||||||
|
return msg
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def MsgInverterInd(): # Data indication from the inverter
|
||||||
|
msg = b'\x00\x00\x05\x02\x10'+ get_sn() + b'\x91\x04\x01\x90\x00\x01\x10'+get_inv_no()
|
||||||
|
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x61\x08'
|
||||||
|
msg += b'\x00\x00\x00\xa3\x00\x00\x00\x64\x53\x00\x01\x00\x00\x00\xc8\x53\x00\x02\x00\x00\x01\x2c\x53\x00\x00\x00\x00\x01\x90\x49\x00\x00\x00\x00\x00\x00\x01\x91\x53\x00\x00'
|
||||||
|
msg += b'\x00\x00\x01\x92\x53\x00\x00\x00\x00\x01\x93\x53\x00\x00\x00\x00\x01\x94\x53\x00\x00\x00\x00\x01\x95\x53\x00\x00\x00\x00\x01\x96\x53\x00\x00\x00\x00\x01\x97\x53\x00'
|
||||||
|
msg += b'\x00\x00\x00\x01\x98\x53\x00\x00\x00\x00\x01\x99\x53\x00\x00\x00\x00\x01\x9a\x53\x00\x00\x00\x00\x01\x9b\x53\x00\x00\x00\x00\x01\x9c\x53\x00\x00\x00\x00\x01\x9d\x53'
|
||||||
|
msg += b'\x00\x00\x00\x00\x01\x9e\x53\x00\x00\x00\x00\x01\x9f\x53\x00\x00\x00\x00\x01\xa0\x53\x00\x00\x00\x00\x01\xf4\x49\x00\x00\x00\x00\x00\x00\x01\xf5\x53\x00\x00\x00\x00'
|
||||||
|
msg += b'\x01\xf6\x53\x00\x00\x00\x00\x01\xf7\x53\x00\x00\x00\x00\x01\xf8\x53\x00\x00\x00\x00\x01\xf9\x53\x00\x00\x00\x00\x01\xfa\x53\x00\x00\x00\x00\x01\xfb\x53\x00\x00\x00'
|
||||||
|
msg += b'\x00\x01\xfc\x53\x00\x00\x00\x00\x01\xfd\x53\x00\x00\x00\x00\x01\xfe\x53\x00\x00\x00\x00\x01\xff\x53\x00\x00\x00\x00\x02\x00\x53\x00\x00\x00\x00\x02\x01\x53\x00\x00'
|
||||||
|
msg += b'\x00\x00\x02\x02\x53\x00\x00\x00\x00\x02\x03\x53\x00\x00\x00\x00\x02\x04\x53\x00\x00\x00\x00\x02\x58\x49\x00\x00\x00\x00\x00\x00\x02\x59\x53\x00\x00\x00\x00\x02\x5a'
|
||||||
|
msg += b'\x53\x00\x00\x00\x00\x02\x5b\x53\x00\x00\x00\x00\x02\x5c\x53\x00\x00\x00\x00\x02\x5d\x53\x00\x00\x00\x00\x02\x5e\x53\x00\x00\x00\x00\x02\x5f\x53\x00\x00\x00\x00\x02'
|
||||||
|
msg += b'\x60\x53\x00\x00\x00\x00\x02\x61\x53\x00\x00\x00\x00\x02\x62\x53\x00\x00\x00\x00\x02\x63\x53\x00\x00\x00\x00\x02\x64\x53\x00\x00\x00\x00\x02\x65\x53\x00\x00\x00\x00'
|
||||||
|
msg += b'\x02\x66\x53\x00\x00\x00\x00\x02\x67\x53\x00\x00\x00\x00\x02\x68\x53\x00\x00\x00\x00\x02\xbc\x49\x00\x00\x00\x00\x00\x00\x02\xbd\x53\x00\x00\x00\x00\x02\xbe\x53\x00'
|
||||||
|
msg += b'\x00\x00\x00\x02\xbf\x53\x00\x00\x00\x00\x02\xc0\x53\x00\x00\x00\x00\x02\xc1\x53\x00\x00\x00\x00\x02\xc2\x53\x00\x00\x00\x00\x02\xc3\x53\x00\x00\x00\x00\x02\xc4\x53'
|
||||||
|
msg += b'\x00\x00\x00\x00\x02\xc5\x53\x00\x00\x00\x00\x02\xc6\x53\x00\x00\x00\x00\x02\xc7\x53\x00\x00\x00\x00\x02\xc8\x53\x00\x00\x00\x00\x02\xc9\x53\x00\x00\x00\x00\x02\xca'
|
||||||
|
msg += b'\x53\x00\x00\x00\x00\x02\xcb\x53\x00\x00\x00\x00\x02\xcc\x53\x00\x00\x00\x00\x03\x20\x53\x00\x00\x00\x00\x03\x84\x53\x50\x11\x00\x00\x03\xe8\x46\x43\x61\x66\x66\x00'
|
||||||
|
msg += b'\x00\x04\x4c\x46\x3e\xeb\x85\x1f\x00\x00\x04\xb0\x46\x42\x48\x14\x7b\x00\x00\x05\x14\x53\x00\x17\x00\x00\x05\x78\x53\x00\x00\x00\x00\x05\xdc\x53\x02\x58\x00\x00\x06'
|
||||||
|
msg += b'\x40\x46\x42\xd3\x66\x66\x00\x00\x06\xa4\x46\x42\x06\x66\x66\x00\x00\x07\x08\x46\x3f\xf4\x7a\xe1\x00\x00\x07\x6c\x46\x42\x81\x00\x00\x00\x00\x07\xd0\x46\x42\x06\x00'
|
||||||
|
msg += b'\x00\x00\x00\x08\x34\x46\x3f\xae\x14\x7b\x00\x00\x08\x98\x46\x42\x36\xcc\xcd\x00\x00\x08\xfc\x46\x00\x00\x00\x00\x00\x00\x09\x60\x46\x00\x00\x00\x00\x00\x00\x09\xc4'
|
||||||
|
msg += b'\x46\x00\x00\x00\x00\x00\x00\x0a\x28\x46\x00\x00\x00\x00\x00\x00\x0a\x8c\x46\x00\x00\x00\x00\x00\x00\x0a\xf0\x46\x00\x00\x00\x00\x00\x00\x0b\x54\x46\x3f\xd9\x99\x9a'
|
||||||
|
msg += b'\x00\x00\x0b\xb8\x46\x41\x8a\xe1\x48\x00\x00\x0c\x1c\x46\x3f\x8a\x3d\x71\x00\x00\x0c\x80\x46\x41\x1b\xd7\x0a\x00\x00\x0c\xe4\x46\x3f\x1e\xb8\x52\x00\x00\x0d\x48\x46'
|
||||||
|
msg += b'\x40\xf3\xd7\x0a\x00\x00\x0d\xac\x46\x00\x00\x00\x00\x00\x00\x0e\x10\x46\x00\x00\x00\x00\x00\x00\x0e\x74\x46\x00\x00\x00\x00\x00\x00\x0e\xd8\x46\x00\x00\x00\x00\x00'
|
||||||
|
msg += b'\x00\x0f\x3c\x53\x00\x00\x00\x00\x0f\xa0\x53\x00\x00\x00\x00\x10\x04\x53\x55\xaa\x00\x00\x10\x68\x53\x00\x00\x00\x00\x10\xcc\x53\x00\x00\x00\x00\x11\x30\x53\x00\x00'
|
||||||
|
msg += b'\x00\x00\x11\x94\x53\x00\x00\x00\x00\x11\xf8\x53\xff\xff\x00\x00\x12\x5c\x53\xff\xff\x00\x00\x12\xc0\x53\x00\x02\x00\x00\x13\x24\x53\xff\xff\x00\x00\x13\x88\x53\xff'
|
||||||
|
msg += b'\xff\x00\x00\x13\xec\x53\xff\xff\x00\x00\x14\x50\x53\xff\xff\x00\x00\x14\xb4\x53\xff\xff\x00\x00\x15\x18\x53\xff\xff\x00\x00\x15\x7c\x53\x00\x00\x00\x00\x27\x10\x53'
|
||||||
|
msg += b'\x00\x02\x00\x00\x27\x74\x53\x00\x3c\x00\x00\x27\xd8\x53\x00\x68\x00\x00\x28\x3c\x53\x05\x00\x00\x00\x28\xa0\x46\x43\x79\x00\x00\x00\x00\x29\x04\x46\x43\x48\x00\x00'
|
||||||
|
msg += b'\x00\x00\x29\x68\x46\x42\x48\x33\x33\x00\x00\x29\xcc\x46\x42\x3e\x3d\x71\x00\x00\x2a\x30\x53\x00\x01\x00\x00\x2a\x94\x46\x43\x37\x00\x00\x00\x00\x2a\xf8\x46\x42\xce'
|
||||||
|
msg += b'\x00\x00\x00\x00\x2b\x5c\x53\x00\x96\x00\x00\x2b\xc0\x53\x00\x10\x00\x00\x2c\x24\x46\x43\x90\x00\x00\x00\x00\x2c\x88\x46\x43\x95\x00\x00\x00\x00\x2c\xec\x53\x00\x06'
|
||||||
|
msg += b'\x00\x00\x2d\x50\x53\x00\x06\x00\x00\x2d\xb4\x46\x43\x7d\x00\x00\x00\x00\x2e\x18\x46\x42\x3d\xeb\x85\x00\x00\x2e\x7c\x46\x42\x3d\xeb\x85\x00\x00\x2e\xe0\x53\x00\x03'
|
||||||
|
msg += b'\x00\x00\x2f\x44\x53\x00\x03\x00\x00\x2f\xa8\x46\x42\x4d\xeb\x85\x00\x00\x30\x0c\x46\x42\x4d\xeb\x85\x00\x00\x30\x70\x53\x00\x03\x00\x00\x30\xd4\x53\x00\x03\x00\x00'
|
||||||
|
msg += b'\x31\x38\x46\x42\x08\x00\x00\x00\x00\x31\x9c\x53\x00\x05\x00\x00\x32\x00\x53\x04\x00\x00\x00\x32\x64\x53\x00\x01\x00\x00\x32\xc8\x53\x13\x9c\x00\x00\x33\x2c\x53\x0f'
|
||||||
|
msg += b'\xa0\x00\x00\x33\x90\x53\x00\x4f\x00\x00\x33\xf4\x53\x00\x66\x00\x00\x34\x58\x53\x03\xe8\x00\x00\x34\xbc\x53\x04\x00\x00\x00\x35\x20\x53\x00\x00\x00\x00\x35\x84\x53'
|
||||||
|
msg += b'\x00\x00\x00\x00\x35\xe8\x53\x00\x00\x00\x00\x36\x4c\x53\x00\x00\x00\x01\x38\x80\x53\x00\x02\x00\x01\x38\x81\x53\x00\x01\x00\x01\x38\x82\x53\x00\x01\x00\x01\x38\x83'
|
||||||
|
msg += b'\x53\x00\x00'
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def ClientConnection():
|
||||||
|
#host = '172.16.30.7'
|
||||||
|
host = 'logger.talent-monitoring.com'
|
||||||
|
#host = '127.0.0.1'
|
||||||
|
port = 5005
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
s.connect((host, port))
|
||||||
|
s.settimeout(1)
|
||||||
|
yield s
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
def tempClientConnection():
|
||||||
|
#host = '172.16.30.7'
|
||||||
|
host = 'logger.talent-monitoring.com'
|
||||||
|
#host = '127.0.0.1'
|
||||||
|
port = 5005
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
s.connect((host, port))
|
||||||
|
s.settimeout(1)
|
||||||
|
yield s
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
def test_open_close():
|
||||||
|
try:
|
||||||
|
for s in tempClientConnection():
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
assert False
|
||||||
|
assert True
|
||||||
|
|
||||||
|
def test_send_contact_info1(ClientConnection, MsgContactInfo, MsgContactResp):
|
||||||
|
s = ClientConnection
|
||||||
|
try:
|
||||||
|
s.sendall(MsgContactInfo)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
assert data == MsgContactResp
|
||||||
|
|
||||||
|
def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, MsgContactResp):
|
||||||
|
s = ClientConnection
|
||||||
|
try:
|
||||||
|
s.sendall(MsgContactInfo2)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
assert True
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
try:
|
||||||
|
s.sendall(MsgContactInfo)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
assert data == MsgContactResp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_contact_resp(ClientConnection, MsgContactResp):
|
||||||
|
s = ClientConnection
|
||||||
|
try:
|
||||||
|
s.sendall(MsgContactResp)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
assert True
|
||||||
|
else:
|
||||||
|
assert data ==''
|
||||||
|
|
||||||
|
def test_send_ctrl_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgContollerInd):
|
||||||
|
s = ClientConnection
|
||||||
|
try:
|
||||||
|
s.sendall(MsgTimeStampReq)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
# time.sleep(2.5)
|
||||||
|
# assert data == MsgTimeStampResp
|
||||||
|
try:
|
||||||
|
s.sendall(MsgContollerInd)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_send_inv_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgInvData, MsgInverterInd):
|
||||||
|
s = ClientConnection
|
||||||
|
try:
|
||||||
|
s.sendall(MsgTimeStampReq)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
|
# time.sleep(32.5)
|
||||||
|
# assert data == MsgTimeStampResp
|
||||||
|
try:
|
||||||
|
s.sendall(MsgInvData)
|
||||||
|
data = s.recv(1024)
|
||||||
|
s.sendall(MsgInverterInd)
|
||||||
|
data = s.recv(1024)
|
||||||
|
except TimeoutError:
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user