Skip to content

Add asyncio support #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 121 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
3e94e82
Move system imports to separate file
GimmickNG Jan 21, 2023
d89130f
Refactor TCP and Modbus classes
GimmickNG Jan 21, 2023
f54f4de
Add asyncio support
GimmickNG Jan 21, 2023
5a6b8bf
Merge branch 'develop' into mpmodbus-compatibility
GimmickNG Feb 25, 2023
0642b8a
Rename async folder to asynchronous
GimmickNG Feb 25, 2023
bfd4e21
fix flake8 errors, add docstrings, fix async serial
GimmickNG Feb 25, 2023
1fe6adb
Updated changelog to add #5 and #11
GimmickNG Feb 25, 2023
d867a47
Modify changelog - from code review
GimmickNG Mar 1, 2023
8f11675
Make request parameter optional
GimmickNG Mar 1, 2023
0cd100c
Make changes based on code review
GimmickNG Mar 1, 2023
6c3632d
Update changelog to reflect reverted breaking change
GimmickNG Mar 1, 2023
a0ced88
Add async examples
GimmickNG Mar 11, 2023
d2e6d0a
Refactored serial/RTU to match TCP
GimmickNG Mar 11, 2023
86021c6
Fix setup.py to include async package
GimmickNG Mar 11, 2023
1b1dfde
Add header to async examples
GimmickNG Mar 17, 2023
4353498
Fix unused import, incorrect formatting
GimmickNG Mar 17, 2023
9427597
Fix flake8 formatting error
GimmickNG Mar 17, 2023
e7246e4
Merge branch 'brainelectronics:develop' into mpmodbus-compatibility
GimmickNG Mar 20, 2023
26355df
Fix overload decorator
GimmickNG Mar 20, 2023
114e448
Merge branch 'brainelectronics:develop' into mpmodbus-compatibility
GimmickNG Mar 24, 2023
829ca17
Auto-connect TCP client in constructor
GimmickNG Mar 27, 2023
7b3b49c
Add auto-connect note to async tcp client
GimmickNG Mar 27, 2023
f803842
Move async TCP examples to separate files
GimmickNG Mar 28, 2023
f66fdd8
Fix examples and async tcp server
GimmickNG Mar 29, 2023
50b9bb3
Delete pycache files
GimmickNG Mar 29, 2023
05ebce4
Delete pycache files
GimmickNG Mar 29, 2023
c2ab8a2
Remove duplicate function
GimmickNG Mar 29, 2023
2b538f8
Fix trailing whitespace
GimmickNG Mar 30, 2023
d0614db
Refactor, add async RTU and multi-server examples
GimmickNG Mar 30, 2023
17647cf
Delete async_examples.py
GimmickNG Mar 30, 2023
cf44c22
Remove redundant common files
GimmickNG Mar 30, 2023
c2c9dd6
do not use relative imports in examples
GimmickNG Mar 30, 2023
6f557cc
use fully qualified names for common
GimmickNG Mar 30, 2023
8fad878
Make changes by @beyonlo, fix RTU set_params
GimmickNG Mar 31, 2023
85486d0
fix flake8 error
GimmickNG Mar 31, 2023
b8a712d
Updated async serial server + examples
GimmickNG Apr 11, 2023
deed1b5
modify callback traces to help in debugging
GimmickNG Apr 11, 2023
2cd997d
revert change to _process_read_access
GimmickNG Apr 13, 2023
d098627
add exit function
GimmickNG Apr 13, 2023
6ef64c9
fix flake8 error
GimmickNG Apr 13, 2023
0fb6951
fix broken import
GimmickNG Apr 13, 2023
385aca4
use registers in example.json for tests
GimmickNG Apr 13, 2023
8a74346
flake8 ignore redefinition
GimmickNG Apr 13, 2023
8e55144
change gitignore
GimmickNG Apr 16, 2023
e4acfd6
use req_handler for process() in async RTU
GimmickNG Apr 18, 2023
d36a8c3
fix async serial host example
GimmickNG May 2, 2023
da4cc3c
add debug logging statements
GimmickNG May 22, 2023
22196af
debugging: narrowing logging
GimmickNG May 27, 2023
eebb5a5
debug serial.py
GimmickNG Jun 17, 2023
c10f592
debug: print entire request
GimmickNG Jun 24, 2023
f7c503d
debug: narrow logging statements
GimmickNG Jun 26, 2023
ba4b299
Fix index-out-of-bounds issue when reading slave response
wpyoga Jun 29, 2023
547d0a8
Simplify calculation of inter-frame delay
wpyoga Jun 29, 2023
06aeda0
Fix receive long slave response by waiting longer
wpyoga Jun 29, 2023
c7cd786
Fix receive missing initial bytes by blocking instead of polling
wpyoga Jun 29, 2023
e4bdeea
replace machine idle with time sleep_us for MicroPython below v1.20.0…
brainelectronics Jul 1, 2023
c0de0bd
update changelog
brainelectronics Jul 1, 2023
b5416a9
remove flush function from machine UART fake
brainelectronics Jul 1, 2023
b9a3901
Merge pull request #75 from wpyoga/fix-timing
brainelectronics Jul 2, 2023
1858fae
add missing empty line in several files
brainelectronics Jul 5, 2023
45b5492
validate package.json content on each test workflow run
brainelectronics Jul 5, 2023
9217dae
add precommit hook, contributes to #67
brainelectronics Jul 5, 2023
8d1bec0
Use sync read
GimmickNG Jul 16, 2023
96a3064
add basic contribution guideline, see #67
brainelectronics Jul 19, 2023
0424460
validate package.json and package version file before running all tests
brainelectronics Jul 19, 2023
2646996
update changelog, package version and package.json
brainelectronics Jul 19, 2023
15d90a0
replace upip ulogging installation with ulogging file in tests folder
brainelectronics Jul 19, 2023
481efde
Merge pull request #78 from brainelectronics/feature/improve-contribu…
brainelectronics Jul 19, 2023
9fb326c
Add single char wait time after flush to avoid RTU control pin timing…
brainelectronics Jul 19, 2023
e9ab05a
Merge pull request #79 from brainelectronics/bugfix/yet-another-ctrl-…
brainelectronics Jul 19, 2023
c0a5467
sleep if no data available
GimmickNG Aug 1, 2023
14f0ca1
#56: add asyncio support
GimmickNG Aug 5, 2023
8816948
#56: add asyncio tests, refactor sync tests
GimmickNG Aug 5, 2023
3ca7759
Merge remote-tracking branch 'origin/mpmodbus-compatibility' into asy…
GimmickNG Aug 5, 2023
9d76ee9
fix broken imports
GimmickNG Aug 7, 2023
37de00f
debugging: reraise on exception for rtu example
GimmickNG Aug 8, 2023
da02956
fix local variable reference error
GimmickNG Aug 10, 2023
ec4b728
return request after process
GimmickNG Aug 14, 2023
9b6b3f7
update async serial send to match sync version
GimmickNG Aug 14, 2023
36df0ac
use time.sleep_us where applicable
GimmickNG Aug 15, 2023
ddcd134
add hybrid sleep
GimmickNG Aug 15, 2023
889096f
change import name and type
GimmickNG Aug 16, 2023
2cb2dea
add timeout option for rtu client
GimmickNG Aug 17, 2023
afb0ffd
add read timeout param for sync rtu, move to common
GimmickNG Aug 17, 2023
7f153fa
change delay, _uart_read_timeout to be in ms
GimmickNG Aug 18, 2023
85da811
repeat tests when timeout occurs
GimmickNG Aug 28, 2023
b5fad0a
add async server restart example
GimmickNG Aug 28, 2023
353260e
fix error calling create_servers
GimmickNG Sep 3, 2023
8ff6326
add logging to pack and unpack
GimmickNG Sep 16, 2023
fc186b4
fix struct pack error
GimmickNG Sep 24, 2023
c652e05
add extra/custom args for testing
GimmickNG Sep 24, 2023
c25c1a9
use await for uart streamwriter
GimmickNG Oct 1, 2023
cab9132
test using sync write for uart
GimmickNG Oct 2, 2023
b30f90b
revert safe_struct, refactor common functions
GimmickNG Oct 8, 2023
392e82e
refactor examples to use pipeline
GimmickNG Oct 8, 2023
1517427
add on_pre_set_cb callback
GimmickNG Oct 8, 2023
1a6e95c
#69: add write beyond limit test + cleanup tests
GimmickNG Oct 8, 2023
4f6806d
fix formatting, add debug logs, add client_connect todo
GimmickNG Oct 15, 2023
813d8a6
add more logging
GimmickNG Nov 6, 2023
b5dfbf7
add more logging
GimmickNG Nov 10, 2023
46e3929
add more logging
GimmickNG Nov 20, 2023
327907b
fix MRO for async classes
GimmickNG Nov 25, 2023
5e91a6d
fix MRO for AsyncRTUServer
GimmickNG Nov 27, 2023
4079fe5
inherit from sync version instead of being mixin
GimmickNG Nov 29, 2023
6759430
add on_tcp_connect and on_tcp_disconnect callbacks
GimmickNG Dec 29, 2023
e891a2e
add example which updates registers in background
GimmickNG Dec 30, 2023
fd694b5
Improve uart_read_frame (#5)
hmaerki Dec 31, 2023
ad38bb7
fix on_connect_cb and on_disconnect_cb for async
GimmickNG Jan 22, 2024
a7d6517
change t1char_ms timing in async rtu
GimmickNG Feb 4, 2024
e29ca16
add inet_ntop compatibility function
GimmickNG Feb 4, 2024
2cbe3cf
remove unused print statements
GimmickNG Feb 4, 2024
77ec202
add type parameter to inet_ntop
GimmickNG Feb 5, 2024
fb5911a
add reference
GimmickNG Feb 5, 2024
dfa3062
update example for multi-valued registers
GimmickNG Feb 11, 2024
bce10d3
fix flake8 errors
GimmickNG Feb 19, 2024
7ea5124
fix more flake8 errors
GimmickNG Feb 19, 2024
a77db08
change multi client examples to share registers
GimmickNG Feb 19, 2024
2b4a1d3
fix flake8 errors
GimmickNG Feb 20, 2024
d79f8e9
fix imports, rtu server init errors
GimmickNG Mar 18, 2024
6c15435
revert changes to examples
GimmickNG May 20, 2024
a62dbe0
increase version, add changelog notes, address review comments
GimmickNG Jul 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
-->

<!-- ## [Unreleased] -->
## [Unreleased]
## [2.3.4] - 2023-02-25
### Added
- Support for asynchronous TCP and RTU/Serial servers and clients, through the `asynchronous` package, see #5
### Fixed
- flake8 errors in both synchronous and asynchronous versions (line too long, incorrect indent level, etc.)
- Multiple connections can now be handled by TCP servers through async implementation, see #11
### Changed
- **Breaking:** Renamed `Serial` class to `RTUServer` to match `TCPServer` terminology


## Released
## [2.3.3] - 2023-01-29
Expand Down
2 changes: 2 additions & 0 deletions umodbus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
# -*- coding: UTF-8 -*-

from .version import __version__

__all__ = ["__version__"]
Empty file.
244 changes: 244 additions & 0 deletions umodbus/asynchronous/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
#!/usr/bin/env python
#
# Copyright (c) 2019, Pycom Limited.
#
# This software is licensed under the GNU GPL version 3 or any
# later version, with permitted additional terms. For more information
# see the Pycom Licence v1.0 document supplied with this file, or
# available at https://www.pycom.io/opensource/licensing
#

# system packages
from ..sys_imports import List, Optional, Tuple, Union

# custom packages
from .. import functions, const as Const
from ..common import CommonModbusFunctions, Request


class AsyncRequest(Request):
"""Asynchronously deconstruct request data received via TCP or Serial"""

async def send_response(self,
values: Optional[list] = None,
signed: bool = True) -> None:
"""
Send a response via the configured interface.

:param values: The values
:type values: Optional[list]
:param signed: Indicates if signed values are used
:type signed: bool
"""
await self._itf.send_response(self,
self.unit_addr,
self.function,
self.register_addr,
self.quantity,
self.data,
values,
signed)

async def send_exception(self, exception_code: int) -> None:
"""
Send an exception response.

:param exception_code: The exception code
:type exception_code: int
"""
await self._itf.send_exception_response(self,
self.unit_addr,
self.function,
exception_code)


class CommonAsyncModbusFunctions(CommonModbusFunctions):
"""Common Async Modbus functions"""

async def read_coils(self,
slave_addr: int,
starting_addr: int,
coil_qty: int) -> List[bool]:
"""@see CommonModbusFunctions.read_coils"""

modbus_pdu = functions.read_coils(starting_address=starting_addr,
quantity=coil_qty)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=True)

