1616import argparse
1717import asyncio
1818import struct
19+ import logging
1920from functools import partial
2021from umodbus .client .serial .redundancy_check import get_crc
2122from pysolarmanv5 import PySolarmanV5Async
2223
24+ logging .basicConfig (
25+ level = logging .INFO ,
26+ format = "%(asctime)s [%(levelname)s] %(name)s: %(message)s" ,
27+ )
28+ log = logging .getLogger ("solarman" )
2329
2430async def handle_client (
2531 reader : asyncio .StreamReader ,
@@ -28,49 +34,49 @@ async def handle_client(
2834 logger_serial : int ,
2935):
3036 solarmanv5 = PySolarmanV5Async (
31- logger_address , logger_serial , verbose = True , auto_reconnect = True
37+ address = logger_address , serial = logger_serial , verbose = True , auto_reconnect = True , logger = log
3238 )
3339 await solarmanv5 .connect ()
3440
3541 addr = writer .get_extra_info ("peername" )
3642
37- print (f"{ addr } : New connection" )
43+ log . info (f"{ addr } : New connection" )
3844
3945 try :
4046 while True :
41- # Convert TCP to RTU
42- header = await reader .readexactly (6 )
43- if not header :
47+ try :
48+ # read Modbus TCP frame
49+ header = await reader .readexactly (6 )
50+ # decode header
51+ trans_id , proto_id , length = struct .unpack (">HHH" , header )
52+ unit_id = await reader .readexactly (1 )
53+ pdu = await reader .readexactly (length - 1 ) # length includes unit_id
54+ except asyncio .IncompleteReadError :
55+ # connection closed
4456 break
4557
46- trans_id , proto_id , length = struct .unpack (">HHH" , header )
47- unit_id = await reader .readexactly (1 )
48- pdu = await reader .readexactly (length - 1 ) # length includes unit_id
49-
58+ # create equivalent RTU frame
5059 slave_id = b"\x01 "
5160 modbus_rtu = slave_id + pdu + get_crc (slave_id + pdu )
5261
53- try :
54- # Convert RTU back to TCP
55- reply_rtu = await solarmanv5 .send_raw_modbus_frame (modbus_rtu )
62+ reply_rtu = await solarmanv5 .send_raw_modbus_frame (modbus_rtu )
5663
57- slave_id_reply = reply_rtu [0 :1 ]
58- pdu_reply = reply_rtu [1 :- 2 ]
59- crc_reply = reply_rtu [- 2 :]
64+ # slave_id_reply = reply_rtu[0:1]
65+ pdu_reply = reply_rtu [1 :- 2 ]
66+ # crc_reply = reply_rtu[-2:]
6067
61- mbap = struct .pack (">HHH" , trans_id , 0 , len (pdu_reply ) + 1 )
62- reply_tcp = mbap + unit_id + pdu_reply
68+ # Convert RTU back to TCP
69+ mbap = struct .pack (">HHH" , trans_id , 0 , len (pdu_reply ) + 1 )
70+ reply_tcp = mbap + unit_id + pdu_reply
6371
64- writer .write (reply_tcp )
65- except :
66- pass
72+ writer .write (reply_tcp )
6773
6874 await writer .drain ()
6975 except OSError :
7076 # https://github.com/python/cpython/issues/83037
7177 pass
7278
73- print (f"{ addr } : Connection closed" )
79+ log . info (f"{ addr } : Connection closed" )
7480 await solarmanv5 .disconnect ()
7581
7682
@@ -85,14 +91,14 @@ async def run_proxy(
8591 port ,
8692 )
8793 async with server :
88- print (f"Listening on { bind_address } :{ port } " )
94+ log . info (f"Listening on { bind_address } :{ port } " )
8995 await server .serve_forever ()
9096
9197
9298def main ():
9399 parser = argparse .ArgumentParser (
94- prog = "solarman rtu proxy" ,
95- description = "A Modbus RTU over TCP Proxy for Solarman loggers" ,
100+ prog = "solarman-tcp- proxy" ,
101+ description = "A Modbus TCP Proxy for Solarman loggers" ,
96102 )
97103 parser .add_argument (
98104 "-b" , "--bind" , default = "0.0.0.0" , help = "The address to listen on"
@@ -104,11 +110,7 @@ def main():
104110 "-l" , "--logger" , required = True , help = "The IP address of the logger"
105111 )
106112 parser .add_argument (
107- "-s" ,
108- "--serial" ,
109- required = True ,
110- type = int ,
111- help = "The serial number of the logger" ,
113+ "-s" , "--serial" , required = True , type = int , help = "The serial number of the logger"
112114 )
113115 args = parser .parse_args ()
114116
0 commit comments