From 1d3a44c9f0043c5c1cf45c408976ae295385a8bf Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Fri, 12 Apr 2024 18:57:48 +0200 Subject: [PATCH] first self-sufficient island support - add Sequence class to handle the sequence of packets - send response for received packets directly - don't forward responses anymore - addapt tests to new behavior --- app/src/gen3plus/solarman_v5.py | 125 +++++++++++++++++++++----------- app/tests/test_solarman.py | 109 +++++++++++++++++----------- 2 files changed, 151 insertions(+), 83 deletions(-) diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index fc189b3..86b64f1 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -1,7 +1,7 @@ import struct # import json import logging -# import time +import time from datetime import datetime if __name__ == "app.src.gen3plus.solarman_v5": @@ -19,6 +19,32 @@ else: # pragma: no cover logger = logging.getLogger('msg') +class Sequence(): + def __init__(self, server_side: bool): + self.rcv_idx = 0 + self.snd_idx = 0 + self.server_side = server_side + + def set_recv(self, val: int): + if self.server_side: + self.rcv_idx = val >> 8 + self.snd_idx = val & 0xff + else: + self.rcv_idx = val & 0xff + self.snd_idx = val >> 8 + + def get_send(self): + self.snd_idx += 1 + self.snd_idx &= 0xff + if self.server_side: + return (self.rcv_idx << 8) | self.snd_idx + else: + return (self.snd_idx << 8) | self.rcv_idx + + def __str__(self): + return f'{self.rcv_idx:02x}:{self.snd_idx:02x}' + + class SolarmanV5(Message): def __init__(self, server_side: bool): @@ -26,7 +52,7 @@ class SolarmanV5(Message): self.header_len = 11 # overwrite construcor in class Message self.control = 0 - self.serial = 0 + self.seq = Sequence(server_side) self.snr = 0 self.db = InfosG3P() self.switch = { @@ -160,6 +186,13 @@ class SolarmanV5(Message): type += 'S' return switch.get(type, '???') + def _timestamp(self): # pragma: no cover + # utc as epoche + return int(time.time()) + + def _heartbeat(self) -> int: + return 60 + def __parse_header(self, buf: bytes, buf_len: int) -> None: if (buf_len < self.header_len): # enough bytes for complete header? @@ -168,10 +201,10 @@ class SolarmanV5(Message): result = struct.unpack_from(' 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''' + _len = len(self._send_buffer) - self.send_msg_ofs + struct.pack_into(' None: fnc = self.switch.get(self.control, self.msg_unknown) if self.unique_id: @@ -220,41 +284,7 @@ class SolarmanV5(Message): self._recv_buffer = self._recv_buffer[(self.header_len + self.data_len+2):] self.header_valid = False - ''' - def modbus(self, data): - POLY = 0xA001 - crc = 0xFFFF - for byte in data: - crc ^= byte - for _ in range(8): - crc = ((crc >> 1) ^ POLY - if (crc & 0x0001) - else crc >> 1) - return crc - - def validate_modbus_crc(self, frame): - # Calculate crc with all but the last 2 bytes of - # the frame (they contain the crc) - calc_crc = 0xFFFF - for pos in frame[:-2]: - calc_crc ^= pos - for i in range(8): - if (calc_crc & 1) != 0: - calc_crc >>= 1 - calc_crc ^= 0xA001 # bitwise 'or' with modbus magic - # number (0xa001 == bitwise - # reverse of 0x8005) - else: - calc_crc >>= 1 - - # Compare calculated crc with the one supplied in the frame.... - frame_crc, = struct.unpack(' int: + return heartbeat + def append_msg(self, msg): self.__msg += msg @@ -42,9 +54,6 @@ class MemoryStream(SolarmanV5): pass return copied_bytes - def _timestamp(self): - return 1700260990000 - def _SolarmanV5__flush_recv_msg(self) -> None: super()._SolarmanV5__flush_recv_msg() self.msg_count += 1 @@ -60,6 +69,16 @@ def get_inv_no() -> bytes: def get_invalid_sn(): return b'R170000000000002' +def total(): + ts = timestamp + # convert int to little-endian bytes + return struct.pack('