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:
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user