From ab9e798152dbdbfdd8a28e7d7ad0b866714be63c Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Tue, 28 May 2024 19:30:58 +0200 Subject: [PATCH 1/3] add typing --- app/src/messages.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/messages.py b/app/src/messages.py index 4968609..16e6d1b 100644 --- a/app/src/messages.py +++ b/app/src/messages.py @@ -1,5 +1,7 @@ import logging import weakref +from typing import Callable + if __name__ == "app.src.messages": from app.src.infos import Infos @@ -56,7 +58,8 @@ class Message(metaclass=IterRegistry): STATE_UP = 2 STATE_CLOSED = 3 - def __init__(self, server_side: bool, send_modbus_cb, mb_timeout): + def __init__(self, server_side: bool, send_modbus_cb: + Callable[[bytes, int, str], None], mb_timeout): self._registry.append(weakref.ref(self)) self.server_side = server_side From 66657888ddfced280e6cdab8b822ef7d52501050 Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Tue, 28 May 2024 19:32:20 +0200 Subject: [PATCH 2/3] add log_level support for modbus commands --- app/src/gen3/talent.py | 17 +++++++++-------- app/src/gen3plus/solarman_v5.py | 16 ++++++++-------- app/src/modbus.py | 18 ++++++++++++------ app/src/mqtt.py | 2 +- app/src/scheduler.py | 4 ++-- app/tests/test_modbus.py | 2 +- app/tests/test_solarman.py | 7 ++++--- app/tests/test_talent.py | 6 +++--- system_tests/test_tcp_socket_v2.py | 16 ++++++++++++---- 9 files changed, 52 insertions(+), 36 deletions(-) diff --git a/app/src/gen3/talent.py b/app/src/gen3/talent.py index 7e54d92..caf327d 100644 --- a/app/src/gen3/talent.py +++ b/app/src/gen3/talent.py @@ -122,25 +122,25 @@ class Talent(Message): f' Ctl: {int(self.ctrl):#02x} Msg: {fnc.__name__!r}') return - def send_modbus_cb(self, modbus_pdu: bytearray, state: str): + def send_modbus_cb(self, modbus_pdu: bytearray, log_lvl: int, state: str): if self.state != self.STATE_UP: return - self.__build_header(0x70, 0x77) + self.__build_header(0x70, 0x77, log_lvl) self._send_buffer += b'\x00\x01\xa3\x28' # fixme self._send_buffer += struct.pack('!B', len(modbus_pdu)) self._send_buffer += modbus_pdu self.__finish_send_msg() - hex_dump_memory(logging.INFO, f'Send Modbus {state}:{self.addr}:', + hex_dump_memory(log_lvl, f'Send Modbus {state}:{self.addr}:', self._send_buffer, len(self._send_buffer)) self.writer.write(self._send_buffer) self._send_buffer = bytearray(0) # self._send_buffer[sent:] - async def send_modbus_cmd(self, func, addr, val) -> None: + async def send_modbus_cmd(self, func, addr, val, log_lvl) -> None: if self.state != self.STATE_UP: return - self.mb.build_msg(Modbus.INV_ADDR, func, addr, val) + self.mb.build_msg(Modbus.INV_ADDR, func, addr, val, log_lvl) def _init_new_client_conn(self) -> bool: contact_name = self.contact_name @@ -217,15 +217,16 @@ class Talent(Message): self.header_valid = True return - def __build_header(self, ctrl, msg_id=None) -> None: + def __build_header(self, ctrl, msg_id=None, + log_lvl: int = logging.INFO) -> None: if not msg_id: msg_id = self.msg_id self.send_msg_ofs = len(self._send_buffer) self._send_buffer += struct.pack(f'!l{len(self.id_str)+1}pBB', 0, self.id_str, ctrl, msg_id) fnc = self.switch.get(msg_id, self.msg_unknown) - logger.info(self.__flow_str(self.server_side, 'tx') + - f' Ctl: {int(ctrl):#02x} Msg: {fnc.__name__!r}') + logger.log(log_lvl, self.__flow_str(self.server_side, 'tx') + + f' Ctl: {int(ctrl):#02x} Msg: {fnc.__name__!r}') def __finish_send_msg(self) -> None: _len = len(self._send_buffer) - self.send_msg_ofs diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 423123a..1529057 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -248,15 +248,15 @@ class SolarmanV5(Message): return True - def __build_header(self, ctrl) -> None: + def __build_header(self, ctrl, log_lvl: int = logging.INFO) -> None: '''build header for new transmit message''' self.send_msg_ofs = len(self._send_buffer) self._send_buffer += struct.pack( ' None: '''finish the transmit message, set lenght and checksum''' @@ -302,23 +302,23 @@ class SolarmanV5(Message): self._heartbeat()) self.__finish_send_msg() - def send_modbus_cb(self, pdu: bytearray, state: str): + def send_modbus_cb(self, pdu: bytearray, log_lvl: int, state: str): if self.state != self.STATE_UP: return - self.__build_header(0x4510) + self.__build_header(0x4510, log_lvl) self._send_buffer += struct.pack(' None: + async def send_modbus_cmd(self, func, addr, val, log_lvl) -> None: if self.state != self.STATE_UP: return - self.mb.build_msg(Modbus.INV_ADDR, func, addr, val) + self.mb.build_msg(Modbus.INV_ADDR, func, addr, val, log_lvl) async def send_at_cmd(self, AT_cmd: str) -> None: if self.state != self.STATE_UP: diff --git a/app/src/modbus.py b/app/src/modbus.py index 8f83c9c..0727650 100644 --- a/app/src/modbus.py +++ b/app/src/modbus.py @@ -74,7 +74,8 @@ class Modbus(): 0x3029: {'reg': Register.PV4_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 } - def __init__(self, snd_handler: Callable[[str], None], timeout: int = 1): + def __init__(self, snd_handler: Callable[[bytes, int, str], None], + timeout: int = 1): if not len(self.__crc_tab): self.__build_crc_tab(CRC_POLY) self.que = asyncio.Queue(100) @@ -94,6 +95,7 @@ class Modbus(): self.counter['retries'] = {} for i in range(0, self.max_retries+1): self.counter['retries'][f'{i}'] = 0 + self.last_log_lvl = logging.DEBUG self.last_addr = 0 self.last_fcode = 0 self.last_len = 0 @@ -106,7 +108,8 @@ class Modbus(): def __del__(self): logging.debug(f'Modbus __del__:\n {self.counter}') - def build_msg(self, addr: int, func: int, reg: int, val: int) -> None: + def build_msg(self, addr: int, func: int, reg: int, val: int, + log_lvl=logging.DEBUG) -> None: """Build MODBUS RTU request frame and add it to the tx queue Keyword arguments: @@ -118,7 +121,8 @@ class Modbus(): msg = struct.pack('>BBHH', addr, func, reg, val) msg += struct.pack(' bytes: def get_invalid_sn(): return b'R170000000000002' +def correct_checksum(buf): + checksum = sum(buf[1:]) & 0xff + return checksum.to_bytes(length=1) @pytest.fixture def MsgContactInfo(): # Contact Info message @@ -61,10 +64,11 @@ def MsgDataInd(): msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' msg += b'\x00\x01\x12\x02\x12\x12\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' msg += b'\x40\x10\x08\xd8\x00\x09\x13\x84\x00\x35\x00\x00\x02\x58\x00\xd8' - msg += b'\x01\x3f\x00\x17\x00\x4d\x01\x44\x00\x14\x00\x43\x01\x45\x00\x18' + msg += b'\x01\x3f\x00\x17\x00\x4d\x01\x44\x00\x14\x00\x43\x01\x45\x00\x18' msg += b'\x00\x52\x00\x12\x00\x01\x00\x00\x00\x7c\x00\x00\x24\xed\x00\x2c' msg += b'\x00\x00\x0b\x10\x00\x26\x00\x00\x0a\x0f\x00\x30\x00\x00\x0b\x76' - msg += b'\x00\x00\x00\x00\x06\x16\x00\x00\x00\x00\x55\xaa\x00\x01\x00\x00' + + msg += b'\x00\x00\x00\x00\x06\x16\x00\x00\x00\x01\x55\xaa\x00\x01\x00\x00' msg += b'\x00\x00\x00\x00\xff\xff\x07\xd0\x00\x03\x04\x00\x04\x00\x04\x00' msg += b'\x04\x00\x00\x01\xff\xff\x00\x01\x00\x06\x00\x68\x00\x68\x05\x00' msg += b'\x09\xcd\x07\xb6\x13\x9c\x13\x24\x00\x01\x07\xae\x04\x0f\x00\x41' @@ -73,7 +77,9 @@ def MsgDataInd(): msg += b'\x04\x00\x00\x01\x13\x9c\x0f\xa0\x00\x4e\x00\x66\x03\xe8\x04\x00' msg += b'\x09\xce\x07\xa8\x13\x9c\x13\x26\x00\x00\x00\x00\x00\x00\x00\x00' msg += b'\x00\x00\x00\x00\x04\x00\x04\x00\x00\x00\x00\x00\xff\xff\x00\x00' - msg += b'\x00\x00\x00\x00\x24\x15' + msg += b'\x00\x00\x00\x00' + msg += correct_checksum(msg) + msg += b'\x15' return msg @pytest.fixture @@ -147,4 +153,6 @@ def test_data_ind(ClientConnection,MsgDataInd, MsgDataResp): except TimeoutError: pass # time.sleep(2.5) - checkResponse(data, MsgDataResp) \ No newline at end of file + checkResponse(data, MsgDataResp) + + \ No newline at end of file From 3980ac013bf38101b2f3b5581543a69d82fcf791 Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Tue, 28 May 2024 21:55:42 +0200 Subject: [PATCH 3/3] catch all OSError errors in the read loop --- CHANGELOG.md | 2 ++ app/src/async_stream.py | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8412be2..830c501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- catch all OSError errors in the read loop +- log Modbus traces with different log levels - add Modbus fifo and timeout handler - build version string in the same format as TSUN for GEN3 invterts - add graceful shutdown diff --git a/app/src/async_stream.py b/app/src/async_stream.py index 196a01f..563b948 100644 --- a/app/src/async_stream.py +++ b/app/src/async_stream.py @@ -65,9 +65,7 @@ class AsyncStream(): await self.__async_forward() await self.async_publ_mqtt() - except (ConnectionResetError, - ConnectionAbortedError, - BrokenPipeError) as error: + except OSError as error: logger.error(f'{error} for l{self.l_addr} | ' f'r{self.r_addr}') await self.disc()