forked from sfera-labs/exo-sense-py-modbus
-
Notifications
You must be signed in to change notification settings - Fork 51
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
GimmickNG
wants to merge
121
commits into
brainelectronics:develop
Choose a base branch
from
GimmickNG:mpmodbus-compatibility
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
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 d89130f
Refactor TCP and Modbus classes
GimmickNG f54f4de
Add asyncio support
GimmickNG 5a6b8bf
Merge branch 'develop' into mpmodbus-compatibility
GimmickNG 0642b8a
Rename async folder to asynchronous
GimmickNG bfd4e21
fix flake8 errors, add docstrings, fix async serial
GimmickNG 1fe6adb
Updated changelog to add #5 and #11
GimmickNG d867a47
Modify changelog - from code review
GimmickNG 8f11675
Make request parameter optional
GimmickNG 0cd100c
Make changes based on code review
GimmickNG 6c3632d
Update changelog to reflect reverted breaking change
GimmickNG a0ced88
Add async examples
GimmickNG d2e6d0a
Refactored serial/RTU to match TCP
GimmickNG 86021c6
Fix setup.py to include async package
GimmickNG 1b1dfde
Add header to async examples
GimmickNG 4353498
Fix unused import, incorrect formatting
GimmickNG 9427597
Fix flake8 formatting error
GimmickNG e7246e4
Merge branch 'brainelectronics:develop' into mpmodbus-compatibility
GimmickNG 26355df
Fix overload decorator
GimmickNG 114e448
Merge branch 'brainelectronics:develop' into mpmodbus-compatibility
GimmickNG 829ca17
Auto-connect TCP client in constructor
GimmickNG 7b3b49c
Add auto-connect note to async tcp client
GimmickNG f803842
Move async TCP examples to separate files
GimmickNG f66fdd8
Fix examples and async tcp server
GimmickNG 50b9bb3
Delete pycache files
GimmickNG 05ebce4
Delete pycache files
GimmickNG c2ab8a2
Remove duplicate function
GimmickNG 2b538f8
Fix trailing whitespace
GimmickNG d0614db
Refactor, add async RTU and multi-server examples
GimmickNG 17647cf
Delete async_examples.py
GimmickNG cf44c22
Remove redundant common files
GimmickNG c2c9dd6
do not use relative imports in examples
GimmickNG 6f557cc
use fully qualified names for common
GimmickNG 8fad878
Make changes by @beyonlo, fix RTU set_params
GimmickNG 85486d0
fix flake8 error
GimmickNG b8a712d
Updated async serial server + examples
GimmickNG deed1b5
modify callback traces to help in debugging
GimmickNG 2cd997d
revert change to _process_read_access
GimmickNG d098627
add exit function
GimmickNG 6ef64c9
fix flake8 error
GimmickNG 0fb6951
fix broken import
GimmickNG 385aca4
use registers in example.json for tests
GimmickNG 8a74346
flake8 ignore redefinition
GimmickNG 8e55144
change gitignore
GimmickNG e4acfd6
use req_handler for process() in async RTU
GimmickNG d36a8c3
fix async serial host example
GimmickNG da4cc3c
add debug logging statements
GimmickNG 22196af
debugging: narrowing logging
GimmickNG eebb5a5
debug serial.py
GimmickNG c10f592
debug: print entire request
GimmickNG f7c503d
debug: narrow logging statements
GimmickNG ba4b299
Fix index-out-of-bounds issue when reading slave response
wpyoga 547d0a8
Simplify calculation of inter-frame delay
wpyoga 06aeda0
Fix receive long slave response by waiting longer
wpyoga c7cd786
Fix receive missing initial bytes by blocking instead of polling
wpyoga e4bdeea
replace machine idle with time sleep_us for MicroPython below v1.20.0…
brainelectronics c0de0bd
update changelog
brainelectronics b5416a9
remove flush function from machine UART fake
brainelectronics b9a3901
Merge pull request #75 from wpyoga/fix-timing
brainelectronics 1858fae
add missing empty line in several files
brainelectronics 45b5492
validate package.json content on each test workflow run
brainelectronics 9217dae
add precommit hook, contributes to #67
brainelectronics 8d1bec0
Use sync read
GimmickNG 96a3064
add basic contribution guideline, see #67
brainelectronics 0424460
validate package.json and package version file before running all tests
brainelectronics 2646996
update changelog, package version and package.json
brainelectronics 15d90a0
replace upip ulogging installation with ulogging file in tests folder
brainelectronics 481efde
Merge pull request #78 from brainelectronics/feature/improve-contribu…
brainelectronics 9fb326c
Add single char wait time after flush to avoid RTU control pin timing…
brainelectronics e9ab05a
Merge pull request #79 from brainelectronics/bugfix/yet-another-ctrl-…
brainelectronics c0a5467
sleep if no data available
GimmickNG 14f0ca1
#56: add asyncio support
GimmickNG 8816948
#56: add asyncio tests, refactor sync tests
GimmickNG 3ca7759
Merge remote-tracking branch 'origin/mpmodbus-compatibility' into asy…
GimmickNG 9d76ee9
fix broken imports
GimmickNG 37de00f
debugging: reraise on exception for rtu example
GimmickNG da02956
fix local variable reference error
GimmickNG ec4b728
return request after process
GimmickNG 9b6b3f7
update async serial send to match sync version
GimmickNG 36df0ac
use time.sleep_us where applicable
GimmickNG ddcd134
add hybrid sleep
GimmickNG 889096f
change import name and type
GimmickNG 2cb2dea
add timeout option for rtu client
GimmickNG afb0ffd
add read timeout param for sync rtu, move to common
GimmickNG 7f153fa
change delay, _uart_read_timeout to be in ms
GimmickNG 85da811
repeat tests when timeout occurs
GimmickNG b5fad0a
add async server restart example
GimmickNG 353260e
fix error calling create_servers
GimmickNG 8ff6326
add logging to pack and unpack
GimmickNG fc186b4
fix struct pack error
GimmickNG c652e05
add extra/custom args for testing
GimmickNG c25c1a9
use await for uart streamwriter
GimmickNG cab9132
test using sync write for uart
GimmickNG b30f90b
revert safe_struct, refactor common functions
GimmickNG 392e82e
refactor examples to use pipeline
GimmickNG 1517427
add on_pre_set_cb callback
GimmickNG 1a6e95c
#69: add write beyond limit test + cleanup tests
GimmickNG 4f6806d
fix formatting, add debug logs, add client_connect todo
GimmickNG 813d8a6
add more logging
GimmickNG b5dfbf7
add more logging
GimmickNG 46e3929
add more logging
GimmickNG 327907b
fix MRO for async classes
GimmickNG 5e91a6d
fix MRO for AsyncRTUServer
GimmickNG 4079fe5
inherit from sync version instead of being mixin
GimmickNG 6759430
add on_tcp_connect and on_tcp_disconnect callbacks
GimmickNG e891a2e
add example which updates registers in background
GimmickNG fd694b5
Improve uart_read_frame (#5)
hmaerki ad38bb7
fix on_connect_cb and on_disconnect_cb for async
GimmickNG a7d6517
change t1char_ms timing in async rtu
GimmickNG e29ca16
add inet_ntop compatibility function
GimmickNG 2cbe3cf
remove unused print statements
GimmickNG 77ec202
add type parameter to inet_ntop
GimmickNG fb5911a
add reference
GimmickNG dfa3062
update example for multi-valued registers
GimmickNG bce10d3
fix flake8 errors
GimmickNG 7ea5124
fix more flake8 errors
GimmickNG a77db08
change multi client examples to share registers
GimmickNG 2b4a1d3
fix flake8 errors
GimmickNG d79f8e9
fix imports, rtu server init errors
GimmickNG 6c15435
revert changes to examples
GimmickNG a62dbe0
increase version, add changelog notes, address review comments
GimmickNG File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
GimmickNG marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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, | ||
GimmickNG marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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.") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.