status_pdu = functions.bytes_to_bool(byte_list=response,
bit_qty=coil_qty)

return status_pdu

async def read_discrete_inputs(self,
slave_addr: int,
starting_addr: int,
input_qty: int) -> List[bool]:
"""@see CommonModbusFunctions.read_discrete_inputs"""

modbus_pdu = functions.read_discrete_inputs(
starting_address=starting_addr,
quantity=input_qty)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=True)

status_pdu = functions.bytes_to_bool(byte_list=response,
bit_qty=input_qty)

return status_pdu

async def read_holding_registers(self,
slave_addr: int,
starting_addr: int,
register_qty: int,
signed: bool = True) -> Tuple[int, ...]:
"""@see CommonModbusFunctions.read_holding_registers"""

modbus_pdu = functions.read_holding_registers(
starting_address=starting_addr,
quantity=register_qty)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=True)

register_value = functions.to_short(byte_array=response, signed=signed)

return register_value

async def read_input_registers(self,
slave_addr: int,
starting_addr: int,
register_qty: int,
signed: bool = True) -> Tuple[int, ...]:
"""@see CommonModbusFunctions.read_input_registers"""

modbus_pdu = functions.read_input_registers(
starting_address=starting_addr,
quantity=register_qty)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=True)

register_value = functions.to_short(byte_array=response, signed=signed)

