-
Notifications
You must be signed in to change notification settings - Fork 949
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TLS feature for Modbus asynchronous (#470)
* Add TLS feature for Modbus asynchronous client Since we have Modbus TLS client in synchronous mode, we can also implement Modbus TLS client in asynchronous mode with ASYNC_IO. * Add TLS feature for Modbus asynchronous server Since we have Modbus TLS server in synchronous mode, we can also implement Modbus TLS server in asynchronous mode with ASYNC_IO.
- Loading branch information
Showing
8 changed files
with
464 additions
and
4 deletions.
There are no files selected for viewing
This file contains 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
40 changes: 40 additions & 0 deletions
40
examples/contrib/asynchronous_asyncio_modbus_tls_client.py
This file contains 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,40 @@ | ||
#!/usr/bin/env python | ||
""" | ||
Simple Asynchronous Modbus TCP over TLS client | ||
--------------------------------------------------------------------------- | ||
This is a simple example of writing a asynchronous modbus TCP over TLS client | ||
that uses Python builtin module ssl - TLS/SSL wrapper for socket objects for | ||
the TLS feature and asyncio. | ||
""" | ||
# -------------------------------------------------------------------------- # | ||
# import neccessary libraries | ||
# -------------------------------------------------------------------------- # | ||
import ssl | ||
from pymodbus.client.asynchronous.tls import AsyncModbusTLSClient | ||
from pymodbus.client.asynchronous.schedulers import ASYNC_IO | ||
|
||
# -------------------------------------------------------------------------- # | ||
# the TLS detail security can be set in SSLContext which is the context here | ||
# -------------------------------------------------------------------------- # | ||
context = ssl.create_default_context() | ||
context.options |= ssl.OP_NO_SSLv2 | ||
context.options |= ssl.OP_NO_SSLv3 | ||
context.options |= ssl.OP_NO_TLSv1 | ||
context.options |= ssl.OP_NO_TLSv1_1 | ||
|
||
async def start_async_test(client): | ||
result = await client.read_coils(1, 8) | ||
print(result.bits) | ||
await client.write_coils(1, [False]*3) | ||
result = await client.read_coils(1, 8) | ||
print(result.bits) | ||
|
||
if __name__ == '__main__': | ||
# -------------------------------------------------------------------------- # | ||
# pass SSLContext which is the context here to ModbusTcpClient() | ||
# -------------------------------------------------------------------------- # | ||
loop, client = AsyncModbusTLSClient(ASYNC_IO, 'test.host.com', 8020, | ||
sslctx=context) | ||
loop.run_until_complete(start_async_test(client.protocol)) | ||
loop.close() |
This file contains 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 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,60 @@ | ||
""" | ||
Factory to create asynchronous tls clients based on asyncio | ||
""" | ||
from __future__ import unicode_literals | ||
from __future__ import absolute_import | ||
|
||
import logging | ||
|
||
from pymodbus.client.asynchronous import schedulers | ||
from pymodbus.client.asynchronous.thread import EventLoopThread | ||
from pymodbus.constants import Defaults | ||
|
||
LOGGER = logging.getLogger(__name__) | ||
|
||
def async_io_factory(host="127.0.0.1", port=Defaults.TLSPort, sslctx=None, | ||
server_hostname=None, framer=None, source_address=None, | ||
timeout=None, **kwargs): | ||
""" | ||
Factory to create asyncio based asynchronous tls clients | ||
:param host: Host IP address | ||
:param port: Port | ||
:param sslctx: The SSLContext to use for TLS (default None and auto create) | ||
:param server_hostname: Target server's name matched for certificate | ||
:param framer: Modbus Framer | ||
:param source_address: Bind address | ||
:param timeout: Timeout in seconds | ||
:param kwargs: | ||
:return: asyncio event loop and tcp client | ||
""" | ||
import asyncio | ||
from pymodbus.client.asynchronous.asyncio import init_tls_client | ||
loop = kwargs.get("loop") or asyncio.new_event_loop() | ||
proto_cls = kwargs.get("proto_cls", None) | ||
if not loop.is_running(): | ||
asyncio.set_event_loop(loop) | ||
cor = init_tls_client(proto_cls, loop, host, port, sslctx, server_hostname, | ||
framer) | ||
client = loop.run_until_complete(asyncio.gather(cor))[0] | ||
else: | ||
cor = init_tls_client(proto_cls, loop, host, port, sslctx, server_hostname, | ||
framer) | ||
future = asyncio.run_coroutine_threadsafe(cor, loop=loop) | ||
client = future.result() | ||
|
||
return loop, client | ||
|
||
|
||
def get_factory(scheduler): | ||
""" | ||
Gets protocol factory based on the backend scheduler being used | ||
:param scheduler: ASYNC_IO | ||
:return | ||
""" | ||
if scheduler == schedulers.ASYNC_IO: | ||
return async_io_factory | ||
else: | ||
LOGGER.warning("Allowed Schedulers: {}".format( | ||
schedulers.ASYNC_IO | ||
)) | ||
raise Exception("Invalid Scheduler '{}'".format(scheduler)) |
This file contains 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,52 @@ | ||
from __future__ import unicode_literals | ||
from __future__ import absolute_import | ||
|
||
import logging | ||
from pymodbus.client.asynchronous.factory.tls import get_factory | ||
from pymodbus.constants import Defaults | ||
from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION | ||
from pymodbus.client.asynchronous.schedulers import ASYNC_IO | ||
from pymodbus.factory import ClientDecoder | ||
from pymodbus.transaction import ModbusTlsFramer | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class AsyncModbusTLSClient(object): | ||
""" | ||
Actual Async TLS Client to be used. | ||
To use do:: | ||
from pymodbus.client.asynchronous.tls import AsyncModbusTLSClient | ||
""" | ||
def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.TLSPort, | ||
framer=None, sslctx=None, server_hostname=None, | ||
source_address=None, timeout=None, **kwargs): | ||
""" | ||
Scheduler to use: | ||
- async_io (asyncio) | ||
:param scheduler: Backend to use | ||
:param host: Host IP address | ||
:param port: Port | ||
:param framer: Modbus Framer to use | ||
:param sslctx: The SSLContext to use for TLS (default None and auto create) | ||
:param server_hostname: Target server's name matched for certificate | ||
:param source_address: source address specific to underlying backend | ||
:param timeout: Time out in seconds | ||
:param kwargs: Other extra args specific to Backend being used | ||
:return: | ||
""" | ||
if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) | ||
and scheduler == ASYNC_IO): | ||
logger.critical("ASYNCIO is supported only on python3") | ||
import sys | ||
sys.exit(1) | ||
framer = framer or ModbusTlsFramer(ClientDecoder()) | ||
factory_class = get_factory(scheduler) | ||
yieldable = factory_class(host=host, port=port, sslctx=sslctx, | ||
server_hostname=server_hostname, | ||
framer=framer, source_address=source_address, | ||
timeout=timeout, **kwargs) | ||
return yieldable | ||
|
Oops, something went wrong.