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
This commit is contained in:
Stefan Allius
2024-04-12 18:57:48 +02:00
parent 22f68ab330
commit 1d3a44c9f0
2 changed files with 151 additions and 83 deletions

View File

@@ -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('<BHHHL', buf, 0)
# store parsed header values in the class
start = result[0] # len of complete message
start = result[0] # start byte
self.data_len = result[1] # len of variable id string
self.control = result[2]
self.serial = result[3]
self.seq.set_recv(result[3])
self.snr = result[4]
if start != 0xA5:
@@ -194,6 +227,7 @@ class SolarmanV5(Message):
self._recv_buffer = bytearray()
return False
check = sum(buf[1:buf_len-2]) & 0xff
if check != crc:
self.inc_counter('Invalid_Msg_Format')
@@ -204,6 +238,36 @@ class SolarmanV5(Message):
return True
def __build_header(self, ctrl) -> None:
'''build header for new transmit message'''
self.send_msg_ofs = len(self._send_buffer)
self._send_buffer += struct.pack(
'<BHHHL', 0xA5, 0, ctrl, self.seq.get_send(), self.snr)
fnc = self.switch.get(ctrl, self.msg_unknown)
logger.info(self.__flow_str(self.server_side, 'tx') +
f' Ctl: {int(ctrl):#04x} Msg: {fnc.__name__!r}')
def __finish_send_msg(self) -> None:
'''finish the transmit message, set lenght and checksum'''
_len = len(self._send_buffer) - self.send_msg_ofs
struct.pack_into('<H', self._send_buffer, self.send_msg_ofs+1, _len-11)
check = sum(self._send_buffer[self.send_msg_ofs+1:self.send_msg_ofs +
_len]) & 0xff
self._send_buffer += struct.pack('<BB', check, 0x15) # crc & stop
def _update_header(self, _forward_buffer):
'''update header for message before forwarding,
set sequence and checksum'''
_len = len(_forward_buffer)
struct.pack_into('<H', _forward_buffer, 1,
_len-13)
struct.pack_into('<H', _forward_buffer, 5,
self.seq.get_send())
check = sum(_forward_buffer[1:_len-2]) & 0xff
struct.pack_into('<B', _forward_buffer, _len-2, check)
def __dispatch_msg(self) -> 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('<H', frame[-2:])
if calc_crc == frame_crc:
return 1
else:
return 0
'''
'''
Message handler methods
'''
@@ -280,6 +310,11 @@ class SolarmanV5(Message):
self.__process_data(ftype)
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
self.__build_header(0x1110)
self._send_buffer += struct.pack('<BBLL', ftype, 1,
self._timestamp(),
self._heartbeat())
self.__finish_send_msg()
def msg_dev_rsp(self):
self.msg_response()
@@ -299,9 +334,13 @@ class SolarmanV5(Message):
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
ftype &= 0x7f # mask bit 7 (0x80)
self.__process_data(ftype)
self.__process_data(ftype & 0x7f) # mask bit 7 (0x80)
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
self.__build_header(0x1210)
self._send_buffer += struct.pack('<BBLL', ftype, 1,
self._timestamp(),
self._heartbeat())
self.__finish_send_msg()
def __process_data(self, ftype):
inv_update = False
@@ -343,6 +382,11 @@ class SolarmanV5(Message):
def msg_hbeat_ind(self):
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
self.__build_header(0x1710)
self._send_buffer += struct.pack('<BBLL', 0, 1,
self._timestamp(),
self._heartbeat())
self.__finish_send_msg()
def msg_hbeat_rsp(self):
self.msg_response()
@@ -359,7 +403,6 @@ class SolarmanV5(Message):
dt = datetime.fromtimestamp(ts)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
def at_command_ind(self):
self.inc_counter('AT_Command')