Skip to content

Commit f0405b2

Browse files
authored
Remove async_io, simplify AsyncModbus<x>Client. (#1009)
1 parent 1ee937a commit f0405b2

File tree

13 files changed

+690
-944
lines changed

13 files changed

+690
-944
lines changed

doc/source/library/pymodbus.client.asynchronous.async_io.rst

Lines changed: 0 additions & 8 deletions
This file was deleted.

doc/source/library/pymodbus.client.asynchronous.rst

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,6 @@ pymodbus\.client\.asynchronous package
66
:undoc-members:
77
:show-inheritance:
88

9-
Subpackages
10-
-----------
11-
12-
.. toctree::
13-
14-
pymodbus.client.asynchronous.async_io
15-
169
Submodules
1710
----------
1811

@@ -32,14 +25,6 @@ pymodbus\.client\.asynchronous\.tcp module
3225
:undoc-members:
3326
:show-inheritance:
3427

35-
pymodbus\.client\.asynchronous\.thread module
36-
---------------------------------------------
37-
38-
.. automodule:: pymodbus.client.asynchronous.thread
39-
:members:
40-
:undoc-members:
41-
:show-inheritance:
42-
4328
pymodbus\.client\.asynchronous\.udp module
4429
------------------------------------------
4530

doc/source/library/pymodbus.client.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pymodbus\.client package
22
========================
33

4-
Pymodbus offers a :mod:`synchronous client <pymodbus.client.sync>`, and async clients based on :mod:`asyncio <pymodbus.client.asynchronous.async_io>` and :mod:`Twisted <pymodbus.client.asynchronous.Twisted>`.
4+
Pymodbus offers a :mod:`synchronous client <pymodbus.client.sync>`, and async clients based on :mod:`asyncio <pymodbus.client.asynchronous>`.
55

66
Each client shares a :mod:`common client mixin <pymodbus.client.common>` which offers simple methods for reading and writing.
77

@@ -30,5 +30,3 @@ pymodbus\.client\.sync module
3030
:members:
3131
:undoc-members:
3232
:show-inheritance:
33-
34-

pymodbus/client/async_helper.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
"""Modbus Client Common.
2+
3+
This is a common client mixin that can be used by
4+
both the synchronous and asynchronous clients to
5+
simplify the interface.
6+
"""
7+
# pylint: disable=missing-type-doc
8+
import asyncio
9+
import logging
10+
11+
from pymodbus.utilities import hexlify_packets
12+
from pymodbus.exceptions import ConnectionException
13+
from pymodbus.client.asynchronous.mixins import AsyncModbusClientMixin
14+
15+
_logger = logging.getLogger(__name__)
16+
17+
18+
class BaseModbusAsyncClientProtocol(AsyncModbusClientMixin):
19+
"""Asyncio specific implementation of asynchronous modbus client protocol."""
20+
21+
#: Factory that created this instance.
22+
factory = None
23+
transport = None
24+
25+
async def execute(self, request=None): # pylint: disable=invalid-overridden-method
26+
"""Execute requests asynchronously.
27+
28+
:param request:
29+
:return:
30+
"""
31+
req = self._execute(request)
32+
if self.broadcast_enable and not request.unit_id:
33+
resp = b"Broadcast write sent - no response expected"
34+
else:
35+
resp = await asyncio.wait_for(req, timeout=self._timeout)
36+
return resp
37+
38+
def connection_made(self, transport):
39+
"""Call when a connection is made.
40+
41+
The transport argument is the transport representing the connection.
42+
43+
:param transport:
44+
"""
45+
self.transport = transport
46+
self._connection_made()
47+
48+
if self.factory:
49+
self.factory.protocol_made_connection(self)
50+
51+
def connection_lost(self, reason):
52+
"""Call when the connection is lost or closed.
53+
54+
The argument is either an exception object or None
55+
56+
:param reason:
57+
"""
58+
self.transport = None
59+
self._connection_lost(reason)
60+
61+
if self.factory:
62+
self.factory.protocol_lost_connection(self)
63+
64+
def data_received(self, data):
65+
"""Call when some data is received.
66+
67+
data is a non-empty bytes object containing the incoming data.
68+
69+
:param data:
70+
"""
71+
self._data_received(data)
72+
73+
def create_future(self):
74+
"""Help function to create asyncio Future object."""
75+
return asyncio.Future()
76+
77+
def resolve_future(self, my_future, result):
78+
"""Resolve the completed future and sets the result.
79+
80+
:param my_future:
81+
:param result:
82+
"""
83+
if not my_future.done():
84+
my_future.set_result(result)
85+
86+
def raise_future(self, my_future, exc):
87+
"""Set exception of a future if not done.
88+
89+
:param my_future:
90+
:param exc:
91+
"""
92+
if not my_future.done():
93+
my_future.set_exception(exc)
94+
95+
def _connection_made(self):
96+
"""Call upon a successful client connection."""
97+
_logger.debug("Client connected to modbus server")
98+
self._connected = True
99+
100+
def _connection_lost(self, reason):
101+
"""Call upon a client disconnect
102+
103+
:param reason: The reason for the disconnect
104+
"""
105+
txt = f"Client disconnected from modbus server: {reason}"
106+
_logger.debug(txt)
107+
self._connected = False
108+
for tid in list(self.transaction):
109+
self.raise_future(
110+
self.transaction.getTransaction(tid),
111+
ConnectionException("Connection lost during request"),
112+
)
113+
114+
@property
115+
def connected(self):
116+
"""Return connection status."""
117+
return self._connected
118+
119+
def write_transport(self, packet):
120+
"""Write transport."""
121+
return self.transport.write(packet)
122+
123+
def _execute(self, request, **kwargs): # pylint: disable=unused-argument
124+
"""Start the producer to send the next request to consumer.write(Frame(request))."""
125+
request.transaction_id = self.transaction.getNextTID()
126+
packet = self.framer.buildPacket(request)
127+
txt = f"send: {hexlify_packets(packet)}"
128+
_logger.debug(txt)
129+
self.write_transport(packet)
130+
return self._build_response(request.transaction_id)
131+
132+
def _data_received(self, data):
133+
"""Get response, check for valid message, decode result
134+
135+
:param data: The data returned from the server
136+
"""
137+
txt = f"recv: {hexlify_packets(data)}"
138+
_logger.debug(txt)
139+
unit = self.framer.decode_data(data).get("unit", 0)
140+
self.framer.processIncomingPacket(data, self._handle_response, unit=unit)
141+
142+
def _handle_response(self, reply, **kwargs): # pylint: disable=unused-argument
143+
"""Handle the processed response and link to correct deferred
144+
145+
:param reply: The reply to process
146+
:param kwargs: The rest
147+
"""
148+
if reply is not None:
149+
tid = reply.transaction_id
150+
if handler := self.transaction.getTransaction(tid):
151+
self.resolve_future(handler, reply)
152+
else:
153+
txt = f"Unrequested message: {str(reply)}"
154+
_logger.debug(txt)
155+
156+
def _build_response(self, tid):
157+
"""Return a deferred response for the current request.
158+
159+
:param tid: The transaction identifier for this response
160+
:returns: A defer linked to the latest request
161+
"""
162+
my_future = self.create_future()
163+
if not self._connected:
164+
self.raise_future(my_future, ConnectionException("Client is not connected"))
165+
else:
166+
self.transaction.addTransaction(my_future, tid)
167+
return my_future
168+
169+
def close(self):
170+
"""Close."""
171+
self.transport.close()
172+
self._connected = False
173+
174+
175+
class ModbusClientProtocol(BaseModbusAsyncClientProtocol, asyncio.Protocol):
176+
"""Asyncio specific implementation of asynchronous modbus client protocol."""
177+
178+
#: Factory that created this instance.
179+
factory = None
180+
transport = None
181+
182+
def data_received(self, data):
183+
"""Call when some data is received.
184+
185+
data is a non-empty bytes object containing the incoming data.
186+
187+
:param data:
188+
"""
189+
self._data_received(data)

0 commit comments

Comments
 (0)