From 893a81eeb63bac3c23aa2a25a3f546b9b08e8ed1 Mon Sep 17 00:00:00 2001 From: mle Date: Sun, 26 May 2024 19:33:24 +0200 Subject: [PATCH] Add __repr__ to aa55 protocol messages --- goodwe/es.py | 1 + goodwe/protocol.py | 30 +++++++++++++--- tests/mock_aa55_server.py | 75 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 tests/mock_aa55_server.py diff --git a/goodwe/es.py b/goodwe/es.py index 3eed3d7..b71d0fd 100644 --- a/goodwe/es.py +++ b/goodwe/es.py @@ -221,6 +221,7 @@ async def read_setting(self, setting_id: str) -> Any: response = await self._read_from_socket(self._read_command(int(setting_id[7:]), 1)) return int.from_bytes(response.read(2), byteorder="big", signed=True) elif setting_id in self._settings: + logger.debug("Reading setting %s", setting_id) all_settings = await self.read_settings_data() return all_settings.get(setting_id) else: diff --git a/goodwe/protocol.py b/goodwe/protocol.py index f801d20..43e2673 100644 --- a/goodwe/protocol.py +++ b/goodwe/protocol.py @@ -482,7 +482,7 @@ class Aa55ProtocolCommand(ProtocolCommand): The last 2 bytes are again plain checksum of header+payload. """ - def __init__(self, payload: str, response_type: str): + def __init__(self, payload: str, response_type: str, offset: int = 0, value: int = 0): super().__init__( bytes.fromhex( "AA55C07F" @@ -491,6 +491,8 @@ def __init__(self, payload: str, response_type: str): ), lambda x: self._validate_aa55_response(x, response_type), ) + self.first_address: int = offset + self.value = value @staticmethod def _checksum(data: bytes) -> bytes: @@ -534,6 +536,17 @@ def trim_response(self, raw_response: bytes): """Trim raw response from header and checksum data""" return raw_response[7:-2] + def __repr__(self): + if self.request[4] == 1: + if self.request[5] == 2: + return f'READ device info ({self.request.hex()})' + elif self.request[5] == 6: + return f'READ runtime data ({self.request.hex()})' + elif self.request[5] == 9: + return f'READ settings ({self.request.hex()})' + else: + return self.request.hex() + class Aa55ReadCommand(Aa55ProtocolCommand): """ @@ -541,7 +554,13 @@ class Aa55ReadCommand(Aa55ProtocolCommand): """ def __init__(self, offset: int, count: int): - super().__init__("011A03" + "{:04x}".format(offset) + "{:02x}".format(count), "019A") + super().__init__("011A03" + "{:04x}".format(offset) + "{:02x}".format(count), "019A", offset, count) + + def __repr__(self): + if self.value > 1: + return f'READ {self.value} registers from {self.first_address} ({self.request.hex()})' + else: + return f'READ register {self.first_address} ({self.request.hex()})' class Aa55WriteCommand(Aa55ProtocolCommand): @@ -550,7 +569,10 @@ class Aa55WriteCommand(Aa55ProtocolCommand): """ def __init__(self, register: int, value: int): - super().__init__("023905" + "{:04x}".format(register) + "01" + "{:04x}".format(value), "02B9") + super().__init__("023905" + "{:04x}".format(register) + "01" + "{:04x}".format(value), "02B9", register, value) + + def __repr__(self): + return f'WRITE {self.value} to register {self.first_address} ({self.request.hex()})' class Aa55WriteMultiCommand(Aa55ProtocolCommand): @@ -560,7 +582,7 @@ class Aa55WriteMultiCommand(Aa55ProtocolCommand): def __init__(self, offset: int, values: bytes): super().__init__("02390B" + "{:04x}".format(offset) + "{:02x}".format(len(values)) + values.hex(), - "02B9") + "02B9", offset, len(values) // 2) class ModbusRtuProtocolCommand(ProtocolCommand): diff --git a/tests/mock_aa55_server.py b/tests/mock_aa55_server.py new file mode 100644 index 0000000..44883c2 --- /dev/null +++ b/tests/mock_aa55_server.py @@ -0,0 +1,75 @@ +import asyncio + + +class EchoServerProtocol(asyncio.Protocol): + def connection_made(self, transport): + peername = transport.get_extra_info('peername') + print('Connection from {}'.format(peername)) + self.transport = transport + + def datagram_received(self, data, addr): + payload = data.hex() + print(payload) + + if payload == "aa55c07f0102000241": + # Device info + self.transport.sendto(bytes.fromhex( + "aa557fc001824d323532354b4757353034382d4553412331300000000000000000000000000039353034384553413030305730303030333630303431302d30343032352d3235203431302d30323033342d323001102f"), + addr) + elif payload == "aa55c07f0106000245": + # Running data + self.transport.sendto(bytes.fromhex( + "aa557fc001868c09270047020fe60042010214000200500118000400000032000064006464020000010a11009e0ce11389010a11000303e11389010202010000000000023780000053c3012600770001ad1510c30100200100000001000003e500000840000018051a0e0e120000000000000000000000000000000000000000000000000000ca260000baab0200000000000012e1"), + addr) + elif payload == "aa55c07f0109000248": + # Settings data + self.transport.sendto(bytes.fromhex( + "aa557fc00189560000000000000000000000000001000100010000000a00d2024000f0008001e0000a000f0000000000000064024003e8001e00f20000000000000000023f000000070000000000000001038403e801e0000a000000220c12"), + addr) + elif payload == "aa55c07f011a030701040268": + # Read eco_mode_1 + self.transport.sendto(bytes.fromhex( + "aa557fc0019a08000000000000007f0360"), + addr) + elif payload == "aa55c07f032c0500000000000272": + self.transport.sendto(bytes.fromhex( + "aa557fc003ac010602f4"), + addr) + elif payload == "aa55c07f032d0500000000000273": + self.transport.sendto(bytes.fromhex( + "aa557fc003ad010602f5"), + addr) + elif payload == "aa55c07f02390507000100010287": + self.transport.sendto(bytes.fromhex( + "aa557fc002b901060300"), + addr) + elif payload == "aa55c07f033601000278": + self.transport.sendto(bytes.fromhex( + "aa557fc003b6010602fe"), + addr) + elif payload == "aa55c07f03590100029b": + self.transport.sendto(bytes.fromhex( + "aa557fc003d901060321"), + addr) + else: + self.transport.sendto(bytes.fromhex("00000000"), addr) + # print('Close the client socket') + # self.transport.close() + + +async def main(): + # Get a reference to the event loop as we plan to use + # low-level APIs. + loop = asyncio.get_running_loop() + + transport, protocol = await loop.create_datagram_endpoint( + lambda: EchoServerProtocol(), + local_addr=('127.0.0.1', 8899)) + + try: + await asyncio.sleep(3600) # Serve for 1 hour. + finally: + transport.close() + + +asyncio.run(main())