return register_value

async def write_single_coil(self,
slave_addr: int,
output_address: int,
output_value: Union[int, bool]) -> bool:
"""@see CommonModbusFunctions.write_single_coil"""

modbus_pdu = functions.write_single_coil(output_address=output_address,
output_value=output_value)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=False)

if response is None:
return False

operation_status = functions.validate_resp_data(
data=response,
function_code=Const.WRITE_SINGLE_COIL,
address=output_address,
value=output_value,
signed=False)

return operation_status

async def write_single_register(self,
slave_addr: int,
register_address: int,
register_value: int,
signed: bool = True) -> bool:
"""@see CommonModbusFunctions.write_single_register"""

modbus_pdu = functions.write_single_register(
register_address=register_address,
register_value=register_value,
signed=signed)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=False)

if response is None:
return False

operation_status = functions.validate_resp_data(
data=response,
function_code=Const.WRITE_SINGLE_REGISTER,
address=register_address,
value=register_value,
signed=signed)

return operation_status

async def write_multiple_coils(self,
slave_addr: int,
starting_address: int,
output_values: list) -> bool:
"""@see CommonModbusFunctions.write_multiple_coils"""

modbus_pdu = functions.write_multiple_coils(
starting_address=starting_address,
value_list=output_values)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=False)

if response is None:
return False

