Pymodbus Readthedocs Io en Stable
Pymodbus Readthedocs Io en Stable
Release 3.0.2
Sanjay
2 CHANGELOGS 9
2.1 version 3.0.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 version 3.0.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 version 3.0.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.4 version 3.0.0dev5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.5 version 3.0.0dev4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.6 version 3.0.0dev3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.7 version 3.0.0dev2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.8 version 3.0.0dev1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.9 version 3.0.0dev0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.10 version 2.5.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.11 version 2.5.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.12 version 2.5.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.13 version 2.5.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.14 version 2.5.0rc3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.15 version 2.5.0rc2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.16 version 2.5.0rc1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.17 Version 2.4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.18 Version 2.3.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.19 Version 2.3.0rc1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.20 Version 2.2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.21 Version 2.1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.22 Version 2.0.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.23 Version 2.0.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.24 Version 2.0.0rc1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
i
2.25 Version 1.5.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.26 Version 1.5.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.27 Version 1.5.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.28 Version 1.4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.29 Version 1.3.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.30 Version 1.3.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.31 Version 1.3.0.rc2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.32 Version 1.3.0.rc1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.33 Version 1.2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.34 Version 1.1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.35 Version 1.0.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.36 Version 0.9.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3 Pymodbus REPL 21
3.1 Dependencies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Usage Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3 DEMO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4 Examples. 29
4.1 Examples. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2 Examples version 2.5.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
4.3 Examples contributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
5 Pymodbus 141
5.1 pymodbus package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
Index 267
ii
CHAPTER
ONE
Version 2.5.3 is the last 2.x release (Supports python 2.7.x - 3.7).
Version 3.0.2 is the current release (Supports Python >=3.8).
Remark: “Supports” means that we only test with those versions, lower versions (e.g. 3.7) might work depending on
the functionality used.
1.2 Summary
Pymodbus is a full Modbus protocol implementation using a synchronous or asynchronous (using asyncio) core.
Supported modbus communication modes: tcp, rtu-over-tcp, udp, serial, tls
Pymodbus can be used without any third party dependencies (aside from pyserial) and is a very lightweight project.
Pymodbus also provides a lot of ready to use examples as well as a server/client simulator which can be controlled via
a REST API and can be easily integrated into test suites.
Requires Python >= 3.8
The tests are run against Python 3.8, 3.9, 3.10 on Windows, Linux and MacOS.
1
PyModbus Documentation, Release 3.0.2
1.3 Features
Use Cases
Although most system administrators will find little need for a Modbus server on any modern hardware, they may find
the need to query devices on their network for status (PDU, PDR, UPS, etc). Since the library is written in python, it
allows for easy scripting and/or integration into their existing solutions.
Continuing, most monitoring software needs to be stress tested against hundreds or even thousands of devices (why
this was originally written), but getting access to that many is unwieldy at best.
The pymodbus server will allow a user to test as many devices as their base operating system will allow (allow in this
case means how many Virtual IP addresses are allowed).
For more information please browse the project documentation:
https://riptideio.github.io/pymodbus/ or https://readthedocs.org/docs/pymodbus/en/latest/index.html
For those of you that just want to get started fast, here you go:
client = ModbusTcpClient('127.0.0.1')
client.write_coil(1, True)
result = client.read_coils(1,1)
print(result.bits[0])
client.close()
For more advanced examples, check out the Examples included in the repository. If you have created any utilities that
meet a specific need, feel free to submit them so others can benefit.
examples -> Essential examples guaranteed to work (tested with our CI)
v2.5.3 -> Examples not updated to version 3.0.0.
contrib -> Examples contributed by contributors.
Also, if you have a question, please create a post in discussions q&a topic, so that others can benefit from the results.
If you think, that something in the code is broken/not running well, please open an issue, read the Template-text first
and then post your issue with your setup information.
Pymodbus REPL comes with many handy features such as payload decoder to directly retrieve the values in desired
format and supports all the diagnostic function codes directly .
For more info on REPL Client refer Pymodbus REPL Client
Pymodbus also comes with a REPL server to quickly run an asynchronous server with additional capabilities out of the
box like simulating errors, delay, mangled messages etc.
For more info on REPL Server refer Pymodbus REPL Server
1.6 Installing
You can install using pip or easy install by issuing the following commands in a terminal window (make sure you have
correct permissions or a virtualenv currently running):
pip install -U pymodbus
This will install a base version of pymodbus.
To install pymodbus with options run:
pip install -U pymodbus[<option>,. . . ]
Available options are:
• repl, installs pymodbus REPL.
• serial, installs serial drivers.
• datastore, installs databases (SQLAlchemy and Redis) for datastore.
• documentation, installs tools to generate documentation.
• development, installs development tools needed to enable test/check of pymodbus changes.
Or to install a specific release:
pip install -U pymodbus==X.Y.Z
You can also use Docker to run a local image with the package installed on the image:
docker pull riptideio/pymodbus
To run this, you will need to expose ports 8080 and 5020, you can the container running:
docker run -it -p 8080:8080 -p 5020:502 riptideio/pymodbus
You can also override the default command running the server with any of the examples:
docker run -it -p 8080:8080 -p 5020:502 riptideio/pymodbus examples/server_sync.py
Otherwise you can pull the trunk source and install from there:
Before cloning the repo, you need to install python3 (preferable 3.10) and make a virtual environment:
source .venv/bin/activate
To get latest release (for now v3.0.0 with Python 3.8 support):
If you would like to use this image as part of a docker compose project, you can provide a custom command. For
example, you can spin the server by creating a docker compose file containing the following:
pymodbus-server:
container_name: pymodbus-server
image: riptideio/pymodbus:X.Y.Z
command: ["./examples/server_sync.py"]
After running docker compose up, you should have running the server_sync.py example, ready to accept connections
from a client. You can, of course, add a custom script instead, to run your own logic instead.
The maintenance team is very small with limited capacity and few modbus devices.
However, if you would like your device tested, we accept devices via mail or by IP address.
That said, the current work mainly involves polishing the library and solving issues:
• Fixing bugs/feature requests
• Architecture documentation
• Functional testing against any reference we can find
• The remaining edges of the protocol (that we think no one uses)
The current code base is compatible python >= 3.8. Here are some of the common commands to perform a range of
activities
pip install -r requirements.txt install all requirements
pip install -e . source directory is “release”, useful for testing
tox -e py38 (or py39, py310, pypy38) Run pytest on source code
tox -e pylint Run pylint on source code
tox -e codespell Run codespell on source code
tox -e bandit Run bandit on source code
tox -e flake8 Run flake8 on source code
tox -e black Run black on source code
1.12 Contributing
Just fork the repo and raise your PR against dev branch.
Here are some of the items waiting to be done:
https://github.com/riptideio/pymodbus/blob/dev/doc/TODO
TWO
CHANGELOGS
9
PyModbus Documentation, Release 3.0.2
• Faulty release!
• Remove server sync code and combine with async code. (#1092)
• Solve test of tls by adding certificates and remove bugs (#1080)
• Simplify server implementation. (#1071)
• Do not filter using unit id in the received response (#1076)
• Hex values for repl arguments (#1075)
• All parameters in class parameter. (#1070)
• Add len parameter to decode_bits. (#1062)
• New combined test for all types of clients. (#1061)
• Dev mixin client (#1056)
• Add/update client documentation, including docstrings etc. (#1055)
• Add unit to arguments (#1041)
• Add timeout to all pytest. (#1037)
• Simplify client parent classes. (#1018)
• Clean copyright statements, to ensure we follow FOSS rules. (#1014)
• Rectify sync/async client parameters. (#1013)
• Clean client directory structure for async. (#1010)
• Remove async_io, simplify AsyncModbus<x>Client. (#1009)
10 Chapter 2. CHANGELOGS
PyModbus Documentation, Release 3.0.2
• Documentation updates
• PEP8 compatibale code
• More tooling and CI updates
• Support python3.10
• Implement asyncio ModbusSerialServer
• ModbusTLS updates (tls handshake, default framer)
• Support broadcast messages with asyncio client
• Fix for lazy loading serial module with asyncio clients.
• Updated examples and tests
12 Chapter 2. CHANGELOGS
PyModbus Documentation, Release 3.0.2
• Add kwarg reset_socket to control closing of the socket on read failures (set to True by default).
• Add –reset-socket/–no-reset-socket to REPL client.
• Documentation updates
• Disable strict mode by default.
• Fix ReportSlaveIdRequest request
• Sparse datablock initialization updates.
14 Chapter 2. CHANGELOGS
PyModbus Documentation, Release 3.0.2
• Fix tornado async serial client TypeError while processing incoming packet.
• Fix erroneous CRC handling in Modbus RTU framer.
• Support broadcasting in Modbus Client and Servers (sync).
• Fix asyncio examples.
• Improved logging in Modbus Server .
• ReportSlaveIdRequest would fetch information from Device identity instead of hardcoded Pymodbus.
• Fix regression introduced in 2.2.0rc2 (Modbus sync client transaction failing)
• Minor update in factory.py, now server logs prints received request instead of only function code
# Now
# DEBUG:pymodbus.factory:Factory Request[ReadInputRegistersRequest: 4]
# Before
# DEBUG:pymodbus.factory:Factory Request[4]
• Fix Issues with Serial client where in partial data was read when the response size is unknown.
• Fix Infinite sleep loop in RTU Framer.
• Add pygments as extra requirement for repl.
• Add support to modify modbus client attributes via repl.
• Update modbus repl documentation.
• More verbose logs for repl.
Note This is a Major release and might affect your existing Async client implementation. Refer examples on how
to use the latest async clients.
• Async client implementation based on Tornado, Twisted and asyncio with backward compatibility support for
twisted client.
• Allow reusing existing[running] asyncio loop when creating async client based on asyncio.
• Allow reusing address for Modbus TCP sync server.
• Add support to install tornado as extra requirement while installing pymodbus.
• Support Pymodbus REPL
• Add support to python 3.7.
16 Chapter 2. CHANGELOGS
PyModbus Documentation, Release 3.0.2
Note This is a Major release and might affect your existing Async client implementation. Refer examples on how
to use the latest async clients.
• Async client implementation based on Tornado, Twisted and asyncio
res = client.read_holding_registers(...)
if not res.isError():
# proceed
else:
# handle error or raise
"""
• Improve transaction speeds for sync clients (RTU/ASCII), now retry on empty happens only when
retry_on_empty kwarg is passed to client during intialization
client = Client(. . . , retry_on_empty=True)
• Fix tcp servers (sync/async) not processing requests with transaction id > 255
• Introduce new api to check if the received response is an error or not (response.isError())
• Move timing logic to framers so that irrespective of client, correct timing logics are followed.
18 Chapter 2. CHANGELOGS
PyModbus Documentation, Release 3.0.2
• Reworking the transaction managers to be more explicit and to handle modbus RTU over TCP.
• Adding examples for a number of unique requested use cases
• Allow RTU framers to fail fast instead of staying at fault
• Working on datastore saving and loading
• Adding support for payload builders to form complex encoding and decoding of messages.
• Adding BCD and binary payload builders
• Adding support for pydev
• Cleaning up the build tools
• Adding a message encoding generator for testing.
• Now passing kwargs to base of PDU so arguments can be used correctly at all levels of the protocol.
• A number of bug fixes (see bug tracker and commit messages)
20 Chapter 2. CHANGELOGS
CHAPTER
THREE
PYMODBUS REPL
3.1 Dependencies
bash-3.2$ pymodbus.console
Usage: pymodbus.console [OPTIONS] COMMAND [ARGS]...
Options:
--version Show the version and exit.
--verbose Verbose logs
--support-diag Support Diagnostic messages
--help Show this message and exit.
Commands:
serial
tcp
TCP Options
Options:
--host TEXT Modbus TCP IP
--port INTEGER Modbus TCP port
--help Show this message and exit.
21
PyModbus Documentation, Release 3.0.2
SERIAL Options
Options:
--method TEXT Modbus Serial Mode (rtu/ascii)
--port TEXT Modbus RTU port
--baudrate INTEGER Modbus RTU serial baudrate to use. Defaults to 9600
--bytesize [5|6|7|8] Modbus RTU serial Number of data bits. Possible
values: FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS.
Defaults to 8
--parity [N|E|O|M|S] Modbus RTU serial parity. Enable parity checking.
Possible values: PARITY_NONE, PARITY_EVEN, PARITY_ODD
PARITY_MARK, PARITY_SPACE. Default to 'N'
--stopbits [1|1.5|2] Modbus RTU serial stop bits. Number of stop bits.
Possible values: STOPBITS_ONE,
STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to '1'
--xonxoff INTEGER Modbus RTU serial xonxoff. Enable software flow
control.Defaults to 0
--rtscts INTEGER Modbus RTU serial rtscts. Enable hardware (RTS/CTS)
flow control. Defaults to 0
--dsrdtr INTEGER Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR)
flow control. Defaults to 0
--timeout FLOAT Modbus RTU serial read timeout. Defaults to 0.025 sec
--write-timeout FLOAT Modbus RTU serial write timeout. Defaults to 2 sec
--help Show this message and exit.
> help
Available commands:
client.change_ascii_input_delimiter Diagnostic sub command, Change message␣
˓→delimiter for future requests.
SERIAL
Every command has auto suggestion on the arguments supported, arg and value are to be supplied in arg=val format.
> result.raw
{
"registers": [
15626,
55203,
28733,
18368
]
}
For Holding and Input register reads, the decoded value could be viewed with result.decode
>
> client.get_serial_settings
{
"t1.5": 0.00171875,
"baudrate": 9600,
"read timeout": 0.5,
"port": "/dev/ptyp0",
"t3.5": 0.00401,
"bytesize": 8,
"parity": "N",
"stopbits": 1.0
}
> client.set_timeout value=1
(continues on next page)
> client.get_timeout
1.0
> client.get_serial_settings
{
"t1.5": 0.00171875,
"baudrate": 9600,
"read timeout": 1.0,
"port": "/dev/ptyp0",
"t3.5": 0.00401,
"bytesize": 8,
"parity": "N",
"stopbits": 1.0
}
3.3 DEMO
3.3. DEMO 27
PyModbus Documentation, Release 3.0.2
FOUR
EXAMPLES.
4.1 Examples.
#!/usr/bin/env python3
"""Pymodbus Aynchronous Client Example.
29
PyModbus Documentation, Release 3.0.2
# --------------------------------------------------------------------------- #
# import the various client implementations
# --------------------------------------------------------------------------- #
from examples.helper import get_commandline
from pymodbus.client import (
AsyncModbusSerialClient,
AsyncModbusTcpClient,
AsyncModbusTlsClient,
AsyncModbusUdpClient,
)
_logger = logging.getLogger()
def setup_async_client(args):
"""Run client setup."""
_logger.info("### Create client object")
if args.comm == "tcp":
client = AsyncModbusTcpClient(
"127.0.0.1",
port=args.port, # on which port
# Common optional paramers:
framer=args.framer,
# timeout=10,
# retries=3,
# retry_on_empty=False,
# close_comm_on_error=False,
# strict=True,
# TCP setup parameters
# source_address=("localhost", 0),
)
elif args.comm == "udp":
client = AsyncModbusUdpClient(
"127.0.0.1",
port=args.port,
# Common optional paramers:
framer=args.framer,
# timeout=10,
# retries=3,
# retry_on_empty=False,
# close_comm_on_error=False,
(continues on next page)
30 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
4.1. Examples. 31
PyModbus Documentation, Release 3.0.2
if __name__ == "__main__":
cmd_args = get_commandline(
server=False,
description="Run asynchronous client.",
)
testclient = setup_async_client(cmd_args)
asyncio.run(run_async_client(testclient), debug=True)
#!/usr/bin/env python3
"""Pymodbus Client modbus call examples.
Please see:
async_template_call
template_call
for a template on how to make modbus calls and check for different
error conditions.
The _handle_.... functions each handle a set of modbus calls with the
same register type (e.g. coils).
All available modbus calls are present. The difference between async
and sync is a single 'await' so the calls are not repeated.
request = ClearCountersRequest()
response = client.execute(request)
if isinstance(response, ClearCountersResponse):
... do something with the response
32 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
./server_async.py
"""
import asyncio
import logging
_logger = logging.getLogger()
SLAVE = 0x01
# --------------------------------------------------
# Template on how to make modbus calls (sync/async).
# all calls follow the same schema,
# --------------------------------------------------
# Validate data
txt = f"### Template coils response: {str(rr.bits)}"
_logger.debug(txt)
4.1. Examples. 33
PyModbus Documentation, Release 3.0.2
# Validate data
txt = f"### Template coils response: {str(rr.bits)}"
_logger.debug(txt)
# -------------------------------------------------
# Generic error handling, to avoid duplicating code
# -------------------------------------------------
def _check_call(rr):
"""Check modbus call worked generically."""
assert not rr.isError() # test that call was OK
assert not isinstance(rr, ExceptionResponse) # Device rejected request
return rr
# ------------------------------------------------------
# Call modbus device (all possible calls are presented).
# ------------------------------------------------------
async def _handle_coils(client):
"""Read/Write coils."""
_logger.info("### Reading Coil different number of bits (return 8 bits multiples)")
rr = _check_call(await client.read_coils(1, 1, slave=SLAVE))
assert len(rr.bits) == 8
34 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
4.1. Examples. 35
PyModbus Documentation, Release 3.0.2
rr = _check_call(await client.execute(req_other.ReportSlaveIdRequest(unit=SLAVE)))
assert rr.status
rr = _check_call(
await client.execute(req_other.ReadExceptionStatusRequest(unit=SLAVE))
)
assert not rr.status
rr = _check_call(
await client.execute(req_other.GetCommEventCounterRequest(unit=SLAVE))
)
assert rr.status and not rr.count
rr = _check_call(await client.execute(req_other.GetCommEventLogRequest(unit=SLAVE)))
assert rr.status and not (rr.event_count + rr.message_count + len(rr.events))
_check_call(
await client.execute(req_diag.RestartCommunicationsOptionRequest(unit=SLAVE))
)
_check_call(
await client.execute(req_diag.ReturnDiagnosticRegisterRequest(unit=SLAVE))
)
_check_call(
await client.execute(req_diag.ChangeAsciiInputDelimiterRequest(unit=SLAVE))
)
_check_call(await client.execute(req_diag.ClearCountersRequest()))
(continues on next page)
36 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ------------------------
# Run the calls in groups.
# ------------------------
def run_sync_calls(client):
"""Demonstrate basic read/write calls."""
template_call(client)
if __name__ == "__main__":
cmd_args = get_commandline(
server=False,
description="Run modbus calls in asynchronous client.",
(continues on next page)
4.1. Examples. 37
PyModbus Documentation, Release 3.0.2
#!/usr/bin/env python3
"""Pymodbus Client Payload Example.
_logger = logging.getLogger()
ORDER_DICT = {"<": "LITTLE", ">": "BIG"}
# ----------------------------------------------------------------------- #
# We are going to use a simple sync client to send our requests
# ----------------------------------------------------------------------- #
client = AsyncModbusTcpClient("127.0.0.1", port=port)
await client.connect()
# ----------------------------------------------------------------------- #
# If you need to build a complex message to send, you can use the payload
# builder to simplify the packing logic
#
# Packing/unpacking depends on your CPU´s word/byte order. Modbus messages
# are always using big endian. BinaryPayloadBuilder will pr default use
# what your CPU uses.
# The wordorder is applicable only for 32 and 64 bit values
(continues on next page)
38 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
for word_endian, byte_endian in (
(Endian.Big, Endian.Big),
(Endian.Big, Endian.Little),
(Endian.Little, Endian.Big),
(Endian.Little, Endian.Little),
):
print("-" * 60)
print(f"Word Order: {ORDER_DICT[word_endian]}")
print(f"Byte Order: {ORDER_DICT[byte_endian]}")
print()
builder = BinaryPayloadBuilder(
wordorder=word_endian,
byteorder=byte_endian,
)
# Normally just do: builder = BinaryPayloadBuilder()
my_string = "abcdefgh"
builder.add_string(my_string)
builder.add_bits([0, 1, 0, 1, 1, 0, 1, 0])
builder.add_8bit_int(-0x12)
builder.add_8bit_uint(0x12)
builder.add_16bit_int(-0x5678)
builder.add_16bit_uint(0x1234)
builder.add_32bit_int(-0x1234)
builder.add_32bit_uint(0x12345678)
builder.add_16bit_float(12.34)
builder.add_16bit_float(-12.34)
builder.add_32bit_float(22.34)
builder.add_32bit_float(-22.34)
builder.add_64bit_int(-0xDEADBEEF)
builder.add_64bit_uint(0x12345678DEADBEEF)
builder.add_64bit_uint(0x12345678DEADBEEF)
builder.add_64bit_float(123.45)
builder.add_64bit_float(-123.45)
registers = builder.to_registers()
print("Writing Registers:")
print(registers)
print("\n")
payload = builder.build()
address = 0
slave = 1
(continues on next page)
4.1. Examples. 39
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
# If you need to decode a collection of registers in a weird layout, the
# payload decoder can help you as well.
# ----------------------------------------------------------------------- #
print("Reading Registers:")
count = len(payload)
rr = await client.read_holding_registers(address, count, slave=slave)
assert not rr.isError()
print(rr.registers)
print("\n")
decoder = BinaryPayloadDecoder.fromRegisters(
rr.registers, byteorder=byte_endian, wordorder=word_endian
)
# Make sure word/byte order is consistent between BinaryPayloadBuilder and␣
˓→BinaryPayloadDecoder
assert (
decoder._byteorder == builder._byteorder # pylint: disable=protected-access
) # nosec
assert (
decoder._wordorder == builder._wordorder # pylint: disable=protected-access
) # nosec
decoded = OrderedDict(
[
("string", decoder.decode_string(len(my_string))),
("bits", decoder.decode_bits()),
("8int", decoder.decode_8bit_int()),
("8uint", decoder.decode_8bit_uint()),
("16int", decoder.decode_16bit_int()),
("16uint", decoder.decode_16bit_uint()),
("32int", decoder.decode_32bit_int()),
("32uint", decoder.decode_32bit_uint()),
("16float", decoder.decode_16bit_float()),
("16float2", decoder.decode_16bit_float()),
("32float", decoder.decode_32bit_float()),
("32float2", decoder.decode_32bit_float()),
("64int", decoder.decode_64bit_int()),
("64uint", decoder.decode_64bit_uint()),
("ignore", decoder.skip_bytes(8)),
("64float", decoder.decode_64bit_float()),
("64float2", decoder.decode_64bit_float()),
]
)
print("Decoded Data")
for name, value in iter(decoded.items()):
(continues on next page)
40 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
# close the client
# ----------------------------------------------------------------------- #
await client.close()
if __name__ == "__main__":
asyncio.run(run_binary_payload_client(5020))
#!/usr/bin/env python3
"""Pymodbus Synchronous Client Example.
# --------------------------------------------------------------------------- #
# import the various client implementations
# --------------------------------------------------------------------------- #
from examples.helper import get_commandline
from pymodbus.client import (
ModbusSerialClient,
ModbusTcpClient,
ModbusTlsClient,
ModbusUdpClient,
)
4.1. Examples. 41
PyModbus Documentation, Release 3.0.2
_logger = logging.getLogger()
def setup_sync_client(args):
"""Run client setup."""
_logger.info("### Create client object")
if args.comm == "tcp":
client = ModbusTcpClient(
"127.0.0.1",
port=args.port,
# Common optional paramers:
framer=args.framer,
# timeout=10,
# retries=3,
# retry_on_empty=False,y
# close_comm_on_error=False,
# strict=True,
# TCP setup parameters
# source_address=("localhost", 0),
)
elif args.comm == "udp":
client = ModbusUdpClient(
"localhost",
port=args.port,
# Common optional paramers:
framer=args.framer,
# timeout=10,
# retries=3,
# retry_on_empty=False,
# close_comm_on_error=False,
# strict=True,
# UDP setup parameters
# source_address=None,
)
elif args.comm == "serial":
client = ModbusSerialClient(
port=args.port, # serial port
# Common optional paramers:
# framer=ModbusRtuFramer,
# timeout=10,
# retries=3,
# retry_on_empty=False,
# close_comm_on_error=False,.
# strict=True,
# Serial setup parameters
# baudrate=9600,
# bytesize=8,
# parity="N",
# stopbits=1,
# handle_local_echo=False,
)
(continues on next page)
42 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
if __name__ == "__main__":
cmd_args = get_commandline(
server=False,
description="Run synchronous client.",
)
testclient = setup_sync_client(cmd_args)
run_sync_client(testclient)
4.1. Examples. 43
PyModbus Documentation, Release 3.0.2
#!/usr/bin/env python3
"""Pymodbus synchronous forwarder.
This is a repeater or converter and an example of just how powerful datastore is.
Both server and client are tcp based, but it can be easily modified to any server/client
(see client_sync.py and server_sync.py for other communication types)
"""
import asyncio
import logging
_logger = logging.getLogger()
def setup_forwarder(args):
"""Do setup forwarder."""
args.client = ModbusTcpClient(
host="localhost",
port=args.client_port,
)
44 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
if __name__ == "__main__":
cmd_args = get_commandline(
server=True,
description="Run asynchronous forwarder.",
extras=[
(
"--client_port",
{
"help": "the port to use",
"type": int,
},
)
],
)
cmd_args.port = 5021
cmd_args.client_port = 5020
run_args = setup_forwarder(cmd_args)
asyncio.run(run_forwarder(run_args))
#!/usr/bin/env python3
"""Pymodbus asynchronous Server Example.
options:
-h, --help show this help message and exit
--comm {tcp,udp,serial,tls}
"serial", "tcp", "udp" or "tls"
--framer {ascii,binary,rtu,socket,tls}
"ascii", "binary", "rtu", "socket" or "tls"
(continues on next page)
4.1. Examples. 45
PyModbus Documentation, Release 3.0.2
# --------------------------------------------------------------------------- #
# import the various client implementations
# --------------------------------------------------------------------------- #
from pymodbus.server import (
StartAsyncSerialServer,
StartAsyncTcpServer,
StartAsyncTlsServer,
StartAsyncUdpServer,
)
from pymodbus.version import version
_logger = logging.getLogger()
def setup_server(args):
"""Run server setup."""
# The datastores only respond to the addresses that are initialized
# If you initialize a DataBlock to addresses of 0x00 to 0xFF, a request to
# 0x100 will respond with an invalid address exception.
# This is because many devices exhibit this kind of behavior (but not all)
_logger.info("### Create datastore")
if args.store == "sequential":
# Continuing, use a sequential block without gaps.
datablock = ModbusSequentialDataBlock(0x00, [17] * 100)
elif args.store == "sparse":
# Continuing, or use a sparse DataBlock which can have gaps
datablock = ModbusSparseDataBlock({0x00: 0, 0x05: 1})
elif args.store == "factory":
(continues on next page)
46 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
if args.slaves:
# The server then makes use of a server context that allows the server
# to respond with different slave contexts for different unit ids.
# By default it will return the same context for every unit id supplied
# (broadcast mode).
# However, this can be overloaded by setting the single flag to False and
# then supplying a dictionary of unit id to context mapping::
#
# The slave context can also be initialized in zero_mode which means
# that a request to address(0-7) will map to the address (0-7).
# The default is False which is based on section 4.4 of the
# specification, so address(0-7) will map to (1-8)::
context = {
0x01: ModbusSlaveContext(
di=datablock,
co=datablock,
hr=datablock,
ir=datablock,
),
0x02: ModbusSlaveContext(
di=datablock,
co=datablock,
hr=datablock,
ir=datablock,
),
0x03: ModbusSlaveContext(
di=datablock, co=datablock, hr=datablock, ir=datablock, zero_mode=True
),
}
single = False
else:
context = ModbusSlaveContext(
di=datablock, co=datablock, hr=datablock, ir=datablock, unit=1
)
single = True
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
# If you don"t set this or any fields, they are defaulted to empty strings.
# ----------------------------------------------------------------------- #
args.identity = ModbusDeviceIdentification(
info_name={
"VendorName": "Pymodbus",
(continues on next page)
4.1. Examples. 47
PyModbus Documentation, Release 3.0.2
48 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# sslctx=sslctx, # The SSLContext to use for TLS (default None and auto␣
˓→create)
# password="none", # The password for for decrypting the private key file
# reqclicert=False, # Force the sever request client"s certificate
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat unit_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
defer_start=False, # Only define server do not activate
)
return server
4.1. Examples. 49
PyModbus Documentation, Release 3.0.2
if __name__ == "__main__":
cmd_args = get_commandline(
server=True,
description="Run asynchronous server.",
)
run_args = setup_server(cmd_args)
asyncio.run(run_async_server(run_args), debug=True)
#!/usr/bin/env python3
"""Pymodbus Server Payload Example.
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.version import version
_logger = logging.getLogger()
# ----------------------------------------------------------------------- #
# build your payload
# ----------------------------------------------------------------------- #
builder = BinaryPayloadBuilder(byteorder=Endian.Little, wordorder=Endian.Little)
builder.add_string("abcdefgh")
(continues on next page)
50 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
# use that payload in the data store
# Here we use the same reference block for each underlying store.
# ----------------------------------------------------------------------- #
# ----------------------------------------------------------------------- #
# initialize the server information
# If you don't set this or any fields, they are defaulted to empty strings.
# ----------------------------------------------------------------------- #
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "Pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "Pymodbus Server",
"ModelName": "Pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
await StartAsyncTcpServer(
context,
identity=identity,
address=("127.0.0.1", port),
allow_reuse_address=True,
)
if __name__ == "__main__":
asyncio.run(run_payload_server(5020))
4.1. Examples. 51
PyModbus Documentation, Release 3.0.2
#!/usr/bin/env python3
"""Pymodbus Synchronous Server Example.
options:
-h, --help show this help message and exit
--comm {tcp,udp,serial,tls}
"serial", "tcp", "udp" or "tls"
--framer {ascii,binary,rtu,socket,tls}
"ascii", "binary", "rtu", "socket" or "tls"
--log {critical,error,warning,info,debug}
"critical", "error", "warning", "info" or "debug"
--port PORT the port to use
--store {sequential,sparse,factory,none}
"sequential", "sparse", "factory" or "none"
--slaves SLAVES number of slaves to respond to
# --------------------------------------------------------------------------- #
# import the various client implementations
# --------------------------------------------------------------------------- #
from pymodbus.server import (
StartSerialServer,
StartTcpServer,
StartTlsServer,
StartUdpServer,
)
_logger = logging.getLogger()
(continues on next page)
52 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
def run_sync_server(args):
"""Run server."""
txt = f"### start SYNC server, listening on {args.port} - {args.comm}"
_logger.info(txt)
if args.comm == "tcp":
address = ("", args.port) if args.port else None
server = StartTcpServer(
context=args.context, # Data storage
identity=args.identity, # server identify
# TBD host=
# TBD port=
address=address, # listen address
# custom_functions=[], # allow custom handling
framer=args.framer, # The framer strategy to use
# TBD handler=None, # handler for each session
allow_reuse_address=True, # allow the reuse of an address
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat unit_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
# defer_start=False, # Only define server do not activate
)
elif args.comm == "udp":
address = ("", args.port) if args.port else None
server = StartUdpServer(
context=args.context, # Data storage
identity=args.identity, # server identify
# TBD host=
# TBD port=
address=address, # listen address
# custom_functions=[], # allow custom handling
framer=args.framer, # The framer strategy to use
# TBD handler=None, # handler for each session
# TBD allow_reuse_address=True, # allow the reuse of an address
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat unit_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
# defer_start=False, # Only define server do not activate
)
elif args.comm == "serial":
# socat -d -d PTY,link=/tmp/ptyp0,raw,echo=0,ispeed=9600
# PTY,link=/tmp/ttyp0,raw,echo=0,ospeed=9600
server = StartSerialServer(
context=args.context, # Data storage
identity=args.identity, # server identify
# timeout=1, # waiting time for request to complete
port=args.port, # serial port
# custom_functions=[], # allow custom handling
framer=args.framer, # The framer strategy to use
(continues on next page)
4.1. Examples. 53
PyModbus Documentation, Release 3.0.2
# sslctx=None, # The SSLContext to use for TLS (default None and auto␣
˓→create)
# password=None, # The password for for decrypting the private key file
# reqclicert=False, # Force the sever request client"s certificate
# ignore_missing_slaves=True, # ignore request to a missing slave
# broadcast_enable=False, # treat unit_id 0 as broadcast address,
# timeout=1, # waiting time for request to complete
# TBD strict=True, # use strict timing, t1.5 for Modbus RTU
# defer_start=False, # Only define server do not activate
)
return server
if __name__ == "__main__":
cmd_args = get_commandline(
server=True,
description="Run synchronous server.",
)
(continues on next page)
54 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
#!/usr/bin/env python3
"""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 asyncio
# -------------------------------------------------------------------------- #
# import necessary libraries
# -------------------------------------------------------------------------- #
import ssl
# -------------------------------------------------------------------------- #
# the TLS detail security can be set in SSLContext which is the context here
# -------------------------------------------------------------------------- #
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
sslctx.verify_mode = ssl.CERT_REQUIRED
sslctx.check_hostname = True
# Prepare client"s certificate which the server requires for TLS full handshake
# sslctx.load_cert_chain(certfile="client.crt", keyfile="client.key",
# password="pwd")
if __name__ == "__main__":
# ----------------------------------------------------------------------- #
# pass SSLContext which is the context here to ModbusTcpClient()
(continues on next page)
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc,missing-raises-
˓→doc,missing-any-param-doc
def convert_to_bcd(decimal):
"""Convert a decimal value to a bcd value
def convert_from_bcd(bcd):
"""Convert a bcd value to a decimal value
56 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
def count_bcd_digits(bcd):
"""Count the number of digits in a bcd value
class BcdPayloadBuilder(IPayloadBuilder):
"""A utility that helps build binary coded decimal payload messages
builder = BcdPayloadBuilder()
builder.add_number(1)
builder.add_number(int(2.234 * 1000))
payload = builder.build()
"""
def __str__(self):
"""Return the payload buffer as a string
def reset(self):
"""Reset the payload buffer"""
(continues on next page)
def build(self):
"""Return the payload buffer as a list
class BcdPayloadDecoder:
"""A utility that helps decode binary coded decimal payload messages from a modbus␣
(continues on next page)
58 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
decoder = BcdPayloadDecoder(payload)
first = decoder.decode_int(2)
second = decoder.decode_int(5) / 100
"""
@staticmethod
def fromRegisters(registers, endian=Endian.Little): # pylint: disable=invalid-name
"""Initialize a payload decoder
@staticmethod
def fromCoils(coils, endian=Endian.Little): # pylint: disable=invalid-name
"""Initialize a payload decoder.
def reset(self):
"""Reset the decoder pointer back to the start"""
self._pointer = 0x00
def decode_bits(self):
"""Decode a byte worth of bits from the buffer"""
self._pointer += 1
handle = self._payload[self._pointer - 1 : self._pointer]
return unpack_bitstring(handle)
# --------------------------------------------------------------------------- #
# Exported Identifiers
# --------------------------------------------------------------------------- #
#!/usr/bin/env python3
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc
"""Pymodbus Server With Callbacks.
60 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# --------------------------------------------------------------------------- #
# import the modbus libraries we need
# --------------------------------------------------------------------------- #
from pymodbus.version import version
# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# create your custom data block with callbacks
# --------------------------------------------------------------------------- #
class CallbackDataBlock(ModbusSparseDataBlock):
"""A datablock that stores the new value in memory,
# --------------------------------------------------------------------------- #
# define your callback process
# --------------------------------------------------------------------------- #
(continues on next page)
def rescale_value(value):
"""Rescale the input value from the range of 0..100 to -3200..3200.
def device_writer(queue):
"""Process new messages from a queue to write to device outputs
# --------------------------------------------------------------------------- #
# initialize your device map
# --------------------------------------------------------------------------- #
def read_device_map(path):
"""Read the device path to address mapping from file::
0x0001,/dev/device1
0x0002,/dev/device2
def run_callback_server():
"""Run callback server."""
(continues on next page)
62 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "pymodbus Server",
"ModelName": "pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #
thread = Thread(target=device_writer, args=(queue,))
thread.start()
StartTcpServer(context, identity=identity, address=("localhost", 5020))
if __name__ == "__main__":
run_callback_server()
#!/usr/bin/env python3
"""Pymodbus Client Framer Overload.
It should be noted that although you are not limited to trying whatever
you would like, the library makes no guarantees that all framers with
all transports will produce predictable or correct results (for example
tcp transport with an RTU framer). However, please let us know of any
success cases that are not documented!
"""
(continues on next page)
# --------------------------------------------------------------------------- #
# import the modbus client and the framers
# --------------------------------------------------------------------------- #
from pymodbus.client import ModbusTcpClient as ModbusClient
from pymodbus.transaction import ModbusSocketFramer as ModbusFramer
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
if __name__ == "__main__":
# ----------------------------------------------------------------------- #
# Initialize the client
# ----------------------------------------------------------------------- #
client = ModbusClient("localhost", port=5020, framer=ModbusFramer)
client.connect()
# ----------------------------------------------------------------------- #
# perform your requests
# ----------------------------------------------------------------------- #
rq = client.write_coil(1, True)
rr = client.read_coils(1, 1)
assert not rq.isError() # nosec test that we are not an error
assert rr.bits[0] # nosec test the expected value
# ----------------------------------------------------------------------- #
# close the client
# ---------------------------------------------------------------------- #
client.close()
#!/usr/bin/env python3
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc
"""Concurrent Modbus Client.
64 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# -------------------------------------------------------------------------- #
# import system libraries
# -------------------------------------------------------------------------- #
import logging
import multiprocessing
import threading
from collections import namedtuple
from concurrent.futures import Future
from multiprocessing import Event as mEvent
from multiprocessing import Process as mProcess
from multiprocessing import Queue as mQueue
from queue import Queue as qQueue
from threading import Event, Thread
# -------------------------------------------------------------------------- #
# import necessary modbus libraries
# -------------------------------------------------------------------------- #
from pymodbus.client.mixin import ModbusClientMixin
# -------------------------------------------------------------------------- #
# configure the client logging
# -------------------------------------------------------------------------- #
log = logging.getLogger("pymodbus")
log.setLevel(logging.DEBUG)
logging.basicConfig()
# -------------------------------------------------------------------------- #
# Initialize out concurrency primitives
# -------------------------------------------------------------------------- #
class _Primitives: # pylint: disable=too-few-public-methods)
"""This is a helper class.
@classmethod
def create(cls, in_process=False):
"""Initialize a new instance of the concurrency primitives.
# -------------------------------------------------------------------------- #
# Define our data transfer objects
# -------------------------------------------------------------------------- #
# These will be used to serialize state between the various workers.
# We use named tuples here as they are very lightweight while giving us
# all the benefits of classes.
# -------------------------------------------------------------------------- #
WorkRequest = namedtuple("WorkRequest", "request, work_id")
WorkResponse = namedtuple("WorkResponse", "is_exception, work_id, response")
# -------------------------------------------------------------------------- #
# Define our worker processes
# -------------------------------------------------------------------------- #
def _client_worker_process(factory, input_queue, output_queue, is_shutdown):
"""Take input requests,
It should be noted that there are N of these workers and they can
be run in process or out of process as all the state serializes.
66 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
It should be noted that there are one of these threads and it must
be an in process thread as the futures will not serialize across
processes..
# -------------------------------------------------------------------------- #
# Define our concurrent client
# -------------------------------------------------------------------------- #
class ConcurrentClient(ModbusClientMixin):
"""This is a high performance client.
def shutdown(self):
"""Shutdown all the workersbeing used to concurrently process the requests."""
log.info("stating to shut down workers")
self.is_shutdown.set()
# to wake up the manager
self.output_queue.put(WorkResponse(None, None, None))
for worker in self.workers:
worker.join()
log.info("finished shutting down workers")
enqueue it to be processed
and then return a future linked to the response
(continues on next page)
68 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
if __name__ == "__main__":
from pymodbus.client import ModbusTcpClient
def client_factory():
"""Client factory."""
txt = f"creating client for: {threading.current_thread()}"
log.debug(txt)
my_client = ModbusTcpClient("127.0.0.1", port=5020)
my_client.connect()
return client
client = ConcurrentClient(factory=client_factory)
try:
log.info("issuing concurrent requests")
futures = [client.read_coils(i * 8, 8) for i in range(10)]
log.info("waiting on futures to complete")
for future in futures:
txt = f"future result: {future.result(timeout=1)}"
log.info(txt)
finally:
client.shutdown()
#!/usr/bin/env python3
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc
"""Pymodbus Server With Custom Datablock Side Effect.
# --------------------------------------------------------------------------- #
# import the modbus libraries we need
# --------------------------------------------------------------------------- #
from pymodbus.version import version
# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# create your custom data block here
# --------------------------------------------------------------------------- #
class CustomDataBlock(ModbusSparseDataBlock):
"""A datablock that stores the new value in memory,
70 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
def run_custom_db_server():
"""Run custom db server."""
# ----------------------------------------------------------------------- #
# initialize your data store
# ----------------------------------------------------------------------- #
block = CustomDataBlock([0] * 100)
store = ModbusSlaveContext(di=block, co=block, hr=block, ir=block)
context = ModbusServerContext(slaves=store, single=True)
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "pymodbus Server",
"ModelName": "pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #
# p = Process(target=device_writer, args=(queue,))
# p.start()
StartTcpServer(context, identity=identity, address=("localhost", 5020))
if __name__ == "__main__":
run_custom_db_server()
#!/usr/bin/env python3
# pylint: disable=missing-type-doc
"""Pymodbus Synchronous Client Examples.
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.pdu import ModbusExceptions, ModbusRequest, ModbusResponse
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# create your custom message
# --------------------------------------------------------------------------- #
# The following is simply a read coil request that always reads 16 coils.
# Since the function code is already registered with the decoder factory,
# this will be decoded as a read coil response. If you implement a new
# method that is not currently implemented, you must register the request
# and response with a ClientDecoder factory.
# --------------------------------------------------------------------------- #
class CustomModbusResponse(ModbusResponse):
"""Custom modbus response."""
function_code = 55
_rtu_byte_count_pos = 2
72 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
class CustomModbusRequest(ModbusRequest):
"""Custom modbus request."""
function_code = 55
_rtu_frame_size = 8
def encode(self):
"""Encode."""
return struct.pack(">HH", self.address, self.count)
# --------------------------------------------------------------------------- #
(continues on next page)
class Read16CoilsRequest(ReadCoilsRequest):
"""Read 16 coils in one request."""
# --------------------------------------------------------------------------- #
# execute the request with your client
# --------------------------------------------------------------------------- #
# using the with context, the client will automatically be connected
# and closed when it leaves the current scope.
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
with ModbusClient(host="localhost", port=5020) as client:
client.register(CustomModbusResponse)
request = CustomModbusRequest(1, unit=1)
result = client.execute(request)
print(result.values)
# pylint: disable=differing-param-doc,missing-any-param-doc
"""Pymodbus Server With Updating Thread.
This scrit generates a random address range (within 0 - 65000) and a random
value and stores it in a database. It then reads the same address to verify
that the process works as expected
74 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# --------------------------------------------------------------------------- #
# import the modbus libraries we need
# --------------------------------------------------------------------------- #
from pymodbus.version import version
# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# define your callback process
# --------------------------------------------------------------------------- #
def updating_writer(parm1):
"""Run every so often,
and updates live values of the context which resides in an SQLite3 database.
It should be noted that there is a race condition for the update.
:param arguments: The input arguments to the call
"""
log.debug("Updating the database context")
context = parm1[0]
readfunction = 0x03 # read holding registers
writefunction = 0x10
slave_id = 0x01 # slave address
count = 50
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "pymodbus Server",
"ModelName": "pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #
time = 5 # 5 seconds delay
loop = asyncio.get_event_loop()
loop.start(time, now=False) # initially delay by time
loop.stop()
await StartAsyncTcpServer(context, identity=identity, address=("", 5020))
if __name__ == "__main__":
asyncio.run(run_dbstore_update_server())
#!/usr/bin/env python3
"""Pymodbus Synchronous Client Example to showcase Device Information.
# --------------------------------------------------------------------------- #
(continues on next page)
76 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# --------------------------------------------------------------------------- #
# import the request
# --------------------------------------------------------------------------- #
from pymodbus.mei_message import ReadDeviceInformationRequest
# --------------------------------------------------------------------------- #
# configure the client logging
# --------------------------------------------------------------------------- #
FORMAT = (
"%(asctime)-15s %(threadName)-15s "
"%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s"
)
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
UNIT = 0x1
def run_sync_client():
"""Run sync client."""
# ------------------------------------------------------------------------#
# choose the client you want
# ------------------------------------------------------------------------#
# make sure to start an implementation to hit against. For this
# you can use an existing device, the reference implementation in the tools
# directory, or start a pymodbus server.
#
# If you use the UDP or TCP clients, you can override the framer being used
# to use a custom implementation (say RTU over TCP). By default they use
# the socket framer::
#
# client = ModbusClient("localhost", port=5020, framer=ModbusRtuFramer)
#
# It should be noted that you can supply an ipv4 or an ipv6 host address
# for both the UDP and TCP clients.
#
# There are also other options that can be set on the client that controls
# how transactions are performed. The current ones are:
#
# * retries - Specify how many retries to allow per transaction (default=3)
# * retry_on_empty - Is an empty response a retry (default = False)
(continues on next page)
# ------------------------------------------------------------------------#
# specify slave to query
# ------------------------------------------------------------------------#
# The slave to query is specified in an optional parameter for each
# individual request. This can be done by specifying the `unit` parameter
# which defaults to `0x00`
# ----------------------------------------------------------------------- #
log.debug("Reading Device Information")
information = {}
rr = None
# ----------------------------------------------------------------------- #
# You can also have the information parsed through the
# ModbusDeviceIdentificiation class, which gets you a more usable way
# to access the Basic and Regular device information objects which are
# specifically listed in the Modbus specification
# ----------------------------------------------------------------------- #
device_id = ModbusDeviceIdentification(info=information)
print("Product Name : ", device_id.ProductName)
78 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
if __name__ == "__main__":
run_sync_client()
#!/usr/bin/env python3
"""Pymodbus Synchronous Server Example to showcase Device Information.
# --------------------------------------------------------------------------- #
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.version import version
# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
FORMAT = (
"%(asctime)-15s %(threadName)-15s"
" %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s"
)
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
# If you don"t set this or any fields, they are defaulted to empty strings.
# ----------------------------------------------------------------------- #
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "Pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "Pymodbus Server",
"ModelName": "Pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
# ----------------------------------------------------------------------- #
# Add an example which is long enough to force the ReadDeviceInformation
# request / response to require multiple responses to send back all of the
# information.
# ----------------------------------------------------------------------- #
identity[0x80] = (
"Lorem ipsum dolor sit amet, consectetur adipiscing "
"elit. Vivamus rhoncus massa turpis, sit amet "
"ultrices orci semper ut. Aliquam tristique sapien in "
"lacus pharetra, in convallis nunc consectetur. Nunc "
"velit elit, vehicula tempus tempus sed. "
)
# ----------------------------------------------------------------------- #
# Add an example with repeated object IDs. The MODBUS specification is
# entirely silent on whether or not this is allowed. In practice, this
# should be assumed to be contrary to the MODBUS specification and other
# clients (other than pymodbus) might behave differently when presented
# with an object ID occurring twice in the returned information.
#
# Use this at your discretion, and at the very least ensure that all
# objects which share a single object ID can fit together within a single
# ADU unit. In the case of Modbus RTU, this is about 240 bytes or so. In
# other words, when the spec says "An object is indivisible, therefore
# any object must have a size consistent with the size of transaction
# response", if you use repeated OIDs, apply that rule to the entire
# grouping of objects with the repeated OID.
(continues on next page)
80 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #
# Tcp:
StartTcpServer(context, identity=identity, address=("localhost", 5020))
# Udp:
# StartUdpServer(context, identity=identity, address=("0.0.0.0", 5020))
# Ascii:
# StartSerialServer(context, identity=identity,
# port="/dev/ttyp0", timeout=1)
# RTU:
# StartSerialServer(context, framer=ModbusRtuFramer, identity=identity,
# port="/dev/ttyp0", timeout=.005, baudrate=9600)
# Binary
# StartSerialServer(context,
# identity=identity,
# framer=ModbusBinaryFramer,
# port="/dev/ttyp0",
# timeout=1)
if __name__ == "__main__":
run_server()
#!/usr/bin/env python3
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc,missing-raises-
˓→doc
* LibmodbusLevel1Client
* LibmodbusClient
This is just another modbus client that can be used just like
any other client in pymodbus.
For these to work, you must have `cffi` and `libmodbus-dev` installed:
# -------------------------------------------------------------------------- #
# import pymodbus libraries
# -------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# create the C interface
# --------------------------------------------------------------------------- #
# * TODO add the protocol needed for the servers
# --------------------------------------------------------------------------- #
compiler = FFI()
(continues on next page)
82 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# -------------------------------------------------------------------------- #
# helper utilities
# -------------------------------------------------------------------------- #
def get_float(data):
(continues on next page)
def cast_to_int16(data):
"""Cast to int16."""
return int(compiler.cast("int16_t", data))
def cast_to_int32(data):
"""Cast to int32."""
return int(compiler.cast("int32_t", data))
class NotImplementedException(Exception):
"""Not implemented exception."""
# -------------------------------------------------------------------------- #
# level1 client
# -------------------------------------------------------------------------- #
class LibmodbusLevel1Client:
"""A raw wrapper around the libmodbus c library.
Feel free to use it if you want increased performance and don"t mind the
entire protocol not being implemented.
"""
@classmethod
def create_tcp_client(cls, my_host="127.0.0.1", my_port=Defaults.TcpPort):
"""Create a TCP modbus client for the supplied parameters.
@classmethod
def create_rtu_client(cls, **kwargs):
"""Create a TCP modbus client for the supplied parameters.
84 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
This method should not be used, instead new instances should be created
using the two supplied factory methods:
* LibmodbusLevel1Client.create_rtu_client(...)
* LibmodbusLevel1Client.create_tcp_client(...)
def connect(self):
"""Attempt to connect to the client target.
def flush(self):
"""Discard the existing bytes on the wire.
86 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
def read_and_write_registers(
self, read_address, read_count, write_address, write_registers
):
"""Read/write registers.
# -------------------------------------------------------------------------- #
# level2 client
# -------------------------------------------------------------------------- #
class LibmodbusClient(ModbusClientMixin):
"""A facade around the raw level 1 libmodbus client.
that implements the pymodbus protocol on top of the lower level client.
"""
# ----------------------------------------------------------------------- #
# these are used to convert from the pymodbus request types to the
# libmodbus operations (overloaded operator).
# ----------------------------------------------------------------------- #
__methods = {
"ReadCoilsRequest": lambda c, r: c.read_bits(r.address, r.count),
"ReadDiscreteInputsRequest": lambda c, r: c.read_input_bits(r.address, r.count),
"WriteSingleCoilRequest": lambda c, r: c.write_bit(r.address, r.value),
"WriteMultipleCoilsRequest": lambda c, r: c.write_bits(r.address, r.values),
"WriteSingleRegisterRequest": lambda c, r: c.write_register(r.address, r.value),
"WriteMultipleRegistersRequest": lambda c, r: c.write_registers(
r.address, r.values
),
(continues on next page)
88 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# ----------------------------------------------------------------------- #
# these are used to convert from the libmodbus result to the
# pymodbus response type
# ----------------------------------------------------------------------- #
__adapters = {
"ReadCoilsRequest": lambda tx, rx: ReadCoilsResponse(list(rx)),
"ReadDiscreteInputsRequest": lambda tx, rx: ReadDiscreteInputsResponse(
list(rx)
),
"WriteSingleCoilRequest": lambda tx, rx: WriteSingleCoilResponse(
tx.address, rx
),
"WriteMultipleCoilsRequest": lambda tx, rx: WriteMultipleCoilsResponse(
tx.address, rx
),
"WriteSingleRegisterRequest": lambda tx, rx: WriteSingleRegisterResponse(
tx.address, rx
),
"WriteMultipleRegistersRequest": lambda tx, rx: WriteMultipleRegistersResponse(
tx.address, rx
),
"ReadHoldingRegistersRequest": lambda tx, rx: ReadHoldingRegistersResponse(
list(rx)
),
"ReadInputRegistersRequest": lambda tx, rx: ReadInputRegistersResponse(
list(rx)
),
"ReadWriteMultipleRegistersRequest": lambda tx, rx:␣
˓→ReadWriteMultipleRegistersResponse(
list(rx)
),
}
* LibmodbusLevel1Client.create_rtu_client(...)
* LibmodbusLevel1Client.create_tcp_client(...)
(continues on next page)
# ----------------------------------------------------------------------- #
# We use the client mixin to implement the api methods which are all
# forwarded to this method. It is implemented using the previously
# defined lookup tables. Any method not defined simply throws.
# ----------------------------------------------------------------------- #
method = request.__class__.__name__
operation = self.__methods.get(method, None)
adapter = self.__adapters.get(method, None)
# ----------------------------------------------------------------------- #
# Other methods can simply be forwarded using the decorator pattern
# ----------------------------------------------------------------------- #
def connect(self):
"""Connect."""
return self.client.connect()
def close(self):
"""Close."""
return self.client.close()
# ----------------------------------------------------------------------- #
# magic methods
# ----------------------------------------------------------------------- #
def __enter__(self):
"""Implement the client with enter block
90 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# -------------------------------------------------------------------------- #
# main example runner
# -------------------------------------------------------------------------- #
if __name__ == "__main__":
#!/usr/bin/env python3
# pylint: disable=missing-type-doc
"""Modbus Message Generator.
# -------------------------------------------------------------------------- #
# import all the available framers
# -------------------------------------------------------------------------- #
from pymodbus.transaction import (
ModbusAsciiFramer,
ModbusBinaryFramer,
ModbusRtuFramer,
(continues on next page)
92 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# -------------------------------------------------------------------------- #
# initialize logging
# -------------------------------------------------------------------------- #
modbus_log = logging.getLogger("pymodbus")
# -------------------------------------------------------------------------- #
# enumerate all request messages
# -------------------------------------------------------------------------- #
_request_messages = [
ReadHoldingRegistersRequest,
ReadDiscreteInputsRequest,
ReadInputRegistersRequest,
ReadCoilsRequest,
WriteMultipleCoilsRequest,
WriteMultipleRegistersRequest,
WriteSingleRegisterRequest,
WriteSingleCoilRequest,
ReadWriteMultipleRegistersRequest,
ReadExceptionStatusRequest,
GetCommEventCounterRequest,
GetCommEventLogRequest,
ReportSlaveIdRequest,
ReadFileRecordRequest,
WriteFileRecordRequest,
MaskWriteRegisterRequest,
ReadFifoQueueRequest,
ReadDeviceInformationRequest,
modbus_diag.ReturnQueryDataRequest,
modbus_diag.RestartCommunicationsOptionRequest,
modbus_diag.ReturnDiagnosticRegisterRequest,
modbus_diag.ChangeAsciiInputDelimiterRequest,
modbus_diag.ForceListenOnlyModeRequest,
modbus_diag.ClearCountersRequest,
modbus_diag.ReturnBusMessageCountRequest,
modbus_diag.ReturnBusCommunicationErrorCountRequest,
modbus_diag.ReturnBusExceptionErrorCountRequest,
modbus_diag.ReturnSlaveMessageCountRequest,
modbus_diag.ReturnSlaveNoResponseCountRequest,
modbus_diag.ReturnSlaveNAKCountRequest,
modbus_diag.ReturnSlaveBusyCountRequest,
modbus_diag.ReturnSlaveBusCharacterOverrunCountRequest,
modbus_diag.ReturnIopOverrunCountRequest,
modbus_diag.ClearOverrunCountRequest,
modbus_diag.GetClearModbusPlusRequest,
]
# -------------------------------------------------------------------------- #
# build an arguments singleton
# -------------------------------------------------------------------------- #
# Feel free to override any values here to generate a specific message
# in question. It should be noted that many argument names are reused
# between different messages, and a number of messages are simply using
# their default values.
# -------------------------------------------------------------------------- #
_arguments = {
"address": 0x12,
(continues on next page)
94 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
# -------------------------------------------------------------------------- #
# generate all the requested messages
# -------------------------------------------------------------------------- #
def generate_messages(framer, options):
"""Parse the command line options
# -------------------------------------------------------------------------- #
# initialize our program settings
# -------------------------------------------------------------------------- #
def get_options():
"""Parse the command line options
parser.add_option(
"-f",
"--framer",
help="The type of framer to use (tcp, rtu, binary, ascii)",
(continues on next page)
parser.add_option(
"-D",
"--debug",
help="Enable debug tracing",
action="store_true",
dest="debug",
default=False,
)
parser.add_option(
"-a",
"--ascii",
help="The indicates that the message is ascii",
action="store_true",
dest="ascii",
default=True,
)
parser.add_option(
"-b",
"--binary",
help="The indicates that the message is binary",
action="store_false",
dest="ascii",
)
parser.add_option(
"-m",
"--messages",
help="The messages to encode (rx, tx)",
dest="messages",
default="rx",
)
(opt, _) = parser.parse_args()
return opt
def main():
"""Run main runner function"""
option = get_options()
if option.debug:
try:
modbus_log.setLevel(logging.DEBUG)
except Exception: # pylint: disable=broad-except
print("Logging is not supported on this system")
96 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
generate_messages(framer, option)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc,missing-any-
˓→param-doc
* tcp
* ascii
* rtu
* binary
"""
# -------------------------------------------------------------------------- #
# import needed libraries
# -------------------------------------------------------------------------- #
import codecs as c
import collections
import logging
import textwrap
from optparse import OptionParser # pylint: disable=deprecated-module
# -------------------------------------------------------------------------- #
# -------------------------------------------------------------------------- #
FORMAT = (
(continues on next page)
# -------------------------------------------------------------------------- #
# build a quick wrapper around the framers
# -------------------------------------------------------------------------- #
class Decoder:
"""Decoder."""
98 Chapter 4. Examples.
PyModbus Documentation, Release 3.0.2
parser.add_option(
"-p",
"--parser",
help="The type of parser to use (tcp, rtu, binary, ascii)",
dest="parser",
default="tcp",
)
parser.add_option(
"-D",
"--debug",
help="Enable debug tracing",
action="store_true",
dest="debug",
default=False,
)
parser.add_option(
"-m", "--message", help="The message to parse", dest="message", default=None
)
parser.add_option(
"-a",
"--ascii",
help="The indicates that the message is ascii",
action="store_true",
dest="ascii",
default=False,
)
parser.add_option(
"-b",
"--binary",
help="The indicates that the message is binary",
action="store_false",
dest="ascii",
)
parser.add_option(
"-f",
"--file",
help="The file containing messages to parse",
(continues on next page)
parser.add_option(
"-t",
"--transaction",
help="If the incoming message is in hexadecimal format",
action="store_true",
dest="transaction",
default=False,
)
parser.add_option(
"--framer",
help="Framer to use",
dest="framer",
default=None,
)
return opt
def get_messages(option):
"""Do a helper method to generate the messages to parse
if not option.ascii:
option.message = c.decode(option.message.encode(), "hex_codec")
yield option.message
elif option.file:
with open(option.file, "r") as handle: # pylint: disable=unspecified-encoding
for line in handle:
if line.startswith("#"):
continue
if not option.ascii:
line = line.strip()
(continues on next page)
def main():
"""Run main runner function"""
option = get_options()
if option.debug:
try:
log.setLevel(logging.DEBUG)
except Exception as exc: # pylint: disable=broad-except
print(f"Logging is not supported on this system- {exc}")
framer = {
"tcp": ModbusSocketFramer,
"rtu": ModbusRtuFramer,
"binary": ModbusBinaryFramer,
"ascii": ModbusAsciiFramer,
}.get(option.framer or option.parser, ModbusSocketFramer)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Pymodbus Logging Examples."""
import logging
import logging.handlers as Handlers
if __name__ == "__main__":
# ----------------------------------------------------------------------- #
# This will simply send everything logged to console
# ----------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# ----------------------------------------------------------------------- #
# This will send the error messages in the specified namespace to a file.
# The available namespaces in pymodbus are as follows:
# ----------------------------------------------------------------------- #
# * pymodbus.* - The root namespace
# * pymodbus.server.* - all logging messages involving the modbus server
(continues on next page)
# ----------------------------------------------------------------------- #
# This will send the error messages to the specified handlers:
# * docs.python.org/library/logging.html
# ----------------------------------------------------------------------- #
log = logging.getLogger("pymodbus")
log.setLevel(logging.ERROR)
handlers = [
Handlers.RotatingFileHandler("logfile", maxBytes=1024 * 1024),
Handlers.SMTPHandler(
"mx.host.com", "pymodbus@host.com", ["support@host.com"], "Pymodbus"
),
Handlers.SysLogHandler(facility="daemon"),
Handlers.DatagramHandler("localhost", 12345),
]
[log.addHandler(h) for h in handlers] # pylint: disable=expression-not-assigned
# pylint: disable=missing-type-doc
r"""This is used to generate decoder blocks.
* csv
* json
* xml
CSV:
address,type,size,name,function
0,int16,1,Comm. count PLC,hr
1,int16,1,Comm. count PLC,hr
2,int16,1,Comm. count PLC,hr
3,int16,1,Comm. count PLC,hr
4,int16,1,Comm. count PLC,hr
5,int16,1,Comm. count PLC,hr
(continues on next page)
CSV:
address,value,function,name,description
0,0,hr,Comm. count PLC,Comm. count PLC
1,10,hr,Comm. count PLC,Comm. count PLC
(continues on next page)
slave_context = modbus_context_decoder(raw_mapping)
context = ModbusServerContext(slaves=slave_context, single=True)
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "Pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "Pymodbus Server",
"ModelName": "Pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
StartTcpServer(context=context, identity=identity, address=("localhost", 5020))
"""
import csv
import json
from collections import defaultdict
from io import StringIO
(continues on next page)
# --------------------------------------------------------------------------- #
# raw mapping input parsers
# --------------------------------------------------------------------------- #
# These generate the raw mapping_blocks from some form of input
# which can then be passed to the decoder in question to supply
# the requested output result.
# --------------------------------------------------------------------------- #
template = {
"Start": "address",
"DataType": "type",
"Length": "size"
# the remaining keys will just pass through
}
mappings = json_mapping_parser("mapping.json", template)
def xml_mapping_parser():
"""Given an xml file of the the mapping data for a modbus device,
# --------------------------------------------------------------------------- #
# modbus context decoders
# --------------------------------------------------------------------------- #
# These are used to decode a raw mapping_block into a slave context with
# populated function data blocks.
# --------------------------------------------------------------------------- #
def modbus_context_decoder(mapping_blocks):
"""Generate a backing slave context with initialized data blocks.
# --------------------------------------------------------------------------- #
# modbus mapping decoder
# --------------------------------------------------------------------------- #
# These are used to decode a raw mapping_block into a request decoder.
# So this allows one to simply grab a number of registers, and then
# pass them to this decoder which will do the rest.
# --------------------------------------------------------------------------- #
class ModbusTypeDecoder:
"""This is a utility to determine the correct decoder to use given a type name.
By default this supports all the types available in the default modbus
decoder, however this can easily be extended this class
and adding new types to the mapper::
class CustomTypeDecoder(ModbusTypeDecoder):
def __init__(self):
ModbusTypeDecode.__init__(self)
self.mapper["type-token"] = self.callback
"""
def __init__(self):
"""Initialize a new instance of the decoder"""
self.default = lambda m: self.parse_16bit_uint
self.parsers = {
"uint": self.parse_16bit_uint,
"uint8": self.parse_8bit_uint,
"uint16": self.parse_16bit_uint,
"uint32": self.parse_32bit_uint,
"uint64": self.parse_64bit_uint,
"int": self.parse_16bit_int,
"int8": self.parse_8bit_int,
"int16": self.parse_16bit_int,
"int32": self.parse_32bit_int,
"int64": self.parse_64bit_int,
"float": self.parse_32bit_float,
"float32": self.parse_32bit_float,
(continues on next page)
# ------------------------------------------------------------ #
# Type parsers
# ------------------------------------------------------------ #
@staticmethod
def parse_string(tokens):
"""Parse value."""
_ = next(tokens)
size = int(next(tokens))
return lambda d: d.decode_string(size=size)
@staticmethod
def parse_bits():
"""Parse value."""
return lambda d: d.decode_bits()
@staticmethod
def parse_8bit_uint():
"""Parse value."""
return lambda d: d.decode_8bit_uint()
@staticmethod
def parse_16bit_uint():
"""Parse value."""
return lambda d: d.decode_16bit_uint()
@staticmethod
def parse_32bit_uint():
"""Parse value."""
return lambda d: d.decode_32bit_uint()
@staticmethod
def parse_64bit_uint():
"""Parse value."""
return lambda d: d.decode_64bit_uint()
@staticmethod
def parse_8bit_int():
"""Parse value."""
return lambda d: d.decode_8bit_int()
@staticmethod
def parse_16bit_int():
"""Parse value."""
return lambda d: d.decode_16bit_int()
@staticmethod
def parse_32bit_int():
(continues on next page)
@staticmethod
def parse_64bit_int():
"""Parse value."""
return lambda d: d.decode_64bit_int()
@staticmethod
def parse_32bit_float():
"""Parse value."""
return lambda d: d.decode_32bit_float()
@staticmethod
def parse_64bit_float():
"""Parse value."""
return lambda d: d.decode_64bit_float()
# ------------------------------------------------------------
# Public Interface
# ------------------------------------------------------------
def tokenize(self, value):
"""Return the tokens
that can be used to save a modbus server context to file for backup,
checkpointing, or any other purpose. There use is very
simple::
context = server.context
saver = JsonDatastoreSaver(context)
saver.save()
* csv
* json
* xml
* handle_store_start(self, store)
* handle_store_end(self, store)
* handle_slave_start(self, slave)
* handle_slave_end(self, slave)
* handle_save_start(self)
* handle_save_end(self)
"""
import json
import xml.etree.ElementTree as xml # nosec
class ModbusDatastoreSaver:
"""An abstract base class.
def save(self):
"""Save the context to file.
which calls the various callbacks which the sub classes will implement.
"""
with open( # pylint: disable=unspecified-encoding
self.path, "w"
) as self.file_handle: # pylint: disable=attribute-defined-outside-init
self.handle_save_start()
for slave_name, slave in self.context:
self.handle_slave_start(slave_name)
for store_name, store in slave.store.iteritems():
self.handle_store_start(store_name)
self.handle_store_values(iter(store)) # pylint: disable=no-member
self.handle_store_end(store_name)
self.handle_slave_end(slave_name)
self.handle_save_end()
# ------------------------------------------------------------
# predefined state machine callbacks
# ------------------------------------------------------------
def handle_save_start(self):
"""Handle save start."""
def handle_save_end(self):
"""Handle save end."""
_context = None
_store = None
_slave = None
STORE_NAMES = {
"i": "input-registers",
"d": "discretes",
"h": "holding-registers",
"c": "coils",
}
def handle_save_start(self):
"""Handle save start."""
self._context = {}
def handle_save_end(self):
"""Handle save end."""
json.dump(self._context, self.file_handle)
class CsvDatastoreSaver(ModbusDatastoreSaver):
"""An implementation of the modbus datastore saver.
_context = None
_store = None
_line = None
NEWLINE = "\r\n"
HEADER = "slave,store,address,value" + NEWLINE
(continues on next page)
def handle_save_start(self):
"""Handle save start."""
self.file_handle.write(self.HEADER)
class XmlDatastoreSaver(ModbusDatastoreSaver):
"""An implementation of the modbus datastore saver.
_context = None
_store = None
STORE_NAMES = {
"i": "input-registers",
"d": "discretes",
"h": "holding-registers",
"c": "coils",
}
def handle_save_start(self):
"""Handle save start."""
self._context = xml.Element("context")
(continues on next page)
def handle_save_end(self):
"""Handle save end."""
self._root.write(self.file_handle)
#!/usr/bin/env python3
# pylint: disable=missing-raises-doc
"""An example of creating a fully implemented modbus server.
# -------------------------------------------------------------------------- #
# Logging
# -------------------------------------------------------------------------- #
server_log = logging.getLogger("pymodbus.server")
protocol_log = logging.getLogger("pymodbus.protocol")
_logger = logging.getLogger(__name__)
def root_test():
"""Check to see if we are running as root"""
return True # removed for the time being as it isn"t portable
# return getpass.getuser() == "root"
# -------------------------------------------------------------------------- #
# Helper Classes
# -------------------------------------------------------------------------- #
class ConfigurationException(Exception):
"""Exception for configuration error"""
def __str__(self):
"""Build a representation of the object
def parse(self):
"""Parse the config file and creates a server context"""
handle = pickle.load(self.file) # nosec
try: # test for existence, or bomb
dsd = handle["di"]
csd = handle["ci"]
hsd = handle["hr"]
isd = handle["ir"]
except Exception:
raise ConfigurationException( # pylint: disable=raise-missing-from
"Invalid Configuration"
)
slave = ModbusSlaveContext(d=dsd, c=csd, h=hsd, i=isd)
return ModbusServerContext(slaves=slave)
# -------------------------------------------------------------------------- #
# Main start point
# -------------------------------------------------------------------------- #
def main():
"""Server launcher"""
parser = OptionParser()
parser.add_option(
"-c", "--conf", help="The configuration file to load", dest="file"
)
parser.add_option(
"-D",
"--debug",
help="Turn on to enable tracing",
action="store_true",
dest="debug",
default=False,
)
(opt, _) = parser.parse_args()
# -------------------------------------------------------------------------- #
# Main jumper
# -------------------------------------------------------------------------- #
if __name__ == "__main__":
if root_test():
main()
else:
print("This script must be run as root!")
#!/usr/bin/env python3
"""Simple Modbus TCP over TLS client.
This is a simple example of writing a modbus TCP over TLS client that uses
Python builtin module ssl - TLS/SSL wrapper for socket objects for the TLS
feature.
"""
# -------------------------------------------------------------------------- #
# import necessary libraries
# -------------------------------------------------------------------------- #
import ssl
# -------------------------------------------------------------------------- #
# the TLS detail security can be set in SSLContext which is the context here
# -------------------------------------------------------------------------- #
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
sslctx.verify_mode = ssl.CERT_REQUIRED
sslctx.check_hostname = True
# Prepare client"s certificate which the server requires for TLS full handshake
# sslctx.load_cert_chain(certfile="client.crt", keyfile="client.key",
# password="pwd")
result = client.read_coils(1, 8)
print(result.bits)
client.close()
# pylint: disable=missing-type-doc,missing-raises-doc
"""Modbus Modicon Payload Builder.
class ModiconPayloadBuilder(IPayloadBuilder):
"""A utility that helps build modicon encoded payload messages.
builder = ModiconPayloadBuilder()
builder.add_8bit_uint(1)
builder.add_16bit_uint(2)
payload = builder.build()
"""
def __str__(self):
(continues on next page)
def reset(self):
"""Reset the payload buffer"""
self._payload = []
def build(self):
"""Return the payload buffer as a list
class ModiconPayloadDecoder:
"""A utility that helps decode modicon encoded payload messages from a modbus␣
˓→response message.
decoder = ModiconPayloadDecoder(payload)
first = decoder.decode_8bit_uint()
second = decoder.decode_16bit_uint()
"""
@staticmethod
def from_registers(registers, endian=Endian.Little):
"""Initialize a payload decoder.
@staticmethod
def from_coils(coils, endian=Endian.Little):
"""Initialize a payload decoder.
def reset(self):
"""Reset the decoder pointer back to the start"""
self._pointer = 0x00
def decode_8bit_uint(self):
"""Decode a 8 bit unsigned int from the buffer"""
self._pointer += 1
fstring = self._endian + "B"
handle = self._payload[self._pointer - 1 : self._pointer]
return unpack(fstring, handle)[0]
def decode_16bit_uint(self):
"""Decode a 16 bit unsigned int from the buffer"""
self._pointer += 2
fstring = self._endian + "H"
handle = self._payload[self._pointer - 2 : self._pointer]
return unpack(fstring, handle)[0]
def decode_32bit_uint(self):
"""Decode a 32 bit unsigned int from the buffer"""
self._pointer += 4
fstring = self._endian + "I"
handle = self._payload[self._pointer - 4 : self._pointer]
handle = handle[2:] + handle[:2]
return unpack(fstring, handle)[0]
def decode_8bit_int(self):
"""Decode a 8 bit signed int from the buffer"""
self._pointer += 1
fstring = self._endian + "b"
handle = self._payload[self._pointer - 1 : self._pointer]
return unpack(fstring, handle)[0]
def decode_16bit_int(self):
"""Decode a 16 bit signed int from the buffer"""
self._pointer += 2
fstring = self._endian + "h"
handle = self._payload[self._pointer - 2 : self._pointer]
return unpack(fstring, handle)[0]
def decode_32bit_int(self):
"""Decode a 32 bit signed int from the buffer"""
self._pointer += 4
fstring = self._endian + "i"
handle = self._payload[self._pointer - 4 : self._pointer]
(continues on next page)
def decode_32bit_float(self):
"""Decode a float from the buffer"""
self._pointer += 4
fstring = self._endian + "f"
handle = self._payload[self._pointer - 4 : self._pointer]
handle = handle[2:] + handle[:2]
return unpack(fstring, handle)[0]
def decode_bits(self):
"""Decode a byte worth of bits from the buffer"""
self._pointer += 1
handle = self._payload[self._pointer - 1 : self._pointer]
return unpack_bitstring(handle)
# -------------------------------------------------------------------------- #
# Exported Identifiers
# -------------------------------------------------------------------------- #
__all__ = ["ModiconPayloadBuilder", "ModiconPayloadDecoder"]
#!/usr/bin/env python3
# pylint: disable=missing-type-doc
"""Pymodbus Performance Example.
try:
from multiprocessing import Process as mWorker
from multiprocessing import log_to_stderr
except ImportError:
log_to_stderr = logging.getLogger
# --------------------------------------------------------------------------- #
# choose between threads or processes
# --------------------------------------------------------------------------- #
# --------------------------------------------------------------------------- #
# perform the test
# --------------------------------------------------------------------------- #
# This test is written such that it can be used by many threads of processes
# although it should be noted that there are performance penalties
# associated with each strategy.
# --------------------------------------------------------------------------- #
def single_client_test(n_host, n_cycles):
"""Perform a single threaded test of a synchronous client against the specified host
try:
count = 0
client = ModbusTcpClient(n_host, port=5020)
(continues on next page)
# --------------------------------------------------------------------------- #
# run our test and check results
# --------------------------------------------------------------------------- #
# We shard the total number of requests to perform between the number of
# threads that was specified. We then start all the threads and block on
# them to finish. This may need to switch to another mechanism to signal
# finished as the process/thread start up/shut down may skew the test a bit.
# --------------------------------------------------------------------------- #
if __name__ == "__main__":
args = (host, int(cycles * 1.0 / workers))
# with Worker(max_workers=workers, thread_name_prefix="Perform") as exe:
# futures = {exe.submit(single_client_test, *args): job for job in␣
˓→range(workers)}
# pylint: disable=missing-type-doc,missing-param-doc,differing-param-doc,missing-raises-
˓→doc
it works under the assumption that users would have a server context
of the following form::
server_context = {
0x00: client("host1.something.com"),
0x01: client("host2.something.com"),
0x02: client("host3.something.com")
}
server_context = {
0x00: client("host1.something.com", 0x00),
0x01: client("host1.something.com", 0x01),
(continues on next page)
# -------------------------------------------------------------------------- #
# Logging
# -------------------------------------------------------------------------- #
_logger = logging.getLogger(__name__)
# -------------------------------------------------------------------------- #
# Slave Context
# -------------------------------------------------------------------------- #
# Basically we create a new slave context for the given slave identifier so
# that this slave context will only make requests to that slave with the
# client that the server is maintaining.
# -------------------------------------------------------------------------- #
class RemoteSingleSlaveContext(IModbusSlaveContext):
"""This is a remote server context,
that allows one to create a server context backed by a single client that
may be attached to many slave units. This can be used to
effectively create a modbus forwarding server.
"""
def reset(self):
"""Reset all the datastores to their default values"""
raise NotImplementedException()
def __str__(self):
"""Return a string representation of the context
The future api should make the result consistent so we can just call `result.
˓→getValues()`.
# -------------------------------------------------------------------------- #
# Server Context
# -------------------------------------------------------------------------- #
# Think of this as simply a dictionary of { unit_id: client(req, unit_id) }
# -------------------------------------------------------------------------- #
class RemoteServerContext:
"""This is a remote server context,
that allows one to create a server context backed by a single client that
may be attached to many slave units. This can be used to
effectively create a modbus forwarding server.
"""
a, c, s
),
"c": lambda a, c, s: client.read_coils( # pylint: disable=unnecessary-lambda
a, c, s
),
"h": lambda a, c, s: client.read_holding_registers( # pylint:␣
˓→disable=unnecessary-lambda
a, c, s
),
"i": lambda a, c, s: client.read_input_registers( # pylint:␣
˓→disable=unnecessary-lambda
a, c, s
),
}
self.set_callbacks = {
"d": lambda a, v, s: client.write_coils( # pylint: disable=unnecessary-
˓→lambda
a, v, s
),
"c": lambda a, v, s: client.write_coils( # pylint: disable=unnecessary-
˓→lambda
a, v, s
),
(continues on next page)
def __str__(self):
"""Return a string representation of the context
def __iter__(self):
"""Iterate over the current collection of slave contexts.
# pylint: disable=missing-type-doc
"""Thread safe datastore."""
import threading
from contextlib import contextmanager
class ContextWrapper:
"""This is a simple wrapper around enter and exit functions
def __enter__(self):
"""Do on enter."""
if self.enter: # pylint: disable=no-member
self._enter()
return self if not self._factory else self._factory()
class ReadWriteLock:
"""This reader writer lock guarantees write order,
(continues on next page)
but not read order and is generally biased towards allowing writes
if they are available to prevent starvation.
TODO:
* allow user to choose between read/write/random biasing
- currently write biased
- read biased allow N readers in queue
- random is 50/50 choice of next
"""
def __init__(self):
"""Initialize a new instance of the ReadWriteLock"""
self.queue = [] # the current writer queue
self.lock = threading.Lock() # the underlying condition lock
self.read_condition = threading.Condition(
self.lock
) # the single reader condition
self.readers = 0 # the number of current readers
self.writer = False # is there a current writer
def __is_pending_writer(self):
"""Check is pending writer."""
return self.writer or ( # if there is a current writer
self.queue # or if there is a waiting writer
and (self.queue[0] != self.read_condition)
)
def acquire_reader(self):
"""Notify the lock that a new reader is requesting the underlying resource."""
with self.lock:
if self.__is_pending_writer(): # if there are existing writers waiting
if (
self.read_condition not in self.queue
): # do not pollute the queue with readers
self.queue.append(
self.read_condition
) # add the readers in line for the queue
while (
self.__is_pending_writer()
): # until the current writer is finished
self.read_condition.wait(1) # wait on our condition
if self.queue and self.read_condition == self.queue[0]:
self.queue.pop(0) # then go ahead and remove it
self.readers += 1 # update the current number of readers
def acquire_writer(self):
"""Notify the lock that a new writer is requesting the underlying resource."""
with self.lock:
if self.writer or self.readers:
condition = threading.Condition(self.lock)
# create a condition just for this writer
self.queue.append(condition) # and put it on the waiting queue
(continues on next page)
def release_reader(self):
"""Notify the lock that an existing reader is finished with the underlying␣
˓→resource."""
with self.lock:
self.readers = max(0, self.readers - 1) # readers should never go below 0
if not self.readers and self.queue: # if there are no active readers
self.queue[0].notify_all() # then notify any waiting writers
def release_writer(self):
"""Notify the lock that an existing writer is finished with the underlying␣
˓→resource."""
with self.lock:
self.writer = False # give up current writing handle
if self.queue: # if someone is waiting in the queue
self.queue[0].notify_all() # wake them up first
else:
self.read_condition.notify_all() # otherwise wake up all possible␣
˓→readers
@contextmanager
def get_reader_lock(self):
"""Wrap some code with a reader lock using the python context manager protocol::
with rwlock.get_reader_lock():
do_read_operation()
"""
try:
self.acquire_reader()
yield self
finally:
self.release_reader()
@contextmanager
def get_writer_lock(self):
"""Wrap some code with a writer lock using the python context manager protocol::
with rwlock.get_writer_lock():
do_read_operation()
"""
try:
self.acquire_writer()
yield self
finally:
self.release_writer()
class ThreadSafeDataBlock(BaseModbusDataBlock):
(continues on next page)
This allows a user to inject an existing data block which can then be
safely operated on from multiple cocurrent threads.
It should be noted that the choice was made to lock around the
datablock instead of the manager as there is less source of
contention (writes can occur to slave 0x01 while reads can
occur to slave 0x02).
"""
class AtomicCounter:
(continues on next page)
def is_running(self):
"""Is running."""
return self.counter <= self.finish
locker = ReadWriteLock()
readers, writers = AtomicCounter(), AtomicCounter()
def read():
"""Read."""
while writers.is_running() and readers.is_running():
with locker.get_reader_lock():
readers.increment()
def write():
"""Write."""
while writers.is_running() and readers.is_running():
with locker.get_writer_lock():
writers.increment()
#!/usr/bin/env python3
# pylint: disable=missing-any-param-doc,differing-param-doc
"""Pymodbus Server With Updating Thread.
# --------------------------------------------------------------------------- #
# configure the service logging
# --------------------------------------------------------------------------- #
log = logging.getLogger()
log.setLevel(logging.DEBUG)
# --------------------------------------------------------------------------- #
# define your callback process
# --------------------------------------------------------------------------- #
def updating_writer(extra):
"""Run every so often,
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0, [17] * 100),
co=ModbusSequentialDataBlock(0, [17] * 100),
hr=ModbusSequentialDataBlock(0, [17] * 100),
ir=ModbusSequentialDataBlock(0, [17] * 100),
)
context = ModbusServerContext(slaves=store, single=True)
# ----------------------------------------------------------------------- #
# initialize the server information
# ----------------------------------------------------------------------- #
identity = ModbusDeviceIdentification(
info_name={
"VendorName": "pymodbus",
"ProductCode": "PM",
"VendorUrl": "https://github.com/riptideio/pymodbus/",
"ProductName": "pymodbus Server",
"ModelName": "pymodbus Server",
"MajorMinorRevision": version.short(),
}
)
# ----------------------------------------------------------------------- #
# run the server you want
# ----------------------------------------------------------------------- #
log.debug("Start server")
await StartAsyncTcpServer(
context, identity=identity, address=("localhost", 5020), defer_start=False
)
log.debug("Done")
if __name__ == "__main__":
asyncio.run(run_updating_server())
usage :
python3 serial_forwarder.py --log DEBUG --port "/dev/ttyUSB0" --baudrate 9600 --server_
˓→ip "192.168.1.27" --server_port 5020 --slaves 1 2 3
"""
import argparse
import asyncio
import logging
(continues on next page)
class SerialForwarderTCPServer:
"""SerialRTU2TCP Forwarder Server"""
def __init__(self):
"""Initialize the server"""
self.server = None
args = parser.parse_args()
# set defaults
_logger.setLevel(
args.log.upper() if args.log.lower() in logchoices else logging.INFO
)
if not args.slaves:
args.slaves = {1, 2, 3}
return args.port, args.baudrate, args.server_port, args.server_ip, args.slaves
if __name__ == "__main__":
server = SerialForwarderTCPServer()
try:
signal.signal(signal.SIGINT, raise_graceful_exit)
asyncio.run(server.run())
finally:
asyncio.run(server.stop())
FIVE
PYMODBUS
5.1.1 Subpackages
pymodbus.client
# connect to device
client.connect()
# set/set information
rr = client.read_coils(0x01)
(continues on next page)
141
PyModbus Documentation, Release 3.0.2
# disconnect device
client.close()
# connect to device
await async_client.connect()
# set/set information
rr = await async_client.read_coils(0x01)
await async_client.write_coil(0x01, values)
# disconnect device
await async_client.close()
Large parts of the implementation are shared between the different classes, to ensure high stability and efficient main-
tenance.
Client setup.
class pymodbus.client.base.ModbusBaseClient(framer: str = None, timeout: str | float = 3, retries: str | int
= 3, retry_on_empty: bool = False, close_comm_on_error:
bool = False, strict: bool = True, broadcast_enable: bool
= False, reconnect_delay: int = 300000, **kwargs: any)
ModbusBaseClient
Parameters common to all clients:
Parameters
• framer – (optional) Modbus Framer class.
• timeout – (optional) Timeout for a request, in seconds.
• retries – (optional) Max number of retries pr request.
• retry_on_empty – (optional) Retry on empty response.
• close_comm_on_error – (optional) Close connection on error.
• strict – (optional) Strict timing, 1.5 character between requests.
• broadcast_enable – (optional) True to treat id 0 as broadcast address.
• reconnect_delay – (optional) Delay in milliseconds before reconnecting.
• kwargs – (optional) Experimental parameters.
Tip: Common parameters and all external methods for all clients are documented here, and not repeated with
each client.
Tip: reconnect_delay doubles automatically with each unsuccessful connect. Set reconnect_delay=0 to avoid
automatic reconnection.
ModbusBaseClient is normally not referenced outside pymodbus, unless you want to make a custom client.
Custom client class must inherit ModbusBaseClient, example:
class myOwnClient(ModbusBaseClient):
def run():
client = myOwnClient(...)
client.connect()
rr = client.read_coils(0x01)
client.close()
Returns
The result of the request execution
Raises
ConnectionException – Check exception text.
close() → None
Close the underlying socket connection (call sync/async).
await client.connect()
...
await client.close()
Parameters
• port – Serial port used for communication.
• framer – (optional) Framer class.
• baudrate – (optional) Bits pr second.
• bytesize – (optional) Number of bits pr byte 7-8.
• parity – (optional) ‘E’ven, ‘O’dd or ‘N’one
• stopbits – (optional) Number of stop bits 0-2¡.
• handle_local_echo – (optional) Discard local echo from dongle.
• kwargs – (optional) Experimental parameters
The serial communication is RS-485 based, and usually used vith a usb RS485 dongle.
Example:
def run():
client = ModbusSerialClient("dev/serial0")
client.connect()
...
client.close()
TCP transport.
await client.connect()
(continues on next page)
client.connect()
...
client.close()
TLS transport.
await client.connect()
...
await client.close()
client.connect()
...
client.close()
UDP transport.
await client.connect()
...
await client.close()
client.connect()
(continues on next page)
request = ReadCoilsRequest(1,10)
response = client.execute(request)
# or
request = ReadCoilsRequest(1,10)
response = await client.execute(request)
Tip: All methods can be used directly (synchronous) or with await <method> depending on the instantiated
client.
Parameters
• address – Start address to read from
• count – (optional) Number of coils to read
• slave – (optional) Modbus slave unit ID
• kwargs – (optional) Experimental parameters.
Raises
ModbusException –
read_input_registers(address: int, count: int = 1, slave: int = 0, **kwargs: any) →
ReadInputRegistersResponse
Read input registers (function code 0x04).
Parameters
• address – Start address to read from
• count – (optional) Number of coils to read
• slave – (optional) Modbus slave unit ID
• kwargs – (optional) Experimental parameters.
Raises
ModbusException –
readwrite_registers(*args, **kwargs) → ReadWriteMultipleRegistersResponse
Read/Write registers
Parameters
• args –
• kwargs –
Returns
A deferred response handle
write_coil(address: int, value: bool, slave: int = 0, **kwargs: any) → WriteSingleCoilResponse
Write single coil (function code 0x05).
Parameters
• address – Start address to read from
• value – Boolean to write
• slave – (optional) Modbus slave unit ID
• kwargs – (optional) Experimental parameters.
Raises
ModbusException –
write_coils(address: int, values: List[bool], slave: int = 0, **kwargs: any) → WriteMultipleCoilsResponse
Write coils (function code 0x0F).
Parameters
• address – Start address to read from
• values – List of booleans to write
pymodbus.constants package
Retries
The default number of times a client should retry the given request before failing (3)
RetryOnEmpty
A flag indicating if a transaction should be retried in the case that an empty response is received. This is
useful for slow clients that may need more time to process a request.
RetryOnInvalid
A flag indicating if a transaction should be retried in the case that an invalid response is received.
Timeout
The default amount of time a client should wait for a request to be processed (3 seconds)
Reconnects
The default number of times a client should attempt to reconnect before deciding the server is down (0)
TransactionId
The starting transaction identifier number (0)
ProtocolId
The modbus protocol id. Currently this is set to 0 in all but proprietary implementations.
Slave
The modbus slave address. Currently this is set to 0x00 which means this request should be broadcast to
all the slave devices (really means that all the devices should respond).
Baudrate
The speed at which the data is transmitted over the serial line. This defaults to 19200.
Parity
The type of checksum to use to verify data integrity. This can be on of the following:
- (E)ven - 1 0 1 0 | P(0)
- (O)dd - 1 0 1 0 | P(1)
- (N)one - 1 0 1 0 | no parity
Backoff = 0.3
Baudrate = 19200
BroadcastEnable = False
Bytesize = 8
CloseCommOnError = False
Count = 1
HandleLocalEcho = False
IgnoreMissingSlaves = False
Parity = 'N'
ProtocolId = 0
ReadSize = 1024
ReconnectDelay = 300000
Reconnects = 0
Retries = 3
RetryOnEmpty = False
RetryOnInvalid = False
Slave = 0
Stopbits = 1
Strict = True
TcpPort = 502
Timeout = 3
TlsPort = 802
TransactionId = 0
UdpPort = 502
ZeroMode = False
Regular
In addition to basic data objects, the device provides additional and optional identification and description
data objects. All of the objects of this category are defined in the standard but their implementation is
optional.
Extended
In addition to regular data objects, the device provides additional and optional identification and description
private data about the physical device itself. All of these data are device dependent.
Specific
Request to return a single data object.
Basic = 1
Extended = 3
Regular = 2
Specific = 4
Note: I am simply borrowing the format strings from the python struct module for my convenience.
Auto = '@'
Big = '>'
Little = '<'
GetStatistics = 3
On = 65280
Ready = 0
SlaveOff = 0
SlaveOn = 255
Waiting = 65535
Nothing = 0
Submodules
pymodbus.datastore package
Define datastore.
class pymodbus.datastore.ModbusSequentialDataBlock(address, values)
Bases: BaseModbusDataBlock
Creates a sequential modbus datastore.
classmethod create()
Create a datastore.
With the full address space initialized to 0x00
Returns
An initialized datastore
getValues(address, count=1)
Return the requested values of the datastore.
Parameters
• address – The starting address
• count – The number of values to retrieve
Returns
The requested values from a:a+c
setValues(address, values)
Set the requested values of the datastore.
Parameters
• address – The starting address
• values – The new values to be set
validate(address, count=1)
Check to see if the request is in range.
Parameters
• address – The starting address
• count – The number of values to test for
Returns
True if the request in within range, False otherwise
class pymodbus.datastore.ModbusServerContext(slaves=None, single=True)
Bases: object
This represents a master collection of slave contexts.
If single is set to true, it will be treated as a single context so every unit-id returns the same context. If single is
set to false, it will be interpreted as a collection of slave contexts.
slaves()
Define slaves.
class pymodbus.datastore.ModbusSimulatorContext
Bases: IModbusSlaveContext
ModbuSimulatorContext
loads a memory configuration from a json file (see examples/simulator.py for details) and prepares a
simulation of a device.
The integration is simple:
store = ModbusSimulatorContext()
StartAsyncTcpServer(context=store)
Now the server will simulate the defined device with features like:
• invalid addresses
• write protected addresses
• optional control of access for string, uint32, bit/bits
• optional automatic value increment by each read
• builtin functions for e.g. reset/datetime
• custom functions invoked by read/write to a specific address
Description of the json file or dict to be supplied:
{
"registers": 200,
--> Total number of registers
"invalid_address": {
--> List of invalid addresses, Read/Write causes invalid address response.
"registers": [
[78, 99],
--> start, end register, repeated as needed
]},
"write_allowed": {
--> default is ReadOnly, allow write (other addresses causes invalid address␣
˓→response)
"registers": [
[5, 5]
--> start, end register, repeated as needed
[61, 76],
]},
"type_uint32": {
--> Define 32 bit integers 2 registers
"value": 0,
--> Default value of uint32
"action": "random",
--> Default action to use, need to be used with care !
"registers": [
(continues on next page)
validate(address, count=1)
Check to see if the request is in range.
Parameters
• address – The starting address
• count – The number of values to test for
Returns
True if the request in within range, False otherwise
Subpackages
pymodbus.datastore.database package
Define Datastore.
class pymodbus.datastore.database.RedisSlaveContext(**kwargs)
Bases: IModbusSlaveContext
This is a modbus slave context using redis as a backing store.
getValues(fx, address, count=1)
Get count values from datastore.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to retrieve
Returns
The requested values from a:a+c
reset()
Reset all the datastores to their default values.
setValues(fx, address, values)
Set the datastore with the supplied values.
Parameters
• fx – The function we are working with
• address – The starting address
• values – The new values to be set
validate(fx, address, count=1)
Validate the request to make sure it is in range.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to test
Returns
True if the request in within range, False otherwise
class pymodbus.datastore.database.SqlSlaveContext(*args, **kwargs)
Bases: IModbusSlaveContext
This creates a modbus data model with each data access in its a block.
getValues(fx, address, count=1)
Get count values from datastore.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to retrieve
Returns
The requested values from a:a+c
reset()
Reset all the datastores to their default values.
setValues(fx, address, values, update=True)
Set the datastore with the supplied values.
Parameters
• fx – The function we are working with
• address – The starting address
• values – The new values to be set
• update – Update existing register in the db
validate(fx, address, count=1)
Validate the request to make sure it is in range.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to test
Returns
True if the request in within range, False otherwise
Submodules
pymodbus.datastore.database.redis_datastore module
pymodbus.datastore.database.sql_datastore module
reset()
Reset all the datastores to their default values.
setValues(fx, address, values, update=True)
Set the datastore with the supplied values.
Parameters
• fx – The function we are working with
• address – The starting address
• values – The new values to be set
• update – Update existing register in the db
validate(fx, address, count=1)
Validate the request to make sure it is in range.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to test
Returns
True if the request in within range, False otherwise
Submodules
pymodbus.datastore.context module
Returns
The requested values from a:a+c
register(function_code, fc_as_hex, datablock=None)
Register a datablock with the slave context.
Parameters
• function_code – function code (int)
• fc_as_hex – string representation of function code (e.g “cf” )
• datablock – datablock to associate with this function code
reset()
Reset all the datastores to their default values.
setValues(fc_as_hex, address, values)
Set the datastore with the supplied values.
Parameters
• fc_as_hex – The function we are working with
• address – The starting address
• values – The new values to be set
validate(fc_as_hex, address, count=1)
Validate the request to make sure it is in range.
Parameters
• fc_as_hex – The function we are working with
• address – The starting address
• count – The number of values to test
Returns
True if the request in within range, False otherwise
pymodbus.datastore.remote module
Remote datastore.
class pymodbus.datastore.remote.RemoteSlaveContext(client, unit=None)
Bases: IModbusSlaveContext
TODO.
This creates a modbus data model that connects to a remote device (depending on the client used)
getValues(fc_as_hex, address, count=1)
Get values from real call in validate
reset()
Reset all the datastores to their default values.
setValues(fc_as_hex, address, values)
Set the datastore with the supplied values.
Already done in validate
pymodbus.datastore.simulator module
Pymodbus ModbusSimulatorContext.
class pymodbus.datastore.simulator.Cell(type: int = 'H', access: str = 'R', value: int = 0, action:
Optional[Callable] = None)
Handle a single cell.
class pymodbus.datastore.simulator.ModbusSimulatorContext
ModbuSimulatorContext
loads a memory configuration from a json file (see examples/simulator.py for details) and prepares a
simulation of a device.
The integration is simple:
store = ModbusSimulatorContext()
StartAsyncTcpServer(context=store)
Now the server will simulate the defined device with features like:
• invalid addresses
• write protected addresses
• optional control of access for string, uint32, bit/bits
• optional automatic value increment by each read
• builtin functions for e.g. reset/datetime
• custom functions invoked by read/write to a specific address
Description of the json file or dict to be supplied:
{
"registers": 200,
--> Total number of registers
"invalid_address": {
--> List of invalid addresses, Read/Write causes invalid address response.
"registers": [
(continues on next page)
"registers": [
[5, 5]
--> start, end register, repeated as needed
[61, 76],
]},
"type_uint32": {
--> Define 32 bit integers 2 registers
"value": 0,
--> Default value of uint32
"action": "random",
--> Default action to use, need to be used with care !
"registers": [
[1, 2],
--> start, end register, repeated as needed
[3, 6],
--> start, end register can be a group of int32
{"registers": [7, 8], "value": 300},
--> Override default value
{"registers": [14, 20], "action": "increment"},
--> Override default action
{"registers": [14, 20], "value": 117, "action": "increment"},
--> Override default value and action
]},
"type_string": {
--> Define strings, variable number of registers (2 bytes)
"value": " ",
--> Default value of string ONLY 1 register, expanded automatically
"action": "",
--> Default action to use, need to be used with care !
"registers": [
[21, 22],
--> start, end register, define 1 string
{"registers": 23, 25], "value": "String"},
--> start, end register, define 1 string, with value
{"registers": 26, 27], "action": ""},
--> start, end register, define 1 string, with action
{"registers": 28, 29], "action": "", "value": "String"}
--> start, end register, define 1 string, with action and value
]},
"type_bits": {
--> Define 16 bit registers
"value": "0x00",
--> Default value of register in hex
"action": "increment",
--> Default action to use, need to be used with care !
"registers": [
(continues on next page)
pymodbus.datastore.store module
Datastore Implementation
There are two ways that the server datastore can be implemented. The first is a complete range from “address” start to
“count” number of indices. This can be thought of as a straight array:
The other way that the datastore can be implemented (and how many devices implement it) is a associate-array:
The difference between the two is that the latter will allow arbitrary gaps in its datastore while the former will not. This
is seen quite commonly in some modbus implementations. What follows is a clear example from the field:
Say a company makes two devices to monitor power usage on a rack. One works with three-phase and the other with
a single phase. The company will dictate a modbus data mapping such that registers:
n: phase 1 power
n+1: phase 2 power
n+2: phase 3 power
Using this, layout, the first device will implement n, n+1, and n+2, however, the second device may set the latter two
values to 0 or will simply not implemented the registers thus causing a single read or a range read to fail.
I have both methods implemented, and leave it up to the user to change based on their preference.
class pymodbus.datastore.store.BaseModbusDataBlock
Bases: object
Base class for a modbus datastore
Derived classes must create the following fields:
@address The starting address point @defult_value The default value of the datastore @values The actual
datastore values
Derived classes must implemented the following methods:
validate(self, address, count=1) getValues(self, address, count=1) setValues(self, address, values)
default(count, value=False)
Use to initialize a store to one value.
Parameters
• count – The number of fields to set
• value – The default value to set to the fields
getValues(address, count=1)
Return the requested values from the datastore.
Parameters
• address – The starting address
• count – The number of values to retrieve
Raises
NotImplementedException –
reset()
Reset the datastore to the initialized default value.
setValues(address, values)
Return the requested values from the datastore.
Parameters
• address – The starting address
• values – The values to store
Raises
NotImplementedException –
validate(address, count=1)
Check to see if the request is in range.
Parameters
• address – The starting address
• count – The number of values to test for
Raises
NotImplementedException –
class pymodbus.datastore.store.ModbusSequentialDataBlock(address, values)
Bases: BaseModbusDataBlock
Creates a sequential modbus datastore.
classmethod create()
Create a datastore.
With the full address space initialized to 0x00
Returns
An initialized datastore
getValues(address, count=1)
Return the requested values of the datastore.
Parameters
• address – The starting address
• count – The number of values to retrieve
Returns
The requested values from a:a+c
setValues(address, values)
Set the requested values of the datastore.
Parameters
• address – The starting address
• values – The new values to be set
validate(address, count=1)
Check to see if the request is in range.
Parameters
• address – The starting address
• count – The number of values to test for
Returns
True if the request in within range, False otherwise
class pymodbus.datastore.store.ModbusSparseDataBlock(values=None, mutable=True)
Bases: BaseModbusDataBlock
Create a sparse modbus datastore.
E.g Usage. sparse = ModbusSparseDataBlock({10: [3, 5, 6, 8], 30: 1, 40: [0]*20})
This would create a datablock with 3 blocks starting at offset 10 with length 4 , 30 with length 1 and 40 with
length 20
sparse = ModbusSparseDataBlock([10]*100) Creates a sparse datablock of length 100 starting at offset 0 and
default value of 10
sparse = ModbusSparseDataBlock() –> Create Empty datablock sparse.setValues(0, [10]*10) –> Add block 1 at
offset 0 with length 10 (default value 10) sparse.setValues(30, [20]*5) –> Add block 2 at offset 30 with length 5
(default value 20)
if mutable is set to True during initialization, the datablock can not be altered with setValues (new datablocks
can not be added)
classmethod create(values=None)
Create sparse datastore.
Use setValues to initialize registers.
Parameters
values – Either a list or a dictionary of values
Returns
An initialized datastore
getValues(address, count=1)
Return the requested values of the datastore.
Parameters
• address – The starting address
• count – The number of values to retrieve
Returns
The requested values from a:a+c
reset()
Reset the store to the initially provided defaults.
setValues(address, values, use_as_default=False)
Set the requested values of the datastore.
Parameters
• address – The starting address
• values – The new values to be set
• use_as_default – Use the values as default
Raises
ParameterException –
validate(address, count=1)
Check to see if the request is in range.
Parameters
• address – The starting address
• count – The number of values to test for
Returns
True if the request in within range, False otherwise
pymodbus.framer package
Submodules
pymodbus.framer.ascii_framer module
Ascii_framer.
class pymodbus.framer.ascii_framer.ModbusAsciiFramer(decoder, client=None)
Bases: ModbusFramer
Modbus ASCII Frame Controller.
[ Start ][Address ][ Function ][ Data ][ LRC ][ End ]
1c 2c 2c Nc 2c 2c
This framer is used for serial transmission. Unlike the RTU protocol, the data in this framer is transferred in
plain text ascii.
addToFrame(message)
Add the next message to the frame buffer.
This should be used before the decoding while loop to add the received data to the buffer handle.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Built off of a modbus request/response
Parameters
message – The request/response to send
Returns
The encoded packet
checkFrame()
Check and decode the next frame.
Returns
True if we successful, False otherwise
decode_data(data)
Decode data.
getFrame()
Get the next frame from the buffer.
Returns
The frame data or “”
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder know that there is still data
in the buffer.
Returns
True if ready, False otherwise
method = 'ascii'
populateResult(result)
Populate the modbus result header.
The serial packets do not have any header information that is copied.
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
• unit – Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or
single unit id(client/server))
• kwargs –
Raises
ModbusIOException –
resetFrame()
Reset the entire message frame.
This allows us to skip ovver errors that may be in the stream. It is hard to know if we are simply out of sync
or if there is an error in the stream as we have no way to check the start or end of the message (python just
doesn”t have the resolution to check for millisecond delays).
pymodbus.framer.binary_framer module
Binary framer.
class pymodbus.framer.binary_framer.ModbusBinaryFramer(decoder, client=None)
Bases: ModbusFramer
Modbus Binary Frame Controller.
[ Start ][Address ][ Function ][ Data ][ CRC ][ End ]
1b 1b 1b Nb 2b 1b
The idea here is that we implement the RTU protocol, however, instead of using timing for message delimiting,
we use start and end of message characters (in this case { and }). Basically, this is a binary framer.
The only case we have to watch out for is when a message contains the { or } characters. If we encounter these
characters, we simply duplicate them. Hopefully we will not encounter those characters that often and will save
a little bit of bandwitch without a real-time system.
Protocol defined by jamod.sourceforge.net.
addToFrame(message)
Add the next message to the frame buffer.
This should be used before the decoding while loop to add the received data to the buffer handle.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The request/response to send
Returns
The encoded packet
checkFrame()
Check and decode the next frame.
Returns
True if we are successful, False otherwise
decode_data(data)
Decode data.
getFrame()
Get the next frame from the buffer.
Returns
The frame data or “”
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder know that there is still data
in the buffer.
Returns
True if ready, False otherwise
method = 'binary'
populateResult(result)
Populate the modbus result header.
The serial packets do not have any header information that is copied.
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
• unit – Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or
single unit id(client/server)
• kwargs –
Raises
ModbusIOException –
resetFrame()
Reset the entire message frame.
This allows us to skip ovver errors that may be in the stream. It is hard to know if we are simply out of sync
or if there is an error in the stream as we have no way to check the start or end of the message (python just
doesn”t have the resolution to check for millisecond delays).
pymodbus.framer.rtu_framer module
RTU framer.
class pymodbus.framer.rtu_framer.ModbusRtuFramer(decoder, client=None)
Bases: ModbusFramer
Modbus RTU Frame controller.
[ Start Wait ] [Address ][ Function Code] [ Data ][ CRC ][ End Wait ]
3.5 chars 1b 1b Nb 2b 3.5 chars
Wait refers to the amount of time required to transmit at least x many characters. In this case it is 3.5 characters.
Also, if we receive a wait of 1.5 characters at any point, we must trigger an error message. Also, it appears as
though this message is little endian. The logic is simplified as the following:
block-on-read:
read until 3.5 delay
check for errors
decode
The following table is a listing of the baud wait times for the specified baud rates:
------------------------------------------------------------------
Baud 1.5c (18 bits) 3.5c (38 bits)
------------------------------------------------------------------
1200 13333.3 us 31666.7 us
4800 3333.3 us 7916.7 us
9600 1666.7 us 3958.3 us
19200 833.3 us 1979.2 us
38400 416.7 us 989.6 us
------------------------------------------------------------------
1 Byte = start + 8 bits + parity + stop = 11 bits
(1/Baud)(bits) = delay seconds
addToFrame(message)
Add the received data to the buffer handle.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The populated request/response to send
checkFrame()
Check if the next frame is available.
Return True if we were successful.
1. Populate header
populateHeader(data=None)
Try to set the headers uid, len and crc.
This method examines self._buffer and writes meta information into self._header.
Beware that this method will raise an IndexError if self._buffer is not yet long enough.
populateResult(result)
Populate the modbus result header.
The serial packets do not have any header information that is copied.
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
pymodbus.framer.socket_framer module
Socket framer.
class pymodbus.framer.socket_framer.ModbusSocketFramer(decoder, client=None)
Bases: ModbusFramer
Modbus Socket Frame controller.
Before each modbus TCP message is an MBAP header which is used as a message frame. It allows us to easily
separate messages as follows:
addToFrame(message)
Add new packet data to the current frame buffer.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The populated request/response to send
checkFrame()
Check and decode the next frame.
Return true if we were successful.
decode_data(data)
Decode data.
getFrame()
Return the next frame from the buffered data.
Returns
The next full frame buffer
getRawFrame()
Return the complete buffer.
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder factory know that there is
still data in the buffer.
Returns
True if ready, False otherwise
method = 'socket'
populateResult(result)
Populate the modbus result.
With the transport specific header information (pid, tid, uid, checksum, etc)
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
Module contents
Framer start.
class pymodbus.framer.ModbusFramer
Bases: IModbusFramer
Base Framer class.
name = ''
recvPacket(size)
Receive packet from the bus.
With specified len :param size: Number of bytes to read :return:
sendPacket(message)
Send packets on the bus.
With 3.5char delay between frames :param message: Message to be sent over the bus :return:
pymodbus.server
pymodbus.server package
Server.
import external classes, to make them easier to use:
async pymodbus.server.ServerAsyncStop()
Terminate server.
pymodbus.server.ServerStop()
Terminate server.
async pymodbus.server.StartAsyncSerialServer(context=None, identity=None, custom_functions=[],
defer_start=False, **kwargs)
Start and run a serial modbus server.
Parameters
• context – The ModbusServerContext datastore
• identity – An optional identify structure
• custom_functions – An optional list of custom function classes supported by server in-
stance.
• defer_start – if set, the server object will be returned ready to start. Otherwise, the server
will be immediately spun up without the ability to shut it off
• kwargs – The rest
async pymodbus.server.StartAsyncTcpServer(context=None, identity=None, address=None,
custom_functions=[], defer_start=False, **kwargs)
Start and run a tcp modbus server.
Parameters
• context – The ModbusServerContext datastore
• identity – An optional identify structure
• address – An optional (interface, port) to bind to.
• custom_functions – An optional list of custom function classes supported by server in-
stance.
• defer_start – if set, the server object will be returned ready to start. Otherwise, the server
will be immediately spun up without the ability to shut it off
• kwargs – The rest
Returns
an initialized but inactive server object coroutine
async pymodbus.server.StartAsyncTlsServer(context=None, identity=None, address=None, sslctx=None,
certfile=None, keyfile=None, password=None,
reqclicert=False, allow_reuse_address=False,
allow_reuse_port=False, custom_functions=[],
defer_start=False, **kwargs)
Start and run a tls modbus server.
Parameters
• context – The ModbusServerContext datastore
Submodules
pymodbus.server module
class pymodbus.server.async_io.ModbusConnectedRequestHandler(owner)
Bases: ModbusBaseRequestHandler, Protocol
Implements the modbus server protocol
This uses asyncio.Protocol to implement the client handler for a connected protocol (TCP).
connection_lost(call_exc)
Call when the connection is lost or closed.
connection_made(transport)
Call when a connection is made.
data_received(data)
Call when some data is received.
data is a non-empty bytes object containing the incoming data.
class pymodbus.server.async_io.ModbusDisconnectedRequestHandler(owner)
Bases: ModbusBaseRequestHandler, DatagramProtocol
Implements the modbus server protocol
This uses the socketserver.BaseRequestHandler to implement the client handler for a disconnected protocol
(UDP). The only difference is that we have to specify who to send the resulting packet data to.
connection_lost(call_exc)
Handle connection lost.
datagram_received(data, addr)
Call when a datagram is received.
data is a bytes object containing the incoming data. addr is the address of the peer sending the data; the
exact format depends on the transport.
error_received(exc)
Call when a previous send/receive raises an OSError.
exc is the OSError instance.
This method is called in rare conditions, when the transport (e.g. UDP) detects that a datagram could not
be delivered to its recipient. In many conditions though, undeliverable datagrams will be silently dropped.
class pymodbus.server.async_io.ModbusSerialServer(context, framer=<class
'pymodbus.framer.rtu_framer.ModbusRtuFramer'>,
identity=None, **kwargs)
Bases: object
A modbus threaded serial socket server.
We inherit and overload the socket server so that we can control the client threads as well as have a single server
context instance.
handler = None
on_connection_lost()
Call on lost connection.
async serve_forever()
Start endless loop.
async shutdown()
Terminate server.
async start()
Start connecting.
class pymodbus.server.async_io.ModbusSingleRequestHandler(owner)
Bases: ModbusBaseRequestHandler, Protocol
Implement the modbus server protocol.
This uses asyncio.Protocol to implement the client handler for a serial connection.
connection_lost(call_exc)
Handle connection lost.
connection_made(transport)
Handle connect made.
data_received(data)
Receive data.
class pymodbus.server.async_io.ModbusTcpServer(context, framer=None, identity=None, address=None,
handler=None, allow_reuse_address=False,
allow_reuse_port=False, defer_start=False,
backlog=20, **kwargs)
Bases: object
A modbus threaded tcp socket server.
We inherit and overload the socket server so that we can control the client threads as well as have a single server
context instance.
async serve_forever()
Start endless loop.
async server_close()
Close server.
async shutdown()
Shutdown server.
class pymodbus.server.async_io.ModbusTlsServer(context, framer=None, identity=None, address=None,
sslctx=None, certfile=None, keyfile=None,
password=None, reqclicert=False, handler=None,
allow_reuse_address=False, allow_reuse_port=False,
defer_start=False, backlog=20, **kwargs)
Bases: ModbusTcpServer
A modbus threaded tls socket server.
We inherit and overload the socket server so that we can control the client threads as well as have a single server
context instance.
class pymodbus.server.async_io.ModbusUdpServer(context, framer=None, identity=None, address=None,
handler=None, allow_reuse_address=False,
allow_reuse_port=False, defer_start=False,
backlog=20, **kwargs)
Bases: object
A modbus threaded udp socket server.
We inherit and overload the socket server so that we can control the client threads as well as have a single server
context instance.
async serve_forever()
Start endless loop.
async server_close()
Close server.
async shutdown()
Shutdown server.
async pymodbus.server.async_io.ServerAsyncStop()
Terminate server.
pymodbus.server.async_io.ServerStop()
Terminate server.
async pymodbus.server.async_io.StartAsyncSerialServer(context=None, identity=None,
custom_functions=[], defer_start=False,
**kwargs)
Start and run a serial modbus server.
Parameters
• context – The ModbusServerContext datastore
• identity – An optional identify structure
• custom_functions – An optional list of custom function classes supported by server in-
stance.
• defer_start – if set, the server object will be returned ready to start. Otherwise, the server
will be immediately spun up without the ability to shut it off
• kwargs – The rest
async pymodbus.server.async_io.StartAsyncTcpServer(context=None, identity=None, address=None,
custom_functions=[], defer_start=False,
**kwargs)
Start and run a tcp modbus server.
Parameters
• context – The ModbusServerContext datastore
• identity – An optional identify structure
• address – An optional (interface, port) to bind to.
• custom_functions – An optional list of custom function classes supported by server in-
stance.
• defer_start – if set, the server object will be returned ready to start. Otherwise, the server
will be immediately spun up without the ability to shut it off
• kwargs – The rest
Returns
an initialized but inactive server object coroutine
async pymodbus.server.async_io.StartAsyncTlsServer(context=None, identity=None, address=None,
sslctx=None, certfile=None, keyfile=None,
password=None, reqclicert=False,
allow_reuse_address=False,
allow_reuse_port=False, custom_functions=[],
defer_start=False, **kwargs)
Start and run a tls modbus server.
Parameters
• context – The ModbusServerContext datastore
• identity – An optional identify structure
• address – An optional (interface, port) to bind to.
• sslctx – The SSLContext to use for TLS (default None and auto create)
• certfile – The cert file path for TLS (used if sslctx is None)
• keyfile – The key file path for TLS (used if sslctx is None)
• password – The password for for decrypting the private key file
• reqclicert – Force the sever request client”s certificate
• allow_reuse_address – Whether the server will allow the reuse of an address.
• allow_reuse_port – Whether the server will allow the reuse of a port.
• custom_functions – An optional list of custom function classes supported by server in-
stance.
• defer_start – if set, the server object will be returned ready to start. Otherwise, the server
will be immediately spun up without the ability to shut it off
• kwargs – The rest
Returns
an initialized but inactive server object coroutine
async pymodbus.server.async_io.StartAsyncUdpServer(context=None, identity=None, address=None,
custom_functions=[], defer_start=False,
**kwargs)
Start and run a udp modbus server.
Parameters
• context – The ModbusServerContext datastore
• identity – An optional identify structure
• address – An optional (interface, port) to bind to.
• custom_functions – An optional list of custom function classes supported by server in-
stance.
• defer_start – if set, the server object will be returned ready to start. Otherwise, the server
will be immediately spun up without the ability to shut it off
• kwargs –
pymodbus.server.async_io.StartSerialServer(**kwargs)
Start and run a serial modbus server.
pymodbus.server.async_io.StartTcpServer(**kwargs)
Start and run a serial modbus server.
pymodbus.server.async_io.StartTlsServer(**kwargs)
Start and run a serial modbus server.
pymodbus.server.async_io.StartUdpServer(**kwargs)
Start and run a serial modbus server.
pymodbus.server.async_io.sslctx_provider(sslctx=None, certfile=None, keyfile=None, password=None,
reqclicert=False)
Provide the SSLContext for ModbusTlsServer.
If the user defined SSLContext is not passed in, sslctx_provider will produce a default one.
Parameters
• sslctx – The user defined SSLContext to use for TLS (default None and auto create)
• certfile – The cert file path for TLS (used if sslctx is None)
• keyfile – The key file path for TLS (used if sslctx is None)
• password – The password for for decrypting the private key file
• reqclicert – Force the sever request client”s certificate
pymodbus.repl package
Subpackages
pymodbus.repl.client package
Repl client.
Submodules
pymodbus.repl.client.completer module
arg_completions(words, word_before_cursor)
Generate arguments completions based on the input.
Parameters
• words – The input text broken into word tokens.
• word_before_cursor – The current word before the cursor, which might be one or more
blank spaces.
Returns
A list of completions.
property command_names
Return command names.
property commands
Return commands.
completing_arg(words, word_before_cursor)
Determine if we are currently completing an argument.
Parameters
• words – The input text broken into word tokens.
• word_before_cursor – The current word before the cursor, which might be one or more
blank spaces.
Returns
Specifies whether we are currently completing an arg.
completing_command(words, word_before_cursor)
Determine if we are dealing with supported command.
Parameters
• words – Input text broken in to word tokens.
• word_before_cursor – The current word before the cursor, which might be one or more
blank spaces.
Returns
get_completions(document, complete_event)
Get completions for the current scope.
Parameters
• document – An instance of prompt_toolkit.Document.
• complete_event – (Unused).
Returns
Yields an instance of prompt_toolkit.completion.Completion.
word_matches(word, word_before_cursor)
Match the word and word before cursor.
Parameters
• word – The input text broken into word tokens.
• word_before_cursor – The current word before the cursor, which might be one or more
blank spaces.
Returns
True if matched.
pymodbus.repl.client.helper module
print_result(data=None)
Print result object pretty.
Parameters
data – Data to be printed.
raw()
Return raw result dict.
pymodbus.repl.client.helper.get_commands(client)
Retrieve all required methods and attributes.
Of a client object and convert it to commands.
Parameters
client – Modbus Client object.
Returns
pymodbus.repl.client.main module
pymodbus.repl.client.mclient module
clear_counters(data=0, **kwargs)
Clear all counters and diag registers.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
clear_overrun_count(data=0, **kwargs)
Clear over run counter.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
force_listen_only_mode(data=0, **kwargs)
Force addressed remote device to its Listen Only Mode.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
get_clear_modbus_plus(data=0, **kwargs)
Get/clear stats of remote modbus plus device.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
get_com_event_counter(**kwargs)
Read status word and an event count.
From the remote device”s communication event counter.
Parameters
kwargs –
Returns
get_com_event_log(**kwargs)
Read status word.
Event count, message count, and a field of event bytes from the remote device.
Parameters
kwargs –
Returns
Parameters
kwargs –
Returns
read_holding_registers(address, count=1, slave=0, **kwargs)
Read count number of holding registers starting at address.
Parameters
• address – starting register offset to read from
• count – Number of registers to read
• slave – Modbus slave unit ID
• kwargs –
Returns
read_input_registers(address, count=1, slave=0, **kwargs)
Read count number of input registers starting at address.
Parameters
• address – starting register offset to read from to
• count – Number of registers to read
• slave – Modbus slave unit ID
• kwargs –
Returns
readwrite_registers(read_address, read_count, write_address, write_registers, unit=0, **kwargs)
Read read_count number of holding registers.
Starting at read_address and write write_registers starting at write_address.
Parameters
• read_address – register offset to read from
• read_count – Number of registers to read
• write_address – register offset to write to
• write_registers – List of register values to write (comma separated)
• unit – Modbus slave unit ID
• kwargs –
Returns
report_slave_id(unit=0, **kwargs)
Report information about remote slave ID.
Parameters
• unit – Modbus slave unit ID
• kwargs –
Returns
restart_comm_option(toggle=False, **kwargs)
Initialize and restart remote devices.
Serial interface and clear all of its communications event counters.
Parameters
• toggle – Toggle Status [ON(0xff00)/OFF(0x0000]
• kwargs –
Returns
return_bus_com_error_count(data=0, **kwargs)
Return count of CRC errors received by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_bus_exception_error_count(data=0, **kwargs)
Return count of Modbus exceptions returned by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_bus_message_count(data=0, **kwargs)
Return count of message detected on bus by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_diagnostic_register(data=0, **kwargs)
Read 16-bit diagnostic register.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_iop_overrun_count(data=0, **kwargs)
Return count of iop overrun errors by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_query_data(message=0, **kwargs)
Loop back data sent in response.
Parameters
• message – Message to be looped back
• kwargs –
Returns
return_slave_bus_char_overrun_count(data=0, **kwargs)
Return count of messages not handled.
By remote slave due to character overrun condition.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_slave_busy_count(data=0, **kwargs)
Return count of server busy exceptions sent by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_slave_message_count(data=0, **kwargs)
Return count of messages addressed to remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_slave_no_ack_count(data=0, **kwargs)
Return count of NO ACK exceptions sent by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
return_slave_no_response_count(data=0, **kwargs)
Return count of No responses by remote slave.
Parameters
• data – Data field (0x0000)
• kwargs –
Returns
get_baudrate()
Get serial Port baudrate.
Returns
Current baudrate
get_bytesize()
Get number of data bits.
Returns
Current bytesize
get_parity()
Enable Parity Checking.
Returns
Current parity setting
get_port()
Get serial Port.
Returns
Current Serial port
get_serial_settings()
Get Current Serial port settings.
Returns
Current Serial settings as dict.
get_stopbits()
Get number of stop bits.
Returns
Current Stop bits
get_timeout()
Get serial Port Read timeout.
Returns
Current read imeout.
set_baudrate(value)
Set baudrate setter.
Parameters
value – <supported baudrate>
set_bytesize(value)
Set Byte size.
Parameters
value – Possible values (5, 6, 7, 8)
set_parity(value)
Set parity Setter.
Parameters
value – Possible values (“N”, “E”, “O”, “M”, “S”)
set_port(value)
Set serial Port setter.
Parameters
value – New port
set_stopbits(value)
Set stop bit.
Parameters
value – Possible values (1, 1.5, 2)
set_timeout(value)
Read timeout setter.
Parameters
value – Read Timeout in seconds
class pymodbus.repl.client.mclient.ModbusTcpClient(**kwargs)
Bases: ExtendedRequestSupport, ModbusTcpClient
TCP client.
pymodbus.repl.client.mclient.handle_brodcast(func)
Handle broadcast.
pymodbus.repl.client.mclient.make_response_dict(resp)
Make response dict.
pymodbus.repl..server package
Repl server.
Submodules
pymodbus.repl.server.cli module
async pymodbus.repl.server.cli.run_repl(server)
Run repl server.
pymodbus.repl.server.cli.warning(message)
Show warning.
pymodbus.repl.server.main module
binary = 'binary'
rtu = 'rtu'
socket = 'socket'
tls = 'tls'
class pymodbus.repl.server.main.ModbusServerTypes(value)
Bases: str, Enum
Server types.
serial = 'serial'
tcp = 'tcp'
tls = 'tls'
udp = 'udp'
5.1.2 Submodules
The Request PDU specifies the starting address, ie the address of the first coil specified, and the number of coils.
In the PDU Coils are addressed starting at zero. Therefore coils numbered 1-16 are addressed as 0-15.
execute(context)
Run a read coils request against a datastore.
Before running the request, we make sure that the request is in the max valid range (0x001-0x7d0). Next
we make sure that the request is valid against the current datastore.
Parameters
context – The datastore to request from
Returns
An initialized ReadCoilsResponse, or an ExceptionResponse if an error occurred
function_code = 1
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Output Address (2 byte) + Quantity of Outputs (2 Bytes) :return:
class pymodbus.bit_write_message.WriteMultipleCoilsResponse(address=None, count=None,
**kwargs)
Bases: ModbusResponse
The normal response returns the function code.
Starting address, and quantity of coils forced.
decode(data)
Decode a write coils response.
Parameters
data – The packet data to decode
encode()
Encode write coils response.
Returns
The byte encoded message
function_code = 15
function_code = 5
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) :return:
class pymodbus.bit_write_message.WriteSingleCoilResponse(address=None, value=None, **kwargs)
Bases: ModbusResponse
The normal response is an echo of the request.
Returned after the coil state has been written.
decode(data)
Decode a write coil response.
Parameters
data – The packet data to decode
encode()
Encode write coil response.
Returns
The byte encoded message
function_code = 5
property Counter
property Delimiter
property Events
property Identity
property ListenOnly
property Mode
property Plus
addEvent(event)
Add a new event to the event log.
Parameters
event – A new event to add to the log
clearEvents()
Clear the current list of events.
getDiagnostic(bit)
Get the value in the diagnostic register.
Parameters
bit – The bit to get
Returns
The current value of the requested bit
getDiagnosticRegister()
Get the entire diagnostic register.
Returns
The diagnostic register collection
getEvents()
Return an encoded collection of the event log.
Returns
The encoded events packet
reset()
Clear all of the system counters and the diagnostic register.
setDiagnostic(mapping)
Set the value in the diagnostic register.
Parameters
mapping – Dictionary of key:value pairs to set
class pymodbus.device.ModbusDeviceIdentification(info=None, info_name=None)
Bases: object
This is used to supply the device identification.
For the readDeviceIdentification function
For more information read section 6.21 of the modbus application protocol.
property MajorMinorRevision
property ModelName
property ProductCode
property ProductName
property UserApplicationName
property VendorName
property VendorUrl
summary()
Return a summary of the main items.
Returns
An dictionary of the main items
update(value)
Update the values of this identity.
using another identify as the value
Parameters
value – The value to copy values from
class pymodbus.device.ModbusPlusStatistics
Bases: object
This is used to maintain the current modbus plus statistics count.
As of right now this is simply a stub to complete the modbus implementation. For more information, see the
modbus implementation guide page 87.
encode()
Return a summary of the modbus plus statistics.
Returns
54 16-bit words representing the status
reset()
Clear all of the modbus plus statistics.
summary()
Return a summary of the modbus plus statistics.
Returns
54 16-bit words representing the status
execute(*args)
Execute the diagnostic request on the given device.
Returns
The initialized response message
sub_function_code = 20
class pymodbus.diag_message.DiagnosticStatusRequest(**kwargs)
Bases: ModbusRequest
This is a base class for all of the diagnostic request functions.
decode(data)
Decode a diagnostic request.
Parameters
data – The data to decode into the function code
encode()
Encode a diagnostic response.
we encode the data set in self.message
Returns
The encoded packet
function_code = 8
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Sub function code (2 byte) + Data (2 * N bytes) :return:
class pymodbus.diag_message.DiagnosticStatusResponse(**kwargs)
Bases: ModbusResponse
Diagnostic status.
This is a base class for all of the diagnostic response functions
It works by performing all of the encoding and decoding of variable data and lets the higher classes define what
extra data to append and how to execute a request
decode(data)
Decode diagnostic response.
Parameters
data – The data to decode into the function code
encode()
Encode diagnostic response.
we encode the data set in self.message
Returns
The encoded packet
function_code = 8
class pymodbus.diag_message.ForceListenOnlyModeResponse(**kwargs)
Bases: DiagnosticStatusResponse
Forces the addressed remote device to its Listen Only Mode for MODBUS communications.
This isolates it from the other devices on the network, allowing them to continue communicating without inter-
ruption from the addressed remote device. No response is returned.
This does not send a response
should_respond = False
sub_function_code = 4
get_response_pdu_size()
Return a series of 54 16-bit words (108 bytes) in the data field of the response.
This function differs from the usual two-byte length of the data field. The data contains the statistics for
the Modbus Plus peer processor in the slave device. Func_code (1 byte) + Sub function code (2 byte) +
Operation (2 byte) + Data (108 bytes) :return:
sub_function_code = 21
Returns
The initialized response message
sub_function_code = 12
sub_function_code = 11
sub_function_code = 0
sub_function_code = 17
sub_function_code = 15
class pymodbus.events.EnteredListenModeEvent
Bases: ModbusEvent
Enter Remote device Listen Only Mode
The remote device stores this type of event byte when it enters the Listen Only Mode. The event is defined by a
content of 04 hex.
decode(event)
Decode the event message to its status bits.
Parameters
event – The event to decode
Raises
ParameterException –
encode()
Encode the status bits to an event message.
Returns
The encoded event message
value = 4
class pymodbus.events.ModbusEvent
Bases: object
Define modbus events.
decode(event)
Decode the event message to its status bits.
Parameters
event – The event to decode
Raises
NotImplementedException –
encode()
Encode the status bits to an event message.
Raises
NotImplementedException –
class pymodbus.events.RemoteReceiveEvent(**kwargs)
Bases: ModbusEvent
Remote device MODBUS Receive Event.
The remote device stores this type of event byte when a query message is received. It is stored before the remote
device processes the message. This event is defined by bit 7 set to logic “1”. The other bits will be set to a logic
“1” if the corresponding condition is TRUE. The bit layout is:
Bit Contents
----------------------------------
0 Not Used
2 Not Used
3 Not Used
4 Character Overrun
(continues on next page)
decode(event)
Decode the event message to its status bits.
Parameters
event – The event to decode
encode()
Encode the status bits to an event message.
Returns
The encoded event message
class pymodbus.events.RemoteSendEvent(**kwargs)
Bases: ModbusEvent
Remote device MODBUS Send Event.
The remote device stores this type of event byte when it finishes processing a request message. It is stored if the
remote device returned a normal or exception response, or no response.
This event is defined by bit 7 set to a logic “0”, with bit 6 set to a “1”. The other bits will be set to a logic “1” if
the corresponding condition is TRUE. The bit layout is:
Bit Contents
-----------------------------------------------------------
0 Read Exception Sent (Exception Codes 1-3)
1 Slave Abort Exception Sent (Exception Code 4)
2 Slave Busy Exception Sent (Exception Codes 5-6)
3 Slave Program NAK Exception Sent (Exception Code 7)
4 Write Timeout Error Occurred
5 Currently in Listen Only Mode
6 1
7 0
decode(event)
Decode the event message to its status bits.
Parameters
event – The event to decode
encode()
Encode the status bits to an event message.
Returns
The encoded event message
Pymodbus Exceptions.
Custom exceptions to be used in the Modbus code.
exception pymodbus.exceptions.ConnectionException(string='')
Bases: ModbusException
Error resulting from a bad connection.
exception pymodbus.exceptions.InvalidMessageReceivedException(string='')
Bases: ModbusException
Error resulting from invalid response received or decoded.
exception pymodbus.exceptions.MessageRegisterException(string='')
Bases: ModbusException
Error resulting from failing to register a custom message request/response.
exception pymodbus.exceptions.ModbusException(string)
Bases: Exception
Base modbus exception.
isError()
Error
exception pymodbus.exceptions.ModbusIOException(string='', function_code=None)
Bases: ModbusException
Error resulting from data i/o.
exception pymodbus.exceptions.NoSuchSlaveException(string='')
Bases: ModbusException
Error resulting from making a request to a slave that does not exist.
exception pymodbus.exceptions.NotImplementedException(string='')
Bases: ModbusException
Error resulting from not implemented function.
exception pymodbus.exceptions.ParameterException(string='')
Bases: ModbusException
Error resulting from invalid parameter.
class pymodbus.factory.ClientDecoder
Bases: IModbusDecoder
Response Message Factory (Client).
To add more implemented functions, simply add them to the list
decode(message)
Decode a response packet.
Parameters
message – The raw packet to decode
Returns
The decoded modbus message or None if error
lookupPduClass(function_code)
Use function_code to determine the class of the PDU.
Parameters
function_code – The function code specified in a frame.
Returns
The class of the PDU that has a matching function_code.
register(function)
Register a function and sub function class with the decoder.
class pymodbus.factory.ServerDecoder
Bases: IModbusDecoder
Request Message Factory (Server).
To add more implemented functions, simply add them to the list
decode(message)
Decode a request packet
Parameters
message – The raw modbus request packet
Returns
The decoded modbus message or None if error
lookupPduClass(function_code)
Use function_code to determine the class of the PDU.
Parameters
function_code – The function code specified in a frame.
Returns
The class of the PDU that has a matching function_code.
register(function=None)
Register a function and sub function class with the decoder.
Parameters
function – Custom function class to register
Raises
MessageRegisterException –
Returns
The number of bytes in the response.
decode(data)
Decode a the response.
Parameters
data – The packet data to decode
encode()
Encode the response.
Returns
The byte encoded message
function_code = 24
The quantity of registers to be read, combined with all other fields in the expected response, must not exceed the
allowable length of the MODBUS PDU: 235 bytes.
decode(data)
Decode the incoming request.
Parameters
data – The data to decode into the address
encode()
Encode the request packet.
Returns
The byte encoded packet
execute(context)
Run a read exception status request against the store.
Parameters
context – The datastore to request from
Returns
The populated response
function_code = 20
decode(data)
Decode the incoming request.
Parameters
data – The data to decode into the address
encode()
Encode the response.
Returns
The byte encoded message
function_code = 21
Pymodbus Interfaces.
A collection of base classes that are used throughout the pymodbus library.
class pymodbus.interfaces.IModbusDecoder
Bases: object
Modbus Decoder Base Class.
This interface must be implemented by a modbus message decoder factory. These factories are responsible for
abstracting away converting a raw packet into a request / response message object.
decode(message)
Decode a given packet.
Parameters
message – The raw modbus request packet
Raises
NotImplementedException –
lookupPduClass(function_code)
Use function_code to determine the class of the PDU.
Parameters
function_code – The function code specified in a frame.
Raises
NotImplementedException –
register(function)
Register a function and sub function class with the decoder.
Parameters
function – Custom function class to register
Raises
NotImplementedException –
class pymodbus.interfaces.IModbusFramer
Bases: object
A framer strategy interface.
The idea is that we abstract away all the detail about how to detect if a current message frame exists, decoding
it, sending it, etc so that we can plug in a new Framer object (tcp, rtu, ascii).
addToFrame(message)
Add the next message to the frame buffer.
This should be used before the decoding while loop to add the received data to the buffer handle.
Parameters
message – The most recent packet
Raises
NotImplementedException –
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
The raw packet is built off of a fully populated modbus request / response message.
Parameters
message – The request/response to send
Raises
NotImplementedException –
checkFrame()
Check and decode the next frame.
Raises
NotImplementedException –
getFrame()
Get the next frame from the buffer.
Raises
NotImplementedException –
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder know that there is still data
in the buffer.
Raises
NotImplementedException –
populateResult(result)
Populate the modbus result with current frame header.
We basically copy the data back over from the current header to the result header. This may not be needed
for serial messages.
Parameters
result – The response packet
Raises
NotImplementedException –
processIncomingPacket(data, callback)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 / N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
Raises
NotImplementedException –
class pymodbus.interfaces.IModbusSlaveContext
Bases: object
Interface for a modbus slave data context.
Derived classes must implemented the following methods:
reset(self) validate(self, fx, address, count=1) getValues(self, fx, address, count=1) setValues(self, fx, ad-
dress, values)
decode(fx)
Convert the function code to the datastore to.
Parameters
fx – The function we are working with
Returns
one of [d(iscretes),i(nputs),h(olding),c(oils)
getValues(fx, address, count=1)
Get count values from datastore.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to retrieve
Raises
NotImplementedException –
reset()
Reset all the datastores to their default values.
setValues(fx, address, values)
Set the datastore with the supplied values.
Parameters
• fx – The function we are working with
• address – The starting address
• values – The new values to be set
Raises
NotImplementedException –
validate(fx, address, count=1)
Validate the request to make sure it is in range.
Parameters
• fx – The function we are working with
• address – The starting address
• count – The number of values to test
Raises
NotImplementedException –
class pymodbus.interfaces.IPayloadBuilder
Bases: object
This is an interface to a class that can build a payload for a modbus register write command.
It should abstract the codec for encoding data to the required format (bcd, binary, char, etc).
build()
Return the payload buffer as a list.
This list is two bytes per element and can thus be treated as a list of registers.
Raises
NotImplementedException –
class pymodbus.interfaces.Singleton(*args, **kwargs)
Bases: object
Singleton base class.
https://mail.python.org/pipermail/python-list/2007-July/450681.html
encode()
Encode the request packet.
Returns
The byte encoded packet
execute(context)
Run a read exception status request against the store.
Parameters
context – The datastore to request from
Returns
The populated response
function_code = 43
sub_function_code = 14
sub_function_code = 14
class pymodbus.other_message.GetCommEventLogRequest(**kwargs)
Bases: ModbusRequest
This function code is used to get a status word.
Event count, message count, and a field of event bytes from the remote device.
The status word and event counts are identical to that returned by the Get Communications Event Counter function
(11, 0B hex).
The message counter contains the quantity of messages processed by the remote device since its last restart, clear
counters operation, or power-up. This count is identical to that returned by the Diagnostic function (code 08),
sub-function Return Bus Message Count (code 11, 0B hex).
The event bytes field contains 0-64 bytes, with each byte corresponding to the status of one MODBUS send or
receive operation for the remote device. The remote device enters the events into the field in chronological order.
Byte 0 is the most recent event. Each new byte flushes the oldest byte from the field.
decode(data)
Decode data part of the message.
Parameters
data – The incoming data
encode()
Encode the message.
execute(context=None)
Run a read exception status request against the store.
Returns
The populated response
function_code = 12
class pymodbus.other_message.GetCommEventLogResponse(**kwargs)
Bases: ModbusResponse
Get Comm event log response.
The normal response contains a two-byte status word field, a two-byte event count field, a two-byte message
count field, and a field containing 0-64 bytes of events. A byte count field defines the total length of the data in
these four field
decode(data)
Decode a the response.
Parameters
data – The packet data to decode
encode()
Encode the response.
Returns
The byte encoded message
function_code = 12
encode()
Encode the message.
execute(context=None)
Run a report slave id request against the store.
Returns
The populated response
function_code = 17
builder = BinaryPayloadBuilder(byteorder=Endian.Little)
builder.add_8bit_uint(1)
builder.add_16bit_uint(2)
payload = builder.build()
add_16bit_float(value)
Add a 16 bit float to the buffer.
Parameters
value – The value to add to the buffer
add_16bit_int(value)
Add a 16 bit signed int to the buffer.
Parameters
value – The value to add to the buffer
add_16bit_uint(value)
Add a 16 bit unsigned int to the buffer.
Parameters
value – The value to add to the buffer
add_32bit_float(value)
Add a 32 bit float to the buffer.
Parameters
value – The value to add to the buffer
add_32bit_int(value)
Add a 32 bit signed int to the buffer.
Parameters
value – The value to add to the buffer
add_32bit_uint(value)
Add a 32 bit unsigned int to the buffer.
Parameters
value – The value to add to the buffer
add_64bit_float(value)
Add a 64 bit float(double) to the buffer.
Parameters
value – The value to add to the buffer
add_64bit_int(value)
Add a 64 bit signed int to the buffer.
Parameters
value – The value to add to the buffer
add_64bit_uint(value)
Add a 64 bit unsigned int to the buffer.
Parameters
value – The value to add to the buffer
add_8bit_int(value)
Add a 8 bit signed int to the buffer.
Parameters
value – The value to add to the buffer
add_8bit_uint(value)
Add a 8 bit unsigned int to the buffer.
Parameters
value – The value to add to the buffer
add_bits(values)
Add a collection of bits to be encoded.
If these are less than a multiple of eight, they will be left padded with 0 bits to make it so.
Parameters
values – The value to add to the buffer
add_string(value)
Add a string to the buffer.
Parameters
value – The value to add to the buffer
build()
Return the payload buffer as a list.
This list is two bytes per element and can thus be treated as a list of registers.
Returns
The payload buffer as a list
reset()
Reset the payload buffer.
to_coils()
Convert the payload buffer into a coil layout that can be used as a context block.
Returns
The coil layout to use as a block
to_registers()
Convert the payload buffer to register layout that can be used as a context block.
Returns
The register layout to use as a block
to_string()
Return the payload buffer as a string.
Returns
The payload buffer as a string
class pymodbus.payload.BinaryPayloadDecoder(payload, byteorder='<', wordorder='>')
Bases: object
A utility that helps decode payload messages from a modbus response message.
It really is just a simple wrapper around the struct module, however it saves time looking up the format strings.
What follows is a simple example:
decoder = BinaryPayloadDecoder(payload)
first = decoder.decode_8bit_uint()
second = decoder.decode_16bit_uint()
decode_16bit_int()
Decode a 16 bit signed int from the buffer.
decode_16bit_uint()
Decode a 16 bit unsigned int from the buffer.
decode_32bit_float()
Decode a 32 bit float from the buffer.
decode_32bit_int()
Decode a 32 bit signed int from the buffer.
decode_32bit_uint()
Decode a 32 bit unsigned int from the buffer.
decode_64bit_float()
Decode a 64 bit float(double) from the buffer.
decode_64bit_int()
Decode a 64 bit signed int from the buffer.
decode_64bit_uint()
Decode a 64 bit unsigned int from the buffer.
decode_8bit_int()
Decode a 8 bit signed int from the buffer.
decode_8bit_uint()
Decode a 8 bit unsigned int from the buffer.
decode_bits(package_len=1)
Decode a byte worth of bits from the buffer.
decode_string(size=1)
Decode a string from the buffer.
Parameters
size – The size of the string to decode
classmethod fromCoils(coils, byteorder='<', wordorder='>')
Initialize a payload decoder with the result of reading of coils.
The coils are treated as a list of bit(boolean) values.
Parameters
• coils – The coil results to initialize with
• byteorder – The endianness of the payload
• wordorder – The endianness of the payload
Returns
An initialized PayloadDecoder
Raises
ParameterException –
decode(data)
Decode a modbus exception response.
Parameters
data – The packet data to decode
encode()
Encode a modbus exception response.
Returns
The encoded exception packet
class pymodbus.pdu.IllegalFunctionRequest(function_code, **kwargs)
Bases: ModbusRequest
Define the Modbus slave exception type “Illegal Function”.
This exception code is returned if the slave:
ErrorCode = 1
decode(data)
Decode so this failure will run correctly.
Parameters
data – Not used
execute(context)
Build an illegal function request error response.
Parameters
context – The current context for the message
Returns
The error response packet
class pymodbus.pdu.ModbusExceptions(*args, **kwargs)
Bases: Singleton
An enumeration of the valid modbus exceptions.
Acknowledge = 5
GatewayNoResponse = 11
GatewayPathUnavailable = 10
IllegalAddress = 2
IllegalFunction = 1
IllegalValue = 3
MemoryParityError = 8
SlaveBusy = 6
SlaveFailure = 4
classmethod decode(code)
Give an error code, translate it to a string error name.
Parameters
code – The code number to translate
class pymodbus.pdu.ModbusRequest(unit=0, **kwargs)
Bases: ModbusPDU
Base class for a modbus request PDU.
doException(exception)
Build an error response based on the function.
Parameters
exception – The exception to return
Raises
An exception response
function_code = -1
registers
A list of register values
class pymodbus.register_read_message.ReadWriteMultipleRegistersRequest(**kwargs)
Bases: ModbusRequest
Read/write multiple registers.
This function code performs a combination of one read operation and one write operation in a single MODBUS
transaction. The write operation is performed before the read.
Holding registers are addressed starting at zero. Therefore holding registers 1-16 are addressed in the PDU as
0-15.
The request specifies the starting address and number of holding registers to be read as well as the starting address,
number of holding registers, and the data to be written. The byte count specifies the number of bytes to follow
in the write data field.”
decode(data)
Decode the register request packet.
Parameters
data – The request to decode
encode()
Encode the request packet.
Returns
The encoded packet
execute(context)
Run a write single register request against a datastore.
Parameters
context – The datastore to request from
Returns
An initialized ReadWriteMultipleRegistersResponse, or an ExceptionResponse if
an error occurred
function_code = 23
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) :return:
class pymodbus.register_read_message.ReadWriteMultipleRegistersResponse(values=None,
**kwargs)
Bases: ModbusResponse
Read/write multiple registers.
The normal response contains the data from the group of registers that were read. The byte count field specifies
the quantity of bytes to follow in the read data field.
The requested registers can be found in the .registers list.
decode(data)
Decode the register response packet.
Parameters
data – The response to decode
encode()
Encode the response packet.
Returns
The encoded packet
function_code = 23
encode()
Encode the response.
Returns
The byte encoded message
function_code = 22
class pymodbus.register_write_message.WriteMultipleRegistersRequest(address=None,
values=None, unit=None,
**kwargs)
Bases: ModbusRequest
This function code is used to write a block.
Of contiguous registers (1 to approx. 120 registers) in a remote device.
The requested written values are specified in the request data field. Data is packed as two bytes per register.
decode(data)
Decode a write single register packet packet request.
Parameters
data – The request to decode
encode()
Encode a write single register packet packet request.
Returns
The encoded packet
execute(context)
Run a write single register request against a datastore.
Parameters
context – The datastore to request from
Returns
An initialized response, exception message otherwise
function_code = 16
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Starting Address (2 byte) + Quantity of Registers (2 Bytes) :return:
class pymodbus.register_write_message.WriteMultipleRegistersResponse(address=None,
count=None, **kwargs)
Bases: ModbusResponse
The normal response returns the function code.
Starting address, and quantity of registers written.
decode(data)
Decode a write single register packet packet request.
Parameters
data – The request to decode
encode()
Encode a write single register packet packet request.
Returns
The encoded packet
function_code = 16
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Register Address(2 byte) + Register Value (2 bytes) :return:
class pymodbus.register_write_message.WriteSingleRegisterResponse(address=None, value=None,
**kwargs)
Bases: ModbusResponse
The normal response is an echo of the request.
Returned after the register contents have been written.
decode(data)
Decode a write single register packet packet request.
Parameters
data – The request to decode
encode()
Encode a write single register packet packet request.
Returns
The encoded packet
function_code = 6
get_response_pdu_size()
Get response pdu size.
Func_code (1 byte) + Starting Address (2 byte) + And_mask (2 Bytes) + OrMask (2 Bytes) :return:
This framer is used for serial transmission. Unlike the RTU protocol, the data in this framer is transferred in
plain text ascii.
addToFrame(message)
Add the next message to the frame buffer.
This should be used before the decoding while loop to add the received data to the buffer handle.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Built off of a modbus request/response
Parameters
message – The request/response to send
Returns
The encoded packet
checkFrame()
Check and decode the next frame.
Returns
True if we successful, False otherwise
decode_data(data)
Decode data.
getFrame()
Get the next frame from the buffer.
Returns
The frame data or “”
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder know that there is still data
in the buffer.
Returns
True if ready, False otherwise
method = 'ascii'
populateResult(result)
Populate the modbus result header.
The serial packets do not have any header information that is copied.
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
• unit – Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or
single unit id(client/server))
• kwargs –
Raises
ModbusIOException –
resetFrame()
Reset the entire message frame.
This allows us to skip ovver errors that may be in the stream. It is hard to know if we are simply out of sync
or if there is an error in the stream as we have no way to check the start or end of the message (python just
doesn”t have the resolution to check for millisecond delays).
The idea here is that we implement the RTU protocol, however, instead of using timing for message delimiting,
we use start and end of message characters (in this case { and }). Basically, this is a binary framer.
The only case we have to watch out for is when a message contains the { or } characters. If we encounter these
characters, we simply duplicate them. Hopefully we will not encounter those characters that often and will save
a little bit of bandwitch without a real-time system.
Protocol defined by jamod.sourceforge.net.
addToFrame(message)
Add the next message to the frame buffer.
This should be used before the decoding while loop to add the received data to the buffer handle.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The request/response to send
Returns
The encoded packet
checkFrame()
Check and decode the next frame.
Returns
True if we are successful, False otherwise
decode_data(data)
Decode data.
getFrame()
Get the next frame from the buffer.
Returns
The frame data or “”
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder know that there is still data
in the buffer.
Returns
True if ready, False otherwise
method = 'binary'
populateResult(result)
Populate the modbus result header.
The serial packets do not have any header information that is copied.
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
• unit – Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or
single unit id(client/server)
• kwargs –
Raises
ModbusIOException –
resetFrame()
Reset the entire message frame.
This allows us to skip ovver errors that may be in the stream. It is hard to know if we are simply out of sync
or if there is an error in the stream as we have no way to check the start or end of the message (python just
doesn”t have the resolution to check for millisecond delays).
class pymodbus.transaction.ModbusRtuFramer(decoder, client=None)
Bases: ModbusFramer
Modbus RTU Frame controller.
[ Start Wait ] [Address ][ Function Code] [ Data ][ CRC ][ End Wait ]
3.5 chars 1b 1b Nb 2b 3.5 chars
Wait refers to the amount of time required to transmit at least x many characters. In this case it is 3.5 characters.
Also, if we receive a wait of 1.5 characters at any point, we must trigger an error message. Also, it appears as
though this message is little endian. The logic is simplified as the following:
block-on-read:
read until 3.5 delay
check for errors
decode
The following table is a listing of the baud wait times for the specified baud rates:
------------------------------------------------------------------
Baud 1.5c (18 bits) 3.5c (38 bits)
------------------------------------------------------------------
1200 13333.3 us 31666.7 us
4800 3333.3 us 7916.7 us
9600 1666.7 us 3958.3 us
19200 833.3 us 1979.2 us
38400 416.7 us 989.6 us
------------------------------------------------------------------
1 Byte = start + 8 bits + parity + stop = 11 bits
(1/Baud)(bits) = delay seconds
addToFrame(message)
Add the received data to the buffer handle.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The populated request/response to send
checkFrame()
Check if the next frame is available.
Return True if we were successful.
1. Populate header
2. Discard frame if UID does not match
decode_data(data)
Decode data.
getFrame()
Get the next frame from the buffer.
Returns
The frame data or “”
getRawFrame()
Return the complete buffer.
get_expected_response_length(data)
Get the expected response length.
Parameters
data – Message data read so far
Raises
IndexError – If not enough data to read byte count
Returns
Total frame size
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder know that there is still data
in the buffer.
Returns
True if ready, False otherwise
method = 'rtu'
populateHeader(data=None)
Try to set the headers uid, len and crc.
This method examines self._buffer and writes meta information into self._header.
Beware that this method will raise an IndexError if self._buffer is not yet long enough.
populateResult(result)
Populate the modbus result header.
The serial packets do not have any header information that is copied.
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
• unit – Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or
single unit id(client/server)
• kwargs –
recvPacket(size)
Receive packet from the bus with specified len.
Parameters
size – Number of bytes to read
Returns
resetFrame()
Reset the entire message frame.
This allows us to skip over errors that may be in the stream. It is hard to know if we are simply out of sync
or if there is an error in the stream as we have no way to check the start or end of the message (python just
doesn”t have the resolution to check for millisecond delays).
sendPacket(message)
Send packets on the bus with 3.5char delay between frames.
Parameters
message – Message to be sent over the bus
Returns
class pymodbus.transaction.ModbusSocketFramer(decoder, client=None)
Bases: ModbusFramer
Modbus Socket Frame controller.
Before each modbus TCP message is an MBAP header which is used as a message frame. It allows us to easily
separate messages as follows:
addToFrame(message)
Add new packet data to the current frame buffer.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The populated request/response to send
checkFrame()
Check and decode the next frame.
Return true if we were successful.
decode_data(data)
Decode data.
getFrame()
Return the next frame from the buffered data.
Returns
The next full frame buffer
getRawFrame()
Return the complete buffer.
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder factory know that there is
still data in the buffer.
Returns
True if ready, False otherwise
method = 'socket'
populateResult(result)
Populate the modbus result.
With the transport specific header information (pid, tid, uid, checksum, etc)
Parameters
result – The response packet
processIncomingPacket(data, callback, unit, **kwargs)
Process new packet pattern.
This takes in a new request packet, adds it to the current packet stream, and performs framing on it. That
is, checks for complete messages, and once found, will process all that exist. This handles the case when
we read N + 1 or 1 // N messages at a time instead of 1.
The processed and decoded messages are pushed to the callback function to process and send.
Parameters
• data – The new packet data
• callback – The function to send results to
• unit – Process if unit id matches, ignore otherwise (could be a list of unit ids (server) or
single unit id(client/server)
• kwargs –
resetFrame()
Reset the entire message frame.
This allows us to skip ovver errors that may be in the stream. It is hard to know if we are simply out of sync
or if there is an error in the stream as we have no way to check the start or end of the message (python just
doesn”t have the resolution to check for millisecond delays).
class pymodbus.transaction.ModbusTlsFramer(decoder, client=None)
Bases: ModbusFramer
Modbus TLS Frame controller
No prefix MBAP header before decrypted PDU is used as a message frame for Modbus Security Application
Protocol. It allows us to easily separate decrypted messages which is PDU as follows:
[ Function Code] [ Data ]
1b Nb
addToFrame(message)
Add new packet data to the current frame buffer.
Parameters
message – The most recent packet
advanceFrame()
Skip over the current framed message.
This allows us to skip over the current message after we have processed it or determined that it contains an
error. It also has to reset the current frame header handle
buildPacket(message)
Create a ready to send modbus packet.
Parameters
message – The populated request/response to send
checkFrame()
Check and decode the next frame.
Return true if we were successful.
decode_data(data)
Decode data.
getFrame()
Return the next frame from the buffered data.
Returns
The next full frame buffer
getRawFrame()
Return the complete buffer.
isFrameReady()
Check if we should continue decode logic.
This is meant to be used in a while loop in the decoding phase to let the decoder factory know that there is
still data in the buffer.
Returns
True if ready, False otherwise
method = 'tls'
populateResult(result)
Populate the modbus result.
With the transport specific header information (no header before PDU in decrypted message)
Parameters
result – The response packet
Modbus Utilities.
A collection of utilities for packing data, unpacking data computing checksums, and decode checksums.
pymodbus.utilities.checkCRC(data, check)
Check if the data matches the passed in CRC.
Parameters
• data – The data to create a crc16 of
• check – The CRC to validate
Returns
True if matched, False otherwise
pymodbus.utilities.checkLRC(data, check)
Check if the passed in data matches the LRC.
Parameters
• data – The data to calculate
• check – The LRC to validate
Returns
True if matched, False otherwise
pymodbus.utilities.computeCRC(data)
Compute a crc16 on the passed in string.
For modbus, this is only used on the binary serial protocols (in this case RTU).
The difference between modbus”s crc16 and a normal crc16 is that modbus starts the crc value out at 0xffff.
Parameters
data – The data to create a crc16 of
Returns
The calculated CRC
pymodbus.utilities.computeLRC(data)
Use to compute the longitudinal redundancy check against a string.
This is only used on the serial ASCII modbus protocol. A full description of this implementation can be found
in appendix B of the serial line modbus description.
Parameters
data – The data to apply a lrc to
Returns
The calculated LRC
pymodbus.utilities.default(value)
Return the default value of object.
Parameters
value – The value to get the default of
Returns
The default value
pymodbus.utilities.pack_bitstring(bits)
Create a string out of an array of bits.
Parameters
bits – A bit array
example:
pymodbus.utilities.rtuFrameSize(data, byte_count_pos)
Calculate the size of the frame based on the byte count.
Parameters
• data – The buffer containing the frame.
• byte_count_pos – The index of the byte count in the buffer.
Returns
The size of the frame.
The structure of frames with a byte count field is always the same:
• first, there are some header fields
• then the byte count field
• then as many data bytes as indicated by the byte count,
• finally the CRC (two bytes).
To calculate the frame size, it is therefore sufficient to extract the contents of the byte count field, add the position
of this field, and finally increment the sum by three (one byte for the byte count field, two for the CRC).
pymodbus.utilities.unpack_bitstring(string)
Create bit array out of a string.
Parameters
string – The modbus data packet to decode
example:
SIX
• genindex
• modindex
• search
263
PyModbus Documentation, Release 3.0.2
p pymodbus.transaction, 250
pymodbus, 141 pymodbus.utilities, 260
pymodbus.bit_read_message, 207 pymodbus.version, 262
pymodbus.bit_write_message, 209
pymodbus.datastore, 160
pymodbus.datastore.context, 169
pymodbus.datastore.database, 166
pymodbus.datastore.database.redis_datastore,
167
pymodbus.datastore.database.sql_datastore,
168
pymodbus.datastore.remote, 170
pymodbus.datastore.simulator, 171
pymodbus.datastore.store, 174
pymodbus.device, 211
pymodbus.diag_message, 214
pymodbus.events, 222
pymodbus.exceptions, 225
pymodbus.factory, 225
pymodbus.file_message, 227
pymodbus.framer, 186
pymodbus.framer.ascii_framer, 178
pymodbus.framer.binary_framer, 180
pymodbus.framer.rtu_framer, 182
pymodbus.framer.socket_framer, 184
pymodbus.interfaces, 230
pymodbus.mei_message, 233
pymodbus.other_message, 235
pymodbus.payload, 238
pymodbus.register_read_message, 244
pymodbus.register_write_message, 247
pymodbus.repl, 194
pymodbus.repl.client, 194
pymodbus.repl.client.completer, 194
pymodbus.repl.client.helper, 196
pymodbus.repl.client.main, 197
pymodbus.repl.client.mclient, 197
pymodbus.repl.server, 205
pymodbus.repl.server.cli, 205
pymodbus.repl.server.main, 206
pymodbus.server, 187
pymodbus.server.async_io, 189
265
PyModbus Documentation, Release 3.0.2
267
PyModbus Documentation, Release 3.0.2
268 Index
PyModbus Documentation, Release 3.0.2
Index 269
PyModbus Documentation, Release 3.0.2
270 Index
PyModbus Documentation, Release 3.0.2
Index 271
PyModbus Documentation, Release 3.0.2
bus.client.mixin.ModbusClientMixin method), E
149 encode() (pymodbus.bit_read_message.ReadBitsResponseBase
diag_clear_overrun_counter() (pymod- method), 207
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.bit_write_message.WriteMultipleCoilsRequest
149 method), 209
diag_force_listen_only() (pymod- encode() (pymodbus.bit_write_message.WriteMultipleCoilsResponse
bus.client.mixin.ModbusClientMixin method), method), 210
150 encode() (pymodbus.bit_write_message.WriteSingleCoilRequest
diag_getclear_modbus_response() (pymod- method), 210
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.bit_write_message.WriteSingleCoilResponse
150 method), 211
diag_query_data() (pymod- encode() (pymodbus.device.ModbusPlusStatistics
bus.client.mixin.ModbusClientMixin method), method), 213
150 encode() (pymodbus.diag_message.DiagnosticStatusRequest
diag_read_bus_char_overrun_count() (pymod- method), 215
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.diag_message.DiagnosticStatusResponse
150 method), 215
diag_read_bus_comm_error_count() (pymod- encode() (pymodbus.diag_message.GetClearModbusPlusRequest
bus.client.mixin.ModbusClientMixin method), method), 216
150 encode() (pymodbus.events.CommunicationRestartEvent
diag_read_bus_exception_error_count() (pymod- method), 222
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.events.EnteredListenModeEvent
151 method), 223
diag_read_bus_message_count() (pymod- encode() (pymodbus.events.ModbusEvent method), 223
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.events.RemoteReceiveEvent
151 method), 224
diag_read_diagnostic_register() (pymod- encode() (pymodbus.events.RemoteSendEvent method),
bus.client.mixin.ModbusClientMixin method), 224
151 encode() (pymodbus.file_message.ReadFifoQueueRequest
diag_read_iop_overrun_count() (pymod- method), 227
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.file_message.ReadFifoQueueResponse
151 method), 228
diag_read_slave_busy_count() (pymod- encode() (pymodbus.file_message.ReadFileRecordRequest
bus.client.mixin.ModbusClientMixin method), method), 228
151 encode() (pymodbus.file_message.ReadFileRecordResponse
diag_read_slave_message_count() (pymod- method), 229
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.file_message.WriteFileRecordRequest
152 method), 229
diag_read_slave_nak_count() (pymod- encode() (pymodbus.file_message.WriteFileRecordResponse
bus.client.mixin.ModbusClientMixin method), method), 230
152 encode() (pymodbus.mei_message.ReadDeviceInformationRequest
diag_read_slave_no_response_count() (pymod- method), 233
bus.client.mixin.ModbusClientMixin method), encode() (pymodbus.mei_message.ReadDeviceInformationResponse
152 method), 234
diag_restart_communication() (pymod- encode() (pymodbus.other_message.GetCommEventCounterRequest
bus.client.mixin.ModbusClientMixin method), method), 235
152 encode() (pymodbus.other_message.GetCommEventCounterResponse
DiagnosticStatusRequest (class in pymod- method), 235
bus.diag_message), 215 encode() (pymodbus.other_message.GetCommEventLogRequest
DiagnosticStatusResponse (class in pymod- method), 236
bus.diag_message), 215 encode() (pymodbus.other_message.GetCommEventLogResponse
DictTransactionManager (class in pymod- method), 236
bus.transaction), 250 encode() (pymodbus.other_message.ReadExceptionStatusRequest
method), 237
272 Index
PyModbus Documentation, Release 3.0.2
encode() (pymodbus.other_message.ReadExceptionStatusResponse
execute() (pymodbus.diag_message.RestartCommunicationsOptionReque
method), 237 method), 217
encode() (pymodbus.other_message.ReportSlaveIdRequestexecute() (pymodbus.diag_message.ReturnBusCommunicationErrorCoun
method), 237 method), 217
encode() (pymodbus.other_message.ReportSlaveIdResponse execute() (pymodbus.diag_message.ReturnBusExceptionErrorCountRequ
method), 238 method), 218
encode() (pymodbus.register_read_message.ReadRegistersResponseBase
execute() (pymodbus.diag_message.ReturnBusMessageCountRequest
method), 245 method), 218
encode() (pymodbus.register_read_message.ReadWriteMultipleRegistersRequest
execute() (pymodbus.diag_message.ReturnDiagnosticRegisterRequest
method), 246 method), 219
encode() (pymodbus.register_read_message.ReadWriteMultipleRegistersResponse
execute() (pymodbus.diag_message.ReturnIopOverrunCountRequest
method), 247 method), 219
encode() (pymodbus.register_write_message.MaskWriteRegisterRequest
execute() (pymodbus.diag_message.ReturnQueryDataRequest
method), 247 method), 219
encode() (pymodbus.register_write_message.MaskWriteRegisterResponse
execute() (pymodbus.diag_message.ReturnSlaveBusCharacterOverrunCo
method), 247 method), 220
encode() (pymodbus.register_write_message.WriteMultipleRegistersRequest
execute() (pymodbus.diag_message.ReturnSlaveBusyCountRequest
method), 248 method), 220
encode() (pymodbus.register_write_message.WriteMultipleRegistersResponse
execute() (pymodbus.diag_message.ReturnSlaveMessageCountRequest
method), 248 method), 221
encode() (pymodbus.register_write_message.WriteSingleRegisterRequest
execute() (pymodbus.diag_message.ReturnSlaveNAKCountRequest
method), 249 method), 221
encode() (pymodbus.register_write_message.WriteSingleRegisterResponse
execute() (pymodbus.diag_message.ReturnSlaveNoResponseCountReques
method), 249 method), 222
EnteredListenModeEvent (class in pymodbus.events), execute() (pymodbus.file_message.ReadFifoQueueRequest
222 method), 227
error() (in module pymodbus.repl.server.cli), 205 execute() (pymodbus.file_message.ReadFileRecordRequest
error_received() (pymod- method), 228
bus.server.async_io.ModbusDisconnectedRequestHandler
execute() (pymodbus.file_message.WriteFileRecordRequest
method), 190 method), 229
Events (pymodbus.device.ModbusControlBlock prop- execute() (pymodbus.mei_message.ReadDeviceInformationRequest
erty), 212 method), 234
execute() (pymodbus.bit_read_message.ReadCoilsRequestexecute() (pymodbus.other_message.GetCommEventCounterRequest
method), 208 method), 235
execute() (pymodbus.bit_read_message.ReadDiscreteInputsRequest
execute() (pymodbus.other_message.GetCommEventLogRequest
method), 208 method), 236
execute() (pymodbus.bit_write_message.WriteMultipleCoilsRequest
execute() (pymodbus.other_message.ReadExceptionStatusRequest
method), 209 method), 237
execute() (pymodbus.bit_write_message.WriteSingleCoilRequest
execute() (pymodbus.other_message.ReportSlaveIdRequest
method), 210 method), 238
execute() (pymodbus.client.base.ModbusBaseClient execute() (pymodbus.register_read_message.ReadHoldingRegistersReque
method), 143 method), 244
execute() (pymodbus.client.mixin.ModbusClientMixin execute() (pymodbus.register_read_message.ReadInputRegistersRequest
method), 152 method), 245
execute() (pymodbus.diag_message.ChangeAsciiInputDelimiterRequest
execute() (pymodbus.register_read_message.ReadWriteMultipleRegisters
method), 214 method), 246
execute() (pymodbus.diag_message.ClearCountersRequestexecute() (pymodbus.register_write_message.MaskWriteRegisterRequest
method), 214 method), 247
execute() (pymodbus.diag_message.ClearOverrunCountRequest
execute() (pymodbus.register_write_message.WriteMultipleRegistersRequ
method), 214 method), 248
execute() (pymodbus.diag_message.ForceListenOnlyModeRequest
execute() (pymodbus.register_write_message.WriteSingleRegisterRequest
method), 216 method), 249
execute() (pymodbus.diag_message.GetClearModbusPlusRequest
execute() (pymodbus.server.async_io.ModbusBaseRequestHandler
method), 216 method), 189
Index 273
PyModbus Documentation, Release 3.0.2
274 Index
PyModbus Documentation, Release 3.0.2
Index 275
PyModbus Documentation, Release 3.0.2
276 Index
PyModbus Documentation, Release 3.0.2
Index 277
PyModbus Documentation, Release 3.0.2
278 Index
PyModbus Documentation, Release 3.0.2
Index 279
PyModbus Documentation, Release 3.0.2
280 Index
PyModbus Documentation, Release 3.0.2
Index 281
PyModbus Documentation, Release 3.0.2
282 Index
PyModbus Documentation, Release 3.0.2
Index 283
PyModbus Documentation, Release 3.0.2
284 Index
PyModbus Documentation, Release 3.0.2
Index 285
PyModbus Documentation, Release 3.0.2
sub_function_code (pymod- U
bus.diag_message.ReturnSlaveBusyCountRequest udp (pymodbus.repl.server.main.ModbusServerTypes at-
attribute), 220 tribute), 206
sub_function_code (pymod- unpack_bitstring() (in module pymodbus.utilities),
bus.diag_message.ReturnSlaveBusyCountResponse 261
attribute), 220 update() (pymodbus.device.ModbusDeviceIdentification
sub_function_code (pymod- method), 213
bus.diag_message.ReturnSlaveMessageCountRequest
UserApplicationName (pymod-
attribute), 221 bus.device.ModbusDeviceIdentification prop-
sub_function_code (pymod- erty), 213
bus.diag_message.ReturnSlaveMessageCountResponse
attribute), 221 V
sub_function_code (pymod- validate() (pymodbus.datastore.context.ModbusSlaveContext
bus.diag_message.ReturnSlaveNAKCountRequest method), 170
attribute), 221 validate() (pymodbus.datastore.database.redis_datastore.RedisSlaveCon
sub_function_code (pymod- method), 168
bus.diag_message.ReturnSlaveNAKCountResponsevalidate() (pymodbus.datastore.database.RedisSlaveContext
attribute), 221 method), 166
sub_function_code (pymod- validate() (pymodbus.datastore.database.sql_datastore.SqlSlaveContext
bus.diag_message.ReturnSlaveNoReponseCountResponse method), 169
attribute), 221 validate() (pymodbus.datastore.database.SqlSlaveContext
sub_function_code (pymod- method), 167
bus.diag_message.ReturnSlaveNoResponseCountRequest
validate() (pymodbus.datastore.ModbusSequentialDataBlock
attribute), 222 method), 160
sub_function_code (pymod- validate() (pymodbus.datastore.ModbusSlaveContext
bus.mei_message.ReadDeviceInformationRequest method), 164
attribute), 234 validate() (pymodbus.datastore.ModbusSparseDataBlock
sub_function_code (pymod- method), 165
bus.mei_message.ReadDeviceInformationResponse validate() (pymodbus.datastore.remote.RemoteSlaveContext
attribute), 234 method), 170
summary() (pymodbus.device.ModbusDeviceIdentification validate() (pymodbus.datastore.store.BaseModbusDataBlock
method), 213 method), 175
summary() (pymodbus.device.ModbusPlusStatistics validate() (pymodbus.datastore.store.ModbusSequentialDataBlock
method), 213 method), 176
validate() (pymodbus.datastore.store.ModbusSparseDataBlock
T method), 177
tcp (pymodbus.repl.server.main.ModbusServerTypes at- validate() (pymodbus.interfaces.IModbusSlaveContext
tribute), 206 method), 233
Timeout (pymodbus.constants.Defaults attribute), 156 value (pymodbus.events.CommunicationRestartEvent at-
tls (pymodbus.repl.server.main.ModbusFramerTypes at- tribute), 222
tribute), 206 value (pymodbus.events.EnteredListenModeEvent
tls (pymodbus.repl.server.main.ModbusServerTypes at- attribute), 223
tribute), 206 VendorName (pymodbus.device.ModbusDeviceIdentification
TLSPort (pymodbus.constants.Defaults attribute), 155 property), 213
to_coils() (pymodbus.payload.BinaryPayloadBuilder VendorUrl (pymodbus.device.ModbusDeviceIdentification
method), 240 property), 213
to_registers() (pymod-
bus.payload.BinaryPayloadBuilder method), W
240 Waiting (pymodbus.constants.ModbusStatus attribute),
to_string() (pymodbus.payload.BinaryPayloadBuilder 159
method), 240 warning() (in module pymodbus.repl.server.cli), 206
TransactionId (pymodbus.constants.Defaults at- word_matches() (pymod-
tribute), 156 bus.repl.client.completer.CmdCompleter
method), 195
286 Index
PyModbus Documentation, Release 3.0.2
write_coil() (pymod-
bus.client.mixin.ModbusClientMixin method),
154
write_coil() (pymod-
bus.repl.client.mclient.ExtendedRequestSupport
method), 202
write_coils() (pymod-
bus.client.mixin.ModbusClientMixin method),
154
write_coils() (pymod-
bus.repl.client.mclient.ExtendedRequestSupport
method), 203
write_register() (pymod-
bus.client.mixin.ModbusClientMixin method),
155
write_register() (pymod-
bus.repl.client.mclient.ExtendedRequestSupport
method), 203
write_registers() (pymod-
bus.client.mixin.ModbusClientMixin method),
155
write_registers() (pymod-
bus.repl.client.mclient.ExtendedRequestSupport
method), 203
WriteFileRecordRequest (class in pymod-
bus.file_message), 229
WriteFileRecordResponse (class in pymod-
bus.file_message), 229
WriteMultipleCoilsRequest (class in pymod-
bus.bit_write_message), 209
WriteMultipleCoilsResponse (class in pymod-
bus.bit_write_message), 210
WriteMultipleRegistersRequest (class in pymod-
bus.register_write_message), 248
WriteMultipleRegistersResponse (class in pymod-
bus.register_write_message), 248
WriteSingleCoilRequest (class in pymod-
bus.bit_write_message), 210
WriteSingleCoilResponse (class in pymod-
bus.bit_write_message), 211
WriteSingleRegisterRequest (class in pymod-
bus.register_write_message), 249
WriteSingleRegisterResponse (class in pymod-
bus.register_write_message), 249
Z
ZeroMode (pymodbus.constants.Defaults attribute), 156
Index 287