Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct minor framer/pdu errors. #2407

Merged
merged 3 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions examples/client_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
sys.exit(-1)

import pymodbus.client as modbusClient
from pymodbus import ModbusException


_logger = logging.getLogger(__file__)
Expand Down Expand Up @@ -124,15 +123,11 @@ async def run_async_client(client, modbus_calls=None):

async def run_a_few_calls(client):
"""Test connection works."""
try:
rr = await client.read_coils(32, 1, slave=1)
assert len(rr.bits) == 8
rr = await client.read_holding_registers(4, 2, slave=1)
assert rr.registers[0] == 17
assert rr.registers[1] == 17
except ModbusException:
pass

rr = await client.read_coils(32, 1, slave=1)
assert len(rr.bits) == 8
rr = await client.read_holding_registers(4, 2, slave=1)
assert rr.registers[0] == 17
assert rr.registers[1] == 17

async def main(cmdline=None):
"""Combine setup and run."""
Expand Down
84 changes: 59 additions & 25 deletions examples/client_async_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import logging
import sys

from pymodbus import ModbusException
from pymodbus.pdu import FileRecord


try:
import client_async
Expand All @@ -57,14 +60,14 @@ async def async_template_call(client):
"""Show complete modbus call, async version."""
try:
rr = await client.read_coils(1, 1, slave=SLAVE)
except client_async.ModbusException as exc:
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 client_async.ModbusException(txt)
raise ModbusException(txt)

# Validate data
txt = f"### Template coils response: {rr.bits!s}"
Expand Down Expand Up @@ -163,6 +166,24 @@ async def async_handle_input_registers(client):
assert len(rr.registers) == 8


async def async_handle_file_records(client):
"""Read/write file records."""
_logger.info("### Read/write file records")
record = FileRecord(file_number=14, record_number=12, record_length=64)
rr = await client.read_file_record([record, record], slave=SLAVE)
assert not rr.isError()
assert len(rr.records) == 2
assert rr.records[0].record_data == b'SERVER DUMMY RECORD.'
assert rr.records[1].record_data == b'SERVER DUMMY RECORD.'
record.record_data = b'Pure test '
record.record_length = len(record.record_data) / 2
record = FileRecord(file_number=14, record_number=12, record_data=b'Pure test ')
rr = await client.write_file_record([record], slave=1)
assert not rr.isError()




async def async_execute_information_requests(client):
"""Execute extended information requests."""
_logger.info("### Running information requests.")
Expand All @@ -172,21 +193,21 @@ async def async_execute_information_requests(client):

rr = await client.report_slave_id(slave=SLAVE)
assert not rr.isError() # test that call was OK
# assert not rr.status
assert rr.status

rr = await client.read_exception_status(slave=SLAVE)
assert not rr.isError() # test that call was OK
# assert not rr.status
assert not rr.status

rr = await client.diag_get_comm_event_counter(slave=SLAVE)
assert not rr.isError() # test that call was OK
# assert rr.status
# assert not rr.count
assert rr.status
assert not rr.count

rr = await client.diag_get_comm_event_log(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))
assert rr.status
assert not (rr.event_count + rr.message_count + len(rr.events))


async def async_execute_diagnostic_requests(client):
Expand All @@ -197,23 +218,35 @@ async def async_execute_diagnostic_requests(client):
assert not rr.isError() # test that call was OK
assert rr.message == message

await client.diag_restart_communication(True, slave=SLAVE)
await client.diag_read_diagnostic_register(slave=SLAVE)
await client.diag_change_ascii_input_delimeter(slave=SLAVE)

# NOT WORKING: await client.diag_force_listen_only(slave=SLAVE)

