Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen committed Feb 5, 2024
2 parents e3e8537 + 0d4e7ea commit 86dae1a
Show file tree
Hide file tree
Showing 66 changed files with 804 additions and 766 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
- name: pytest
run: |
env
pytest -v --full-trace --timeout=1200
pytest -v --cov --full-trace --timeout=1200
analyze:
name: Analyze Python
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ build/
/pymodbus.egg-info/
venv
downloaded_files/
htmlcov/
4 changes: 4 additions & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Thanks to
- AKJ7
- Alex
- Alex Ruddick
- Alexander Lanin
- Alexandre CUER
- Alois Hockenschlohe
- Arjan
Expand All @@ -38,9 +39,11 @@ Thanks to
- Jakob Ruhe
- Jakob Schlyter
- James Braza
- James Hilliard
- jan iversen
- Jerome Velociter
- Joe Burmeister
- julian
- Kenny Johansson
- Matthias Straka
- Logan Gunthorpe
Expand All @@ -54,6 +57,7 @@ Thanks to
- Philip Couling
- Sebastian Machuca
- Sefa Keleş
- Steffen Beyer
- Thijs W
- Totally a booplicate
- WouterTuinstra
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ helps make pymodbus a better product.

:ref:`Authors`: contains a complete list of volunteers have contributed to each major version.

