Merge branch 'main' of https://github.com/s-allius/tsun-gen3-proxy into dev-0.7.0

This commit is contained in:
Stefan Allius
2024-04-13 23:14:54 +02:00
3 changed files with 128 additions and 86 deletions

View File

@@ -179,10 +179,9 @@ A combination with a red question mark should work, but I have not checked it in
<table align="center">
<tr><th align="center">Micro Inverter Model</th><th align="center">Fw. 1.00.06</th><th align="center">Fw. 1.00.17</th><th align="center">Fw. 1.00.20</th><th align="center">Fw. 1.1.00.0B</th></tr>
<tr><td>GEN3 micro inverters (single MPPT):<br>MS300, MS350, MS400</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center"></td></tr>
<tr><td>GEN3 micro inverters (dual MPPT):<br>MS600, MS700, MS800</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">✔️</td><td align="center"></td></tr>
<tr><td>GEN3 PLUS micro inverters:<br>MS1600, MS1800, MS2000</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">✔️</td></tr>
<tr><td>Balcony micro inverters:<br>MS400-D, MS800-D, MS2000-D</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td></tr>
<tr><td>GEN3 micro inverters (single MPPT):<br>MS300, MS350, MS400<br>MS400-D</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center"></td></tr>
<tr><td>GEN3 micro inverters (dual MPPT):<br>MS600, MS700, MS800<br>MS600-D, MS800-D</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">✔️</td><td align="center"></td></tr>
<tr><td>GEN3 PLUS micro inverters:<br>MS1600, MS1800, MS2000<br>MS2000-D</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center">✔️</td></tr>
<tr><td>TITAN micro inverters:<br>TSOL-MP3000, MP2250, MS3000</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td></tr>
</table>

View File

