S allius/issue33 (#34)

* - fix issue 33

  The TSUN Cloud now responds to contact_info and get_time messages with
  an empty display message and not with a response message as before.
  We tried to parse data from the empty message, which led to an
  exception

* Add test with empty conn_ind from inverter
This commit is contained in:
Stefan Allius
2023-12-31 16:25:21 +01:00
committed by GitHub
parent d7b3ab54e8
commit 7225c20b01
4 changed files with 87 additions and 34 deletions

View File

@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Fixed [#33](https://github.com/s-allius/tsun-gen3-proxy/issues/33)
- Fixed detection of the connected inputs/MPPTs
- Preparation for overwriting received data
- home assistant:

View File

@@ -83,6 +83,7 @@ class Message(metaclass=IterRegistry):
self.unique_id = 0
self.node_id = ''
self.sug_area = ''
self.await_conn_resp_cnt = 0
self.id_str = id_str
self.contact_name = b''
self.contact_mail = b''
@@ -182,6 +183,7 @@ class Message(metaclass=IterRegistry):
def _init_new_client_conn(self, contact_name, contact_mail) -> None:
logger.info(f'name: {contact_name} mail: {contact_mail}')
self.msg_id = 0
self.await_conn_resp_cnt += 1
self.__build_header(0x91)
self._send_buffer += struct.pack(f'!{len(contact_name)+1}p'
f'{len(contact_mail)+1}p',
@@ -282,23 +284,27 @@ class Message(metaclass=IterRegistry):
'''
def msg_contact_info(self):
if self.ctrl.is_ind():
self.__build_header(0x99)
self._send_buffer += b'\x01'
self.__finish_send_msg()
self.__process_contact_info()
if self.server_side and self.__process_contact_info():
self.__build_header(0x91)
self._send_buffer += b'\x01'
self.__finish_send_msg()
# don't forward this contact info here, we will build one
# when the remote connection is established
elif self.await_conn_resp_cnt > 0:
self.await_conn_resp_cnt -= 1
else:
self.forward(self._recv_buffer, self.header_len+self.data_len)
return
elif self.ctrl.is_resp():
return # ignore received response from tsun
else:
logger.warning('Unknown Ctrl')
self.inc_counter('Unknown_Ctrl')
self.forward(self._recv_buffer, self.header_len+self.data_len)
def __process_contact_info(self):
def __process_contact_info(self) -> bool:
result = struct.unpack_from('!B', self._recv_buffer, self.header_len)
name_len = result[0]
if self.data_len < name_len+2:
return False
result = struct.unpack_from(f'!{name_len+1}pB', self._recv_buffer,
self.header_len)
self.contact_name = result[0]
@@ -309,33 +315,34 @@ class Message(metaclass=IterRegistry):
self.header_len+name_len+1)
self.contact_mail = result[0]
logger.info(f'mail: {self.contact_mail}')
return True
def msg_get_time(self):
tsun = Config.get('tsun')
if tsun['enabled']:
if self.ctrl.is_resp():
ts = self._timestamp()
result = struct.unpack_from('!q', self._recv_buffer,
self.header_len)
logger.debug(f'tsun-time: {result[0]:08x}'
f' proxy-time: {ts:08x}')
elif not self.ctrl.is_ind():
if self.ctrl.is_ind():
if self.data_len >= 8:
ts = self._timestamp()
result = struct.unpack_from('!q', self._recv_buffer,
self.header_len)
logger.debug(f'tsun-time: {result[0]:08x}'
f' proxy-time: {ts:08x}')
else:
logger.warning('Unknown Ctrl')
self.inc_counter('Unknown_Ctrl')
self.forward(self._recv_buffer, self.header_len+self.data_len)
else:
if self.ctrl.is_ind():
ts = self._timestamp()
logger.debug(f'time: {ts:08x}')
if self.data_len == 0:
ts = self._timestamp()
logger.debug(f'time: {ts:08x}')
self.__build_header(0x99)
self._send_buffer += struct.pack('!q', ts)
self.__finish_send_msg()
self.__build_header(0x91)
self._send_buffer += struct.pack('!q', ts)
self.__finish_send_msg()
elif self.ctrl.is_resp():
result = struct.unpack_from('!q', self._recv_buffer,
self.header_len)
logger.debug(f'tsun-time: {result[0]:08x}')
else:
logger.warning('Unknown Ctrl')
self.inc_counter('Unknown_Ctrl')
def parse_msg_header(self):
@@ -366,6 +373,7 @@ class Message(metaclass=IterRegistry):
elif self.ctrl.is_resp():
return # ignore received response
else:
logger.warning('Unknown Ctrl')
self.inc_counter('Unknown_Ctrl')
self.forward(self._recv_buffer, self.header_len+self.data_len)
@@ -380,6 +388,7 @@ class Message(metaclass=IterRegistry):
elif self.ctrl.is_resp():
return # ignore received response
else:
logger.warning('Unknown Ctrl')
self.inc_counter('Unknown_Ctrl')
self.forward(self._recv_buffer, self.header_len+self.data_len)
@@ -398,6 +407,7 @@ class Message(metaclass=IterRegistry):
elif self.ctrl.is_ind():
pass
else:
logger.warning('Unknown Ctrl')
self.inc_counter('Unknown_Ctrl')
self.forward(self._recv_buffer, self.header_len+self.data_len)

View File

@@ -67,11 +67,11 @@ def Msg2ContactInfo(): # two Contact Info messages
@pytest.fixture
def MsgContactResp(): # Contact Response message
return b'\x00\x00\x00\x14\x10R170000000000001\x99\x00\x01'
return b'\x00\x00\x00\x14\x10R170000000000001\x91\x00\x01'
@pytest.fixture
def MsgContactResp2(): # Contact Response message
return b'\x00\x00\x00\x14\x10R170000000000002\x99\x00\x01'
return b'\x00\x00\x00\x14\x10R170000000000002\x91\x00\x01'
@pytest.fixture
def MsgContactInvalid(): # Contact Response message
@@ -83,7 +83,7 @@ def MsgGetTime(): # Get Time Request message
@pytest.fixture
def MsgTimeResp(): # Get Time Resonse message
return b'\x00\x00\x00\x1b\x10R170000000000001\x99\x22\x00\x00\x01\x89\xc6\x63\x4d\x80'
return b'\x00\x00\x00\x1b\x10R170000000000001\x91\x22\x00\x00\x01\x89\xc6\x63\x4d\x80'
@pytest.fixture
def MsgTimeInvalid(): # Get Time Request message
@@ -316,13 +316,15 @@ def test_read_two_messages(ConfigTsunAllowAll, Msg2ContactInfo,MsgContactResp,Ms
def test_msg_contact_resp(ConfigTsunInv1, MsgContactResp):
ConfigTsunInv1
m = MemoryStream(MsgContactResp, (0,), False)
m.await_conn_resp_cnt = 1
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.msg_count == 1
assert m.await_conn_resp_cnt == 0
assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001'
assert int(m.ctrl)==153
assert int(m.ctrl)==145
assert m.msg_id==0
assert m.header_len==23
assert m.data_len==1
@@ -331,6 +333,46 @@ def test_msg_contact_resp(ConfigTsunInv1, MsgContactResp):
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close()
def test_msg_contact_resp_2(ConfigTsunInv1, MsgContactResp):
ConfigTsunInv1
m = MemoryStream(MsgContactResp, (0,), False)
m.await_conn_resp_cnt = 0
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.msg_count == 1
assert m.await_conn_resp_cnt == 0
assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001'
assert int(m.ctrl)==145
assert m.msg_id==0
assert m.header_len==23
assert m.data_len==1
assert m._forward_buffer==MsgContactResp
assert m._send_buffer==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close()
def test_msg_contact_resp_3(ConfigTsunInv1, MsgContactResp):
ConfigTsunInv1
m = MemoryStream(MsgContactResp, (0,), True)
m.await_conn_resp_cnt = 0
m.db.stat['proxy']['Unknown_Ctrl'] = 0
m.read() # read complete msg, and dispatch msg
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.msg_count == 1
assert m.await_conn_resp_cnt == 0
assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001'
assert int(m.ctrl)==145
assert m.msg_id==0
assert m.header_len==23
assert m.data_len==1
assert m._forward_buffer==MsgContactResp
assert m._send_buffer==b''
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close()
def test_msg_contact_invalid(ConfigTsunInv1, MsgContactInvalid):
ConfigTsunInv1
m = MemoryStream(MsgContactInvalid, (0,))
@@ -381,7 +423,7 @@ def test_msg_get_time_autark(ConfigNoTsunInv1, MsgGetTime):
assert m.header_len==23
assert m.data_len==0
assert m._forward_buffer==b''
assert m._send_buffer==b'\x00\x00\x00\x1b\x10R170000000000001\x99"\x00\x00\x01\x8b\xdfs\xcc0'
assert m._send_buffer==b'\x00\x00\x00\x1b\x10R170000000000001\x91"\x00\x00\x01\x8b\xdfs\xcc0'
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
m.close()
@@ -394,7 +436,7 @@ def test_msg_time_resp(ConfigTsunInv1, MsgTimeResp):
assert m.msg_count == 1
assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001'
assert int(m.ctrl)==153
assert int(m.ctrl)==145
assert m.msg_id==34
assert m.header_len==23
assert m.data_len==8
@@ -412,7 +454,7 @@ def test_msg_time_resp_autark(ConfigNoTsunInv1, MsgTimeResp):
assert m.msg_count == 1
assert m.id_str == b"R170000000000001"
assert m.unique_id == 'R170000000000001'
assert int(m.ctrl)==153
assert int(m.ctrl)==145
assert m.msg_id==34
assert m.header_len==23
assert m.data_len==8

View File

@@ -20,7 +20,7 @@ def MsgContactInfo(): # Contact Info message
@pytest.fixture
def MsgContactResp(): # Contact Response message
return b'\x00\x00\x00\x14\x10'+get_sn()+b'\x99\x00\x01'
return b'\x00\x00\x00\x14\x10'+get_sn()+b'\x91\x00\x01'
@pytest.fixture
def MsgContactInfo2(): # Contact Info message
@@ -28,7 +28,7 @@ def MsgContactInfo2(): # Contact Info message
@pytest.fixture
def MsgContactResp2(): # Contact Response message
return b'\x00\x00\x00\x14\x10'+get_invalid_sn()+b'\x99\x00\x01'
return b'\x00\x00\x00\x14\x10'+get_invalid_sn()+b'\x91\x00\x01'
@pytest.fixture
def MsgTimeStampReq(): # Get Time Request message
@@ -199,7 +199,7 @@ def test_send_contact_resp(ClientConnection, MsgContactResp):
except TimeoutError:
assert True
else:
assert data ==''
assert data == b''
def test_send_ctrl_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgContollerInd):
s = ClientConnection