operation_status = functions.validate_resp_data(
data=response,
function_code=Const.WRITE_MULTIPLE_COILS,
address=starting_address,
quantity=len(output_values))

return operation_status

async def write_multiple_registers(self,
slave_addr: int,
starting_address: int,
register_values: List[int],
signed: bool = True) -> bool:
"""@see CommonModbusFunctions.write_multiple_registers"""

modbus_pdu = functions.write_multiple_registers(
starting_address=starting_address,
register_values=register_values,
signed=signed)

response = await self._send_receive(slave_addr=slave_addr,
modbus_pdu=modbus_pdu,
count=False)

if response is None:
return False

operation_status = functions.validate_resp_data(
data=response,
function_code=Const.WRITE_MULTIPLE_REGISTERS,
address=starting_address,
quantity=len(register_values),
signed=signed
)

return operation_status

async def _send_receive(self,
slave_addr: int,
modbus_pdu: bytes,
count: bool) -> bytes:
raise NotImplementedError("Must be overridden by subclass.")
56 changes: 56 additions & 0 deletions umodbus/asynchronous/modbus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

"""
Modbus register abstraction class

Used to add, remove, set and get values or states of a register or coil.
Additional helper properties and functions like getters for changed registers
are available as well.

This class is inherited by the Modbus client implementations
:py:class:`umodbus.serial.ModbusRTU` and :py:class:`umodbus.tcp.ModbusTCP`
"""

# system packages
from ..sys_imports import List, Optional, Union

# custom packages
from .common import AsyncRequest
from ..modbus import Modbus


class AsyncModbus(Modbus):
"""Modbus register abstraction."""

def __init__(self,
# in quotes because of circular import errors
itf: Union["AsyncTCPServer", "AsyncRTUServer"], # noqa: F821
addr_list: Optional[List[int]] = None):
super().__init__(itf, addr_list)
self._itf.set_params(addr_list=addr_list, req_handler=self.process)

async def process(self, request: Optional[AsyncRequest] = None) -> None:
"""@see Modbus.process"""

result = super().process(request)
if result is not None:
await result

async def _process_read_access(self,
request: AsyncRequest,
reg_type: str) -> None:
"""@see Modbus._process_read_access"""

task = super()._process_read_access(request, reg_type)
if task is not None:
await task

async def _process_write_access(self,
request: AsyncRequest,
reg_type: str) -> None:
"""@see Modbus._process_write_access"""

task = super()._process_write_access(request, reg_type)
if task is not None:
await task
Loading