@@ -44,7 +44,7 @@ If you use a Pi-hole, you can also store the host entry in the Pi-hole.
|
|||||||
- supports TSUN GEN3 inverters: TSOL-MS800, MS700, MS600, MS400, MS350 and MS300
|
- supports TSUN GEN3 inverters: TSOL-MS800, MS700, MS600, MS400, MS350 and MS300
|
||||||
- `MQTT` support
|
- `MQTT` support
|
||||||
- `Home-Assistant` auto-discovery support
|
- `Home-Assistant` auto-discovery support
|
||||||
- Self-sufficient island operation without internet (for TSUN GEN3 PLUS inverters in preparation)
|
- Self-sufficient island operation without internet
|
||||||
- runs in a non-root Docker Container
|
- runs in a non-root Docker Container
|
||||||
|
|
||||||
## Home Assistant Screenshots
|
## Home Assistant Screenshots
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class AsyncStream():
|
|||||||
await self.remoteStream.__async_write()
|
await self.remoteStream.__async_write()
|
||||||
|
|
||||||
if self.remoteStream:
|
if self.remoteStream:
|
||||||
|
self.remoteStream._update_header(self._forward_buffer)
|
||||||
hex_dump_memory(logging.INFO,
|
hex_dump_memory(logging.INFO,
|
||||||
f'Forward to {self.remoteStream.addr}:',
|
f'Forward to {self.remoteStream.addr}:',
|
||||||
self._forward_buffer,
|
self._forward_buffer,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import struct
|
import struct
|
||||||
# import json
|
# import json
|
||||||
import logging
|
import logging
|
||||||
# import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
if __name__ == "app.src.gen3plus.solarman_v5":
|
if __name__ == "app.src.gen3plus.solarman_v5":
|
||||||
@@ -19,6 +19,32 @@ else: # pragma: no cover
|
|||||||
logger = logging.getLogger('msg')
|
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):
|
class SolarmanV5(Message):
|
||||||
|
|
||||||
def __init__(self, server_side: bool):
|
def __init__(self, server_side: bool):
|
||||||
@@ -26,7 +52,7 @@ class SolarmanV5(Message):
|
|||||||
|
|
||||||
self.header_len = 11 # overwrite construcor in class Message
|
self.header_len = 11 # overwrite construcor in class Message
|
||||||
self.control = 0
|
self.control = 0
|
||||||
self.serial = 0
|
self.seq = Sequence(server_side)
|
||||||
self.snr = 0
|
self.snr = 0
|
||||||
self.db = InfosG3P()
|
self.db = InfosG3P()
|
||||||
self.switch = {
|
self.switch = {
|
||||||
@@ -160,6 +186,13 @@ class SolarmanV5(Message):
|
|||||||
type += 'S'
|
type += 'S'
|
||||||
return switch.get(type, '???')
|
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:
|
def __parse_header(self, buf: bytes, buf_len: int) -> None:
|
||||||
|
|
||||||
if (buf_len < self.header_len): # enough bytes for complete header?
|
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)
|
result = struct.unpack_from('<BHHHL', buf, 0)
|
||||||
|
|
||||||
# store parsed header values in the class
|
# 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.data_len = result[1] # len of variable id string
|
||||||
self.control = result[2]
|
self.control = result[2]
|
||||||
self.serial = result[3]
|
self.seq.set_recv(result[3])
|
||||||
self.snr = result[4]
|
self.snr = result[4]
|
||||||
|
|
||||||
if start != 0xA5:
|
if start != 0xA5:
|
||||||
@@ -194,6 +227,7 @@ class SolarmanV5(Message):
|
|||||||
self._recv_buffer = bytearray()
|
self._recv_buffer = bytearray()
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
check = sum(buf[1:buf_len-2]) & 0xff
|
check = sum(buf[1:buf_len-2]) & 0xff
|
||||||
if check != crc:
|
if check != crc:
|
||||||
self.inc_counter('Invalid_Msg_Format')
|
self.inc_counter('Invalid_Msg_Format')
|
||||||
@@ -204,6 +238,36 @@ class SolarmanV5(Message):
|
|||||||
|
|
||||||
return True
|
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:
|
def __dispatch_msg(self) -> None:
|
||||||
fnc = self.switch.get(self.control, self.msg_unknown)
|
fnc = self.switch.get(self.control, self.msg_unknown)
|
||||||
if self.unique_id:
|
if self.unique_id:
|
||||||
@@ -220,41 +284,7 @@ class SolarmanV5(Message):
|
|||||||
self._recv_buffer = self._recv_buffer[(self.header_len +
|
self._recv_buffer = self._recv_buffer[(self.header_len +
|
||||||
self.data_len+2):]
|
self.data_len+2):]
|
||||||
self.header_valid = False
|
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
|
Message handler methods
|
||||||
'''
|
'''
|
||||||
@@ -280,6 +310,11 @@ class SolarmanV5(Message):
|
|||||||
|
|
||||||
self.__process_data(ftype)
|
self.__process_data(ftype)
|
||||||
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
|
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):
|
def msg_dev_rsp(self):
|
||||||
self.msg_response()
|
self.msg_response()
|
||||||
@@ -299,9 +334,13 @@ class SolarmanV5(Message):
|
|||||||
dt = datetime.fromtimestamp(total)
|
dt = datetime.fromtimestamp(total)
|
||||||
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
|
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
|
||||||
|
|
||||||
ftype &= 0x7f # mask bit 7 (0x80)
|
self.__process_data(ftype & 0x7f) # mask bit 7 (0x80)
|
||||||
self.__process_data(ftype)
|
|
||||||
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
|
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):
|
def __process_data(self, ftype):
|
||||||
inv_update = False
|
inv_update = False
|
||||||
@@ -343,6 +382,11 @@ class SolarmanV5(Message):
|
|||||||
|
|
||||||
def msg_hbeat_ind(self):
|
def msg_hbeat_ind(self):
|
||||||
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
|
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):
|
def msg_hbeat_rsp(self):
|
||||||
self.msg_response()
|
self.msg_response()
|
||||||
@@ -359,7 +403,6 @@ class SolarmanV5(Message):
|
|||||||
|
|
||||||
dt = datetime.fromtimestamp(ts)
|
dt = datetime.fromtimestamp(ts)
|
||||||
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
|
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):
|
def at_command_ind(self):
|
||||||
self.inc_counter('AT_Command')
|
self.inc_counter('AT_Command')
|
||||||
|
|||||||
@@ -74,6 +74,10 @@ class Message(metaclass=IterRegistry):
|
|||||||
# to our _recv_buffer
|
# to our _recv_buffer
|
||||||
return # pragma: no cover
|
return # pragma: no cover
|
||||||
|
|
||||||
|
def _update_header(self, _forward_buffer):
|
||||||
|
'''callback for updating the header of the forward buffer'''
|
||||||
|
return
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Our puplic methods
|
Our puplic methods
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Mqtt(metaclass=Singleton):
|
|||||||
logger_mqtt.info(
|
logger_mqtt.info(
|
||||||
f"Connection lost; Reconnecting in {interval}"
|
f"Connection lost; Reconnecting in {interval}"
|
||||||
" seconds ...")
|
" seconds ...")
|
||||||
|
|
||||||
await asyncio.sleep(interval)
|
await asyncio.sleep(interval)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger_mqtt.debug("MQTT task cancelled")
|
logger_mqtt.debug("MQTT task cancelled")
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import pytest, json
|
import pytest
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||||
from app.src.config import Config
|
from app.src.config import Config
|
||||||
from app.src.infos import Infos, Register
|
from app.src.infos import Infos, Register
|
||||||
@@ -6,6 +9,9 @@ from app.src.infos import Infos, Register
|
|||||||
# initialize the proxy statistics
|
# initialize the proxy statistics
|
||||||
Infos.static_init()
|
Infos.static_init()
|
||||||
|
|
||||||
|
timestamp = int(time.time()) # 1712861197
|
||||||
|
heartbeat = 60
|
||||||
|
|
||||||
class MemoryStream(SolarmanV5):
|
class MemoryStream(SolarmanV5):
|
||||||
def __init__(self, msg, chunks = (0,), server_side: bool = True):
|
def __init__(self, msg, chunks = (0,), server_side: bool = True):
|
||||||
super().__init__(server_side)
|
super().__init__(server_side)
|
||||||
@@ -19,6 +25,12 @@ class MemoryStream(SolarmanV5):
|
|||||||
self.db.stat['proxy']['Invalid_Msg_Format'] = 0
|
self.db.stat['proxy']['Invalid_Msg_Format'] = 0
|
||||||
self.db.stat['proxy']['AT_Command'] = 0
|
self.db.stat['proxy']['AT_Command'] = 0
|
||||||
|
|
||||||
|
def _timestamp(self):
|
||||||
|
return timestamp
|
||||||
|
|
||||||
|
def _heartbeat(self) -> int:
|
||||||
|
return heartbeat
|
||||||
|
|
||||||
|
|
||||||
def append_msg(self, msg):
|
def append_msg(self, msg):
|
||||||
self.__msg += msg
|
self.__msg += msg
|
||||||
@@ -42,9 +54,6 @@ class MemoryStream(SolarmanV5):
|
|||||||
pass
|
pass
|
||||||
return copied_bytes
|
return copied_bytes
|
||||||
|
|
||||||
def _timestamp(self):
|
|
||||||
return 1700260990000
|
|
||||||
|
|
||||||
def _SolarmanV5__flush_recv_msg(self) -> None:
|
def _SolarmanV5__flush_recv_msg(self) -> None:
|
||||||
super()._SolarmanV5__flush_recv_msg()
|
super()._SolarmanV5__flush_recv_msg()
|
||||||
self.msg_count += 1
|
self.msg_count += 1
|
||||||
@@ -60,6 +69,16 @@ def get_inv_no() -> bytes:
|
|||||||
def get_invalid_sn():
|
def get_invalid_sn():
|
||||||
return b'R170000000000002'
|
return b'R170000000000002'
|
||||||
|
|
||||||
|
def total():
|
||||||
|
ts = timestamp
|
||||||
|
# convert int to little-endian bytes
|
||||||
|
return struct.pack('<L',ts)
|
||||||
|
|
||||||
|
def hb():
|
||||||
|
hb = heartbeat
|
||||||
|
# convert int to little-endian bytes
|
||||||
|
return struct.pack('<L',hb)
|
||||||
|
|
||||||
def correct_checksum(buf):
|
def correct_checksum(buf):
|
||||||
checksum = sum(buf[1:]) & 0xff
|
checksum = sum(buf[1:]) & 0xff
|
||||||
return checksum.to_bytes(length=1)
|
return checksum.to_bytes(length=1)
|
||||||
@@ -90,8 +109,9 @@ def DeviceIndMsg(): # 0x4110
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def DeviceRspMsg(): # 0x1110
|
def DeviceRspMsg(): # 0x1110
|
||||||
msg = b'\xa5\x0a\x00\x10\x11\x10\x84' +get_sn() +b'\x01\x01\x69\x6f\x09'
|
msg = b'\xa5\x0a\x00\x10\x11\x01\x01' +get_sn() +b'\x02\x01'
|
||||||
msg += b'\x66\x78\x00\x00\x00'
|
msg += total()
|
||||||
|
msg += hb()
|
||||||
msg += correct_checksum(msg)
|
msg += correct_checksum(msg)
|
||||||
msg += b'\x15'
|
msg += b'\x15'
|
||||||
return msg
|
return msg
|
||||||
@@ -158,7 +178,7 @@ def InvalidChecksum(): # 0x4110
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def InverterIndMsg(): # 0x4210
|
def InverterIndMsg(): # 0x4210
|
||||||
msg = b'\xa5\x99\x01\x10\x42\xe6\x9e' +get_sn() +b'\x01\xb0\x02\xbc\xc8'
|
msg = b'\xa5\x99\x01\x10\x42\x01\x02' +get_sn() +b'\x01\xb0\x02\xbc\xc8'
|
||||||
msg += b'\x24\x32\x6c\x1f\x00\x00\xa0\x47\xe4\x33\x01\x00\x03\x08\x00\x00'
|
msg += b'\x24\x32\x6c\x1f\x00\x00\xa0\x47\xe4\x33\x01\x00\x03\x08\x00\x00'
|
||||||
msg += b'\x59\x31\x37\x45\x37\x41\x30\x46\x30\x31\x30\x42\x30\x31\x33\x45'
|
msg += b'\x59\x31\x37\x45\x37\x41\x30\x46\x30\x31\x30\x42\x30\x31\x33\x45'
|
||||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
@@ -290,8 +310,9 @@ def InverterIndMsg2000(): # 0x4210 rated Power 2000W
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def InverterRspMsg(): # 0x1210
|
def InverterRspMsg(): # 0x1210
|
||||||
msg = b'\xa5\x0a\x00\x10\x12\x10\x84' +get_sn() +b'\x01\x01\x69\x6f\x09'
|
msg = b'\xa5\x0a\x00\x10\x12\x02\02' +get_sn() +b'\x01\x01'
|
||||||
msg += b'\x66\x78\x00\x00\x00'
|
msg += total()
|
||||||
|
msg += hb()
|
||||||
msg += correct_checksum(msg)
|
msg += correct_checksum(msg)
|
||||||
msg += b'\x15'
|
msg += b'\x15'
|
||||||
return msg
|
return msg
|
||||||
@@ -314,8 +335,9 @@ def HeartbeatIndMsg(): # 0x4710
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def HeartbeatRspMsg(): # 0x1710
|
def HeartbeatRspMsg(): # 0x1710
|
||||||
msg = b'\xa5\x0a\x00\x10\x17\x10\x84' +get_sn() +b'\x00\x01\x22\x71\x09'
|
msg = b'\xa5\x0a\x00\x10\x17\x11\x84' +get_sn() +b'\x00\x01'
|
||||||
msg += b'\x66\x78\x00\x00\x00'
|
msg += total()
|
||||||
|
msg += hb()
|
||||||
msg += correct_checksum(msg)
|
msg += correct_checksum(msg)
|
||||||
msg += b'\x15'
|
msg += b'\x15'
|
||||||
return msg
|
return msg
|
||||||
@@ -349,7 +371,7 @@ def test_read_message(DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == None
|
assert m.unique_id == None
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -370,7 +392,7 @@ def test_invalid_start_byte(InvalidStartByte, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == 0
|
assert m.unique_id == 0
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -390,7 +412,7 @@ def test_invalid_stop_byte(InvalidStopByte):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == 0
|
assert m.unique_id == 0
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -410,7 +432,7 @@ def test_invalid_stop_byte2(InvalidStopByte, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == 0
|
assert m.unique_id == 0
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==DeviceIndMsg
|
assert m._recv_buffer==DeviceIndMsg
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -424,7 +446,7 @@ def test_invalid_stop_byte2(InvalidStopByte, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == None
|
assert m.unique_id == None
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -446,7 +468,7 @@ def test_invalid_stop_start_byte(InvalidStopByte, InvalidStartByte):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == 0
|
assert m.unique_id == 0
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -466,7 +488,7 @@ def test_invalid_checksum(InvalidChecksum, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == 0
|
assert m.unique_id == 0
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==DeviceIndMsg
|
assert m._recv_buffer==DeviceIndMsg
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -480,7 +502,7 @@ def test_invalid_checksum(InvalidChecksum, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == None
|
assert m.unique_id == None
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -488,7 +510,7 @@ def test_invalid_checksum(InvalidChecksum, DeviceIndMsg):
|
|||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 1
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
def test_read_message_twice(ConfigNoTsunInv1, DeviceIndMsg):
|
def test_read_message_twice(ConfigNoTsunInv1, DeviceIndMsg, DeviceRspMsg):
|
||||||
ConfigNoTsunInv1
|
ConfigNoTsunInv1
|
||||||
m = MemoryStream(DeviceIndMsg, (0,))
|
m = MemoryStream(DeviceIndMsg, (0,))
|
||||||
m.append_msg(DeviceIndMsg)
|
m.append_msg(DeviceIndMsg)
|
||||||
@@ -499,10 +521,13 @@ def test_read_message_twice(ConfigNoTsunInv1, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:01'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
|
assert m._send_buffer==DeviceRspMsg
|
||||||
assert m._forward_buffer==b''
|
assert m._forward_buffer==b''
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
|
|
||||||
|
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||||
m.read() # read complete msg, and dispatch msg
|
m.read() # read complete msg, and dispatch msg
|
||||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||||
assert m.msg_count == 2
|
assert m.msg_count == 2
|
||||||
@@ -510,8 +535,9 @@ def test_read_message_twice(ConfigNoTsunInv1, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:01'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
|
assert m._send_buffer==DeviceRspMsg
|
||||||
assert m._forward_buffer==b''
|
assert m._forward_buffer==b''
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.close()
|
m.close()
|
||||||
@@ -528,7 +554,7 @@ def test_read_message_in_chunks(DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == 0 # should be None ?
|
assert m.unique_id == 0 # should be None ?
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:00'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.read() # read rest of message
|
m.read() # read rest of message
|
||||||
@@ -552,7 +578,7 @@ def test_read_message_in_chunks2(ConfigTsunInv1, DeviceIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:01'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m.msg_count == 1
|
assert m.msg_count == 1
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
@@ -563,7 +589,7 @@ def test_read_message_in_chunks2(ConfigTsunInv1, DeviceIndMsg):
|
|||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
def test_read_two_messages(ConfigTsunAllowAll, DeviceIndMsg, InverterIndMsg):
|
def test_read_two_messages(ConfigTsunAllowAll, DeviceIndMsg, DeviceRspMsg, InverterIndMsg, InverterRspMsg):
|
||||||
ConfigTsunAllowAll
|
ConfigTsunAllowAll
|
||||||
m = MemoryStream(DeviceIndMsg, (0,))
|
m = MemoryStream(DeviceIndMsg, (0,))
|
||||||
m.append_msg(InverterIndMsg)
|
m.append_msg(InverterIndMsg)
|
||||||
@@ -574,14 +600,13 @@ def test_read_two_messages(ConfigTsunAllowAll, DeviceIndMsg, InverterIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4110
|
assert m.control == 0x4110
|
||||||
assert m.serial == 0x0100
|
assert str(m.seq) == '01:01'
|
||||||
assert m.data_len == 0xd4
|
assert m.data_len == 0xd4
|
||||||
assert m.msg_count == 1
|
assert m.msg_count == 1
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
assert m._forward_buffer==DeviceIndMsg
|
assert m._forward_buffer==DeviceIndMsg
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==DeviceRspMsg
|
||||||
# assert m._send_buffer==MsgContactResp
|
|
||||||
|
|
||||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||||
m._init_new_client_conn()
|
m._init_new_client_conn()
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -597,11 +622,11 @@ def test_read_two_messages(ConfigTsunAllowAll, DeviceIndMsg, InverterIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4210
|
assert m.control == 0x4210
|
||||||
assert m.serial == 0x9ee6
|
assert str(m.seq) == '02:02'
|
||||||
assert m.data_len == 0x199
|
assert m.data_len == 0x199
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
assert m._forward_buffer==InverterIndMsg
|
assert m._forward_buffer==InverterIndMsg
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==InverterRspMsg
|
||||||
|
|
||||||
m._send_buffer = bytearray(0) # clear send buffer for next test
|
m._send_buffer = bytearray(0) # clear send buffer for next test
|
||||||
m._init_new_client_conn()
|
m._init_new_client_conn()
|
||||||
@@ -618,7 +643,7 @@ def test_unkown_message(ConfigTsunInv1, UnknownMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x5110
|
assert m.control == 0x5110
|
||||||
assert m.serial == 0x8410
|
assert str(m.seq) == '84:10'
|
||||||
assert m.data_len == 0x0a
|
assert m.data_len == 0x0a
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
@@ -636,11 +661,11 @@ def test_device_rsp(ConfigTsunInv1, DeviceRspMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x1110
|
assert m.control == 0x1110
|
||||||
assert m.serial == 0x8410
|
assert str(m.seq) == '01:01'
|
||||||
assert m.data_len == 0x0a
|
assert m.data_len == 0x0a
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
assert m._forward_buffer==DeviceRspMsg
|
assert m._forward_buffer==b'' # DeviceRspMsg
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
@@ -654,15 +679,15 @@ def test_inverter_rsp(ConfigTsunInv1, InverterRspMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x1210
|
assert m.control == 0x1210
|
||||||
assert m.serial == 0x8410
|
assert str(m.seq) == '02:02'
|
||||||
assert m.data_len == 0x0a
|
assert m.data_len == 0x0a
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
assert m._forward_buffer==InverterRspMsg
|
assert m._forward_buffer==b'' # InverterRspMsg
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
def test_heartbeat_ind(ConfigTsunInv1, HeartbeatIndMsg):
|
def test_heartbeat_ind(ConfigTsunInv1, HeartbeatIndMsg, HeartbeatRspMsg):
|
||||||
ConfigTsunInv1
|
ConfigTsunInv1
|
||||||
m = MemoryStream(HeartbeatIndMsg, (0,))
|
m = MemoryStream(HeartbeatIndMsg, (0,))
|
||||||
m.read() # read complete msg, and dispatch msg
|
m.read() # read complete msg, and dispatch msg
|
||||||
@@ -672,10 +697,10 @@ def test_heartbeat_ind(ConfigTsunInv1, HeartbeatIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
# assert m.unique_id == '2070233889'
|
# assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4710
|
assert m.control == 0x4710
|
||||||
assert m.serial == 0x8410
|
assert str(m.seq) == '84:11' # value after sending response
|
||||||
assert m.data_len == 0x01
|
assert m.data_len == 0x01
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==HeartbeatRspMsg
|
||||||
assert m._forward_buffer==HeartbeatIndMsg
|
assert m._forward_buffer==HeartbeatIndMsg
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.close()
|
m.close()
|
||||||
@@ -690,11 +715,11 @@ def test_heartbeat_rsp(ConfigTsunInv1, HeartbeatRspMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
assert m.unique_id == '2070233889'
|
assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x1710
|
assert m.control == 0x1710
|
||||||
assert m.serial == 0x8410
|
assert str(m.seq) == '11:84' # value after sending response
|
||||||
assert m.data_len == 0x0a
|
assert m.data_len == 0x0a
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
assert m._forward_buffer==HeartbeatRspMsg
|
assert m._forward_buffer==b'' # HeartbeatRspMsg
|
||||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
@@ -708,7 +733,7 @@ def test_at_command_ind(ConfigTsunInv1, AtCommandIndMsg):
|
|||||||
assert m.snr == 2070233889
|
assert m.snr == 2070233889
|
||||||
# assert m.unique_id == '2070233889'
|
# assert m.unique_id == '2070233889'
|
||||||
assert m.control == 0x4510
|
assert m.control == 0x4510
|
||||||
assert m.serial == 0x8410
|
assert str(m.seq) == '84:10'
|
||||||
assert m.data_len == 0x01
|
assert m.data_len == 0x01
|
||||||
assert m._recv_buffer==b''
|
assert m._recv_buffer==b''
|
||||||
assert m._send_buffer==b''
|
assert m._send_buffer==b''
|
||||||
|
|||||||
Reference in New Issue
Block a user