diff --git a/examples/client_async_calls.py b/examples/client_async_calls.py new file mode 100755 index 0000000000..9e7ffce00f --- /dev/null +++ b/examples/client_async_calls.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 +"""Pymodbus Client modbus async call examples. + +Please see: + + async_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. + +If you are performing a request that is not available in the client +mixin, you have to perform the request like this instead: + +from pymodbus.diag_message import ClearCountersRequest +from pymodbus.diag_message import ClearCountersResponse + +request = ClearCountersRequest() +response = client.execute(request) +if isinstance(response, ClearCountersResponse): + ... do something with the response + +This example uses client_async.py and client_sync.py to handle connection, +and have the same options. + +The corresponding server must be started before e.g. as: + + ./server_async.py +""" +import asyncio +import logging + +import pymodbus.diag_message as req_diag +import pymodbus.mei_message as req_mei +import pymodbus.other_message as req_other +from examples.client_async import run_async_client, setup_async_client +from pymodbus.exceptions import ModbusException + + +logging.basicConfig() +_logger = logging.getLogger(__file__) +_logger.setLevel("DEBUG") + + +SLAVE = 0x01 + + +# -------------------------------------------------- +# Template on how to make modbus calls (sync/async). +# all calls follow the same schema, +# -------------------------------------------------- +async def async_template_call(client): + """Show complete modbus call, async version.""" + try: + rr = await client.read_coils(1, 1, slave=SLAVE) + except ModbusException as exc: + txt = f"ERROR: exception in pymodbus {exc}" + _logger.error(txt) + raise exc + if rr.isError(): + txt = "ERROR: pymodbus returned an error!" + _logger.error(txt) + raise ModbusException(txt) + + # Validate data + txt = f"### Template coils response: {rr.bits!s}" + _logger.debug(txt) + + +# ------------------------------------------------------ +# Call modbus device (all possible calls are presented). +# ------------------------------------------------------ +async def async_handle_coils(client): + """Read/Write coils.""" + _logger.info("### Reading Coil different number of bits (return 8 bits multiples)") + rr = await client.read_coils(1, 1, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert len(rr.bits) == 8 + + rr = await client.read_coils(1, 5, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert len(rr.bits) == 8 + + rr = await client.read_coils(1, 12, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert len(rr.bits) == 16 + + rr = await client.read_coils(1, 17, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert len(rr.bits) == 24 + + _logger.info("### Write false/true to coils and read to verify") + await client.write_coil(0, True, slave=SLAVE) + rr = await client.read_coils(0, 1, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert rr.bits[0] # test the expected value + + await client.write_coils(1, [True] * 21, slave=SLAVE) + rr = await client.read_coils(1, 21, slave=SLAVE) + assert not rr.isError() # test that call was OK + resp = [True] * 21 + # If the returned output quantity is not a multiple of eight, + # the remaining bits in the final data byte will be padded with zeros + # (toward the high order end of the byte). + resp.extend([False] * 3) + assert rr.bits == resp # test the expected value + + _logger.info("### Write False to address 1-8 coils") + await client.write_coils(1, [False] * 8, slave=SLAVE) + rr = await client.read_coils(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert rr.bits == [False] * 8 # test the expected value + + +async def async_handle_discrete_input(client): + """Read discrete inputs.""" + _logger.info("### Reading discrete input, Read address:0-7") + rr = await client.read_discrete_inputs(0, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert len(rr.bits) == 8 + + +async def async_handle_holding_registers(client): + """Read/write holding registers.""" + _logger.info("### write holding register and read holding registers") + await client.write_register(1, 10, slave=SLAVE) + rr = await client.read_holding_registers(1, 1, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert rr.registers[0] == 10 + + await client.write_registers(1, [10] * 8, slave=SLAVE) + rr = await client.read_holding_registers(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert rr.registers == [10] * 8 + + _logger.info("### write read holding registers, using **kwargs") + arguments = { + "read_address": 1, + "read_count": 8, + "write_address": 1, + "values": [256, 128, 100, 50, 25, 10, 5, 1], + } + await client.readwrite_registers(slave=SLAVE, **arguments) + rr = await client.read_holding_registers(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert rr.registers == arguments["values"] + + +async def async_handle_input_registers(client): + """Read input registers.""" + _logger.info("### read input registers") + rr = await client.read_input_registers(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK + assert len(rr.registers) == 8 + + +async def async_execute_information_requests(client): + """Execute extended information requests.""" + _logger.info("### Running information requests.") + rr = await client.execute(req_mei.ReadDeviceInformationRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK + assert rr.information[0] == b"Pymodbus" + + rr = await client.execute(req_other.ReportSlaveIdRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK + # assert rr.status + + rr = await client.execute(req_other.ReadExceptionStatusRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK + # assert not rr.status + + rr = await client.execute(req_other.GetCommEventCounterRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK + # assert rr.status + # assert not rr.count + + rr = await client.execute(req_other.GetCommEventLogRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK + # assert rr.status + # assert not (rr.event_count + rr.message_count + len(rr.events)) + + +async def async_execute_diagnostic_requests(client): + """Execute extended diagnostic requests.""" + _logger.info("### Running diagnostic requests.") + message = b"OK" + rr = await client.execute( + req_diag.ReturnQueryDataRequest(message=message, slave=SLAVE) + ) + assert not rr.isError() # test that call was OK + assert rr.message == message + + await client.execute(req_diag.RestartCommunicationsOptionRequest(slave=SLAVE)) + await client.execute(req_diag.ReturnDiagnosticRegisterRequest(slave=SLAVE)) + await client.execute(req_diag.ChangeAsciiInputDelimiterRequest(slave=SLAVE)) + + # NOT WORKING: _check_call(await client.execute(req_diag.ForceListenOnlyModeRequest(slave=SLAVE))) + # does not send a response + + await client.execute(req_diag.ClearCountersRequest()) + await client.execute(req_diag.ReturnBusCommunicationErrorCountRequest(slave=SLAVE)) + await client.execute(req_diag.ReturnBusExceptionErrorCountRequest(slave=SLAVE)) + await client.execute(req_diag.ReturnSlaveMessageCountRequest(slave=SLAVE)) + await client.execute(req_diag.ReturnSlaveNoResponseCountRequest(slave=SLAVE)) + await client.execute(req_diag.ReturnSlaveNAKCountRequest(slave=SLAVE)) + await client.execute(req_diag.ReturnSlaveBusyCountRequest(slave=SLAVE)) + await client.execute( + req_diag.ReturnSlaveBusCharacterOverrunCountRequest(slave=SLAVE) + ) + await client.execute(req_diag.ReturnIopOverrunCountRequest(slave=SLAVE)) + await client.execute(req_diag.ClearOverrunCountRequest(slave=SLAVE)) + # NOT WORKING _check_call(await client.execute(req_diag.GetClearModbusPlusRequest(slave=SLAVE))) + + +# ------------------------ +# Run the calls in groups. +# ------------------------ +async def run_async_calls(client): + """Demonstrate basic read/write calls.""" + await async_template_call(client) + await async_handle_coils(client) + await async_handle_discrete_input(client) + await async_handle_holding_registers(client) + await async_handle_input_registers(client) + await async_execute_information_requests(client) + await async_execute_diagnostic_requests(client) + + +async def main(cmdline=None): + """Combine setup and run""" + testclient = setup_async_client( + description="Run asynchronous client.", cmdline=cmdline + ) + await run_async_client(testclient, modbus_calls=run_async_calls) + + +if __name__ == "__main__": + asyncio.run(main()) # pragma: no cover diff --git a/examples/client_calls.py b/examples/client_calls.py index 33f07f03e3..a86258e2a1 100755 --- a/examples/client_calls.py +++ b/examples/client_calls.py @@ -3,14 +3,12 @@ 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 +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 @@ -34,16 +32,13 @@ ./server_async.py """ -import asyncio import logging import pymodbus.diag_message as req_diag import pymodbus.mei_message as req_mei import pymodbus.other_message as req_other -from examples.client_async import run_async_client, setup_async_client from examples.client_sync import run_sync_client, setup_sync_client from pymodbus.exceptions import ModbusException -from pymodbus.pdu import ExceptionResponse logging.basicConfig() @@ -58,31 +53,6 @@ # Template on how to make modbus calls (sync/async). # all calls follow the same schema, # -------------------------------------------------- - - -async def async_template_call(client): - """Show complete modbus call, async version.""" - try: - rr = await client.read_coils(1, 1, slave=SLAVE) - except ModbusException as exc: - txt = f"ERROR: exception in pymodbus {exc}" - _logger.error(txt) - raise exc - if rr.isError(): - txt = "ERROR: pymodbus returned an error!" - _logger.error(txt) - raise ModbusException(txt) - if isinstance(rr, ExceptionResponse): - txt = "ERROR: received exception from device {rr}!" - _logger.error(txt) - # THIS IS NOT A PYTHON EXCEPTION, but a valid modbus message - raise ModbusException(txt) - - # Validate data - txt = f"### Template coils response: {rr.bits!s}" - _logger.debug(txt) - - def template_call(client): """Show complete modbus call, sync version.""" try: @@ -95,54 +65,43 @@ def template_call(client): txt = "ERROR: pymodbus returned an error!" _logger.error(txt) raise ModbusException(txt) - if isinstance(rr, ExceptionResponse): - txt = "ERROR: received exception from device {rr}!" - _logger.error(txt) - # THIS IS NOT A PYTHON EXCEPTION, but a valid modbus message - raise ModbusException(txt) # Validate data txt = f"### Template coils response: {rr.bits!s}" _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): +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)) + rr = client.read_coils(1, 1, slave=SLAVE) + assert not rr.isError() # test that call was OK assert len(rr.bits) == 8 - rr = _check_call(await client.read_coils(1, 5, slave=SLAVE)) + rr = client.read_coils(1, 5, slave=SLAVE) + assert not rr.isError() # test that call was OK assert len(rr.bits) == 8 - rr = _check_call(await client.read_coils(1, 12, slave=SLAVE)) + rr = client.read_coils(1, 12, slave=SLAVE) + assert not rr.isError() # test that call was OK assert len(rr.bits) == 16 - rr = _check_call(await client.read_coils(1, 17, slave=SLAVE)) + rr = client.read_coils(1, 17, slave=SLAVE) + assert not rr.isError() # test that call was OK assert len(rr.bits) == 24 _logger.info("### Write false/true to coils and read to verify") - _check_call(await client.write_coil(0, True, slave=SLAVE)) - rr = _check_call(await client.read_coils(0, 1, slave=SLAVE)) + client.write_coil(0, True, slave=SLAVE) + rr = client.read_coils(0, 1, slave=SLAVE) + assert not rr.isError() # test that call was OK assert rr.bits[0] # test the expected value - _check_call(await client.write_coils(1, [True] * 21, slave=SLAVE)) - rr = _check_call(await client.read_coils(1, 21, slave=SLAVE)) + client.write_coils(1, [True] * 21, slave=SLAVE) + rr = client.read_coils(1, 21, slave=SLAVE) + assert not rr.isError() # test that call was OK resp = [True] * 21 # If the returned output quantity is not a multiple of eight, # the remaining bits in the final data byte will be padded with zeros @@ -151,27 +110,31 @@ async def _handle_coils(client): assert rr.bits == resp # test the expected value _logger.info("### Write False to address 1-8 coils") - _check_call(await client.write_coils(1, [False] * 8, slave=SLAVE)) - rr = _check_call(await client.read_coils(1, 8, slave=SLAVE)) + client.write_coils(1, [False] * 8, slave=SLAVE) + rr = client.read_coils(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK assert rr.bits == [False] * 8 # test the expected value -async def _handle_discrete_input(client): +def handle_discrete_input(client): """Read discrete inputs.""" _logger.info("### Reading discrete input, Read address:0-7") - rr = _check_call(await client.read_discrete_inputs(0, 8, slave=SLAVE)) + rr = client.read_discrete_inputs(0, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK assert len(rr.bits) == 8 -async def _handle_holding_registers(client): +def handle_holding_registers(client): """Read/write holding registers.""" _logger.info("### write holding register and read holding registers") - _check_call(await client.write_register(1, 10, slave=SLAVE)) - rr = _check_call(await client.read_holding_registers(1, 1, slave=SLAVE)) + client.write_register(1, 10, slave=SLAVE) + rr = client.read_holding_registers(1, 1, slave=SLAVE) + assert not rr.isError() # test that call was OK assert rr.registers[0] == 10 - _check_call(await client.write_registers(1, [10] * 8, slave=SLAVE)) - rr = _check_call(await client.read_holding_registers(1, 8, slave=SLAVE)) + client.write_registers(1, [10] * 8, slave=SLAVE) + rr = client.read_holding_registers(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK assert rr.registers == [10] * 8 _logger.info("### write read holding registers, using **kwargs") @@ -181,139 +144,93 @@ async def _handle_holding_registers(client): "write_address": 1, "values": [256, 128, 100, 50, 25, 10, 5, 1], } - _check_call(await client.readwrite_registers(slave=SLAVE, **arguments)) - rr = _check_call(await client.read_holding_registers(1, 8, slave=SLAVE)) + client.readwrite_registers(slave=SLAVE, **arguments) + rr = client.read_holding_registers(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK assert rr.registers == arguments["values"] -async def _handle_input_registers(client): +def handle_input_registers(client): """Read input registers.""" _logger.info("### read input registers") - rr = _check_call(await client.read_input_registers(1, 8, slave=SLAVE)) + rr = client.read_input_registers(1, 8, slave=SLAVE) + assert not rr.isError() # test that call was OK assert len(rr.registers) == 8 -async def _execute_information_requests(client): +def execute_information_requests(client): """Execute extended information requests.""" _logger.info("### Running information requests.") - rr = _check_call( - await client.execute(req_mei.ReadDeviceInformationRequest(slave=SLAVE)) - ) + rr = client.execute(req_mei.ReadDeviceInformationRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK assert rr.information[0] == b"Pymodbus" - rr = _check_call(await client.execute(req_other.ReportSlaveIdRequest(slave=SLAVE))) + rr = client.execute(req_other.ReportSlaveIdRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK # assert rr.status - rr = _check_call( - await client.execute(req_other.ReadExceptionStatusRequest(slave=SLAVE)) - ) + rr = client.execute(req_other.ReadExceptionStatusRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK # assert not rr.status - rr = _check_call( - await client.execute(req_other.GetCommEventCounterRequest(slave=SLAVE)) - ) + rr = client.execute(req_other.GetCommEventCounterRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK # assert rr.status # assert not rr.count - rr = _check_call( - await client.execute(req_other.GetCommEventLogRequest(slave=SLAVE)) - ) + rr = client.execute(req_other.GetCommEventLogRequest(slave=SLAVE)) + assert not rr.isError() # test that call was OK # assert rr.status # assert not (rr.event_count + rr.message_count + len(rr.events)) -async def _execute_diagnostic_requests(client): +def execute_diagnostic_requests(client): """Execute extended diagnostic requests.""" _logger.info("### Running diagnostic requests.") message = b"OK" - rr = _check_call( - await client.execute( - req_diag.ReturnQueryDataRequest(message=message, slave=SLAVE) - ) - ) + rr = client.execute(req_diag.ReturnQueryDataRequest(message=message, slave=SLAVE)) + assert not rr.isError() # test that call was OK assert rr.message == message - _check_call( - await client.execute(req_diag.RestartCommunicationsOptionRequest(slave=SLAVE)) - ) - _check_call( - await client.execute(req_diag.ReturnDiagnosticRegisterRequest(slave=SLAVE)) - ) - _check_call( - await client.execute(req_diag.ChangeAsciiInputDelimiterRequest(slave=SLAVE)) - ) - - # NOT WORKING: _check_call(await client.execute(req_diag.ForceListenOnlyModeRequest(slave=SLAVE))) + client.execute(req_diag.RestartCommunicationsOptionRequest(slave=SLAVE)) + client.execute(req_diag.ReturnDiagnosticRegisterRequest(slave=SLAVE)) + client.execute(req_diag.ChangeAsciiInputDelimiterRequest(slave=SLAVE)) + + # NOT WORKING: _check_call(client.execute(req_diag.ForceListenOnlyModeRequest(slave=SLAVE))) # does not send a response - _check_call(await client.execute(req_diag.ClearCountersRequest())) - _check_call( - await client.execute( - req_diag.ReturnBusCommunicationErrorCountRequest(slave=SLAVE) - ) - ) - _check_call( - await client.execute(req_diag.ReturnBusExceptionErrorCountRequest(slave=SLAVE)) - ) - _check_call( - await client.execute(req_diag.ReturnSlaveMessageCountRequest(slave=SLAVE)) - ) - _check_call( - await client.execute(req_diag.ReturnSlaveNoResponseCountRequest(slave=SLAVE)) - ) - _check_call(await client.execute(req_diag.ReturnSlaveNAKCountRequest(slave=SLAVE))) - _check_call(await client.execute(req_diag.ReturnSlaveBusyCountRequest(slave=SLAVE))) - _check_call( - await client.execute( - req_diag.ReturnSlaveBusCharacterOverrunCountRequest(slave=SLAVE) - ) - ) - _check_call( - await client.execute(req_diag.ReturnIopOverrunCountRequest(slave=SLAVE)) - ) - _check_call(await client.execute(req_diag.ClearOverrunCountRequest(slave=SLAVE))) - # NOT WORKING _check_call(await client.execute(req_diag.GetClearModbusPlusRequest(slave=SLAVE))) + client.execute(req_diag.ClearCountersRequest()) + client.execute(req_diag.ReturnBusCommunicationErrorCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnBusExceptionErrorCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnSlaveMessageCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnSlaveNoResponseCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnSlaveNAKCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnSlaveBusyCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnSlaveBusCharacterOverrunCountRequest(slave=SLAVE)) + client.execute(req_diag.ReturnIopOverrunCountRequest(slave=SLAVE)) + client.execute(req_diag.ClearOverrunCountRequest(slave=SLAVE)) + # NOT WORKING _check_call(client.execute(req_diag.GetClearModbusPlusRequest(slave=SLAVE))) # ------------------------ # Run the calls in groups. # ------------------------ - - -async def run_async_calls(client): - """Demonstrate basic read/write calls.""" - await async_template_call(client) - await _handle_coils(client) - await _handle_discrete_input(client) - await _handle_holding_registers(client) - await _handle_input_registers(client) - await _execute_information_requests(client) - await _execute_diagnostic_requests(client) - - def run_sync_calls(client): """Demonstrate basic read/write calls.""" template_call(client) - - -async def async_main(cmdline=None): - """Combine setup and run""" - testclient = setup_async_client( - description="Run asynchronous client.", cmdline=cmdline - ) - await testclient.connect() - await async_template_call(testclient) - await run_async_client(testclient, modbus_calls=run_async_calls) + handle_coils(client) + handle_discrete_input(client) + handle_holding_registers(client) + handle_input_registers(client) + # awaiting fix: execute_information_requests(client) + # awaiting fix: execute_diagnostic_requests(client) def main(cmdline=None): """Combine setup and run.""" - testclient = setup_sync_client( - description="Run synchronous client.", cmdline=cmdline - ) - run_sync_client(testclient, modbus_calls=run_sync_calls) + client = setup_sync_client(description="Run synchronous client.", cmdline=cmdline) + run_sync_client(client, modbus_calls=run_sync_calls) if __name__ == "__main__": - asyncio.run(async_main()) # pragma: no cover - main() # pragma: no cover + main() # pragma: no cove diff --git a/test/sub_examples/test_examples.py b/test/sub_examples/test_examples.py index 1485938323..25d07f5ac5 100755 --- a/test/sub_examples/test_examples.py +++ b/test/sub_examples/test_examples.py @@ -4,24 +4,33 @@ """ import asyncio +from threading import Thread +from time import sleep +from unittest import mock import pytest from examples.client_async import run_a_few_calls, run_async_client, setup_async_client +from examples.client_async_calls import async_template_call +from examples.client_async_calls import main as main_client_async_calls from examples.client_calls import main as main_client_calls +from examples.client_calls import template_call from examples.client_custom_msg import run_custom_client from examples.client_payload import run_payload_calls from examples.datastore_simulator import run_server_simulator, setup_simulator from examples.message_generator import generate_messages from examples.message_parser import parse_messages -from examples.server_async import run_async_server +from examples.server_async import run_async_server, setup_server from examples.server_callback import run_callback_server from examples.server_payload import setup_payload_server +from examples.server_sync import run_sync_server from examples.server_updating import run_updating_server, setup_updating_server from examples.simple_async_client import run_async_simple_client from examples.simple_sync_client import run_sync_simple_client from examples.simulator import run_simulator -from pymodbus.server import ServerAsyncStop +from pymodbus.exceptions import ModbusException +from pymodbus.pdu import ExceptionResponse +from pymodbus.server import ServerAsyncStop, ServerStop BASE_PORT = 6400 @@ -55,9 +64,75 @@ def test_message_parser(self): ("use_comm", "use_framer", "use_port"), USE_CASES, ) - async def test_client_calls(self, use_comm, mock_server): + async def test_client_async_calls(self, mock_server): + """Test client_async_calls.""" + await main_client_async_calls(cmdline=mock_server) + + @pytest.mark.parametrize( + ("use_comm", "use_framer", "use_port"), + [ + ("tcp", "socket", BASE_PORT + 1), + ], + ) + async def test_client_async_calls_errors(self, mock_server): + """Test client_async_calls.""" + + async def run_template_call(client): + """Demonstrate basic read/write calls.""" + await async_template_call(client) + + client = setup_async_client(cmdline=mock_server) + client.read_coils = mock.AsyncMock(side_effect=ModbusException("test")) + with pytest.raises(ModbusException): + await run_async_client(client, modbus_calls=run_template_call) + client.read_coils = mock.AsyncMock(return_value=ExceptionResponse(0x05, 0x10)) + with pytest.raises(ModbusException): + await run_async_client(client, modbus_calls=run_template_call) + + @pytest.mark.parametrize("use_host", ["localhost"]) + @pytest.mark.parametrize( + ("use_comm", "use_framer", "use_port"), + [ + ("tcp", "socket", BASE_PORT + 1), + ("tcp", "rtu", BASE_PORT + 2), + # awaiting fix: ("tls", "tls", BASE_PORT + 3), + ("udp", "socket", BASE_PORT + 4), + ("udp", "rtu", BASE_PORT + 5), + ("serial", "rtu", BASE_PORT + 6), + # awaiting fix: ("serial", "ascii", BASE_PORT + 7), + # awaiting fix: ("serial", "binary", BASE_PORT + 8), + ], + ) + def test_client_calls(self, mock_clc, mock_cls): """Test client_calls.""" - main_client_calls(cmdline=mock_server) + server_args = setup_server(cmdline=mock_cls) + thread = Thread(target=run_sync_server, args=(server_args,)) + thread.daemon = True + thread.start() + sleep(1) + main_client_calls(cmdline=mock_clc) + ServerStop() + + @pytest.mark.parametrize( + ("use_comm", "use_framer", "use_port"), + [ + ("tcp", "socket", BASE_PORT + 1), + ], + ) + async def test_client_calls_errors(self, mock_server): + """Test client_calls.""" + + def run_template_call(client): + """Demonstrate basic read/write calls.""" + template_call(client) + + client = setup_async_client(cmdline=mock_server) + client.read_coils = mock.Mock(side_effect=ModbusException("test")) + with pytest.raises(ModbusException): + await run_async_client(client, modbus_calls=run_template_call) + client.read_coils = mock.Mock(return_value=ExceptionResponse(0x05, 0x10)) + with pytest.raises(ModbusException): + await run_async_client(client, modbus_calls=run_template_call) @pytest.mark.parametrize("use_host", ["localhost"]) @pytest.mark.parametrize(