await client.diag_clear_counters()
await client.diag_read_bus_comm_error_count(slave=SLAVE)
await client.diag_read_bus_exception_error_count(slave=SLAVE)
await client.diag_read_slave_message_count(slave=SLAVE)
await client.diag_read_slave_no_response_count(slave=SLAVE)
await client.diag_read_slave_nak_count(slave=SLAVE)
await client.diag_read_slave_busy_count(slave=SLAVE)
await client.diag_read_bus_char_overrun_count(slave=SLAVE)
await client.diag_read_iop_overrun_count(slave=SLAVE)
await client.diag_clear_overrun_counter(slave=SLAVE)
# NOT WORKING await client.diag_getclear_modbus_response(slave=SLAVE)
rr = await client.diag_restart_communication(True, slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_diagnostic_register(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_change_ascii_input_delimeter(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_clear_counters()
assert not rr.isError() # test that call was OK
rr = await client.diag_read_bus_comm_error_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_bus_exception_error_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_slave_message_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_slave_no_response_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_slave_nak_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_slave_busy_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_bus_char_overrun_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_read_iop_overrun_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_clear_overrun_counter(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = await client.diag_getclear_modbus_response(slave=SLAVE)
assert not rr.isError() # test that call was OK
assert not await client.diag_force_listen_only(slave=SLAVE, no_response_expected=True)


# ------------------------
Expand All @@ -226,6 +259,7 @@ async def run_async_calls(client):
await async_handle_discrete_input(client)
await async_handle_holding_registers(client)
await async_handle_input_registers(client)
await async_handle_file_records(client)
await async_execute_information_requests(client)
await async_execute_diagnostic_requests(client)

Expand Down
98 changes: 67 additions & 31 deletions examples/client_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import logging
import sys

from pymodbus.pdu import FileRecord


try:
import client_sync
Expand Down Expand Up @@ -159,57 +161,90 @@ def handle_input_registers(client):
assert len(rr.registers) == 8


def handle_file_records(client):
"""Read/write file records."""
_logger.info("### Read/write file records")
record = FileRecord(file_number=14, record_number=12, record_length=64)
rr = client.read_file_record([record, record], slave=SLAVE)
assert not rr.isError()
assert len(rr.records) == 2
assert rr.records[0].record_data == b'SERVER DUMMY RECORD.'
assert rr.records[1].record_data == b'SERVER DUMMY RECORD.'
record.record_data = b'Pure test '
record.record_length = len(record.record_data) / 2
record = FileRecord(file_number=14, record_number=12, record_data=b'Pure test ')
rr = client.write_file_record([record], slave=1)
assert not rr.isError()


def execute_information_requests(client):
"""Execute extended information requests."""
_logger.info("### Running information requests.")
rr = client.read_device_information(slave=SLAVE)
assert not rr.isError() # test that call was OK
assert rr.information[0] == b"Pymodbus"
# NOT WORKING: ONLY SYNC.
# FAILS WITH framer = RTU
# rr = client.read_device_information(slave=SLAVE)
# assert not rr.isError() # test that call was OK
# assert rr.information[0] == b"Pymodbus"

rr = client.report_slave_id(slave=SLAVE)
assert not rr.isError() # test that call was OK
# assert not rr.status
assert rr.status

rr = client.read_exception_status(slave=SLAVE)
assert not rr.isError() # test that call was OK
# assert not rr.status
assert not rr.status

rr = client.diag_get_comm_event_counter(slave=SLAVE)
assert not rr.isError() # test that call was OK
# assert rr.status
# assert not rr.count
assert rr.status
assert not rr.count

rr = client.diag_get_comm_event_log(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))
assert rr.status
assert not (rr.event_count + rr.message_count + len(rr.events))


def execute_diagnostic_requests(client):
"""Execute extended diagnostic requests."""
_logger.info("### Running diagnostic requests.")
message = b"OK"
rr = client.diag_query_data(msg=message, slave=SLAVE)
assert not rr.isError() # test that call was OK
assert rr.message == message

client.diag_restart_communication(True, slave=SLAVE)
client.diag_read_diagnostic_register(slave=SLAVE)
client.diag_change_ascii_input_delimeter(slave=SLAVE)
# NOT WORKING: ONLY SYNC
# message = b"OK"
# rr = client.diag_query_data(msg=message, slave=SLAVE)
# assert not rr.isError() # test that call was OK
# assert rr.message == message

# NOT WORKING: await client.diag_force_listen_only(slave=SLAVE)
rr = client.diag_restart_communication(True, slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_diagnostic_register(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_change_ascii_input_delimeter(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_clear_counters()
assert not rr.isError() # test that call was OK
rr = client.diag_read_bus_comm_error_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_bus_exception_error_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_slave_message_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_slave_no_response_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_slave_nak_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_slave_busy_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_bus_char_overrun_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_read_iop_overrun_count(slave=SLAVE)
assert not rr.isError() # test that call was OK
rr = client.diag_clear_overrun_counter(slave=SLAVE)
assert not rr.isError() # test that call was OK
# NOT WORKING rr = client.diag_getclear_modbus_response(slave=SLAVE)
assert not rr.isError() # test that call was OK
# NOT WORKING: rr = client.diag_force_listen_only(slave=SLAVE)
assert not rr.isError() # test that call was OK

client.diag_clear_counters()
client.diag_read_bus_comm_error_count(slave=SLAVE)
client.diag_read_bus_exception_error_count(slave=SLAVE)
client.diag_read_slave_message_count(slave=SLAVE)
client.diag_read_slave_no_response_count(slave=SLAVE)
client.diag_read_slave_nak_count(slave=SLAVE)
client.diag_read_slave_busy_count(slave=SLAVE)
client.diag_read_bus_char_overrun_count(slave=SLAVE)
client.diag_read_iop_overrun_count(slave=SLAVE)
client.diag_clear_overrun_counter(slave=SLAVE)
# NOT WORKING client.diag_getclear_modbus_response(slave=SLAVE)


# ------------------------
Expand All @@ -222,8 +257,9 @@ def run_sync_calls(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)
handle_file_records(client)
execute_information_requests(client)
execute_diagnostic_requests(client)


def main(cmdline=None):
Expand Down
9 changes: 4 additions & 5 deletions pymodbus/client/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,23 +367,22 @@ def report_slave_id(self, slave: int = 1, no_response_expected: bool = False) ->
"""
return self.execute(no_response_expected, pdu_other_msg.ReportSlaveIdRequest(slave=slave))

def read_file_record(self, records: list[tuple], slave: int = 1, no_response_expected: bool = False) -> T:
def read_file_record(self, records: list[pdu_file_msg.FileRecord], slave: int = 1, no_response_expected: bool = False) -> T:
"""Read file record (code 0x14).

:param records: List of (Reference type, File number, Record Number, Record Length)
:param records: List of FileRecord (Reference type, File number, Record Number)
:param slave: device id
:param no_response_expected: (optional) The client will not expect a response to the request
:raises ModbusException:
"""
return self.execute(no_response_expected, pdu_file_msg.ReadFileRecordRequest(records, slave=slave))

def write_file_record(self, records: list[tuple], slave: int = 1, no_response_expected: bool = False) -> T:
def write_file_record(self, records: list[pdu_file_msg.FileRecord], slave: int = 1, no_response_expected: bool = False) -> T:
"""Write file record (code 0x15).

:param records: List of (Reference type, File number, Record Number, Record Length)
:param records: List of File_record (Reference type, File number, Record Number, Record Length, Record Data)
:param slave: (optional) Device id
:param no_response_expected: (optional) The client will not expect a response to the request
:param no_response_expected: (optional) The client will not expect a response to the request
:raises ModbusException:
"""
return self.execute(no_response_expected, pdu_file_msg.WriteFileRecordRequest(records,slave=slave))
Expand Down
1 change: 0 additions & 1 deletion pymodbus/framer/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ def __init__(
) -> None:
"""Initialize a ADU (framer) instance."""
self.decoder = decoder
self.databuffer = b""

def decode(self, _data: bytes) -> tuple[int, int, int, bytes]:
"""Decode ADU.
Expand Down
2 changes: 2 additions & 0 deletions pymodbus/pdu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"DecodePDU",
"ExceptionResponse",
"ExceptionResponse",
"FileRecord",
"ModbusExceptions",
"ModbusPDU",
]

from pymodbus.pdu.decoders import DecodePDU
from pymodbus.pdu.file_message import FileRecord
from pymodbus.pdu.pdu import ExceptionResponse, ModbusExceptions, ModbusPDU
2 changes: 0 additions & 2 deletions pymodbus/pdu/bit_read_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ class ReadCoilsRequest(ReadBitsRequestBase):
"""

function_code = 1
function_code_name = "read_coils"

def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False):
"""Initialize a new instance.
Expand Down Expand Up @@ -200,7 +199,6 @@ class ReadDiscreteInputsRequest(ReadBitsRequestBase):
"""

function_code = 2
function_code_name = "read_discrete_input"

def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False):
"""Initialize a new instance.
Expand Down
2 changes: 0 additions & 2 deletions pymodbus/pdu/bit_write_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ class WriteSingleCoilRequest(ModbusPDU):
"""

function_code = 5
function_code_name = "write_coil"

_rtu_frame_size = 8

Expand Down Expand Up @@ -163,7 +162,6 @@ class WriteMultipleCoilsRequest(ModbusPDU):
"""

function_code = 15
function_code_name = "write_coils"
_rtu_byte_count_pos = 6

def __init__(self, address=0, values=None, slave=None, transaction=0, skip_encode=0):
Expand Down
Loading