3131
3232 Send raw data packets to the server (remark data is frame+request)
3333
34- *** handle_client_data (transport, data ) ***
34+ *** simulate_server (transport, request ) ***
3535
36- Called when data is received from the client/server (remark data is frame+request)
36+ Called when data is received from the client (remark data is frame+request)
3737
3838 The function generates frame+response and sends it.
3939
40- And one function which can be modified to test the server functionality:
40+ *** simulate_client(transport, response) ***
41+
42+ Called when data is received from the server (remark data is frame+request)
43+
4144"""
4245from __future__ import annotations
4346
4649
4750import pymodbus .client as modbusClient
4851import pymodbus .server as modbusServer
49- from pymodbus import Framer , pymodbus_apply_logging_config
52+ from pymodbus import Framer , ModbusException , pymodbus_apply_logging_config
5053from pymodbus .datastore import (
5154 ModbusSequentialDataBlock ,
5255 ModbusServerContext ,
@@ -69,6 +72,7 @@ def __init__(
6972 """Initialize a stub instance."""
7073 self .stub_handle_data = handler
7174 super ().__init__ (params , is_server )
75+ self .is_tcp = params .comm_type == CommType .TCP
7276
7377 async def start_run (self ):
7478 """Call need functions to start server/client."""
@@ -78,7 +82,7 @@ async def start_run(self):
7882
7983 def callback_data (self , data : bytes , addr : tuple | None = None ) -> int :
8084 """Handle received data."""
81- self .stub_handle_data (self , data )
85+ self .stub_handle_data (self , self . is_tcp , data )
8286 return len (data )
8387
8488 def callback_connected (self ) -> None :
@@ -95,27 +99,33 @@ def callback_new_connection(self) -> ModbusProtocol:
9599 return new_stub
96100
97101
102+ test_port = 5004 # pylint: disable=invalid-name
103+
98104class ClientTester : # pylint: disable=too-few-public-methods
99105 """Main program."""
100106
101107 def __init__ (self , comm : CommType ):
102108 """Initialize runtime tester."""
109+ global test_port # pylint: disable=global-statement
103110 self .comm = comm
111+ host = NULLMODEM_HOST
104112
105113 if comm == CommType .TCP :
106114 self .client = modbusClient .AsyncModbusTcpClient (
107- NULLMODEM_HOST ,
108- port = 5004 ,
115+ host ,
116+ port = test_port ,
109117 )
110118 elif comm == CommType .SERIAL :
119+ host = f"{ NULLMODEM_HOST } :{ test_port } "
111120 self .client = modbusClient .AsyncModbusSerialClient (
112- f" { NULLMODEM_HOST } :5004" ,
121+ host ,
113122 )
114123 else :
115124 raise RuntimeError ("ERROR: CommType not implemented" )
116125 server_params = self .client .comm_params .copy ()
117- server_params .source_address = (f"{ NULLMODEM_HOST } :5004" , 5004 )
118- self .stub = TransportStub (server_params , True , handle_client_data )
126+ server_params .source_address = (host , test_port )
127+ self .stub = TransportStub (server_params , True , simulate_server )
128+ test_port += 1
119129
120130
121131 async def run (self ):
@@ -135,6 +145,7 @@ class ServerTester: # pylint: disable=too-few-public-methods
135145
136146 def __init__ (self , comm : CommType ):
137147 """Initialize runtime tester."""
148+ global test_port # pylint: disable=global-statement
138149 self .comm = comm
139150 self .store = ModbusSlaveContext (
140151 di = ModbusSequentialDataBlock (0 , [17 ] * 100 ),
@@ -151,22 +162,23 @@ def __init__(self, comm: CommType):
151162 self .context ,
152163 framer = Framer .SOCKET ,
153164 identity = self .identity ,
154- address = (NULLMODEM_HOST , 5004 ),
165+ address = (NULLMODEM_HOST , test_port ),
155166 )
156167 elif comm == CommType .SERIAL :
157168 self .server = modbusServer .ModbusSerialServer (
158169 self .context ,
159170 framer = Framer .SOCKET ,
160171 identity = self .identity ,
161- port = f"{ NULLMODEM_HOST } :5004 " ,
172+ port = f"{ NULLMODEM_HOST } :{ test_port } " ,
162173 )
163174 else :
164175 raise RuntimeError ("ERROR: CommType not implemented" )
165176 client_params = self .server .comm_params .copy ()
166177 client_params .host = client_params .source_address [0 ]
167178 client_params .port = client_params .source_address [1 ]
168179 client_params .timeout_connect = 1.0
169- self .stub = TransportStub (client_params , False , handle_server_data )
180+ self .stub = TransportStub (client_params , False , simulate_client )
181+ test_port += 1
170182
171183
172184 async def run (self ):
@@ -175,7 +187,7 @@ async def run(self):
175187 Log .debug ("--> Start testing." )
176188 await self .server .listen ()
177189 await self .stub .start_run ()
178- await server_calls (self .stub )
190+ await server_calls (self .stub , ( self . comm == CommType . TCP ) )
179191 Log .debug ("--> Shutting down." )
180192 await self .server .shutdown ()
181193
@@ -194,21 +206,43 @@ async def main(comm: CommType, use_server: bool):
194206async def client_calls (client ):
195207 """Test client API."""
196208 Log .debug ("--> Client calls starting." )
197- _resp = await client .read_holding_registers (address = 124 , count = 4 , slave = 0 )
198-
209+ try :
210+ resp = await client .read_holding_registers (address = 124 , count = 4 , slave = 0 )
211+ except ModbusException as exc :
212+ txt = f"ERROR: exception in pymodbus { exc } "
213+ Log .error (txt )
214+ return
215+ if resp .isError ():
216+ txt = "ERROR: pymodbus returned an error!"
217+ Log .error (txt )
218+ await asyncio .sleep (1 )
219+ client .close ()
220+ print ("---> CLIENT all done" )
199221
200- async def server_calls (transport : ModbusProtocol ):
201- """Test client API ."""
222+ async def server_calls (transport : ModbusProtocol , is_tcp : bool ):
223+ """Test server functionality ."""
202224 Log .debug ("--> Server calls starting." )
203- _resp = transport .send (b'\x00 \x02 \x00 \x00 \x00 \x06 \x01 \x03 \x00 \x00 \x00 \x01 ' +
204- b'\x07 \x00 \x03 \x00 \x00 \x06 \x01 \x03 \x00 \x00 \x00 \x01 ' )
225+
226+ if is_tcp :
227+ request = b'\x00 \x02 \x00 \x00 \x00 \x06 \x01 \x03 \x00 \x00 \x00 \x01 '
228+ else :
229+ # 2 responses:
230+ # response = b'\x00\x02\x00\x00\x00\x06\x01\x03\x00\x00\x00\x01' +
231+ # b'\x07\x00\x03\x00\x00\x06\x01\x03\x00\x00\x00\x01')
232+ # 1 response:
233+ request = b'\x00 \x02 \x00 \x00 \x00 \x06 \x01 \x03 \x00 \x00 \x00 \x01 '
234+ transport .send (request )
205235 await asyncio .sleep (1 )
206- print ("---> all done" )
236+ transport .close ()
237+ print ("---> SERVER all done" )
207238
208- def handle_client_data (transport : ModbusProtocol , data : bytes ):
239+ def simulate_server (transport : ModbusProtocol , is_tcp : bool , request : bytes ):
209240 """Respond to request at transport level."""
210- Log .debug ("--> stub called with request {}." , data , ":hex" )
211- response = b'\x01 \x03 \x08 \x00 \x05 \x00 \x05 \x00 \x00 \x00 \x00 \x0c \xd7 '
241+ Log .debug ("--> Server simulator called with request {}." , request , ":hex" )
242+ if is_tcp :
243+ response = b'\x00 \x01 \x00 \x00 \x00 \x06 \x00 \x03 \x00 \x7c \x00 \x04 '
244+ else :
245+ response = b'\x01 \x03 \x08 \x00 \x05 \x00 \x05 \x00 \x00 \x00 \x00 \x0c \xd7 '
212246
213247 # Multiple send is allowed, to test fragmentation
214248 # for data in response:
@@ -217,12 +251,14 @@ def handle_client_data(transport: ModbusProtocol, data: bytes):
217251 transport .send (response )
218252
219253
220- def handle_server_data (_transport : ModbusProtocol , data : bytes ):
254+ def simulate_client (_transport : ModbusProtocol , _is_tcp : bool , response : bytes ):
221255 """Respond to request at transport level."""
222- Log .debug ("--> stub called with response {}." , data , ":hex" )
256+ Log .debug ("--> Client simulator called with response {}." , response , ":hex" )
223257
224258
225259if __name__ == "__main__" :
226260 # True for Server test, False for Client test
227- # asyncio.run(main(CommType.SERIAL, False), debug=True)
261+ asyncio .run (main (CommType .SERIAL , False ), debug = True )
262+ asyncio .run (main (CommType .SERIAL , True ), debug = True )
263+ asyncio .run (main (CommType .TCP , False ), debug = True )
228264 asyncio .run (main (CommType .TCP , True ), debug = True )
0 commit comments