diff --git a/doc/source/_static/examples.tgz b/doc/source/_static/examples.tgz index 6f5117edd..180fa71e0 100644 Binary files a/doc/source/_static/examples.tgz and b/doc/source/_static/examples.tgz differ diff --git a/doc/source/_static/examples.zip b/doc/source/_static/examples.zip index 32350499a..2844cadfc 100644 Binary files a/doc/source/_static/examples.zip and b/doc/source/_static/examples.zip differ diff --git a/doc/source/examples.rst b/doc/source/examples.rst index 1ec97e8cf..2be001006 100644 --- a/doc/source/examples.rst +++ b/doc/source/examples.rst @@ -210,28 +210,10 @@ Source: :github:`examples/contrib/solar.py` :noindex: -Redis datastore -^^^^^^^^^^^^^^^ -Source: :github:`examples/contrib/redis_datastore.py` - -.. automodule:: examples.contrib.redis_datastore - :undoc-members: - :noindex: - - Serial Forwarder ^^^^^^^^^^^^^^^^ Source: :github:`examples/contrib/serial_forwarder.py` .. automodule:: examples.contrib.serial_forwarder :undoc-members: - :noindex: - - -Sqlalchemy datastore -^^^^^^^^^^^^^^^^^^^^ -Source: :github:`examples/contrib/sql_datastore.py` - -.. automodule:: examples.contrib.sql_datastore - :undoc-members: - :noindex: + :noindex: \ No newline at end of file diff --git a/doc/source/library/pymodbus.rst b/doc/source/library/pymodbus.rst index 610a440d1..9dc7e5079 100644 --- a/doc/source/library/pymodbus.rst +++ b/doc/source/library/pymodbus.rst @@ -45,12 +45,7 @@ PDU classes :undoc-members: :show-inheritance: -.. automodule:: pymodbus.pdu.bit_read_message - :members: - :undoc-members: - :show-inheritance: - -.. automodule:: pymodbus.pdu.bit_write_message +.. automodule:: pymodbus.pdu.bit_message :members: :undoc-members: :show-inheritance: diff --git a/examples/client_custom_msg.py b/examples/client_custom_msg.py index 0820cf161..f1093f882 100755 --- a/examples/client_custom_msg.py +++ b/examples/client_custom_msg.py @@ -16,7 +16,7 @@ from pymodbus import FramerType from pymodbus.client import AsyncModbusTcpClient as ModbusClient from pymodbus.pdu import ModbusExceptions, ModbusPDU -from pymodbus.pdu.bit_read_message import ReadCoilsRequest +from pymodbus.pdu.bit_message import ReadCoilsRequest # --------------------------------------------------------------------------- # @@ -39,7 +39,7 @@ class CustomModbusPDU(ModbusPDU): def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): """Initialize.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.values = values or [] def encode(self): @@ -72,7 +72,7 @@ class CustomRequest(ModbusPDU): def __init__(self, address=None, slave=1, transaction=0, skip_encode=False): """Initialize.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.count = 16 diff --git a/pymodbus/client/mixin.py b/pymodbus/client/mixin.py index cf0f92412..b1d4d7509 100644 --- a/pymodbus/client/mixin.py +++ b/pymodbus/client/mixin.py @@ -6,8 +6,7 @@ from enum import Enum from typing import Generic, TypeVar -import pymodbus.pdu.bit_read_message as pdu_bit_read -import pymodbus.pdu.bit_write_message as pdu_bit_write +import pymodbus.pdu.bit_message as pdu_bit import pymodbus.pdu.diag_message as pdu_diag import pymodbus.pdu.file_message as pdu_file_msg import pymodbus.pdu.mei_message as pdu_mei @@ -71,7 +70,7 @@ def read_coils(self, address: int, count: int = 1, slave: int = 1, no_response_e :param no_response_expected: (optional) The client will not expect a response to the request :raises ModbusException: """ - return self.execute(no_response_expected, pdu_bit_read.ReadCoilsRequest(address=address, count=count, slave=slave)) + return self.execute(no_response_expected, pdu_bit.ReadCoilsRequest(address=address, count=count, slave=slave)) def read_discrete_inputs(self, address: int, @@ -86,7 +85,7 @@ def read_discrete_inputs(self, :param no_response_expected: (optional) The client will not expect a response to the request :raises ModbusException: """ - return self.execute(no_response_expected, pdu_bit_read.ReadDiscreteInputsRequest(address=address, count=count, slave=slave, )) + return self.execute(no_response_expected, pdu_bit.ReadDiscreteInputsRequest(address=address, count=count, slave=slave, )) def read_holding_registers(self, address: int, @@ -127,7 +126,7 @@ def write_coil(self, address: int, value: bool, slave: int = 1, no_response_expe :param no_response_expected: (optional) The client will not expect a response to the request :raises ModbusException: """ - return self.execute(no_response_expected, pdu_bit_write.WriteSingleCoilRequest(address, value, slave=slave)) + return self.execute(no_response_expected, pdu_bit.WriteSingleCoilRequest(address, value, slave=slave)) def write_register(self, address: int, value: bytes | int, slave: int = 1, no_response_expected: bool = False) -> T: """Write register (code 0x06). @@ -337,7 +336,7 @@ def write_coils( :param no_response_expected: (optional) The client will not expect a response to the request :raises ModbusException: """ - return self.execute(no_response_expected, pdu_bit_write.WriteMultipleCoilsRequest(address, values=values, slave=slave)) + return self.execute(no_response_expected, pdu_bit.WriteMultipleCoilsRequest(address, values=values, slave=slave)) def write_registers( self, diff --git a/pymodbus/pdu/bit_message.py b/pymodbus/pdu/bit_message.py new file mode 100644 index 000000000..6c276a82d --- /dev/null +++ b/pymodbus/pdu/bit_message.py @@ -0,0 +1,487 @@ +"""Bit Reading Request/Response messages.""" + +# pylint: disable=missing-type-doc +import struct + +from pymodbus.constants import ModbusStatus +from pymodbus.pdu.pdu import ModbusExceptions as merror +from pymodbus.pdu.pdu import ModbusPDU +from pymodbus.utilities import pack_bitstring, unpack_bitstring + + +_turn_coil_on = struct.pack(">H", ModbusStatus.ON) +_turn_coil_off = struct.pack(">H", ModbusStatus.OFF) + + +class ReadBitsRequestBase(ModbusPDU): + """Base class for Messages Requesting bit values.""" + + _rtu_frame_size = 8 + + def __init__(self, address, count, slave, transaction, skip_encode): + """Initialize the read request data. + + :param address: The start address to read from + :param count: The number of bits after "address" to read + :param slave: Modbus slave slave ID + """ + super().__init__() + super().setBaseData(slave, transaction, skip_encode) + self.address = address + self.count = count + + def encode(self): + """Encode a request pdu. + + :returns: The encoded pdu + """ + return struct.pack(">HH", self.address, self.count) + + def decode(self, data): + """Decode a request pdu. + + :param data: The packet data to decode + """ + self.address, self.count = struct.unpack(">HH", data) + + def get_response_pdu_size(self): + """Get response pdu size. + + Func_code (1 byte) + Byte Count(1 byte) + Quantity of Coils (n Bytes)/8, + if the remainder is different of 0 then N = N+1 + :return: + """ + count = self.count // 8 + if self.count % 8: + count += 1 + + return 1 + 1 + count + + def __str__(self): + """Return a string representation of the instance.""" + return f"ReadBitRequest({self.address},{self.count})" + + +class ReadBitsResponseBase(ModbusPDU): + """Base class for Messages responding to bit-reading values. + + The requested bits can be found in the .bits list. + """ + + _rtu_byte_count_pos = 2 + + def __init__(self, values, slave, transaction, skip_encode): + """Initialize a new instance. + + :param values: The requested values to be returned + :param slave: Modbus slave slave ID + """ + super().__init__() + super().setBaseData(slave, transaction, skip_encode) + + #: A list of booleans representing bit values + self.bits = values or [] + + def encode(self): + """Encode response pdu. + + :returns: The encoded packet message + """ + result = pack_bitstring(self.bits) + packet = struct.pack(">B", len(result)) + result + return packet + + def decode(self, data): + """Decode response pdu. + + :param data: The packet data to decode + """ + self.byte_count = int(data[0]) # pylint: disable=attribute-defined-outside-init + self.bits = unpack_bitstring(data[1:]) + + def __str__(self): + """Return a string representation of the instance.""" + return f"{self.__class__.__name__}({len(self.bits)})" + + +class ReadCoilsRequest(ReadBitsRequestBase): + """This function code is used to read from 1 to 2000(0x7d0) contiguous status of coils in a remote device. + + 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. + """ + + function_code = 1 + + def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): + """Initialize a new instance. + + :param address: The address to start reading from + :param count: The number of bits to read + :param slave: Modbus slave slave ID + """ + ReadBitsRequestBase.__init__(self, address, count, slave, transaction, skip_encode) + + async def update_datastore(self, 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. + + :param context: The datastore to request from + :returns: An initialized :py:class:`~pymodbus.register_read_message.ReadCoilsResponse`, or an :py:class:`~pymodbus.pdu.ExceptionResponse` if an error occurred + """ + if not (1 <= self.count <= 0x7D0): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = await context.async_getValues( + self.function_code, self.address, self.count + ) + return ReadCoilsResponse(values) + + +class ReadCoilsResponse(ReadBitsResponseBase): + """The coils in the response message are packed as one coil per bit of the data field. + + Status is indicated as 1= ON and 0= OFF. The LSB of the + first data byte contains the output addressed in the query. The other + coils follow toward the high order end of this byte, and from low order + to high order in subsequent bytes. + + 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). The Byte Count field specifies + the quantity of complete bytes of data. + + The requested coils can be found in boolean form in the .bits list. + """ + + function_code = 1 + + def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): + """Initialize a new instance. + + :param values: The request values to respond with + :param slave: Modbus slave slave ID + """ + ReadBitsResponseBase.__init__(self, values, slave, transaction, skip_encode) + + +class ReadDiscreteInputsRequest(ReadBitsRequestBase): + """This function code is used to read from 1 to 2000(0x7d0). + + Contiguous status of discrete inputs in a remote device. The Request PDU specifies the + starting address, ie the address of the first input specified, and the + number of inputs. In the PDU Discrete Inputs are addressed starting at + zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15. + """ + + function_code = 2 + + def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): + """Initialize a new instance. + + :param address: The address to start reading from + :param count: The number of bits to read + :param slave: Modbus slave slave ID + """ + ReadBitsRequestBase.__init__(self, address, count, slave, transaction, skip_encode) + + async def update_datastore(self, context): + """Run a read discrete input 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. + + :param context: The datastore to request from + :returns: An initialized :py:class:`~pymodbus.register_read_message.ReadDiscreteInputsResponse`, or an :py:class:`~pymodbus.pdu.ExceptionResponse` if an error occurred + """ + if not (1 <= self.count <= 0x7D0): + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, self.count): + return self.doException(merror.IllegalAddress) + values = await context.async_getValues( + self.function_code, self.address, self.count + ) + return ReadDiscreteInputsResponse(values) + + +class ReadDiscreteInputsResponse(ReadBitsResponseBase): + """The discrete inputs in the response message are packed as one input per bit of the data field. + + Status is indicated as 1= ON; 0= OFF. The LSB of + the first data byte contains the input addressed in the query. The other + inputs follow toward the high order end of this byte, and from low order + to high order in subsequent bytes. + + If the returned input 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). The Byte Count field specifies + the quantity of complete bytes of data. + + The requested coils can be found in boolean form in the .bits list. + """ + + function_code = 2 + + def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): + """Initialize a new instance. + + :param values: The request values to respond with + :param slave: Modbus slave slave ID + """ + ReadBitsResponseBase.__init__(self, values, slave, transaction, skip_encode) + + +class WriteSingleCoilRequest(ModbusPDU): + """This function code is used to write a single output to either ON or OFF in a remote device. + + The requested ON/OFF state is specified by a constant in the request + data field. A value of FF 00 hex requests the output to be ON. A value + of 00 00 requests it to be OFF. All other values are illegal and will + not affect the output. + + The Request PDU specifies the address of the coil to be forced. Coils + are addressed starting at zero. Therefore coil numbered 1 is addressed + as 0. The requested ON/OFF state is specified by a constant in the Coil + Value field. A value of 0XFF00 requests the coil to be ON. A value of + 0X0000 requests the coil to be off. All other values are illegal and + will not affect the coil. + """ + + function_code = 5 + + _rtu_frame_size = 8 + + def __init__(self, address=None, value=None, slave=None, transaction=0, skip_encode=0): + """Initialize a new instance. + + :param address: The variable address to write + :param value: The value to write at address + """ + super().__init__() + super().setBaseData(slave, transaction, skip_encode) + self.address = address + self.value = bool(value) + + def encode(self): + """Encode write coil request. + + :returns: The byte encoded message + """ + result = struct.pack(">H", self.address) + if self.value: + result += _turn_coil_on + else: + result += _turn_coil_off + return result + + def decode(self, data): + """Decode a write coil request. + + :param data: The packet data to decode + """ + self.address, value = struct.unpack(">HH", data) + self.value = value == ModbusStatus.ON + + async def update_datastore(self, context): + """Run a write coil request against a datastore. + + :param context: The datastore to request from + :returns: The populated response or exception message + """ + # if self.value not in [ModbusStatus.Off, ModbusStatus.On]: + # return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, 1): + return self.doException(merror.IllegalAddress) + + await context.async_setValues(self.function_code, self.address, [self.value]) + values = await context.async_getValues(self.function_code, self.address, 1) + return WriteSingleCoilResponse(self.address, values[0]) + + def get_response_pdu_size(self): + """Get response pdu size. + + Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) + :return: + """ + return 1 + 2 + 2 + + def __str__(self): + """Return a string representation of the instance.""" + return f"WriteCoilRequest({self.address}, {self.value}) => " + + +class WriteSingleCoilResponse(ModbusPDU): + """The normal response is an echo of the request. + + Returned after the coil state has been written. + """ + + function_code = 5 + _rtu_frame_size = 8 + + def __init__(self, address=None, value=None, slave=1, transaction=0, skip_encode=False): + """Initialize a new instance. + + :param address: The variable address written to + :param value: The value written at address + """ + super().__init__() + super().setBaseData(slave, transaction, skip_encode) + self.address = address + self.value = value + + def encode(self): + """Encode write coil response. + + :return: The byte encoded message + """ + result = struct.pack(">H", self.address) + if self.value: + result += _turn_coil_on + else: + result += _turn_coil_off + return result + + def decode(self, data): + """Decode a write coil response. + + :param data: The packet data to decode + """ + self.address, value = struct.unpack(">HH", data) + self.value = value == ModbusStatus.ON + + def __str__(self): + """Return a string representation of the instance. + + :returns: A string representation of the instance + """ + return f"WriteCoilResponse({self.address}) => {self.value}" + + +class WriteMultipleCoilsRequest(ModbusPDU): + """This function code is used to forcea sequence of coils. + + To either ON or OFF in a remote device. The Request PDU specifies the coil + references to be forced. Coils are addressed starting at zero. Therefore + coil numbered 1 is addressed as 0. + + The requested ON/OFF states are specified by contents of the request + data field. A logical "1" in a bit position of the field requests the + corresponding output to be ON. A logical "0" requests it to be OFF." + """ + + function_code = 15 + _rtu_byte_count_pos = 6 + + def __init__(self, address=0, values=None, slave=None, transaction=0, skip_encode=0): + """Initialize a new instance. + + :param address: The starting request address + :param values: The values to write + """ + super().__init__() + super().setBaseData(slave, transaction, skip_encode) + self.address = address + if values is None: + values = [] + elif not hasattr(values, "__iter__"): + values = [values] + self.values = values + self.byte_count = (len(self.values) + 7) // 8 + + def encode(self): + """Encode write coils request. + + :returns: The byte encoded message + """ + count = len(self.values) + self.byte_count = (count + 7) // 8 + packet = struct.pack(">HHB", self.address, count, self.byte_count) + packet += pack_bitstring(self.values) + return packet + + def decode(self, data): + """Decode a write coils request. + + :param data: The packet data to decode + """ + self.address, count, self.byte_count = struct.unpack(">HHB", data[0:5]) + values = unpack_bitstring(data[5:]) + self.values = values[:count] + + async def update_datastore(self, context): + """Run a write coils request against a datastore. + + :param context: The datastore to request from + :returns: The populated response or exception message + """ + count = len(self.values) + if not 1 <= count <= 0x07B0: + return self.doException(merror.IllegalValue) + if self.byte_count != (count + 7) // 8: + return self.doException(merror.IllegalValue) + if not context.validate(self.function_code, self.address, count): + return self.doException(merror.IllegalAddress) + + await context.async_setValues( + self.function_code, self.address, self.values + ) + return WriteMultipleCoilsResponse(self.address, count) + + def __str__(self): + """Return a string representation of the instance.""" + return f"WriteNCoilRequest ({self.address}) => {len(self.values)}" + + def get_response_pdu_size(self): + """Get response pdu size. + + Func_code (1 byte) + Output Address (2 byte) + Quantity of Outputs (2 Bytes) + :return: + """ + return 1 + 2 + 2 + + +class WriteMultipleCoilsResponse(ModbusPDU): + """The normal response returns the function code. + + Starting address, and quantity of coils forced. + """ + + function_code = 15 + _rtu_frame_size = 8 + + def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): + """Initialize a new instance. + + :param address: The starting variable address written to + :param count: The number of values written + """ + super().__init__() + super().setBaseData(slave, transaction, skip_encode) + self.address = address + self.count = count + + def encode(self): + """Encode write coils response. + + :returns: The byte encoded message + """ + return struct.pack(">HH", self.address, self.count) + + def decode(self, data): + """Decode a write coils response. + + :param data: The packet data to decode + """ + self.address, self.count = struct.unpack(">HH", data) + + def __str__(self): + """Return a string representation of the instance.""" + return f"WriteNCoilResponse({self.address}, {self.count})" diff --git a/pymodbus/pdu/bit_read_message.py b/pymodbus/pdu/bit_read_message.py deleted file mode 100644 index 2873937b8..000000000 --- a/pymodbus/pdu/bit_read_message.py +++ /dev/null @@ -1,256 +0,0 @@ -"""Bit Reading Request/Response messages.""" - -# pylint: disable=missing-type-doc -import struct - -from pymodbus.pdu.pdu import ModbusExceptions as merror -from pymodbus.pdu.pdu import ModbusPDU -from pymodbus.utilities import pack_bitstring, unpack_bitstring - - -class ReadBitsRequestBase(ModbusPDU): - """Base class for Messages Requesting bit values.""" - - _rtu_frame_size = 8 - - def __init__(self, address, count, slave, transaction, skip_encode): - """Initialize the read request data. - - :param address: The start address to read from - :param count: The number of bits after "address" to read - :param slave: Modbus slave slave ID - """ - super().__init__() - super().setData(slave, transaction, skip_encode) - self.address = address - self.count = count - - def encode(self): - """Encode a request pdu. - - :returns: The encoded pdu - """ - return struct.pack(">HH", self.address, self.count) - - def decode(self, data): - """Decode a request pdu. - - :param data: The packet data to decode - """ - self.address, self.count = struct.unpack(">HH", data) - - def get_response_pdu_size(self): - """Get response pdu size. - - Func_code (1 byte) + Byte Count(1 byte) + Quantity of Coils (n Bytes)/8, - if the remainder is different of 0 then N = N+1 - :return: - """ - count = self.count // 8 - if self.count % 8: # pragma: no cover - count += 1 - - return 1 + 1 + count - - def __str__(self): - """Return a string representation of the instance.""" - return f"ReadBitRequest({self.address},{self.count})" - - -class ReadBitsResponseBase(ModbusPDU): - """Base class for Messages responding to bit-reading values. - - The requested bits can be found in the .bits list. - """ - - _rtu_byte_count_pos = 2 - - def __init__(self, values, slave, transaction, skip_encode): - """Initialize a new instance. - - :param values: The requested values to be returned - :param slave: Modbus slave slave ID - """ - super().__init__() - super().setData(slave, transaction, skip_encode) - - #: A list of booleans representing bit values - self.bits = values or [] - - def encode(self): - """Encode response pdu. - - :returns: The encoded packet message - """ - result = pack_bitstring(self.bits) - packet = struct.pack(">B", len(result)) + result - return packet - - def decode(self, data): - """Decode response pdu. - - :param data: The packet data to decode - """ - self.byte_count = int(data[0]) # pylint: disable=attribute-defined-outside-init - self.bits = unpack_bitstring(data[1:]) - - def setBit(self, address, value=1): - """Set the specified bit. - - :param address: The bit to set - :param value: The value to set the bit to - """ - self.bits[address] = bool(value) - - def resetBit(self, address): - """Set the specified bit to 0. - - :param address: The bit to reset - """ - self.setBit(address, 0) - - def getBit(self, address): - """Get the specified bit's value. - - :param address: The bit to query - :returns: The value of the requested bit - """ - return self.bits[address] - - def __str__(self): - """Return a string representation of the instance.""" - return f"{self.__class__.__name__}({len(self.bits)})" - - -class ReadCoilsRequest(ReadBitsRequestBase): - """This function code is used to read from 1 to 2000(0x7d0) contiguous status of coils in a remote device. - - 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. - """ - - function_code = 1 - - def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param address: The address to start reading from - :param count: The number of bits to read - :param slave: Modbus slave slave ID - """ - ReadBitsRequestBase.__init__(self, address, count, slave, transaction, skip_encode) - - async def update_datastore(self, context): # pragma: no cover - """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. - - :param context: The datastore to request from - :returns: An initialized :py:class:`~pymodbus.register_read_message.ReadCoilsResponse`, or an :py:class:`~pymodbus.pdu.ExceptionResponse` if an error occurred - """ - if not (1 <= self.count <= 0x7D0): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, self.count): - return self.doException(merror.IllegalAddress) - values = await context.async_getValues( - self.function_code, self.address, self.count - ) - return ReadCoilsResponse(values) - - -class ReadCoilsResponse(ReadBitsResponseBase): - """The coils in the response message are packed as one coil per bit of the data field. - - Status is indicated as 1= ON and 0= OFF. The LSB of the - first data byte contains the output addressed in the query. The other - coils follow toward the high order end of this byte, and from low order - to high order in subsequent bytes. - - 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). The Byte Count field specifies - the quantity of complete bytes of data. - - The requested coils can be found in boolean form in the .bits list. - """ - - function_code = 1 - - def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param values: The request values to respond with - :param slave: Modbus slave slave ID - """ - ReadBitsResponseBase.__init__(self, values, slave, transaction, skip_encode) - - -class ReadDiscreteInputsRequest(ReadBitsRequestBase): - """This function code is used to read from 1 to 2000(0x7d0). - - Contiguous status of discrete inputs in a remote device. The Request PDU specifies the - starting address, ie the address of the first input specified, and the - number of inputs. In the PDU Discrete Inputs are addressed starting at - zero. Therefore Discrete inputs numbered 1-16 are addressed as 0-15. - """ - - function_code = 2 - - def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param address: The address to start reading from - :param count: The number of bits to read - :param slave: Modbus slave slave ID - """ - ReadBitsRequestBase.__init__(self, address, count, slave, transaction, skip_encode) - - async def update_datastore(self, context): # pragma: no cover - """Run a read discrete input 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. - - :param context: The datastore to request from - :returns: An initialized :py:class:`~pymodbus.register_read_message.ReadDiscreteInputsResponse`, or an :py:class:`~pymodbus.pdu.ExceptionResponse` if an error occurred - """ - if not (1 <= self.count <= 0x7D0): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, self.count): - return self.doException(merror.IllegalAddress) - values = await context.async_getValues( - self.function_code, self.address, self.count - ) - return ReadDiscreteInputsResponse(values) - - -class ReadDiscreteInputsResponse(ReadBitsResponseBase): - """The discrete inputs in the response message are packed as one input per bit of the data field. - - Status is indicated as 1= ON; 0= OFF. The LSB of - the first data byte contains the input addressed in the query. The other - inputs follow toward the high order end of this byte, and from low order - to high order in subsequent bytes. - - If the returned input 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). The Byte Count field specifies - the quantity of complete bytes of data. - - The requested coils can be found in boolean form in the .bits list. - """ - - function_code = 2 - - def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param values: The request values to respond with - :param slave: Modbus slave slave ID - """ - ReadBitsResponseBase.__init__(self, values, slave, transaction, skip_encode) diff --git a/pymodbus/pdu/bit_write_message.py b/pymodbus/pdu/bit_write_message.py deleted file mode 100644 index 06f4aee30..000000000 --- a/pymodbus/pdu/bit_write_message.py +++ /dev/null @@ -1,271 +0,0 @@ -"""Bit Writing Request/Response. - -TODO write mask request/response -""" - - -# pylint: disable=missing-type-doc -import struct - -from pymodbus.constants import ModbusStatus -from pymodbus.pdu.pdu import ModbusExceptions as merror -from pymodbus.pdu.pdu import ModbusPDU -from pymodbus.utilities import pack_bitstring, unpack_bitstring - - -# ---------------------------------------------------------------------------# -# Local Constants -# ---------------------------------------------------------------------------# -# These are defined in the spec to turn a coil on/off -# ---------------------------------------------------------------------------# -_turn_coil_on = struct.pack(">H", ModbusStatus.ON) -_turn_coil_off = struct.pack(">H", ModbusStatus.OFF) - - -class WriteSingleCoilRequest(ModbusPDU): - """This function code is used to write a single output to either ON or OFF in a remote device. - - The requested ON/OFF state is specified by a constant in the request - data field. A value of FF 00 hex requests the output to be ON. A value - of 00 00 requests it to be OFF. All other values are illegal and will - not affect the output. - - The Request PDU specifies the address of the coil to be forced. Coils - are addressed starting at zero. Therefore coil numbered 1 is addressed - as 0. The requested ON/OFF state is specified by a constant in the Coil - Value field. A value of 0XFF00 requests the coil to be ON. A value of - 0X0000 requests the coil to be off. All other values are illegal and - will not affect the coil. - """ - - function_code = 5 - - _rtu_frame_size = 8 - - def __init__(self, address=None, value=None, slave=None, transaction=0, skip_encode=0): - """Initialize a new instance. - - :param address: The variable address to write - :param value: The value to write at address - """ - super().__init__() - super().setData(slave, transaction, skip_encode) - self.address = address - self.value = bool(value) - - def encode(self): - """Encode write coil request. - - :returns: The byte encoded message - """ - result = struct.pack(">H", self.address) - if self.value: # pragma: no cover - result += _turn_coil_on - else: - result += _turn_coil_off # pragma: no cover - return result - - def decode(self, data): - """Decode a write coil request. - - :param data: The packet data to decode - """ - self.address, value = struct.unpack(">HH", data) - self.value = value == ModbusStatus.ON - - async def update_datastore(self, context): # pragma: no cover - """Run a write coil request against a datastore. - - :param context: The datastore to request from - :returns: The populated response or exception message - """ - # if self.value not in [ModbusStatus.Off, ModbusStatus.On]: - # return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, 1): - return self.doException(merror.IllegalAddress) - - await context.async_setValues(self.function_code, self.address, [self.value]) - values = await context.async_getValues(self.function_code, self.address, 1) - return WriteSingleCoilResponse(self.address, values[0]) - - def get_response_pdu_size(self): - """Get response pdu size. - - Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) - :return: - """ - return 1 + 2 + 2 - - def __str__(self): - """Return a string representation of the instance.""" - return f"WriteCoilRequest({self.address}, {self.value}) => " - - -class WriteSingleCoilResponse(ModbusPDU): - """The normal response is an echo of the request. - - Returned after the coil state has been written. - """ - - function_code = 5 - _rtu_frame_size = 8 - - def __init__(self, address=None, value=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param address: The variable address written to - :param value: The value written at address - """ - super().__init__() - super().setData(slave, transaction, skip_encode) - self.address = address - self.value = value - - def encode(self): - """Encode write coil response. - - :return: The byte encoded message - """ - result = struct.pack(">H", self.address) - if self.value: # pragma: no cover - result += _turn_coil_on - else: - result += _turn_coil_off # pragma: no cover - return result - - def decode(self, data): - """Decode a write coil response. - - :param data: The packet data to decode - """ - self.address, value = struct.unpack(">HH", data) - self.value = value == ModbusStatus.ON - - def __str__(self): - """Return a string representation of the instance. - - :returns: A string representation of the instance - """ - return f"WriteCoilResponse({self.address}) => {self.value}" - - -class WriteMultipleCoilsRequest(ModbusPDU): - """This function code is used to forcea sequence of coils. - - To either ON or OFF in a remote device. The Request PDU specifies the coil - references to be forced. Coils are addressed starting at zero. Therefore - coil numbered 1 is addressed as 0. - - The requested ON/OFF states are specified by contents of the request - data field. A logical "1" in a bit position of the field requests the - corresponding output to be ON. A logical "0" requests it to be OFF." - """ - - function_code = 15 - _rtu_byte_count_pos = 6 - - def __init__(self, address=0, values=None, slave=None, transaction=0, skip_encode=0): - """Initialize a new instance. - - :param address: The starting request address - :param values: The values to write - """ - super().__init__() - super().setData(slave, transaction, skip_encode) - self.address = address - if values is None: # pragma: no cover - values = [] - elif not hasattr(values, "__iter__"): - values = [values] - self.values = values - self.byte_count = (len(self.values) + 7) // 8 - - def encode(self): - """Encode write coils request. - - :returns: The byte encoded message - """ - count = len(self.values) - self.byte_count = (count + 7) // 8 - packet = struct.pack(">HHB", self.address, count, self.byte_count) - packet += pack_bitstring(self.values) - return packet - - def decode(self, data): - """Decode a write coils request. - - :param data: The packet data to decode - """ - self.address, count, self.byte_count = struct.unpack(">HHB", data[0:5]) - values = unpack_bitstring(data[5:]) - self.values = values[:count] - - async def update_datastore(self, context): # pragma: no cover - """Run a write coils request against a datastore. - - :param context: The datastore to request from - :returns: The populated response or exception message - """ - count = len(self.values) - if not 1 <= count <= 0x07B0: - return self.doException(merror.IllegalValue) - if self.byte_count != (count + 7) // 8: - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, count): - return self.doException(merror.IllegalAddress) - - await context.async_setValues( - self.function_code, self.address, self.values - ) - return WriteMultipleCoilsResponse(self.address, count) - - def __str__(self): - """Return a string representation of the instance.""" - return f"WriteNCoilRequest ({self.address}) => {len(self.values)}" - - def get_response_pdu_size(self): - """Get response pdu size. - - Func_code (1 byte) + Output Address (2 byte) + Quantity of Outputs (2 Bytes) - :return: - """ - return 1 + 2 + 2 - - -class WriteMultipleCoilsResponse(ModbusPDU): - """The normal response returns the function code. - - Starting address, and quantity of coils forced. - """ - - function_code = 15 - _rtu_frame_size = 8 - - def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode=False): - """Initialize a new instance. - - :param address: The starting variable address written to - :param count: The number of values written - """ - super().__init__() - super().setData(slave, transaction, skip_encode) - self.address = address - self.count = count - - def encode(self): - """Encode write coils response. - - :returns: The byte encoded message - """ - return struct.pack(">HH", self.address, self.count) - - def decode(self, data): - """Decode a write coils response. - - :param data: The packet data to decode - """ - self.address, self.count = struct.unpack(">HH", data) - - def __str__(self): - """Return a string representation of the instance.""" - return f"WriteNCoilResponse({self.address}, {self.count})" diff --git a/pymodbus/pdu/decoders.py b/pymodbus/pdu/decoders.py index f50d037d3..34fa2c767 100644 --- a/pymodbus/pdu/decoders.py +++ b/pymodbus/pdu/decoders.py @@ -1,8 +1,7 @@ """Modbus Request/Response Decoders.""" from __future__ import annotations -import pymodbus.pdu.bit_read_message as bit_r_msg -import pymodbus.pdu.bit_write_message as bit_w_msg +import pymodbus.pdu.bit_message as bit_msg import pymodbus.pdu.diag_message as diag_msg import pymodbus.pdu.file_message as file_msg import pymodbus.pdu.mei_message as mei_msg @@ -19,13 +18,13 @@ class DecodePDU: _pdu_class_table: set[tuple[type[base.ModbusPDU], type[base.ModbusPDU]]] = { (reg_r_msg.ReadHoldingRegistersRequest, reg_r_msg.ReadHoldingRegistersResponse), - (bit_r_msg.ReadDiscreteInputsRequest, bit_r_msg.ReadDiscreteInputsResponse), + (bit_msg.ReadDiscreteInputsRequest, bit_msg.ReadDiscreteInputsResponse), (reg_r_msg.ReadInputRegistersRequest, reg_r_msg.ReadInputRegistersResponse), - (bit_r_msg.ReadCoilsRequest, bit_r_msg.ReadCoilsResponse), - (bit_w_msg.WriteMultipleCoilsRequest, bit_w_msg.WriteMultipleCoilsResponse), + (bit_msg.ReadCoilsRequest, bit_msg.ReadCoilsResponse), + (bit_msg.WriteMultipleCoilsRequest, bit_msg.WriteMultipleCoilsResponse), (reg_w_msg.WriteMultipleRegistersRequest, reg_w_msg.WriteMultipleRegistersResponse), (reg_w_msg.WriteSingleRegisterRequest, reg_w_msg.WriteSingleRegisterResponse), - (bit_w_msg.WriteSingleCoilRequest, bit_w_msg.WriteSingleCoilResponse), + (bit_msg.WriteSingleCoilRequest, bit_msg.WriteSingleCoilResponse), (reg_r_msg.ReadWriteMultipleRegistersRequest, reg_r_msg.ReadWriteMultipleRegistersResponse), (diag_msg.DiagnosticStatusRequest, diag_msg.DiagnosticStatusResponse), (o_msg.ReadExceptionStatusRequest, o_msg.ReadExceptionStatusResponse), @@ -111,7 +110,7 @@ def decode(self, frame: bytes) -> base.ModbusPDU | None: Log.debug("decode PDU failed for function code {}", function_code) raise ModbusException(f"Unknown response {function_code}") pdu = pdu_type() - pdu.setData(0, 0, False) + pdu.setBaseData(0, 0, False) pdu.decode(frame[1:]) Log.debug("decoded PDU function_code({} sub {}) -> {} ", pdu.function_code, pdu.sub_function_code, str(pdu)) diff --git a/pymodbus/pdu/diag_message.py b/pymodbus/pdu/diag_message.py index b8a85b1ee..d37537ce0 100644 --- a/pymodbus/pdu/diag_message.py +++ b/pymodbus/pdu/diag_message.py @@ -10,7 +10,7 @@ from pymodbus.constants import ModbusPlusOperation, ModbusStatus from pymodbus.device import ModbusControlBlock -from pymodbus.exceptions import ModbusException, NotImplementedException +from pymodbus.exceptions import ModbusException from pymodbus.pdu.pdu import ModbusPDU from pymodbus.utilities import pack_bitstring @@ -34,7 +34,7 @@ class DiagnosticStatusRequest(ModbusPDU): def __init__(self, slave=1, transaction=0, skip_encode=False): """Initialize a diagnostic request.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.message = None def encode(self): @@ -46,14 +46,14 @@ def encode(self): """ packet = struct.pack(">H", self.sub_function_code) if self.message is not None: - if isinstance(self.message, str): # pragma: no cover + if isinstance(self.message, str): packet += self.message.encode() elif isinstance(self.message, bytes): packet += self.message elif isinstance(self.message, (list, tuple)): for piece in self.message: packet += struct.pack(">H", piece) - elif isinstance(self.message, int): # pragma: no cover + elif isinstance(self.message, int): packet += struct.pack(">H", self.message) return packet @@ -63,10 +63,10 @@ def decode(self, data): :param data: The data to decode into the function code """ (self.sub_function_code, ) = struct.unpack(">H", data[:2]) - if self.sub_function_code == ReturnQueryDataRequest.sub_function_code: # pragma: no cover + if self.sub_function_code == ReturnQueryDataRequest.sub_function_code: self.message = data[2:] - else: - (self.message,) = struct.unpack(">H", data[2:]) # pragma: no cover + elif len(data) > 2: + (self.message,) = struct.unpack(">H", data[2:]) def get_response_pdu_size(self): """Get response pdu size. @@ -78,6 +78,10 @@ def get_response_pdu_size(self): self.message = [self.message] return 1 + 2 + 2 * len(self.message) + async def update_datastore(self, *args): + """Implement dummy.""" + return DiagnosticStatusResponse(args) + class DiagnosticStatusResponse(ModbusPDU): """Diagnostic status. @@ -96,7 +100,7 @@ class DiagnosticStatusResponse(ModbusPDU): def __init__(self, slave=1, transaction=0, skip_encode=False): """Initialize a diagnostic response.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.message = None def encode(self): @@ -108,14 +112,14 @@ def encode(self): """ packet = struct.pack(">H", self.sub_function_code) if self.message is not None: - if isinstance(self.message, str): # pragma: no cover + if isinstance(self.message, str): packet += self.message.encode() elif isinstance(self.message, bytes): packet += self.message elif isinstance(self.message, (list, tuple)): for piece in self.message: packet += struct.pack(">H", piece) - elif isinstance(self.message, int): # pragma: no cover + elif isinstance(self.message, int): packet += struct.pack(">H", self.message) return packet @@ -130,7 +134,7 @@ def decode(self, data): self.message = data else: word_len = len(data) // 2 - if len(data) % 2: # pragma: no cover + if len(data) % 2: word_len += 1 data += b"0" data = struct.unpack(">" + "H" * word_len, data) @@ -160,10 +164,6 @@ def __init__(self, data=0x0000, slave=1, transaction=0, skip_encode=False): DiagnosticStatusRequest.__init__(self, slave=slave, transaction=transaction, skip_encode=skip_encode) self.message = data - async def update_datastore(self, *args): # pragma: no cover - """Raise if not implemented.""" - raise NotImplementedException("Diagnostic Message Has No update_datastore Method") - class DiagnosticStatusSimpleResponse(DiagnosticStatusResponse): """Diagnostic status. @@ -202,11 +202,11 @@ def __init__(self, message=b"\x00\x00", slave=1, transaction=0, skip_encode=Fals :param message: The message to send to loopback """ DiagnosticStatusRequest.__init__(self, slave=slave, transaction=transaction, skip_encode=skip_encode) - if not isinstance(message, bytes): # pragma: no cover + if not isinstance(message, bytes): raise ModbusException(f"message({type(message)}) must be bytes") self.message = message - async def update_datastore(self, *_args): # pragma: no cover + async def update_datastore(self, *_args): """update_datastore the loopback request (builds the response). :returns: The populated loopback response message @@ -230,7 +230,7 @@ def __init__(self, message=b"\x00\x00", slave=1, transaction=0, skip_encode=Fals :param message: The message to loopback """ DiagnosticStatusResponse.__init__(self, slave=slave, transaction=transaction, skip_encode=skip_encode) - if not isinstance(message, bytes): # pragma: no cover + if not isinstance(message, bytes): raise ModbusException(f"message({type(message)}) must be bytes") self.message = message @@ -257,12 +257,12 @@ def __init__(self, toggle=False, slave=1, transaction=0, skip_encode=False): :param toggle: Set to True to toggle, False otherwise """ DiagnosticStatusRequest.__init__(self, slave=slave, transaction=transaction, skip_encode=skip_encode) - if toggle: # pragma: no cover + if toggle: self.message = [ModbusStatus.ON] else: - self.message = [ModbusStatus.OFF] # pragma: no cover + self.message = [ModbusStatus.OFF] - async def update_datastore(self, *_args): # pragma: no cover + async def update_datastore(self, *_args): """Clear event log and restart. :returns: The initialized response message @@ -290,10 +290,10 @@ def __init__(self, toggle=False, slave=1, transaction=0, skip_encode=False): :param toggle: Set to True if we toggled, False otherwise """ DiagnosticStatusResponse.__init__(self, slave=slave, transaction=transaction, skip_encode=skip_encode) - if toggle: # pragma: no cover + if toggle: self.message = [ModbusStatus.ON] else: - self.message = [ModbusStatus.OFF] # pragma: no cover + self.message = [ModbusStatus.OFF] # ---------------------------------------------------------------------------# @@ -304,7 +304,7 @@ class ReturnDiagnosticRegisterRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0002 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -338,7 +338,7 @@ class ChangeAsciiInputDelimiterRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0003 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -373,7 +373,7 @@ class ForceListenOnlyModeRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0004 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -411,7 +411,7 @@ class ClearCountersRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x000A - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -442,7 +442,7 @@ class ReturnBusMessageCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x000B - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -475,7 +475,7 @@ class ReturnBusCommunicationErrorCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x000C - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -508,7 +508,7 @@ class ReturnBusExceptionErrorCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x000D - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -541,7 +541,7 @@ class ReturnSlaveMessageCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x000E - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -574,7 +574,7 @@ class ReturnSlaveNoResponseCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x000F - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -608,7 +608,7 @@ class ReturnSlaveNAKCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0010 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -642,7 +642,7 @@ class ReturnSlaveBusyCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0011 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -677,7 +677,7 @@ class ReturnSlaveBusCharacterOverrunCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0012 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -710,7 +710,7 @@ class ReturnIopOverrunCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0013 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -743,7 +743,7 @@ class ClearOverrunCountRequest(DiagnosticStatusSimpleRequest): sub_function_code = 0x0014 - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message @@ -789,13 +789,13 @@ def get_response_pdu_size(self): Func_code (1 byte) + Sub function code (2 byte) + Operation (2 byte) + Data (108 bytes) :return: """ - if self.message == ModbusPlusOperation.GET_STATISTICS: # pragma: no cover + if self.message == ModbusPlusOperation.GET_STATISTICS: data = 2 + 108 # byte count(2) + data (54*2) else: data = 0 return 1 + 2 + 2 + 2 + data - async def update_datastore(self, *args): # pragma: no cover + async def update_datastore(self, *args): """update_datastore the diagnostic request on the given device. :returns: The initialized response message diff --git a/pymodbus/pdu/file_message.py b/pymodbus/pdu/file_message.py index ec3594993..30676a8b6 100644 --- a/pymodbus/pdu/file_message.py +++ b/pymodbus/pdu/file_message.py @@ -40,7 +40,7 @@ def __init__(self, file_number=0x00, record_number=0x00, record_data=b'', record def __eq__(self, relf): """Compare the left object to the right.""" - return ( # pragma: no cover + return ( self.file_number == relf.file_number and self.record_number == relf.record_number and self.record_length == relf.record_length @@ -49,9 +49,9 @@ def __eq__(self, relf): def __ne__(self, relf): """Compare the left object to the right.""" - return not self.__eq__(relf) # pragma: no cover + return not self.__eq__(relf) - def __repr__(self): # pragma: no cover + def __repr__(self): """Give a representation of the file record.""" params = (self.file_number, self.record_number, self.record_length) return ( @@ -96,7 +96,7 @@ def __init__(self, records: list[FileRecord] = [], slave=1, transaction=0, skip :param records: The file record requests to be read """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.records = records def encode(self): @@ -131,7 +131,11 @@ def decode(self, data): ) self.records.append(record) - async def update_datastore(self, _context): # pragma: no cover + def get_response_pdu_size(self): + """Get response pdu size.""" + return 0 # 1 + 7 * len(self.records) + + async def update_datastore(self, _context): """Run a read exception status request against the store. :returns: The populated response @@ -160,7 +164,7 @@ def __init__(self, records: list[FileRecord] = [], slave=1, transaction=0, skip_ :param records: The requested file records """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.records = records def encode(self): @@ -214,7 +218,7 @@ def __init__(self, records: list[FileRecord] = [], slave=1, transaction=0, skip_ :param records: The file record requests to be read """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.records = records def encode(self): @@ -255,7 +259,11 @@ def decode(self, data): record.record_length = decoded[3] self.records.append(record) - async def update_datastore(self, _context): # pragma: no cover + def get_response_pdu_size(self): + """Get response pdu size.""" + return 0 # 1 + 7 * len(self.records) + + async def update_datastore(self, _context): """Run the write file record request against the context. :returns: The populated response @@ -275,7 +283,7 @@ def __init__(self, records: list[FileRecord] = [], slave=1, transaction=0, skip_ :param records: The file record requests to be read """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.records = records def encode(self): @@ -339,7 +347,7 @@ def __init__(self, address=0x0000, slave=1, transaction=0, skip_encode=False): :param address: The fifo pointer address (0x0000 to 0xffff) """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.values = [] # this should be added to the context @@ -357,7 +365,11 @@ def decode(self, data): """ self.address = struct.unpack(">H", data)[0] - async def update_datastore(self, _context): # pragma: no cover + def get_response_pdu_size(self): + """Get response pdu size.""" + return 0 # 1 + 7 * len(self.records) + + async def update_datastore(self, _context): """Run a read exception status request against the store. :returns: The populated response @@ -385,7 +397,7 @@ class ReadFifoQueueResponse(ModbusPDU): function_code = 0x18 @classmethod - def calculateRtuFrameSize(cls, buffer): # pragma: no cover + def calculateRtuFrameSize(cls, buffer): """Calculate the size of the message. :param buffer: A buffer containing the data that have been received. @@ -401,7 +413,7 @@ def __init__(self, values=None, slave=1, transaction=0, skip_encode=False): :param values: The list of values of the fifo to return """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.values = values or [] def encode(self): @@ -422,6 +434,6 @@ def decode(self, data): """ self.values = [] _, count = struct.unpack(">HH", data[0:4]) - for index in range(0, count - 4): # pragma: no cover + for index in range(0, count - 4): idx = 4 + index * 2 self.values.append(struct.unpack(">H", data[idx : idx + 2])[0]) diff --git a/pymodbus/pdu/mei_message.py b/pymodbus/pdu/mei_message.py index fe2eb9e9a..719bc1286 100644 --- a/pymodbus/pdu/mei_message.py +++ b/pymodbus/pdu/mei_message.py @@ -27,7 +27,7 @@ class _OutOfSpaceException(Exception): # # See Page 5/50 of MODBUS Application Protocol Specification V1.1b3. - def __init__(self, oid): # pragma: no cover + def __init__(self, oid): self.oid = oid super().__init__() @@ -58,7 +58,7 @@ def __init__(self, read_code=None, object_id=0x00, slave=1, transaction=0, skip_ :param object_id: The object to read from """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.read_code = read_code or DeviceInformation.BASIC self.object_id = object_id @@ -80,7 +80,7 @@ def decode(self, data): params = struct.unpack(">BBB", data) self.sub_function_code, self.read_code, self.object_id = params - async def update_datastore(self, _context): # pragma: no cover + async def update_datastore(self, _context): """Run a read exception status request against the store. :returns: The populated response @@ -112,7 +112,7 @@ class ReadDeviceInformationResponse(ModbusPDU): sub_function_code = 0x0E @classmethod - def calculateRtuFrameSize(cls, buffer): # pragma: no cover + def calculateRtuFrameSize(cls, buffer): """Calculate the size of the message. :param buffer: A buffer containing the data that have been received. @@ -137,7 +137,7 @@ def __init__(self, read_code=None, information=None, slave=1, transaction=0, ski :param information: The requested information request """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.read_code = read_code or DeviceInformation.BASIC self.information = information or {} self.number_of_objects = 0 @@ -146,7 +146,7 @@ def __init__(self, read_code=None, information=None, slave=1, transaction=0, ski self.more_follows = MoreData.NOTHING self.space_left = 253 - 6 - def _encode_object(self, object_id, data): # pragma: no cover + def _encode_object(self, object_id, data): """Encode object.""" self.space_left -= 2 + len(data) if self.space_left <= 0: @@ -168,14 +168,14 @@ def encode(self): ">BBB", self.sub_function_code, self.read_code, self.conformity ) objects = b"" - try: # pragma: no cover + try: for object_id, data in iter(self.information.items()): if isinstance(data, list): for item in data: objects += self._encode_object(object_id, item) else: objects += self._encode_object(object_id, data) - except _OutOfSpaceException as exc: # pragma: no cover + except _OutOfSpaceException as exc: self.next_object_id = exc.oid self.more_follows = MoreData.KEEP_READING @@ -199,12 +199,12 @@ def decode(self, data): while count < len(data): object_id, object_length = struct.unpack(">BB", data[count : count + 2]) count += object_length + 2 - if object_id not in self.information: # pragma: no cover + if object_id not in self.information: self.information[object_id] = data[count - object_length : count] - elif isinstance(self.information[object_id], list): # pragma: no cover + elif isinstance(self.information[object_id], list): self.information[object_id].append(data[count - object_length : count]) else: - self.information[object_id] = [ # pragma: no cover + self.information[object_id] = [ self.information[object_id], data[count - object_length : count], ] diff --git a/pymodbus/pdu/other_message.py b/pymodbus/pdu/other_message.py index c7b28374e..283656e18 100644 --- a/pymodbus/pdu/other_message.py +++ b/pymodbus/pdu/other_message.py @@ -32,7 +32,7 @@ class ReadExceptionStatusRequest(ModbusPDU): def __init__(self, slave=None, transaction=0, skip_encode=0): """Initialize a new instance.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) def encode(self): """Encode the message.""" @@ -44,7 +44,7 @@ def decode(self, data): :param data: The incoming data """ - async def update_datastore(self, _context=None): # pragma: no cover + async def update_datastore(self, _context=None): """Run a read exception status request against the store. :returns: The populated response @@ -75,7 +75,7 @@ def __init__(self, status=0x00, slave=1, transaction=0, skip_encode=False): :param status: The status response to report """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.status = status if status < 256 else 255 def encode(self): @@ -132,7 +132,7 @@ class GetCommEventCounterRequest(ModbusPDU): def __init__(self, slave=1, transaction=0, skip_encode=False): """Initialize a new instance.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) def encode(self): """Encode the message.""" @@ -144,7 +144,7 @@ def decode(self, data): :param data: The incoming data """ - async def update_datastore(self, _context=None): # pragma: no cover + async def update_datastore(self, _context=None): """Run a read exception status request against the store. :returns: The populated response @@ -176,7 +176,7 @@ def __init__(self, count=0x0000, slave=1, transaction=0, skip_encode=False): :param count: The current event counter value """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.count = count self.status = True # this means we are ready, not waiting @@ -185,10 +185,10 @@ def encode(self): :returns: The byte encoded message """ - if self.status: # pragma: no cover + if self.status: ready = ModbusStatus.READY else: - ready = ModbusStatus.WAITING # pragma: no cover + ready = ModbusStatus.WAITING return struct.pack(">HH", ready, self.count) def decode(self, data): @@ -238,7 +238,7 @@ class GetCommEventLogRequest(ModbusPDU): def __init__(self, slave=1, transaction=0, skip_encode=False): """Initialize a new instance.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) def encode(self): """Encode the message.""" @@ -250,7 +250,7 @@ def decode(self, data): :param data: The incoming data """ - async def update_datastore(self, _context=None): # pragma: no cover + async def update_datastore(self, _context=None): """Run a read exception status request against the store. :returns: The populated response @@ -292,7 +292,7 @@ def __init__(self, status=True, message_count=0, event_count=0, events=None, sla :param events: The collection of events to send """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.status = status self.message_count = message_count self.event_count = event_count @@ -303,10 +303,10 @@ def encode(self): :returns: The byte encoded message """ - if self.status: # pragma: no cover + if self.status: ready = ModbusStatus.READY else: - ready = ModbusStatus.WAITING # pragma: no cover + ready = ModbusStatus.WAITING packet = struct.pack(">B", 6 + len(self.events)) packet += struct.pack(">H", ready) packet += struct.pack(">HH", self.event_count, self.message_count) @@ -364,7 +364,7 @@ def __init__(self, slave=1, transaction=0, skip_encode=False): """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) def encode(self): """Encode the message.""" @@ -376,7 +376,7 @@ def decode(self, data): :param data: The incoming data """ - async def update_datastore(self, context=None): # pragma: no cover + async def update_datastore(self, context=None): """Run a report slave id request against the store. :returns: The populated response @@ -424,7 +424,7 @@ def __init__(self, identifier=b"\x00", status=True, slave=1, transaction=0, skip :param status: The status response to report """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.identifier = identifier self.status = status self.byte_count = None @@ -434,10 +434,10 @@ def encode(self): :returns: The byte encoded message """ - if self.status: # pragma: no cover + if self.status: status = ModbusStatus.SLAVE_ON else: - status = ModbusStatus.SLAVE_OFF # pragma: no cover + status = ModbusStatus.SLAVE_OFF length = len(self.identifier) + 1 packet = struct.pack(">B", length) packet += self.identifier # we assume it is already encoded diff --git a/pymodbus/pdu/pdu.py b/pymodbus/pdu/pdu.py index 5a07014ed..8b8ca211a 100644 --- a/pymodbus/pdu/pdu.py +++ b/pymodbus/pdu/pdu.py @@ -26,7 +26,7 @@ def __init__(self) -> None: self.registers: list[int] self.fut: asyncio.Future - def setData(self, slave: int, transaction: int, skip_encode: bool) -> None: + def setBaseData(self, slave: int, transaction: int, skip_encode: bool) -> None: """Set data common for all PDU.""" self.transaction_id = transaction self.slave_id = slave @@ -108,7 +108,7 @@ def __init__( skip_encode: bool = False) -> None: """Initialize the modbus exception response.""" super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.function_code = function_code | 0x80 self.exception_code = exception_code diff --git a/pymodbus/pdu/register_read_message.py b/pymodbus/pdu/register_read_message.py index 7d0abe7fb..887be0979 100644 --- a/pymodbus/pdu/register_read_message.py +++ b/pymodbus/pdu/register_read_message.py @@ -22,7 +22,7 @@ def __init__(self, address, count, slave=1, transaction=0, skip_encode=False): :param slave: Modbus slave slave ID """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.count = count @@ -70,7 +70,7 @@ def __init__(self, values, slave=1, transaction=0, skip_encode=False): :param slave: Modbus slave slave ID """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) #: A list of register values self.registers = values or [] @@ -91,8 +91,8 @@ def decode(self, data): :param data: The request to decode """ byte_count = int(data[0]) - if byte_count < 2 or byte_count > 252 or byte_count % 2 == 1 or byte_count != len(data) - 1: # pragma: no cover - raise ModbusIOException(f"Invalid response {data} has byte count of {byte_count}") # pragma: no cover + if byte_count < 2 or byte_count > 252 or byte_count % 2 == 1 or byte_count != len(data) - 1: + raise ModbusIOException(f"Invalid response {data} has byte count of {byte_count}") self.registers = [] for i in range(1, byte_count + 1, 2): self.registers.append(struct.unpack(">H", data[i : i + 2])[0]) @@ -103,7 +103,7 @@ def getRegister(self, index): :param index: The indexed register to retrieve :returns: The request register """ - return self.registers[index] # pragma: no cover + return self.registers[index] def __str__(self): """Return a string representation of the instance. @@ -134,7 +134,7 @@ def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode """ super().__init__(address, count, slave, transaction, skip_encode) - async def update_datastore(self, context): # pragma: no cover + async def update_datastore(self, context): """Run a read holding request against a datastore. :param context: The datastore to request from @@ -195,7 +195,7 @@ def __init__(self, address=None, count=None, slave=1, transaction=0, skip_encode """ super().__init__(address, count, slave, transaction, skip_encode) - async def update_datastore(self, context): # pragma: no cover + async def update_datastore(self, context): """Run a read input request against a datastore. :param context: The datastore to request from @@ -263,7 +263,7 @@ def __init__(self, read_address=0x00, read_count=0, write_address=0x00, write_re :param write_registers: The registers to write to the specified address """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.read_address = read_address self.read_count = read_count self.write_address = write_address @@ -307,7 +307,7 @@ def decode(self, data): register = struct.unpack(">H", data[i : i + 2])[0] self.write_registers.append(register) - async def update_datastore(self, context): # pragma: no cover + async def update_datastore(self, context): """Run a write single register request against a datastore. :param context: The datastore to request from diff --git a/pymodbus/pdu/register_write_message.py b/pymodbus/pdu/register_write_message.py index 89095979a..69d65709f 100644 --- a/pymodbus/pdu/register_write_message.py +++ b/pymodbus/pdu/register_write_message.py @@ -26,7 +26,7 @@ def __init__(self, address=None, value=None, slave=None, transaction=0, skip_enc :param value: The values to write """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.value = value @@ -36,8 +36,8 @@ def encode(self): :returns: The encoded packet """ packet = struct.pack(">H", self.address) - if self.skip_encode or isinstance(self.value, bytes): # pragma: no cover - packet += self.value # pragma: no cover + if self.skip_encode or isinstance(self.value, bytes): + packet += self.value else: packet += struct.pack(">H", self.value) return packet @@ -49,7 +49,7 @@ def decode(self, data): """ self.address, self.value = struct.unpack(">HH", data) - async def update_datastore(self, context): # pragma: no cover + async def update_datastore(self, context): """Run a write single register request against a datastore. :param context: The datastore to request from @@ -98,7 +98,7 @@ def __init__(self, address=0, value=0, slave=1, transaction=0, skip_encode=False :param value: The values to write """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.value = value @@ -116,14 +116,6 @@ def decode(self, data): """ self.address, self.value = struct.unpack(">HH", data) - def get_response_pdu_size(self): - """Get response pdu size. - - Func_code (1 byte) + Starting Address (2 byte) + And_mask (2 Bytes) + OrMask (2 Bytes) - :return: - """ - return 1 + 2 + 2 + 2 - def __str__(self): """Return a string representation of the instance. @@ -159,7 +151,7 @@ def __init__(self, address=0, values=None, slave=None, transaction=0, skip_encod :param values: The values to write """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address if values is None: values = [] @@ -175,12 +167,12 @@ def encode(self): :returns: The encoded packet """ packet = struct.pack(">HHB", self.address, self.count, self.byte_count) - if self.skip_encode: # pragma: no cover - return packet + b"".join(self.values) # pragma: no cover + if self.skip_encode: + return packet + b"".join(self.values) for value in self.values: - if isinstance(value, bytes): # pragma: no cover - packet += value # pragma: no cover + if isinstance(value, bytes): + packet += value else: packet += struct.pack(">H", value) @@ -196,7 +188,7 @@ def decode(self, data): for idx in range(5, (self.count * 2) + 5, 2): self.values.append(struct.unpack(">H", data[idx : idx + 2])[0]) - async def update_datastore(self, context): # pragma: no cover + async def update_datastore(self, context): """Run a write single register request against a datastore. :param context: The datastore to request from @@ -250,7 +242,7 @@ def __init__(self, address=0, count=0, slave=1, transaction=0, skip_encode=False :param count: The number of registers to write to """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.count = count @@ -299,7 +291,7 @@ def __init__(self, address=0x0000, and_mask=0xFFFF, or_mask=0x0000, slave=1, tra :param or_mask: The or bitmask to apply to the register address """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.and_mask = and_mask self.or_mask = or_mask @@ -318,7 +310,7 @@ def decode(self, data): """ self.address, self.and_mask, self.or_mask = struct.unpack(">HHH", data) - async def update_datastore(self, context): # pragma: no cover + async def update_datastore(self, context): """Run a mask write register request against the store. :param context: The datastore to request from @@ -355,7 +347,7 @@ def __init__(self, address=0x0000, and_mask=0xFFFF, or_mask=0x0000, slave=1, tra :param or_mask: The or bitmask applied to the register address """ super().__init__() - super().setData(slave, transaction, skip_encode) + super().setBaseData(slave, transaction, skip_encode) self.address = address self.and_mask = and_mask self.or_mask = or_mask diff --git a/test/framer/test_framer.py b/test/framer/test_framer.py index 28ab35136..02882666f 100644 --- a/test/framer/test_framer.py +++ b/test/framer/test_framer.py @@ -431,7 +431,7 @@ def test_framer_encode(self, test_framer, msg): """Test a tcp frame transaction.""" with mock.patch.object(ModbusPDU, "encode") as mock_encode: message = ModbusPDU() - message.setData(0, 0, False) + message.setBaseData(0, 0, False) message.transaction_id = 0x0001 message.slave_id = 0xFF message.function_code = 0x01 diff --git a/test/framer/test_multidrop.py b/test/framer/test_multidrop.py index cfbcb96b3..19a42bb43 100644 --- a/test/framer/test_multidrop.py +++ b/test/framer/test_multidrop.py @@ -1,5 +1,4 @@ """Test server working as slave on a multidrop RS485 line.""" -from unittest import mock import pytest @@ -18,11 +17,6 @@ def fixture_framer(self): """Prepare framer.""" return FramerRTU(DecodePDU(True)) - @pytest.fixture(name="callback") - def fixture_callback(self): - """Prepare dummy callback.""" - return mock.Mock() - def test_ok_frame(self, framer): """Test ok frame.""" serial_event = self.good_frame diff --git a/test/pdu/test_bit_pdu.py b/test/pdu/test_bit_pdu.py new file mode 100644 index 000000000..ea316a5d1 --- /dev/null +++ b/test/pdu/test_bit_pdu.py @@ -0,0 +1,201 @@ +"""Bit Message Test Fixture. + +This fixture tests the functionality of all the +bit based request/response messages: + +* Read/Write Discretes +* Read Coils +""" + +import pymodbus.pdu.bit_message as bit_msg +from pymodbus.pdu import ModbusExceptions + +from ..conftest import FakeList, MockContext + + +res = [True] * 21 +res.extend([False] * 3) + + +class TestModbusBitMessage: + """Modbus bit read message tests.""" + + def test_bit_read_base_response_encoding(self): + """Test basic bit message encoding/decoding.""" + for i in range(20): + data = [True] * i + handle = bit_msg.ReadBitsResponseBase(data, 0, 0, False) + result = handle.encode() + handle.decode(result) + assert handle.bits[:i] == data + + def test_bit_read_base_requests(self): + """Test bit read request encoding.""" + messages = { + bit_msg.ReadBitsRequestBase(12, 14, 0, 0, False): b"\x00\x0c\x00\x0e", + bit_msg.ReadBitsResponseBase([1, 0, 1, 1, 0], 0, 0, False): b"\x01\x0d", + } + for request, expected in iter(messages.items()): + assert request.encode() == expected + + async def test_bit_read_update_datastore_value_errors(self): + """Test bit read request encoding.""" + context = MockContext() + requests = [ + bit_msg.ReadCoilsRequest(1, 0x800, 0, 0, False), + bit_msg.ReadDiscreteInputsRequest(1, 0x800, 0, 0, False), + ] + for request in requests: + result = await request.update_datastore(context) + assert ModbusExceptions.IllegalValue == result.exception_code + + async def test_bit_read_update_datastore_address_errors(self): + """Test bit read request encoding.""" + context = MockContext() + requests = [ + bit_msg.ReadCoilsRequest(1, 5, 0, 0, False), + bit_msg.ReadDiscreteInputsRequest(1, 5, 0, 0, False), + ] + for request in requests: + result = await request.update_datastore(context) + assert ModbusExceptions.IllegalAddress == result.exception_code + + async def test_bit_read_update_datastore_success(self): + """Test bit read request encoding.""" + context = MockContext() + context.validate = lambda a, b, c: True + requests = [ + bit_msg.ReadCoilsRequest(1, 5, 0, 0, False), + bit_msg.ReadDiscreteInputsRequest(1, 5, 0, False), + ] + for request in requests: + result = await request.update_datastore(context) + assert result.bits == [True] * 5 + + def test_bit_read_get_response_pdu(self): + """Test bit read message get response pdu.""" + requests = { + bit_msg.ReadCoilsRequest(1, 5, 0, 0, False): 3, + bit_msg.ReadCoilsRequest(1, 8, 0, 0, False): 3, + bit_msg.ReadCoilsRequest(0, 16, 0, 0, False): 4, + bit_msg.ReadDiscreteInputsRequest(1, 21, 0, 0, False): 5, + bit_msg.ReadDiscreteInputsRequest(1, 24, 0, 0, False): 5, + bit_msg.ReadDiscreteInputsRequest(1, 1900, 0, 0, False): 240, + } + for request, expected in iter(requests.items()): + pdu_len = request.get_response_pdu_size() + assert pdu_len == expected + +class TestModbusBitWriteMessage: + """Modbus bit write message tests.""" + + def test_bit_write_base_requests(self): + """Test bit write base.""" + messages = { + bit_msg.WriteSingleCoilRequest(1, 0xABCD): b"\x00\x01\xff\x00", + bit_msg.WriteSingleCoilResponse(1, 0xABCD): b"\x00\x01\xff\x00", + bit_msg.WriteMultipleCoilsRequest(1, [True] * 5): b"\x00\x01\x00\x05\x01\x1f", + bit_msg.WriteMultipleCoilsResponse(1, 5): b"\x00\x01\x00\x05", + bit_msg.WriteMultipleCoilsRequest(1, True): b"\x00\x01\x00\x01\x01\x01", + bit_msg.WriteMultipleCoilsResponse(1, 1): b"\x00\x01\x00\x01", + } + for request, expected in iter(messages.items()): + assert request.encode() == expected + + def test_write_message_get_response_pdu(self): + """Test bit write message.""" + requests = {bit_msg.WriteSingleCoilRequest(1, 0xABCD): 5} + for request, expected in iter(requests.items()): + pdu_len = request.get_response_pdu_size() + assert pdu_len == expected + + def test_write_multiple_coils_request(self): + """Test write multiple coils.""" + request = bit_msg.WriteMultipleCoilsRequest(1, [True] * 5) + request.decode(b"\x00\x01\x00\x05\x01\x1f") + assert request.byte_count == 1 + assert request.address == 1 + assert request.values == [True] * 5 + assert request.get_response_pdu_size() == 5 + + request = bit_msg.WriteMultipleCoilsRequest(1, True) + request.decode(b"\x00\x01\x00\x01\x01\x01") + assert request.byte_count == 1 + assert request.address == 1 + assert request.values == [True] + assert request.get_response_pdu_size() == 5 + + def test_invalid_write_multiple_coils_request(self): + """Test write invalid multiple coils.""" + request = bit_msg.WriteMultipleCoilsRequest(1, None) + assert request.values == [] + + def test_write_single_coil_request_encode(self): + """Test write single coil.""" + request = bit_msg.WriteSingleCoilRequest(1, False) + assert request.encode() == b"\x00\x01\x00\x00" + + async def test_write_single_coil_update_datastore(self): + """Test write single coil.""" + context = MockContext(False, default=True) + request = bit_msg.WriteSingleCoilRequest(2, True) + result = await request.update_datastore(context) + assert result.exception_code == ModbusExceptions.IllegalAddress + + context.valid = True + result = await request.update_datastore(context) + assert result.encode() == b"\x00\x02\xff\x00" + + context = MockContext(True, default=False) + request = bit_msg.WriteSingleCoilRequest(2, False) + result = await request.update_datastore(context) + assert result.encode() == b"\x00\x02\x00\x00" + + async def test_write_multiple_coils_update_datastore(self): + """Test write multiple coils.""" + context = MockContext(False) + # too many values + request = bit_msg.WriteMultipleCoilsRequest(2, FakeList(0x123456)) + result = await request.update_datastore(context) + assert result.exception_code == ModbusExceptions.IllegalValue + + # bad byte count + request = bit_msg.WriteMultipleCoilsRequest(2, [0x00] * 4) + request.byte_count = 0x00 + result = await request.update_datastore(context) + assert result.exception_code == ModbusExceptions.IllegalValue + + # does not validate + context.valid = False + request = bit_msg.WriteMultipleCoilsRequest(2, [0x00] * 4) + result = await request.update_datastore(context) + assert result.exception_code == ModbusExceptions.IllegalAddress + + # validated request + context.valid = True + result = await request.update_datastore(context) + assert result.encode() == b"\x00\x02\x00\x04" + + def test_write_multiple_coils_response(self): + """Test write multiple coils.""" + response = bit_msg.WriteMultipleCoilsResponse() + response.decode(b"\x00\x80\x00\x08") + assert response.address == 0x80 + assert response.count == 0x08 + + def test_serializing_to_string(self): + """Test serializing to string.""" + requests = [ + bit_msg.WriteSingleCoilRequest(1, 0xABCD), + bit_msg.WriteSingleCoilResponse(1, 0xABCD), + bit_msg.WriteMultipleCoilsRequest(1, [True] * 5), + bit_msg.WriteMultipleCoilsResponse(1, 5), + ] + for request in requests: + result = str(request) + assert result + + def test_pass_falsy_value_in_write_multiple_coils_request(self): + """Test pass falsy value to write multiple coils.""" + request = bit_msg.WriteMultipleCoilsRequest(1, 0) + assert request.values == [0] diff --git a/test/pdu/test_bit_read_messages.py b/test/pdu/test_bit_read_messages.py deleted file mode 100644 index b93c7b193..000000000 --- a/test/pdu/test_bit_read_messages.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Bit Message Test Fixture. - -This fixture tests the functionality of all the -bit based request/response messages: - -* Read/Write Discretes -* Read Coils -""" -import struct - -from pymodbus.pdu import ModbusExceptions -from pymodbus.pdu.bit_read_message import ( - ReadBitsRequestBase, - ReadBitsResponseBase, - ReadCoilsRequest, - ReadDiscreteInputsRequest, -) - -from ..conftest import MockContext - - -res = [True] * 21 -res.extend([False] * 3) -# ---------------------------------------------------------------------------# -# Fixture -# ---------------------------------------------------------------------------# - - -class TestModbusBitMessage: - """Modbus bit read message tests.""" - - # -----------------------------------------------------------------------# - # Setup/TearDown - # -----------------------------------------------------------------------# - - def setUp(self): - """Initialize the test environment and builds request/result encoding pairs.""" - - def tearDown(self): - """Clean up the test environment.""" - - def test_read_bit_base_class_methods(self): - """Test basic bit message encoding/decoding.""" - handle = ReadBitsRequestBase(1, 1, 0, 0, False) - msg = "ReadBitRequest(1,1)" - assert msg == str(handle) - handle = ReadBitsResponseBase([1, 1], 0, 0, False) - msg = "ReadBitsResponseBase(2)" - assert msg == str(handle) - - def test_bit_read_base_request_encoding(self): - """Test basic bit message encoding/decoding.""" - for i in range(20): - handle = ReadBitsRequestBase(i, i, 0, 0, False) - result = struct.pack(">HH", i, i) - assert handle.encode() == result - handle.decode(result) - assert (handle.address, handle.count) == (i, i) - - def test_bit_read_base_response_encoding(self): - """Test basic bit message encoding/decoding.""" - for i in range(20): - data = [True] * i - handle = ReadBitsResponseBase(data, 0, 0, False) - result = handle.encode() - handle.decode(result) - assert handle.bits[:i] == data - - def test_bit_read_base_response_helper_methods(self): - """Test the extra methods on a ReadBitsResponseBase.""" - data = [False] * 8 - handle = ReadBitsResponseBase(data, 0, 0, False) - for i in (1, 3, 5): - handle.setBit(i, True) - for i in (1, 3, 5): - handle.resetBit(i) - for i in range(8): - assert not handle.getBit(i) - - def test_bit_read_base_requests(self): - """Test bit read request encoding.""" - messages = { - ReadBitsRequestBase(12, 14, 0, 0, False): b"\x00\x0c\x00\x0e", - ReadBitsResponseBase([1, 0, 1, 1, 0], 0, 0, False): b"\x01\x0d", - } - for request, expected in iter(messages.items()): - assert request.encode() == expected - - async def test_bit_read_message_update_datastore_value_errors(self): - """Test bit read request encoding.""" - context = MockContext() - requests = [ - ReadCoilsRequest(1, 0x800, 0, 0, False), - ReadDiscreteInputsRequest(1, 0x800, 0, 0, False), - ] - for request in requests: - result = await request.update_datastore(context) - assert ModbusExceptions.IllegalValue == result.exception_code - - async def test_bit_read_message_update_datastore_address_errors(self): - """Test bit read request encoding.""" - context = MockContext() - requests = [ - ReadCoilsRequest(1, 5, 0, 0, False), - ReadDiscreteInputsRequest(1, 5, 0, 0, False), - ] - for request in requests: - result = await request.update_datastore(context) - assert ModbusExceptions.IllegalAddress == result.exception_code - - async def test_bit_read_message_update_datastore_success(self): - """Test bit read request encoding.""" - context = MockContext() - context.validate = lambda a, b, c: True - requests = [ - ReadCoilsRequest(1, 5, 0, 0, False), - ReadDiscreteInputsRequest(1, 5, 0, False), - ] - for request in requests: - result = await request.update_datastore(context) - assert result.bits == [True] * 5 - - def test_bit_read_message_get_response_pdu(self): - """Test bit read message get response pdu.""" - requests = { - ReadCoilsRequest(1, 5, 0, 0, False): 3, - ReadCoilsRequest(1, 8, 0, 0, False): 3, - ReadCoilsRequest(0, 16, 0, 0, False): 4, - ReadDiscreteInputsRequest(1, 21, 0, 0, False): 5, - ReadDiscreteInputsRequest(1, 24, 0, 0, False): 5, - ReadDiscreteInputsRequest(1, 1900, 0, 0, False): 240, - } - for request, expected in iter(requests.items()): - pdu_len = request.get_response_pdu_size() - assert pdu_len == expected diff --git a/test/pdu/test_bit_write_messages.py b/test/pdu/test_bit_write_messages.py deleted file mode 100644 index 931d42637..000000000 --- a/test/pdu/test_bit_write_messages.py +++ /dev/null @@ -1,147 +0,0 @@ -"""Bit Message Test Fixture. - -This fixture tests the functionality of all the -bit based request/response messages: - -* Read/Write Discretes -* Read Coils -""" -from pymodbus.pdu import ModbusExceptions -from pymodbus.pdu.bit_write_message import ( - WriteMultipleCoilsRequest, - WriteMultipleCoilsResponse, - WriteSingleCoilRequest, - WriteSingleCoilResponse, -) - -from ..conftest import FakeList, MockContext - - -# ---------------------------------------------------------------------------# -# Fixture -# ---------------------------------------------------------------------------# - - -class TestModbusBitMessage: - """Modbus bit write message tests.""" - - # -----------------------------------------------------------------------# - # Setup/TearDown - # -----------------------------------------------------------------------# - - def setUp(self): - """Initialize the test environment and builds request/result encoding pairs.""" - - def tearDown(self): - """Clean up the test environment.""" - - def test_bit_write_base_requests(self): - """Test bit write base.""" - messages = { - WriteSingleCoilRequest(1, 0xABCD): b"\x00\x01\xff\x00", - WriteSingleCoilResponse(1, 0xABCD): b"\x00\x01\xff\x00", - WriteMultipleCoilsRequest(1, [True] * 5): b"\x00\x01\x00\x05\x01\x1f", - WriteMultipleCoilsResponse(1, 5): b"\x00\x01\x00\x05", - WriteMultipleCoilsRequest(1, True): b"\x00\x01\x00\x01\x01\x01", - WriteMultipleCoilsResponse(1, 1): b"\x00\x01\x00\x01", - } - for request, expected in iter(messages.items()): - assert request.encode() == expected - - def test_bit_write_message_get_response_pdu(self): - """Test bit write message.""" - requests = {WriteSingleCoilRequest(1, 0xABCD): 5} - for request, expected in iter(requests.items()): - pdu_len = request.get_response_pdu_size() - assert pdu_len == expected - - def test_write_multiple_coils_request(self): - """Test write multiple coils.""" - request = WriteMultipleCoilsRequest(1, [True] * 5) - request.decode(b"\x00\x01\x00\x05\x01\x1f") - assert request.byte_count == 1 - assert request.address == 1 - assert request.values == [True] * 5 - assert request.get_response_pdu_size() == 5 - - request = WriteMultipleCoilsRequest(1, True) - request.decode(b"\x00\x01\x00\x01\x01\x01") - assert request.byte_count == 1 - assert request.address == 1 - assert request.values == [True] - assert request.get_response_pdu_size() == 5 - - def test_invalid_write_multiple_coils_request(self): - """Test write invalid multiple coils.""" - request = WriteMultipleCoilsRequest(1, None) - assert request.values == [] - - def test_write_single_coil_request_encode(self): - """Test write single coil.""" - request = WriteSingleCoilRequest(1, False) - assert request.encode() == b"\x00\x01\x00\x00" - - async def test_write_single_coil_update_datastore(self): - """Test write single coil.""" - context = MockContext(False, default=True) - request = WriteSingleCoilRequest(2, True) - result = await request.update_datastore(context) - assert result.exception_code == ModbusExceptions.IllegalAddress - - context.valid = True - result = await request.update_datastore(context) - assert result.encode() == b"\x00\x02\xff\x00" - - context = MockContext(True, default=False) - request = WriteSingleCoilRequest(2, False) - result = await request.update_datastore(context) - assert result.encode() == b"\x00\x02\x00\x00" - - async def test_write_multiple_coils_update_datastore(self): - """Test write multiple coils.""" - context = MockContext(False) - # too many values - request = WriteMultipleCoilsRequest(2, FakeList(0x123456)) - result = await request.update_datastore(context) - assert result.exception_code == ModbusExceptions.IllegalValue - - # bad byte count - request = WriteMultipleCoilsRequest(2, [0x00] * 4) - request.byte_count = 0x00 - result = await request.update_datastore(context) - assert result.exception_code == ModbusExceptions.IllegalValue - - # does not validate - context.valid = False - request = WriteMultipleCoilsRequest(2, [0x00] * 4) - result = await request.update_datastore(context) - assert result.exception_code == ModbusExceptions.IllegalAddress - - # validated request - context.valid = True - result = await request.update_datastore(context) - assert result.encode() == b"\x00\x02\x00\x04" - - def test_write_multiple_coils_response(self): - """Test write multiple coils.""" - response = WriteMultipleCoilsResponse() - response.decode(b"\x00\x80\x00\x08") - assert response.address == 0x80 - assert response.count == 0x08 - - def test_serializing_to_string(self): - """Test serializing to string.""" - requests = [ - WriteSingleCoilRequest(1, 0xABCD), - WriteSingleCoilResponse(1, 0xABCD), - WriteMultipleCoilsRequest(1, [True] * 5), - WriteMultipleCoilsResponse(1, 5), - ] - for request in requests: - result = str(request) - assert result - - def test_pass_falsy_value_in_write_multiple_coils_request(self): - """Test pass falsy value to write multiple coils.""" - request = WriteMultipleCoilsRequest(1, 0) - assert request.values == [0] diff --git a/test/pdu/test_decoders.py b/test/pdu/test_decoders.py index 75021b217..b7ab899b3 100644 --- a/test/pdu/test_decoders.py +++ b/test/pdu/test_decoders.py @@ -106,8 +106,7 @@ def test_server_lookup(self, code, frame): data = b'\x01' + frame pdu = self.client.lookupPduClass(data) assert pdu - if not code & 0x80: - assert pdu.function_code == code + assert pdu.function_code == code @pytest.mark.parametrize(("code", "frame"), list(responses) + list(exceptions)) def test_client_decode(self, code, frame): diff --git a/test/pdu/test_diag_messages.py b/test/pdu/test_diag_messages.py index d20eaf7df..1d3775f0f 100644 --- a/test/pdu/test_diag_messages.py +++ b/test/pdu/test_diag_messages.py @@ -1,8 +1,6 @@ """Test diag messages.""" -import pytest from pymodbus.constants import ModbusPlusOperation -from pymodbus.exceptions import NotImplementedException from pymodbus.pdu.diag_message import ( ChangeAsciiInputDelimiterRequest, ChangeAsciiInputDelimiterResponse, @@ -141,8 +139,6 @@ async def test_diagnostic_simple_requests(self): """Testing diagnostic request messages encoding.""" request = DiagnosticStatusSimpleRequest(b"\x12\x34") request.sub_function_code = 0x1234 - with pytest.raises(NotImplementedException): - await request.update_datastore() assert request.encode() == b"\x12\x34\x12\x34" DiagnosticStatusSimpleResponse() diff --git a/test/pdu/test_file_message.py b/test/pdu/test_file_message.py index 24ed1aa41..35a0af0af 100644 --- a/test/pdu/test_file_message.py +++ b/test/pdu/test_file_message.py @@ -21,11 +21,6 @@ TEST_MESSAGE = b"\x00\n\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04" -# ---------------------------------------------------------------------------# -# Fixture -# ---------------------------------------------------------------------------# - - class TestBitMessage: """Modbus bit message tests.""" diff --git a/test/pdu/test_mei_messages.py b/test/pdu/test_mei_messages.py index 2c81a8538..3a86136bb 100644 --- a/test/pdu/test_mei_messages.py +++ b/test/pdu/test_mei_messages.py @@ -13,10 +13,6 @@ ) -# ---------------------------------------------------------------------------# -# Fixture -# ---------------------------------------------------------------------------# - TEST_VERSION = b"v2.1.12" TEST_MESSAGE = b"\x00\x07Company\x01\x07Product\x02\x07v2.1.12" diff --git a/test/pdu/test_pdu.py b/test/pdu/test_pdu.py index 9b9344218..f3133515f 100644 --- a/test/pdu/test_pdu.py +++ b/test/pdu/test_pdu.py @@ -1,6 +1,13 @@ """Test pdu.""" import pytest +import pymodbus.pdu.bit_message as bit_msg +import pymodbus.pdu.diag_message as diag_msg +import pymodbus.pdu.file_message as file_msg +import pymodbus.pdu.mei_message as mei_msg +import pymodbus.pdu.other_message as o_msg +import pymodbus.pdu.register_read_message as reg_r_msg +import pymodbus.pdu.register_write_message as reg_w_msg from pymodbus.exceptions import NotImplementedException from pymodbus.pdu import ( ExceptionResponse, @@ -8,6 +15,8 @@ ModbusPDU, ) +from ..conftest import MockContext + class TestPdu: """Test modbus PDU.""" @@ -32,7 +41,7 @@ async def test_is_error(self): def test_request_exception(self): """Test request exception.""" request = ModbusPDU() - request.setData(0, 0, False) + request.setBaseData(0, 0, False) request.function_code = 1 errors = {ModbusExceptions.decode(c): c for c in range(1, 20)} for error, code in iter(errors.items()): @@ -46,7 +55,6 @@ def test_calculate_rtu_frame_size(self): ModbusPDU._rtu_frame_size = 5 # pylint: disable=protected-access assert ModbusPDU.calculateRtuFrameSize(b"") == 5 ModbusPDU._rtu_frame_size = None # pylint: disable=protected-access - ModbusPDU._rtu_byte_count_pos = 2 # pylint: disable=protected-access assert ( ModbusPDU.calculateRtuFrameSize( @@ -56,7 +64,6 @@ def test_calculate_rtu_frame_size(self): ) assert not ModbusPDU.calculateRtuFrameSize(b"\x11") ModbusPDU._rtu_byte_count_pos = None # pylint: disable=protected-access - with pytest.raises(NotImplementedException): ModbusPDU.calculateRtuFrameSize(b"") ModbusPDU._rtu_frame_size = 12 # pylint: disable=protected-access @@ -70,3 +77,154 @@ def test_calculate_rtu_frame_size(self): == 0x05 + 5 ) ModbusPDU._rtu_byte_count_pos = None # pylint: disable=protected-access + + # -------------------------- + # Test PDU types generically + # -------------------------- + + requests = [ + (bit_msg.ReadCoilsRequest, (), {"address": 117, "count": 3}, b'\x01\x00\x75\x00\x03'), + (bit_msg.ReadDiscreteInputsRequest, (), {"address": 117, "count": 3}, b'\x02\x00\x75\x00\x03'), + (bit_msg.WriteSingleCoilRequest, (), {"address": 117, "value": True}, b'\x05\x00\x75\xff\x00'), + (bit_msg.WriteMultipleCoilsRequest, (), {"address": 117, "values": [True, False, True]}, b'\x0f\x00\x75\x00\x03\x01\x05'), + (diag_msg.DiagnosticStatusRequest, (), {}, b'\x08\x27\x0f'), + (diag_msg.DiagnosticStatusSimpleRequest, (), {"data": 0x1010}, b'\x08\x27\x0f\x10\x10'), + (diag_msg.ReturnQueryDataRequest, (), {"message": b'\x10\x01'}, b'\x08\x00\x00\x10\x01'), + (diag_msg.RestartCommunicationsOptionRequest, (), {"toggle": True}, b'\x08\x00\x01\xff\x00'), + (diag_msg.ReturnDiagnosticRegisterRequest, (), {"data": 0x1010}, b'\x08\x00\x02\x10\x10'), + (diag_msg.ChangeAsciiInputDelimiterRequest, (), {"data": 0x1010}, b'\x08\x00\x03\x10\x10'), + (diag_msg.ForceListenOnlyModeRequest, (), {}, b'\x08\x00\x04\x00\x00'), + (diag_msg.ClearCountersRequest, (), {"data": 0x1010}, b'\x08\x00\n\x10\x10'), + (diag_msg.ReturnBusMessageCountRequest, (), {"data": 0x1010}, b'\x08\x00\x0b\x10\x10'), + (diag_msg.ReturnBusCommunicationErrorCountRequest, (), {"data": 0x1010}, b'\x08\x00\x0c\x10\x10'), + (diag_msg.ReturnBusExceptionErrorCountRequest, (), {"data": 0x1010}, b'\x08\x00\x0d\x10\x10'), + (diag_msg.ReturnSlaveMessageCountRequest, (), {"data": 0x1010}, b'\x08\x00\x0e\x10\x10'), + (diag_msg.ReturnSlaveNoResponseCountRequest, (), {"data": 0x1010}, b'\x08\x00\x0f\x10\x10'), + (diag_msg.ReturnSlaveNAKCountRequest, (), {"data": 0x1010}, b'\x08\x00\x10\x10\x10'), + (diag_msg.ReturnSlaveBusyCountRequest, (), {"data": 0x1010}, b'\x08\x00\x11\x10\x10'), + (diag_msg.ReturnSlaveBusCharacterOverrunCountRequest, (), {"data": 0x1010}, b'\x08\x00\x12\x10\x10'), + (diag_msg.ReturnIopOverrunCountRequest, (), {"data": 0x1010}, b'\x08\x00\x13\x10\x10'), + (diag_msg.ClearOverrunCountRequest, (), {"data": 0x1010}, b'\x08\x00\x14\x10\x10'), + (diag_msg.GetClearModbusPlusRequest, (), {"data": 0x1010}, b'\x08\x00\x15\x10\x10'), + (file_msg.ReadFileRecordRequest, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x14\x0e\x06\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00'), + (file_msg.WriteFileRecordRequest, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x15\x0e\x06\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00'), + (file_msg.ReadFifoQueueRequest, (), {"address": 117}, b'\x18\x00\x75'), + (mei_msg.ReadDeviceInformationRequest, (), {"read_code": 0x17, "object_id": 0x29}, b'\x2b\x0e\x17\x29'), + (o_msg.ReadExceptionStatusRequest, (), {}, b'\x07'), + (o_msg.GetCommEventCounterRequest, (), {}, b'\x0b'), + (o_msg.GetCommEventLogRequest, (), {}, b'\x0c'), + (o_msg.ReportSlaveIdRequest, (), {}, b'\x11'), + (reg_r_msg.ReadHoldingRegistersRequest, (), {"address": 117, "count": 3}, b'\x03\x00\x75\x00\x03'), + (reg_r_msg.ReadInputRegistersRequest, (), {"address": 117, "count": 3}, b'\x04\x00\x75\x00\x03'), + (reg_r_msg.ReadWriteMultipleRegistersRequest, (), {"read_address": 17, "read_count": 2, "write_address": 25, "write_registers": [111, 112]}, b'\x17\x00\x11\x00\x02\x00\x19\x00\x02\x04\x00\x6f\x00\x70'), + (reg_w_msg.WriteMultipleRegistersRequest, (), {"address": 117, "values": [111, 121, 131]}, b'\x10\x00\x75\x00\x03\x06\x00\x6f\x00\x79\x00\x83'), + (reg_w_msg.WriteSingleRegisterRequest, (), {"address": 117, "value": 112}, b'\x06\x00\x75\x00\x70'), + (reg_w_msg.MaskWriteRegisterRequest, (), {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b'\x16\x01\x04\xe1\xd2\x12\x34'), + ] + + responses = [ + (bit_msg.ReadCoilsResponse, (), {"values": [3, 17]}, b'\x01\x01\x03'), + (bit_msg.ReadDiscreteInputsResponse, (), {"values": [3, 17]}, b'\x02\x01\x03'), + (bit_msg.WriteSingleCoilResponse, (), {"address": 117, "value": True}, b'\x05\x00\x75\xff\x00'), + (bit_msg.WriteMultipleCoilsResponse, (), {"address": 117, "count": 3}, b'\x0f\x00\x75\x00\x03'), + (diag_msg.DiagnosticStatusResponse, (), {}, b'\x08\x27\x0f'), + (diag_msg.DiagnosticStatusSimpleResponse, (), {"data": 0x1010}, b'\x08\x27\x0f\x10\x10'), + (diag_msg.ReturnQueryDataResponse, (), {"message": b'AB'}, b'\x08\x00\x00\x41\x42'), + (diag_msg.RestartCommunicationsOptionResponse, (), {"toggle": True}, b'\x08\x00\x01\xff\x00'), + (diag_msg.ReturnDiagnosticRegisterResponse, (), {"data": 0x1010}, b'\x08\x00\x02\x10\x10'), + (diag_msg.ChangeAsciiInputDelimiterResponse, (), {"data": 0x1010}, b'\x08\x00\x03\x10\x10'), + (diag_msg.ForceListenOnlyModeResponse, (), {}, b'\x08\x00\x04'), + (diag_msg.ClearCountersResponse, (), {"data": 0x1010}, b'\x08\x00\n\x10\x10'), + (diag_msg.ReturnBusMessageCountResponse, (), {"data": 0x1010}, b'\x08\x00\x0b\x10\x10'), + (diag_msg.ReturnBusCommunicationErrorCountResponse, (), {"data": 0x1010}, b'\x08\x00\x0c\x10\x10'), + (diag_msg.ReturnBusExceptionErrorCountResponse, (), {"data": 0x1010}, b'\x08\x00\x0d\x10\x10'), + (diag_msg.ReturnSlaveMessageCountResponse, (), {"data": 0x1010}, b'\x08\x00\x0e\x10\x10'), + (diag_msg.ReturnSlaveNoResponseCountResponse, (), {"data": 0x1010}, b'\x08\x00\x0f\x10\x10'), + (diag_msg.ReturnSlaveNAKCountResponse, (), {"data": 0x1010}, b'\x08\x00\x10\x10\x10'), + (diag_msg.ReturnSlaveBusyCountResponse, (), {"data": 0x1010}, b'\x08\x00\x11\x10\x10'), + (diag_msg.ReturnSlaveBusCharacterOverrunCountResponse, (), {"data": 0x1010}, b'\x08\x00\x12\x10\x10'), + (diag_msg.ReturnIopOverrunCountResponse, (), {"data": 0x1010}, b'\x08\x00\x13\x10\x10'), + (diag_msg.ClearOverrunCountResponse, (), {"data": 0x1010}, b'\x08\x00\x14\x10\x10'), + (diag_msg.GetClearModbusPlusResponse, (), {"data": 0x1010}, b'\x08\x00\x15\x10\x10'), + (file_msg.ReadFileRecordResponse, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x14\x04\x01\x06\x01\x06'), + (file_msg.WriteFileRecordResponse, (), {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b'\x15\x0e\x06\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00'), + (file_msg.ReadFifoQueueResponse, (), {"values": [123, 456]}, b'\x18\x00\x06\x00\x04\x00{\x01\xc8'), + (mei_msg.ReadDeviceInformationResponse, (), {"read_code": 0x17}, b'\x2b\x0e\x17\x83\x00\x00\x00'), + (o_msg.ReadExceptionStatusResponse, (), {"status": 0x23}, b'\x07\x23'), + (o_msg.GetCommEventCounterResponse, (), {"count": 123}, b'\x0b\x00\x00\x00\x7b'), + (o_msg.GetCommEventLogResponse, (), {"status": True, "message_count": 12, "event_count": 7, "events": [12, 14]}, b'\x0c\x08\x00\x00\x00\x07\x00\x0c\x0c\x0e'), + (o_msg.ReportSlaveIdResponse, (), {"identifier": b'\x12', "status": True}, b'\x11\x02\x12\xff'), + (reg_r_msg.ReadHoldingRegistersResponse, (), {"values": [3, 17]}, b'\x03\x04\x00\x03\x00\x11'), + (reg_r_msg.ReadInputRegistersResponse, (), {"values": [3, 17]}, b'\x04\x04\x00\x03\x00\x11'), + (reg_r_msg.ReadWriteMultipleRegistersResponse, (), {"values": [1, 2]}, b'\x17\x04\x00\x01\x00\x02'), + (reg_w_msg.WriteSingleRegisterResponse, (), {"address": 117, "value": 112}, b'\x06\x00\x75\x00\x70'), + (reg_w_msg.WriteMultipleRegistersResponse, (), {"address": 117, "count": 3}, b'\x10\x00\x75\x00\x03'), + (reg_w_msg.MaskWriteRegisterResponse, (), {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b'\x16\x01\x04\xe1\xd2\x12\x34'), + ] + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests) + @pytest.mark.usefixtures("kwargs", "args", "frame") + def test_pdu_instance(self, pdutype): + """Test that all PDU types can be created.""" + pdu = pdutype() + assert pdu + assert str(pdu) + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) + @pytest.mark.usefixtures("frame") + def test_pdu_instance_args(self, pdutype, args, kwargs): + """Test that all PDU types can be created.""" + pdu = pdutype(*args, **kwargs) + assert pdu + assert str(pdu) + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) + @pytest.mark.usefixtures("frame") + def test_pdu_instance_extras(self, pdutype, args, kwargs): + """Test that all PDU types can be created.""" + tid = 9112 + slave_id = 63 + pdu = pdutype(*args, transaction=tid, slave=slave_id, **kwargs) + assert pdu + assert str(pdu) + assert pdu.slave_id == slave_id + assert pdu.transaction_id == tid + assert pdu.function_code > 0 + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) + def test_pdu_instance_encode(self, pdutype, args, kwargs, frame): + """Test that all PDU types can be created.""" + res_frame = pdutype.function_code.to_bytes(1,'big') + pdutype(*args, **kwargs).encode() + assert res_frame == frame + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), responses) + @pytest.mark.usefixtures("frame") + def test_pdu_get_response_pdu_size1(self, pdutype, args, kwargs): + """Test that all PDU types can be created.""" + pdu = pdutype(*args, **kwargs) + assert not pdu.get_response_pdu_size() + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests) + @pytest.mark.usefixtures("frame") + def test_get_response_pdu_size2(self, pdutype, args, kwargs): + """Test that all PDU types can be created.""" + pdu = pdutype(*args, **kwargs) + pdu.get_response_pdu_size() + #FIX size > 0 !! + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests + responses) + def test_pdu_decode(self, pdutype, args, kwargs, frame): + """Test that all PDU types can be created.""" + pdu = pdutype(*args, **kwargs) + pdu.decode(frame[1:]) + + @pytest.mark.parametrize(("pdutype", "args", "kwargs", "frame"), requests) + @pytest.mark.usefixtures("frame") + async def test_pdu_datastore(self, pdutype, args, kwargs): + """Test that all PDU types can be created.""" + pdu = pdutype(*args, **kwargs) + context = MockContext() + context.validate = lambda a, b, c: True + assert await pdu.update_datastore(context) + + diff --git a/test/pdu/test_pdutype.py b/test/pdu/test_pdutype.py deleted file mode 100644 index 38a16cfb9..000000000 --- a/test/pdu/test_pdutype.py +++ /dev/null @@ -1,145 +0,0 @@ -"""Test pdu.""" -import pytest - -import pymodbus.pdu.bit_read_message as bit_r_msg -import pymodbus.pdu.bit_write_message as bit_w_msg -import pymodbus.pdu.diag_message as diag_msg -import pymodbus.pdu.file_message as file_msg -import pymodbus.pdu.mei_message as mei_msg -import pymodbus.pdu.other_message as o_msg -import pymodbus.pdu.register_read_message as reg_r_msg -import pymodbus.pdu.register_write_message as reg_w_msg - - -class TestPduType: - """Test all PDU types requests/responses.""" - - requests = [ - (bit_r_msg.ReadCoilsRequest, {"address": 117, "count": 3}, b''), - (bit_r_msg.ReadDiscreteInputsRequest, {"address": 117, "count": 3}, b''), - (bit_w_msg.WriteSingleCoilRequest, {"address": 117, "value": True}, b''), - (bit_w_msg.WriteMultipleCoilsRequest, {"address": 117, "values": [True, False, True]}, b''), - (diag_msg.DiagnosticStatusRequest, {}, b''), - (diag_msg.DiagnosticStatusSimpleRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnQueryDataRequest, {"message": b'\x10\x01'}, b''), - (diag_msg.RestartCommunicationsOptionRequest, {"toggle": True}, b''), - (diag_msg.ReturnDiagnosticRegisterRequest, {"data": 0x1010}, b''), - (diag_msg.ChangeAsciiInputDelimiterRequest, {"data": 0x1010}, b''), - (diag_msg.ForceListenOnlyModeRequest, {}, b''), - (diag_msg.ClearCountersRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnBusMessageCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnBusCommunicationErrorCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnBusExceptionErrorCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveMessageCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveNoResponseCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveNAKCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveBusyCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveBusCharacterOverrunCountRequest, {"data": 0x1010}, b''), - (diag_msg.ReturnIopOverrunCountRequest, {"data": 0x1010}, b''), - (diag_msg.ClearOverrunCountRequest, {"data": 0x1010}, b''), - (diag_msg.GetClearModbusPlusRequest, {"data": 0x1010}, b''), - (file_msg.ReadFileRecordRequest, {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b''), - (file_msg.WriteFileRecordRequest, {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b''), - (file_msg.ReadFifoQueueRequest, {"address": 117}, b''), - (mei_msg.ReadDeviceInformationRequest, {"read_code": 0x17, "object_id": 0x29}, b''), - (o_msg.ReadExceptionStatusRequest, {}, b''), - (o_msg.GetCommEventCounterRequest, {}, b''), - (o_msg.GetCommEventLogRequest, {}, b''), - (o_msg.ReportSlaveIdRequest, {}, b''), - (reg_r_msg.ReadHoldingRegistersRequest, {"address": 117, "count": 3}, b''), - (reg_r_msg.ReadInputRegistersRequest, {"address": 117, "count": 3}, b''), - (reg_r_msg.ReadWriteMultipleRegistersRequest, {"read_address": 17, "read_count": 2, "write_address": 25, "write_registers": [111, 112]}, b''), - (reg_w_msg.WriteMultipleRegistersRequest, {"address": 117, "values": [111, 121, 131]}, b''), - (reg_w_msg.WriteSingleRegisterRequest, {"address": 117, "value": 112}, b''), - (reg_w_msg.MaskWriteRegisterRequest, {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b''), - ] - - responses = [ - (bit_r_msg.ReadCoilsResponse, {"values": [3, 17]}, b''), - (bit_r_msg.ReadDiscreteInputsResponse, {"values": [3, 17]}, b''), - (bit_w_msg.WriteSingleCoilResponse, {"address": 117, "value": True}, b''), - (bit_w_msg.WriteMultipleCoilsResponse, {"address": 117, "count": 3}, b''), - (diag_msg.DiagnosticStatusResponse, {}, b''), - (diag_msg.DiagnosticStatusSimpleResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnQueryDataResponse, {"message": b'AB'}, b''), - (diag_msg.RestartCommunicationsOptionResponse, {"toggle": True}, b''), - (diag_msg.ReturnDiagnosticRegisterResponse, {"data": 0x1010}, b''), - (diag_msg.ChangeAsciiInputDelimiterResponse, {"data": 0x1010}, b''), - (diag_msg.ForceListenOnlyModeResponse, {}, b''), - (diag_msg.ClearCountersResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnBusMessageCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnBusCommunicationErrorCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnBusExceptionErrorCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveMessageCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveNoResponseCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveNAKCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveBusyCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnSlaveBusCharacterOverrunCountResponse, {"data": 0x1010}, b''), - (diag_msg.ReturnIopOverrunCountResponse, {"data": 0x1010}, b''), - (diag_msg.ClearOverrunCountResponse, {"data": 0x1010}, b''), - (diag_msg.GetClearModbusPlusResponse, {"data": 0x1010}, b''), - (file_msg.ReadFileRecordResponse, {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b''), - (file_msg.WriteFileRecordResponse, {"records": [file_msg.FileRecord(), file_msg.FileRecord()]}, b''), - (file_msg.ReadFifoQueueResponse, {"values": [123, 456]}, b''), - (mei_msg.ReadDeviceInformationResponse, {"read_code": 0x17}, b''), - (o_msg.ReadExceptionStatusResponse, {"status": 0x23}, b''), - (o_msg.GetCommEventCounterResponse, {"count": 123}, b''), - (o_msg.GetCommEventLogResponse, {"status": True, "message_count": 12, "event_count": 7, "events": [12, 14]}, b''), - (o_msg.ReportSlaveIdResponse, {"identifier": b'\x12', "status": True}, b''), - (reg_r_msg.ReadHoldingRegistersResponse, {"values": [3, 17]}, b''), - (reg_r_msg.ReadInputRegistersResponse, {"values": [3, 17]}, b''), - (reg_r_msg.ReadWriteMultipleRegistersResponse, {"values": [1, 2]}, b''), - (reg_w_msg.WriteSingleRegisterResponse, {"address": 117, "value": 112}, b''), - (reg_w_msg.WriteMultipleRegistersResponse, {"address": 117, "count": 3}, b''), - (reg_w_msg.MaskWriteRegisterResponse, {"address": 0x0104, "and_mask": 0xE1D2, "or_mask": 0x1234}, b''), - ] - - - @pytest.mark.parametrize(("pdutype", "kwargs", "framer"), requests) - @pytest.mark.usefixtures("kwargs", "framer") - def test_pdu_instance(self, pdutype): - """Test that all PDU types can be created.""" - pdu = pdutype() - assert pdu - assert str(pdu) - - @pytest.mark.parametrize(("pdutype", "kwargs", "framer"), requests + responses) - @pytest.mark.usefixtures("framer") - def test_pdu_instance_args(self, pdutype, kwargs): - """Test that all PDU types can be created.""" - pdu = pdutype(**kwargs) - assert pdu - assert str(pdu) - - @pytest.mark.parametrize(("pdutype", "kwargs", "framer"), requests + responses) - @pytest.mark.usefixtures("framer") - def test_pdu_instance_extras(self, pdutype, kwargs): - """Test that all PDU types can be created.""" - tid = 9112 - slave_id = 63 - pdu = pdutype(transaction=tid, slave=slave_id, **kwargs) - assert pdu - assert str(pdu) - assert pdu.slave_id == slave_id - assert pdu.transaction_id == tid - assert pdu.function_code > 0 - - @pytest.mark.parametrize(("pdutype", "kwargs", "framer"), requests + responses) - @pytest.mark.usefixtures("framer") - def test_pdu_instance_encode(self, pdutype, kwargs): - """Test that all PDU types can be created.""" - pdutype(**kwargs).encode() - # Fix Check frame against test case - - @pytest.mark.parametrize(("pdutype", "kwargs", "framer"), requests + responses) - @pytest.mark.usefixtures("framer") - def test_pdu_special_methods(self, pdutype, kwargs): - """Test that all PDU types can be created.""" - pdu = pdutype(**kwargs) - pdu.get_response_pdu_size() - if hasattr(pdu, "setBit"): - pdu.setBit(0) - if hasattr(pdu, "resetBit"): - pdu.resetBit(0) - if hasattr(pdu, "getBit"): - pdu.getBit(0) diff --git a/test/sub_client/test_client.py b/test/sub_client/test_client.py index 44482e241..395e05c49 100755 --- a/test/sub_client/test_client.py +++ b/test/sub_client/test_client.py @@ -7,8 +7,7 @@ import pytest import pymodbus.client as lib_client -import pymodbus.pdu.bit_read_message as pdu_bit_read -import pymodbus.pdu.bit_write_message as pdu_bit_write +import pymodbus.pdu.bit_message as pdu_bit import pymodbus.pdu.diag_message as pdu_diag import pymodbus.pdu.file_message as pdu_file_msg import pymodbus.pdu.other_message as pdu_other_msg @@ -46,11 +45,11 @@ @pytest.mark.parametrize( ("method", "arg", "pdu_request"), [ - ("read_coils", 1, pdu_bit_read.ReadCoilsRequest), - ("read_discrete_inputs", 1, pdu_bit_read.ReadDiscreteInputsRequest), + ("read_coils", 1, pdu_bit.ReadCoilsRequest), + ("read_discrete_inputs", 1, pdu_bit.ReadDiscreteInputsRequest), ("read_holding_registers", 1, pdu_reg_read.ReadHoldingRegistersRequest), ("read_input_registers", 1, pdu_reg_read.ReadInputRegistersRequest), - ("write_coil", 2, pdu_bit_write.WriteSingleCoilRequest), + ("write_coil", 2, pdu_bit.WriteSingleCoilRequest), ("write_register", 2, pdu_req_write.WriteSingleRegisterRequest), ("read_exception_status", 0, pdu_other_msg.ReadExceptionStatusRequest), ("diag_query_data", 3, pdu_diag.ReturnQueryDataRequest), @@ -90,7 +89,7 @@ ("diag_read_iop_overrun_count", 0, pdu_diag.ReturnIopOverrunCountRequest), ("diag_clear_overrun_counter", 0, pdu_diag.ClearOverrunCountRequest), ("diag_getclear_modbus_response", 0, pdu_diag.GetClearModbusPlusRequest), - ("write_coils", 5, pdu_bit_write.WriteMultipleCoilsRequest), + ("write_coils", 5, pdu_bit.WriteMultipleCoilsRequest), ("write_registers", 6, pdu_req_write.WriteMultipleRegistersRequest), ("readwrite_registers", 1, pdu_reg_read.ReadWriteMultipleRegistersRequest), ("mask_write_register", 1, pdu_req_write.MaskWriteRegisterRequest), @@ -241,7 +240,7 @@ async def test_client_instanciate( client.connect = lambda: False client.transport = None pdu = ModbusPDU() - pdu.setData(0, 0, False) + pdu.setBaseData(0, 0, False) with pytest.raises(ConnectionException): client.execute(False, pdu) @@ -257,7 +256,7 @@ async def test_client_modbusbaseclient(): comm_type=CommType.TCP, ), ) - client.register(pdu_bit_read.ReadCoilsResponse) + client.register(pdu_bit.ReadCoilsResponse) assert str(client) client.close() @@ -323,7 +322,7 @@ async def test_client_protocol_receiver(): response = base.build_response(0x00) # pylint: disable=protected-access base.ctx.data_received(data) result = response.result() - assert isinstance(result, pdu_bit_read.ReadCoilsResponse) + assert isinstance(result, pdu_bit.ReadCoilsResponse) base.transport = None with pytest.raises(ConnectionException): @@ -363,7 +362,7 @@ async def test_client_protocol_handler(): ) transport = mock.MagicMock() base.ctx.connection_made(transport=transport) - reply = pdu_bit_read.ReadCoilsRequest(1, 1) + reply = pdu_bit.ReadCoilsRequest(1, 1) reply.transaction_id = 0x00 base.ctx._handle_response(None) # pylint: disable=protected-access base.ctx._handle_response(reply) # pylint: disable=protected-access @@ -416,13 +415,13 @@ async def test_client_protocol_execute(): timeout_connect=3, ), ) - request = pdu_bit_read.ReadCoilsRequest(1, 1) + request = pdu_bit.ReadCoilsRequest(1, 1) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) response = await base.async_execute(False, request) assert not response.isError() - assert isinstance(response, pdu_bit_read.ReadCoilsResponse) + assert isinstance(response, pdu_bit.ReadCoilsResponse) async def test_client_execute_broadcast(): """Test the client protocol execute method.""" @@ -434,7 +433,7 @@ async def test_client_execute_broadcast(): host="127.0.0.1", ), ) - request = pdu_bit_read.ReadCoilsRequest(1, 1, slave=0) + request = pdu_bit.ReadCoilsRequest(1, 1, slave=0) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) assert await base.async_execute(False, request) @@ -450,7 +449,7 @@ async def test_client_execute_broadcast_no(): host="127.0.0.1", ), ) - request = pdu_bit_read.ReadCoilsRequest(1, 1, slave=0) + request = pdu_bit.ReadCoilsRequest(1, 1, slave=0) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) assert not await base.async_execute(True, request) @@ -466,14 +465,14 @@ async def test_client_protocol_retry(): timeout_connect=0.1, ), ) - request = pdu_bit_read.ReadCoilsRequest(1, 1) + request = pdu_bit.ReadCoilsRequest(1, 1) transport = MockTransport(base, request, retries=2) base.ctx.connection_made(transport=transport) response = await base.async_execute(False, request) assert transport.retries == 0 assert not response.isError() - assert isinstance(response, pdu_bit_read.ReadCoilsResponse) + assert isinstance(response, pdu_bit.ReadCoilsResponse) async def test_client_protocol_timeout(): @@ -489,7 +488,7 @@ async def test_client_protocol_timeout(): ) # Avoid creating do_reconnect() task base.ctx.connection_lost = mock.MagicMock() - request = pdu_bit_read.ReadCoilsRequest(1, 1) + request = pdu_bit.ReadCoilsRequest(1, 1) transport = MockTransport(base, request, retries=4) base.ctx.connection_made(transport=transport) @@ -693,7 +692,7 @@ async def test_client_build_response(): comm_params=CommParams(), ) pdu = ModbusPDU() - pdu.setData(0, 0, False) + pdu.setBaseData(0, 0, False) with pytest.raises(ConnectionException): await client.build_response(pdu) @@ -702,7 +701,7 @@ async def test_client_mixin_execute(): """Test dummy execute for both sync and async.""" client = ModbusClientMixin() pdu = ModbusPDU() - pdu.setData(0, 0, False) + pdu.setBaseData(0, 0, False) with pytest.raises(NotImplementedError): client.execute(False, pdu) with pytest.raises(NotImplementedError): diff --git a/test/sub_current/test_remote_datastore.py b/test/sub_current/test_remote_datastore.py index 0b6c5d217..f7c6e5ce3 100644 --- a/test/sub_current/test_remote_datastore.py +++ b/test/sub_current/test_remote_datastore.py @@ -7,8 +7,7 @@ from pymodbus.datastore.remote import RemoteSlaveContext from pymodbus.exceptions import NotImplementedException from pymodbus.pdu import ExceptionResponse -from pymodbus.pdu.bit_read_message import ReadCoilsResponse -from pymodbus.pdu.bit_write_message import WriteMultipleCoilsResponse +from pymodbus.pdu.bit_message import ReadCoilsResponse, WriteMultipleCoilsResponse from pymodbus.pdu.register_read_message import ReadInputRegistersResponse diff --git a/test/sub_current/test_transaction.py b/test/sub_current/test_transaction.py index c24c28d79..b0af32860 100755 --- a/test/sub_current/test_transaction.py +++ b/test/sub_current/test_transaction.py @@ -166,7 +166,7 @@ def test_get_transaction_manager_transaction(self): """Test the getting a transaction from the transaction manager.""" self._manager.reset() handle = ModbusPDU() - handle.setData(0, self._manager.getNextTID(), False) + handle.setBaseData(0, self._manager.getNextTID(), False) self._manager.addTransaction(handle) result = self._manager.getTransaction(handle.transaction_id) assert handle is result @@ -175,7 +175,7 @@ def test_delete_transaction_manager_transaction(self): """Test deleting a transaction from the dict transaction manager.""" self._manager.reset() handle = ModbusPDU() - handle.setData(0, self._manager.getNextTID(), False) + handle.setBaseData(0, self._manager.getNextTID(), False) self._manager.addTransaction(handle) self._manager.delTransaction(handle.transaction_id) assert not self._manager.getTransaction(handle.transaction_id) diff --git a/test/transport/test_comm.py b/test/transport/test_comm.py index 2639b3b99..30844425e 100644 --- a/test/transport/test_comm.py +++ b/test/transport/test_comm.py @@ -172,12 +172,9 @@ async def test_split_serial_packet(self, client, server, use_port): (CommType.SERIAL, "socket://localhost:7300"), ], ) + @pytest.mark.skipif(SerialTransport.force_poll, reason="Serial poll not supported") async def test_serial_poll(self, client, server, use_port): """Test connection and data exchange.""" - if SerialTransport.force_poll: # pragma: no cover - client.close() - server.close() - return Log.debug("test_serial_poll {}", use_port) assert await server.listen() SerialTransport.force_poll = True diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index 0d7e7497d..acf73c9cd 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -80,10 +80,9 @@ async def test_create_serial(self): assert protocol transport.close() + @pytest.mark.skipif(SerialTransport.force_poll, reason="Serial poll not supported") async def test_force_poll(self): """Test external methods.""" - if SerialTransport.force_poll: # pragma: no cover - return SerialTransport.force_poll = True transport, protocol = await create_serial_connection( asyncio.get_running_loop(), mock.Mock, "dummy" @@ -94,10 +93,9 @@ async def test_force_poll(self): transport.close() SerialTransport.force_poll = False + @pytest.mark.skipif(SerialTransport.force_poll, reason="Serial poll not supported") async def test_write_force_poll(self): """Test write with poll.""" - if SerialTransport.force_poll: # pragma: no cover - return SerialTransport.force_poll = True transport, protocol = await create_serial_connection( asyncio.get_running_loop(), mock.Mock, "dummy"