Version 3.6.4
-------------
* Update datastore_simulator example with client (#1967)
* Test and correct receiving more than one packet (#1965)
* Remove unused FifoTransactionManager. (#1966)
* Always set exclusive serial port access. (#1964)
* Add server/client network stub, to allow test of network packets. (#1963)
* Combine conftest to a central file (#1962)
* Call on_reconnect_callback. (#1959)
* Readd ModbusBaseClient to external API.
* Update README.rst
* minor fix for typo and consistency (#1946)
* More coverage. (#1947)
* Client coverage 100%. (#1943)
* Run coverage in CI with % check of coverage. (#1945)
* transport 100% coverage. (#1941)
* contrib example: TCP drainage simulator with two devices (#1936)
* Remove "pragma no cover". (#1935)
* transport_serial -> serialtransport. (#1933)
* Fix behavior after Exception response (#1931)
* Correct expected length for udp sync client. (#1930)

Version 3.6.3
-------------
* solve Socket_framer problem with Exception response (#1925)
Expand Down
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ PyModbus - A Python Modbus Stack

Pymodbus is a full Modbus protocol implementation offering client/server with synchronous/asynchronous API a well as simulators.

Current release is `3.6.3 <https://github.com/pymodbus-dev/pymodbus/releases/tag/v3.6.3>`_.
Current release is `3.6.4 <https://github.com/pymodbus-dev/pymodbus/releases/tag/v3.6.4>`_.

Bleeding edge (not released) is `dev <https://github.com/pymodbus-dev/pymodbus/tree/dev>`_.

Expand Down Expand Up @@ -298,7 +298,7 @@ Make a pull request::

Test your changes::

cd pytest
cd test
pytest


Expand Down
Binary file modified doc/source/_static/examples.tgz
Binary file not shown.
Binary file modified doc/source/_static/examples.zip
Binary file not shown.
8 changes: 4 additions & 4 deletions doc/source/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,11 @@ Source: :github:`examples/simulator.py`
:noindex:


Simulator datastore example
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Source: :github:`examples/datastore_simulator.py`
Simulator datastore (shared storage) example
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Source: :github:`examples/datastore_simulator_share.py`

.. automodule:: examples.datastore_simulator
.. automodule:: examples.datastore_simulator_share
:undoc-members:
:noindex:

Expand Down
2 changes: 1 addition & 1 deletion doc/source/library/simulator/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ Example "setup" configuration:
**"co size"**, **"di size"**, **"hr size"**, **"ir size"**:

Define the size of each block.
If using shared block the register list size will be the size of the biggest block (25 reegisters)
If using shared block the register list size will be the size of the biggest block (25 registers)
If not using shared block the register list size will be the sum of the 4 block sizes (70 registers).

**"shared blocks"**
Expand Down
2 changes: 1 addition & 1 deletion examples/client_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,4 @@ async def main(cmdline=None):


if __name__ == "__main__":
asyncio.run(main(), debug=True) # pragma: no cover
asyncio.run(main(), debug=True)
2 changes: 1 addition & 1 deletion examples/client_async_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,4 @@ async def main(cmdline=None):


if __name__ == "__main__":
asyncio.run(main()) # pragma: no cover
asyncio.run(main())
6 changes: 3 additions & 3 deletions examples/client_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def handle_input_registers(client):
assert len(rr.registers) == 8


def execute_information_requests(client): # pragma no cover
def execute_information_requests(client):
"""Execute extended information requests."""
_logger.info("### Running information requests.")
rr = client.read_device_information(slave=SLAVE)
Expand All @@ -176,7 +176,7 @@ def execute_information_requests(client): # pragma no cover
# assert not (rr.event_count + rr.message_count + len(rr.events))


def execute_diagnostic_requests(client): # pragma no cover
def execute_diagnostic_requests(client):
"""Execute extended diagnostic requests."""
_logger.info("### Running diagnostic requests.")
message = b"OK"
Expand Down Expand Up @@ -226,4 +226,4 @@ def main(cmdline=None):


if __name__ == "__main__":
main() # pragma: no cover
main()
8 changes: 4 additions & 4 deletions examples/client_custom_msg.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# --------------------------------------------------------------------------- #


class CustomModbusResponse(ModbusResponse): # pragma no cover
class CustomModbusResponse(ModbusResponse):
"""Custom modbus response."""

function_code = 55
Expand Down Expand Up @@ -78,11 +78,11 @@ def encode(self):
"""Encode."""
return struct.pack(">HH", self.address, self.count)

def decode(self, data): # pragma no cover
def decode(self, data):
"""Decode."""
self.address, self.count = struct.unpack(">HH", data)

def execute(self, context): # pragma no cover
def execute(self, context):
"""Execute."""
if not 1 <= self.count <= 0x7D0:
return self.doException(ModbusExceptions.IllegalValue)
Expand Down Expand Up @@ -134,4 +134,4 @@ async def main(host="localhost", port=5020):


if __name__ == "__main__":
asyncio.run(main()) # pragma: no cover
asyncio.run(main())
2 changes: 1 addition & 1 deletion examples/client_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,4 @@ async def main(cmdline=None):


if __name__ == "__main__":
asyncio.run(main()) # pragma: no cover
asyncio.run(main())
4 changes: 2 additions & 2 deletions examples/client_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def setup_sync_client(description=None, cmdline=None):
# stopbits=1,
# handle_local_echo=False,
)
elif args.comm == "tls": # pragma no cover
elif args.comm == "tls":
client = modbusClient.ModbusTlsClient(
args.host,
port=args.port,
Expand Down Expand Up @@ -146,4 +146,4 @@ def main(cmdline=None):


if __name__ == "__main__":
main() # pragma: no cover
main()
68 changes: 68 additions & 0 deletions examples/contrib/drainage_sim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env python3

# Simulates two Modbus TCP slave servers:
#
# Port 5020: Digital IO (DIO) with 8 discrete inputs and 8 coils. The first two coils each control
# a simulated pump. Inputs are not used.
#
# Port 5021: Water level meter (WLM) returning the current water level in the input register. It
# increases chronologically and decreases rapidly when one or two pumps are active.

import asyncio
import logging
from datetime import datetime

from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext
from pymodbus.server import StartAsyncTcpServer

INITIAL_WATER_LEVEL = 300
WATER_INFLOW = 1
PUMP_OUTFLOW = 8

logging.basicConfig(level = logging.INFO)

dio_di = ModbusSequentialDataBlock(1, [False] * 8)
dio_co = ModbusSequentialDataBlock(1, [False] * 8)
dio_context = ModbusSlaveContext(di = dio_di, co = dio_co)
wlm_ir = ModbusSequentialDataBlock(1, [INITIAL_WATER_LEVEL])
wlm_context = ModbusSlaveContext(ir = wlm_ir)

async def update():
while True:
await asyncio.sleep(1)

# Update water level based on DIO output values (simulating pumps)
water_level = wlm_ir.getValues(1, 1)[0]
dio_outputs = dio_co.getValues(1, 2)

water_level += WATER_INFLOW
water_level -= (int(dio_outputs[0]) + int(dio_outputs[1])) * PUMP_OUTFLOW
water_level = max(0, min(INITIAL_WATER_LEVEL * 10, water_level))
wlm_ir.setValues(1, [water_level])

async def log():
while True:
await asyncio.sleep(10)

dio_outputs = dio_co.getValues(1, 8)
wlm_level = wlm_ir.getValues(1, 1)[0]

logging.info(f"{datetime.now()}: WLM water level: {wlm_level}, DIO outputs: {dio_outputs}")

async def run():
ctx = ModbusServerContext(slaves = dio_context)
dio_server = asyncio.create_task(StartAsyncTcpServer(context = ctx, address = ("0.0.0.0", 5020)))
logging.info("Initialising slave server DIO on port 5020")

ctx = ModbusServerContext(slaves = wlm_context)
wlm_server = asyncio.create_task(StartAsyncTcpServer(context = ctx, address = ("0.0.0.0", 5021)))
logging.info("Initialising slave server WLM on port 5021")

update_task = asyncio.create_task(update())
logging_task = asyncio.create_task(log())

logging.info("Init complete")
await asyncio.gather(dio_server, wlm_server, update_task, logging_task)

if __name__ == "__main__":
asyncio.run(run(), debug=True)
2 changes: 1 addition & 1 deletion examples/contrib/solar.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ def solar_calls(client):


if __name__ == "__main__":
main() # pragma: no cover
main()
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@
An example of using simulator datastore with json interface.
Detailed description of the device definition can be found at:
https://pymodbus.readthedocs.io/en/latest/source/library/simulator/config.html#device-entries
usage::
server_simulator.py [-h]
datastore_simulator_share.py [-h]
[--log {critical,error,warning,info,debug}]
[--port PORT]
[--test_client]
-h, --help
show this help message and exit
-l, --log {critical,error,warning,info,debug}
set log level
-p, --port PORT
set port to use
--test_client
starts a client to test the configuration
The corresponding client can be started as:
python3 client_sync.py
Expand All @@ -25,7 +32,7 @@
import asyncio
import logging

from pymodbus import Framer, pymodbus_apply_logging_config
from pymodbus import pymodbus_apply_logging_config
from pymodbus.datastore import ModbusServerContext, ModbusSimulatorContext
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.server import StartAsyncTcpServer
Expand Down Expand Up @@ -129,24 +136,22 @@ def get_commandline(cmdline=None):
)
parser.add_argument("--port", help="set port", type=str, default="5020")
parser.add_argument("--host", help="set interface", type=str, default="localhost")
parser.add_argument("--test_client", help="start client to test", action="store_true")
args = parser.parse_args(cmdline)

return args


def setup_simulator(setup=None, actions=None, cmdline=None):
"""Run server setup."""
if not setup:
setup=demo_config
if not actions:
actions=demo_actions
args = get_commandline(cmdline=cmdline)
pymodbus_apply_logging_config(args.log.upper())
_logger.setLevel(args.log.upper())
args.framer = Framer.SOCKET
args.port = int(args.port)

_logger.info("### Create datastore")
if not setup:
setup = demo_config
if not actions:
actions = demo_actions
context = ModbusSimulatorContext(setup, actions)
args.context = ModbusServerContext(slaves=context, single=True)
args.identity = ModbusDeviceIdentification(
Expand All @@ -169,7 +174,6 @@ async def run_server_simulator(args):
await StartAsyncTcpServer(
context=args.context,
address=(args.host, args.port),
framer=args.framer,
)


Expand All @@ -180,4 +184,4 @@ async def main(cmdline=None):


if __name__ == "__main__":
asyncio.run(main(), debug=True) # pragma: no cover
asyncio.run(main(), debug=True)
8 changes: 4 additions & 4 deletions examples/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get_commandline(server=False, description=None, extras=None, cmdline=None):
default=10,
type=float,
)
if extras: # pragma no cover
if extras:
for extra in extras:
parser.add_argument(extra[0], **extra[1])
args = parser.parse_args(cmdline)
Expand Down Expand Up @@ -119,13 +119,13 @@ def get_certificate(suffix: str):
delimiter = "\\" if os.name == "nt" else "/"
cwd = os.getcwd().split(delimiter)[-1]
if cwd == "examples":
path = "." # pragma no cover
path = "."
elif cwd == "sub_examples":
path = "../../examples" # pragma no cover
path = "../../examples"
elif cwd == "test":
path = "../examples"
elif cwd == "pymodbus":
path = "examples" # pragma no cover
path = "examples"
else:
raise RuntimeError(f"**Error** Cannot find certificate path={cwd}")
return f"{path}/certificates/pymodbus.{suffix}"
2 changes: 1 addition & 1 deletion examples/message_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,4 @@ def generate_messages(cmdline=None):


if __name__ == "__main__":
generate_messages() # pragma: no cover
generate_messages()
Loading

0 comments on commit 86dae1a

Please sign in to comment.