@@ -58,10 +58,10 @@ class SolarmanV5(Message):
self.switch = {
0x4210: self.msg_data_ind, # real time data
0x1210: self.msg_data_rsp, # at least every 5 minutes
0x1210: self.msg_response, # at least every 5 minutes
0x4710: self.msg_hbeat_ind, # heatbeat
0x1710: self.msg_hbeat_rsp, # every 2 minutes
0x1710: self.msg_response, # every 2 minutes
# every 3 hours comes a sync seuqence:
# 00:00:00 0x4110 device data ftype: 0x02
@@ -72,18 +72,18 @@ class SolarmanV5(Message):
# 00:00:07 0x4310 wifi data ftype: 0x01 sub-id 0x0018: 0c # noqa: E501
# 00:00:08 0x4810 options? ftype: 0x01
0x4110: self.msg_dev_ind, # device data, sync start
0x1110: self.msg_dev_rsp, # every 3 hours
0x4110: self.msg_dev_ind, # device data, sync start
0x1110: self.msg_response, # every 3 hours
0x4310: self.msg_forward, # regulary after 3-6 hours
0x1310: self.msg_forward,
0x4810: self.msg_forward, # sync end
0x1810: self.msg_forward,
0x4310: self.msg_sync_start, # regulary after 3-6 hours
0x1310: self.msg_response,
0x4810: self.msg_sync_end, # sync end
0x1810: self.msg_response,
#
# AT cmd
0x4510: self.at_command_ind, # from server
0x1510: self.msg_forward, # from inverter
# MODbus or AT cmd
0x4510: self.msg_command_req, # from server
0x1510: self.msg_response, # from inverter
}
'''
@@ -285,63 +285,23 @@ class SolarmanV5(Message):
self.data_len+2):]
self.header_valid = False
'''
Message handler methods
'''
def msg_unknown(self):
logger.warning(f"Unknow Msg: ID:{int(self.control):#04x}")
self.inc_counter('Unknown_Msg')
self.msg_forward()
def msg_forward(self):
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
def msg_dev_ind(self):
data = self._recv_buffer[self.header_len:]
result = struct.unpack_from('<BLLL', data, 0)
ftype = result[0] # always 2
total = result[1]
tim = result[2]
res = result[3] # always zero
logger.info(f'frame type:{ftype:02x} total:{total}s'
f' timer:{tim:08x}s null:{res}')
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
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,
def __send_ack_rsp(self, msgtype, ftype, ack=1):
self.__build_header(msgtype)
self._send_buffer += struct.pack('<BBLL', ftype, ack,
self._timestamp(),
self._heartbeat())
self.__finish_send_msg()
def msg_dev_rsp(self):
self.msg_response()
def msg_data_ind(self):
data = self._recv_buffer
result = struct.unpack_from('<BLLLLL', data, self.header_len)
ftype = result[0] # 1 or 0x81
total = result[1]
tim = result[2]
offset = result[3]
unkn = result[4]
cnt = result[5]
logger.info(f'ftype:{ftype:02x} total:{total}s'
f' timer:{tim:08x}s ofs:{offset}'
f' ??: {unkn:08x} cnt:{cnt}')
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
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())
def send_at_cmd(self, AT_cmd: str) -> None:
self.__build_header(0x4510)
self._send_buffer += struct.pack(f'<BHLLL{len(AT_cmd)}sc', 1, 2,
0, 0, 0, AT_cmd.encode('utf-8'),
b'\r')
self.__finish_send_msg()
def __forward_msg(self):
self.forward(self._recv_buffer, self.header_len+self.data_len+2)
def __process_data(self, ftype):
inv_update = False
ctrl_update = False
@@ -377,19 +337,81 @@ class SolarmanV5(Message):
Model = Version.split('_')[0]
self.db.set_db_def_value(Register.CHIP_MODEL, Model)
def msg_data_rsp(self):
self.msg_response()
'''
Message handler methods
'''
def msg_unknown(self):
logger.warning(f"Unknow Msg: ID:{int(self.control):#04x}")
self.inc_counter('Unknown_Msg')
self.__forward_msg()
def msg_dev_ind(self):
data = self._recv_buffer[self.header_len:]
result = struct.unpack_from('<BLLL', data, 0)
ftype = result[0] # always 2
total = result[1]
tim = result[2]
res = result[3] # always zero
logger.info(f'frame type:{ftype:02x} total:{total}s'
f' timer:{tim:08x}s null:{res}')
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
self.__process_data(ftype)
self.__forward_msg()
self.__send_ack_rsp(0x1110, ftype)
def msg_data_ind(self):
data = self._recv_buffer
result = struct.unpack_from('<BLLLLL', data, self.header_len)
ftype = result[0] # 1 or 0x81
total = result[1]
tim = result[2]
offset = result[3]
unkn = result[4]
cnt = result[5]
logger.info(f'ftype:{ftype:02x} total:{total}s'
f' timer:{tim:08x}s ofs:{offset}'
f' ??: {unkn:08x} cnt:{cnt}')
dt = datetime.fromtimestamp(total)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
self.__process_data(ftype & 0x7f) # mask bit 7 (0x80)
self.__forward_msg()
self.__send_ack_rsp(0x1210, ftype)
def msg_sync_start(self):
data = self._recv_buffer[self.header_len:]
result = struct.unpack_from('<B', data, 0)
ftype = result[0]
self.__forward_msg()
self.__send_ack_rsp(0x1310, ftype)
def msg_command_req(self):
data = self._recv_buffer[self.header_len:]
result = struct.unpack_from('<B', data, 0)
ftype = result[0]
self.inc_counter('AT_Command')
self.__forward_msg()
self.__send_ack_rsp(0x1510, ftype)
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()
data = self._recv_buffer[self.header_len:]
result = struct.unpack_from('<B', data, 0)
ftype = result[0]
def msg_hbeat_rsp(self):
self.msg_response()
self.__forward_msg()
self.__send_ack_rsp(0x1710, ftype)
def msg_sync_end(self):
data = self._recv_buffer[self.header_len:]
result = struct.unpack_from('<B', data, 0)
ftype = result[0]
self.__forward_msg()
self.__send_ack_rsp(0x1810, ftype)
def msg_response(self):
data = self._recv_buffer[self.header_len:]
@@ -403,7 +425,3 @@ class SolarmanV5(Message):
dt = datetime.fromtimestamp(ts)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
def at_command_ind(self):
self.inc_counter('AT_Command')
self.msg_forward()

View File

@@ -344,8 +344,18 @@ def HeartbeatRspMsg(): # 0x1710
@pytest.fixture
def AtCommandIndMsg(): # 0x4510
msg = b'\xa5\x01\x00\x10\x45\x10\x84' +get_sn()
msg += b'\x00'
msg = b'\xa5\x27\x00\x10\x45\x02\x01' +get_sn() +b'\x01\x02\x00'
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
msg += b'AT+TIME=214028,1,60,120\r'
msg += correct_checksum(msg)
msg += b'\x15'
return msg
@pytest.fixture
def AtCommandRspMsg(): # 0x1510
msg = b'\xa5\x0a\x00\x10\x15\x03\x01' +get_sn() +b'\x01\x01'
msg += total()
msg += hb()
msg += correct_checksum(msg)
msg += b'\x15'
return msg
@@ -723,7 +733,7 @@ def test_heartbeat_rsp(ConfigTsunInv1, HeartbeatRspMsg):
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
m.close()
def test_at_command_ind(ConfigTsunInv1, AtCommandIndMsg):
def test_at_command_ind(ConfigTsunInv1, AtCommandIndMsg, AtCommandRspMsg):
ConfigTsunInv1
m = MemoryStream(AtCommandIndMsg, (0,))
m.read() # read complete msg, and dispatch msg
@@ -733,10 +743,10 @@ def test_at_command_ind(ConfigTsunInv1, AtCommandIndMsg):
assert m.snr == 2070233889
# assert m.unique_id == '2070233889'
assert m.control == 0x4510
assert str(m.seq) == '84:10'
assert m.data_len == 0x01
assert str(m.seq) == '01:03'
assert m.data_len == 39
assert m._recv_buffer==b''
assert m._send_buffer==b''
assert m._send_buffer==AtCommandRspMsg
assert m._forward_buffer==AtCommandIndMsg
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
assert m.db.stat['proxy']['AT_Command'] == 1
@@ -804,3 +814,18 @@ def test_build_logger_modell(ConfigTsunAllowAll, DeviceIndMsg):
assert 'LSW5BLE_17_02B0_1.05' == m.db.get_db_value(Register.COLLECTOR_FW_VERSION, 0).rstrip('\00')
assert 'LSW5BLE' == m.db.get_db_value(Register.CHIP_MODEL, 0)
m.close()
def test_AT_cmd(ConfigTsunAllowAll, DeviceIndMsg, DeviceRspMsg, AtCommandIndMsg):
ConfigTsunAllowAll
m = MemoryStream(DeviceIndMsg)
m.read()
assert m._recv_buffer==b''
assert m._send_buffer==DeviceRspMsg
assert m._forward_buffer==DeviceIndMsg
m._send_buffer = bytearray(0) # clear send buffer for next test
m._forward_buffer = bytearray(0) # clear send buffer for next test
m.send_at_cmd('AT+TIME=214028,1,60,120')
assert m._recv_buffer==b''
assert m._send_buffer==AtCommandIndMsg
assert m._forward_buffer==b''
m.close()