diff --git a/connect-to-uf.sh b/connect-to-uf.sh new file mode 100644 index 0000000..09583f4 --- /dev/null +++ b/connect-to-uf.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +xfreerdp /u:user /p:12345678 /v:10.1.1.2 /f diff --git a/data/DSEG7Classic-Regular.ttf.import b/data/DSEG7Classic-Regular.ttf.import index 1c1fddb..edd46ee 100644 --- a/data/DSEG7Classic-Regular.ttf.import +++ b/data/DSEG7Classic-Regular.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/DSEG7Classic-Regular.ttf-32df8fc6f2e5584d5a5b Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/data/DejaVuSans.ttf.import b/data/DejaVuSans.ttf.import index 3e412bc..de13c3d 100644 --- a/data/DejaVuSans.ttf.import +++ b/data/DejaVuSans.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/DejaVuSans.ttf-b4085dde282b5d503d6a56e258b526 Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/data/DejaVuSansCondensed.ttf.import b/data/DejaVuSansCondensed.ttf.import index 42c3c52..2d8fd9e 100644 --- a/data/DejaVuSansCondensed.ttf.import +++ b/data/DejaVuSansCondensed.ttf.import @@ -15,6 +15,7 @@ dest_files=["res://.godot/imported/DejaVuSansCondensed.ttf-7a8251b7291db131ffa4e Rendering=null antialiasing=1 generate_mipmaps=false +disable_embedded_bitmaps=true multichannel_signed_distance_field=false msdf_pixel_range=8 msdf_size=48 diff --git a/data/bip.png b/data/bip.png new file mode 100644 index 0000000..3fb0f1c Binary files /dev/null and b/data/bip.png differ diff --git a/data/bip.png.import b/data/bip.png.import new file mode 100644 index 0000000..6331180 --- /dev/null +++ b/data/bip.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cp4bv6lh8w83k" +path="res://.godot/imported/bip.png-6f0fd2c953cace8aaed57abe6fd7518e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://data/bip.png" +dest_files=["res://.godot/imported/bip.png-6f0fd2c953cace8aaed57abe6fd7518e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/data/подкладка.png b/data/подкладка.png new file mode 100644 index 0000000..14e6aeb Binary files /dev/null and b/data/подкладка.png differ diff --git a/data/подкладка.png.import b/data/подкладка.png.import new file mode 100644 index 0000000..d0bec96 --- /dev/null +++ b/data/подкладка.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://o4qqx5cl0m52" +path="res://.godot/imported/подкладка.png-7dcb2892e7adf1f58496d92f942515b9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://data/подкладка.png" +dest_files=["res://.godot/imported/подкладка.png-7dcb2892e7adf1f58496d92f942515b9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/modbus/client-serial-modbus.py b/modbus/client-serial-modbus.py deleted file mode 100644 index 08f2091..0000000 --- a/modbus/client-serial-modbus.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 - -""" -Драйвер клиента для интерфейса Modbus работающего через последовательный порт. -(приложение | UDP | JSON) <-> (этот драйвер клиента) <-> (сервер | Serial | Modbus RTU) -""" - -import os -import json -import argparse -import time -from socket import * - -import logger -from pymodbus.client.sync import ModbusSerialClient - -DEF_SERIAL = 'COM3' if os.name == 'nt' else '/dev/tty0' -log_file_dir = os.path.join(os.path.expanduser('~')) -log = logger.get_logger(__file__, log_file_dir) -EXCEPT_MSGS = { - 'write': 'ошибка: запись в регистры не выполнена', - 'read_holding': 'ошибка: чтение регистров хранения не выполнено', - 'read_input': 'ошибка: чтение регистров входов не выполнено', - '': 'ошибка: получена неверная команда'} - - -def main(): - parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('--modbus-unit', default=1, type=int, help='Номер устройства Modbus') - parser.add_argument('--serial-baud', default=115200, type=int, help='Скорость последовательного порта, бод') - parser.add_argument('--serial-port', default=DEF_SERIAL, type=str, help='Последовательный порт для подключения к Modbus серверу') - parser.add_argument('--udp-addr', default='localhost', type=str, help='Адрес для отправки сообщений приложению') - parser.add_argument('--udp-port-rx', default=44100, type=int, help='Порт для приёма сообщений от приложения') - parser.add_argument('--udp-port-tx', default=48000, type=int, help='Порт для отправки сообщений приложению') - opts, opts_unk = parser.parse_known_args() - print_opts_error(opts_unk) - while True: - run_sync_client(opts.udp_addr, opts.udp_port_rx, opts.udp_port_tx, opts.serial_port, opts.serial_baud, opts.modbus_unit) - time.sleep(3) - log.warning('Переподключение к серверу') - - -def run_sync_client(udp_addr, udp_port_rx, udp_port_tx, serial_port, serial_baud, unit_num): - sock = socket(AF_INET, SOCK_DGRAM) - sock.bind((udp_addr, udp_port_rx)) - log.info('Привязан к адресу %s:%d' % (udp_addr, udp_port_rx)) - - json_decoder = json.JSONDecoder() - json_encoder = json.JSONEncoder() - - client = ModbusSerialClient(method='rtu', port=serial_port, baudrate=int(serial_baud)) - while not client.connect(): # Ждать подключения к серверу - time.sleep(3) - - log.info('Подключен к серверу через \"%s\" на скорости %d бод' % (serial_port, serial_baud)) - tx_data = json_encoder.encode({'code': 'info', 'data': 'ok'}) - sock.sendto(bytes(tx_data, 'utf-8'), (udp_addr, udp_port_tx)) - while True: - try: - udp_data = sock.recv(2048) # Ждать команду от приложения - except: - return - udp_data = udp_data.decode('utf-8') - message = json_decoder.decode(udp_data) # type: dict - - if 'code' not in message: - message['code'] = '' - - if 'data' not in message: - message['data'] = [] - - if 'unit' not in message: - message['unit'] = unit_num - - rc = False - if message['code'] == 'write': - data = message['data'] - rq = client.write_registers(data[0], data[1:], unit=message['unit']) - rc = rq.isError() - if not rc: - tx_data = json_encoder.encode({'code': 'resp_write', 'data': ''}) - sock.sendto(bytes(tx_data, 'utf-8'), (udp_addr, udp_port_tx)) - elif message['code'] == 'read_holding': - data = message['data'] - rq = client.read_holding_registers(data[0], data[1], unit=message['unit']) - rc = rq.isError() - if not rc: - tx_data = json_encoder.encode({'code': 'resp_holding', 'data': [data[0]] + rq.registers}) - sock.sendto(bytes(tx_data, 'utf-8'), (udp_addr, udp_port_tx)) - elif message['code'] == 'read_input': - data = message['data'] - rq = client.read_input_registers(data[0], data[1], unit=message['unit']) - rc = rq.isError() - if not rc: - tx_data = json_encoder.encode({'code': 'resp_input', 'data': [data[0]] + rq.registers}) - sock.sendto(bytes(tx_data, 'utf-8'), (udp_addr, udp_port_tx)) - elif message['code'] == '': - rc = False - - if rc: - code = message['code'] - data = message['data'] - rc_msg = EXCEPT_MSGS[code] - tx_data = json_encoder.encode({'code': 'exception', 'data': rc_msg, 'unit': message['unit']}) - sock.sendto(bytes(tx_data, 'utf-8'), (udp_addr, udp_port_tx)) - log.error('Команда \"%s\" не выполнена. данные: \"%s\", порт: \"%s\", скорость: %s' - % (code, data, serial_port, serial_baud)) - - -def print_opts_error(opts: list) -> None: - for opt in opts: - log.warning('Неизвестный аргумент: \"%s\"' % opt) - - -if __name__ == '__main__': - main() diff --git a/modbus/logger.py b/modbus/logger.py deleted file mode 100644 index 3f57212..0000000 --- a/modbus/logger.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python3 - - -def get_logger(source_file, log_dir='logs'): - import logging - import os - source_file = os.path.split(source_file)[-1] - log_format = '[%(asctime)s] %(module)s:%(lineno)s %(message)s' - logging.basicConfig(format=log_format) - log = logging.getLogger() - log.setLevel(logging.INFO) - file_handler = logging.FileHandler(f'{log_dir}/{source_file}.log') - file_handler.setFormatter(logging.Formatter(log_format)) - log.addHandler(file_handler) - return log diff --git a/modbus/pymodbus/__init__.py b/modbus/pymodbus/__init__.py deleted file mode 100644 index 8319957..0000000 --- a/modbus/pymodbus/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -''' -Pymodbus: Modbus Protocol Implementation ------------------------------------------ - -TwistedModbus is built on top of the code developed by: - - Copyright (c) 2001-2005 S.W.A.C. GmbH, Germany. - Copyright (c) 2001-2005 S.W.A.C. Bohemia s.r.o., Czech Republic. - Hynek Petrak - -Released under the the BSD license -''' - -import pymodbus.version as __version -__version__ = __version.version.short() -__author__ = 'Galen Collins' -__maintainer__ = 'dhoomakethu' - -#---------------------------------------------------------------------------# -# Block unhandled logging -#---------------------------------------------------------------------------# -import logging as __logging -try: - from logging import NullHandler as __null -except ImportError: - class __null(__logging.Handler): - def emit(self, record): - pass - -__logging.getLogger(__name__).addHandler(__null()) - diff --git a/modbus/pymodbus/bit_read_message.py b/modbus/pymodbus/bit_read_message.py deleted file mode 100644 index 0143492..0000000 --- a/modbus/pymodbus/bit_read_message.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Bit Reading Request/Response messages --------------------------------------- - -""" -import struct -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.utilities import pack_bitstring, unpack_bitstring -from pymodbus.compat import byte2int - - -class ReadBitsRequestBase(ModbusRequest): - ''' Base class for Messages Requesting bit values ''' - - _rtu_frame_size = 8 - - def __init__(self, address, count, **kwargs): - ''' Initializes the read request data - - :param address: The start address to read from - :param count: The number of bits after 'address' to read - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - self.count = count - - def encode(self): - ''' Encodes a request pdu - - :returns: The encoded pdu - ''' - return struct.pack('>HH', self.address, self.count) - - def decode(self, data): - ''' Decodes a request pdu - - :param data: The packet data to decode - ''' - self.address, self.count = struct.unpack('>HH', data) - - def get_response_pdu_size(self): - """ - 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): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "ReadBitRequest(%d,%d)" % (self.address, self.count) - - -class ReadBitsResponseBase(ModbusResponse): - ''' Base class for Messages responding to bit-reading values ''' - - _rtu_byte_count_pos = 2 - - def __init__(self, values, **kwargs): - ''' Initializes a new instance - - :param values: The requested values to be returned - ''' - ModbusResponse.__init__(self, **kwargs) - self.bits = values or [] - - def encode(self): - ''' Encodes 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): - ''' Decodes response pdu - - :param data: The packet data to decode - ''' - self.byte_count = byte2int(data[0]) - self.bits = unpack_bitstring(data[1:]) - - def setBit(self, address, value=1): - ''' Helper function to set the specified bit - - :param address: The bit to set - :param value: The value to set the bit to - ''' - self.bits[address] = (value != 0) - - def resetBit(self, address): - ''' Helper function to set the specified bit to 0 - - :param address: The bit to reset - ''' - self.setBit(address, 0) - - def getBit(self, address): - ''' Helper function to 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): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "%s(%d)" % (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, **kwargs): - ''' Initializes a new instance - - :param address: The address to start reading from - :param count: The number of bits to read - ''' - ReadBitsRequestBase.__init__(self, address, count, **kwargs) - - def execute(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: The initializes response message, exception message otherwise - ''' - 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 = context.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. - ''' - function_code = 1 - - def __init__(self, values=None, **kwargs): - ''' Intializes a new instance - - :param values: The request values to respond with - ''' - ReadBitsResponseBase.__init__(self, values, **kwargs) - - -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, **kwargs): - ''' Intializes a new instance - - :param address: The address to start reading from - :param count: The number of bits to read - ''' - ReadBitsRequestBase.__init__(self, address, count, **kwargs) - - def execute(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: The initializes response message, exception message otherwise - ''' - 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 = context.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. - ''' - function_code = 2 - - def __init__(self, values=None, **kwargs): - ''' Intializes a new instance - - :param values: The request values to respond with - ''' - ReadBitsResponseBase.__init__(self, values, **kwargs) - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "ReadCoilsRequest", "ReadCoilsResponse", - "ReadDiscreteInputsRequest", "ReadDiscreteInputsResponse", -] diff --git a/modbus/pymodbus/bit_write_message.py b/modbus/pymodbus/bit_write_message.py deleted file mode 100644 index 2dfb618..0000000 --- a/modbus/pymodbus/bit_write_message.py +++ /dev/null @@ -1,270 +0,0 @@ -""" -Bit Writing Request/Response ------------------------------- - -TODO write mask request/response -""" -import struct -from pymodbus.constants import ModbusStatus -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.pdu import ModbusExceptions as merror -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(ModbusRequest): - ''' - 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, **kwargs): - ''' Initializes a new instance - - :param address: The variable address to write - :param value: The value to write at address - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - self.value = bool(value) - - def encode(self): - ''' Encodes 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): - ''' Decodes a write coil request - - :param data: The packet data to decode - ''' - self.address, value = struct.unpack('>HH', data) - self.value = (value == ModbusStatus.On) - - def execute(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) - - context.setValues(self.function_code, self.address, [self.value]) - values = context.getValues(self.function_code, self.address, 1) - return WriteSingleCoilResponse(self.address, values[0]) - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Output Address (2 byte) + Output Value (2 Bytes) - :return: - """ - return 1 + 2 + 2 - - def __str__(self): - ''' Returns a string representation of the instance - - :return: A string representation of the instance - ''' - return "WriteCoilRequest(%d, %s) => " % (self.address, self.value) - - -class WriteSingleCoilResponse(ModbusResponse): - ''' - 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, **kwargs): - ''' Initializes a new instance - - :param address: The variable address written to - :param value: The value written at address - ''' - ModbusResponse.__init__(self, **kwargs) - self.address = address - self.value = value - - def encode(self): - ''' Encodes 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): - ''' Decodes 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): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "WriteCoilResponse(%d) => %d" % (self.address, self.value) - - -class WriteMultipleCoilsRequest(ModbusRequest): - ''' - "This function code is used to force each coil in a 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=None, values=None, **kwargs): - ''' Initializes a new instance - - :param address: The starting request address - :param values: The values to write - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - if not values: values = [] - elif not hasattr(values, '__iter__'): values = [values] - self.values = values - self.byte_count = (len(self.values) + 7) // 8 - - def encode(self): - ''' Encodes 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): - ''' Decodes 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] - - def execute(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) - - context.setValues(self.function_code, self.address, self.values) - return WriteMultipleCoilsResponse(self.address, count) - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - params = (self.address, len(self.values)) - return "WriteNCoilRequest (%d) => %d " % params - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Output Address (2 byte) + Quantity of Outputs (2 Bytes) - :return: - """ - return 1 + 2 + 2 - - -class WriteMultipleCoilsResponse(ModbusResponse): - ''' - 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, **kwargs): - ''' Initializes a new instance - - :param address: The starting variable address written to - :param count: The number of values written - ''' - ModbusResponse.__init__(self, **kwargs) - self.address = address - self.count = count - - def encode(self): - ''' Encodes write coils response - - :returns: The byte encoded message - ''' - return struct.pack('>HH', self.address, self.count) - - def decode(self, data): - ''' Decodes a write coils response - - :param data: The packet data to decode - ''' - self.address, self.count = struct.unpack('>HH', data) - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "WriteNCoilResponse(%d, %d)" % (self.address, self.count) - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "WriteSingleCoilRequest", "WriteSingleCoilResponse", - "WriteMultipleCoilsRequest", "WriteMultipleCoilsResponse", -] diff --git a/modbus/pymodbus/client/__init__.py b/modbus/pymodbus/client/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/modbus/pymodbus/client/asynchronous/__init__.py b/modbus/pymodbus/client/asynchronous/__init__.py deleted file mode 100644 index b7d084d..0000000 --- a/modbus/pymodbus/client/asynchronous/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Async Modbus Client implementation based on Twisted, tornado and asyncio ------------------------------------------------------------------------- - -Example run:: - - from pymodbus.client.asynchronous import schedulers - - # Import The clients - - from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as Client - from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as Client - from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as Client - - # For tornado based asynchronous client use - event_loop, future = Client(schedulers.IO_LOOP, port=5020) - - # For twisted based asynchronous client use - event_loop, future = Client(schedulers.REACTOR, port=5020) - - # For asyncio based asynchronous client use - event_loop, client = Client(schedulers.ASYNC_IO, port=5020) - - # Here event_loop is a thread which would control the backend and future is - # a Future/deffered object which would be used to - # add call backs to run asynchronously. - - # The Actual client could be accessed with future.result() with Tornado - # and future.result when using twisted - - # For asyncio the actual client is returned and event loop is asyncio loop - -""" -from pymodbus.compat import is_installed - -installed = is_installed('twisted') -if installed: - # Import deprecated async client only if twisted is installed #338 - from pymodbus.client.asynchronous.deprecated.asynchronous import * - import logging - logger = logging.getLogger(__name__) - logger.warning("Importing deprecated clients. " - "Dependency Twisted is Installed") diff --git a/modbus/pymodbus/client/asynchronous/async_io/__init__.py b/modbus/pymodbus/client/asynchronous/async_io/__init__.py deleted file mode 100644 index ff6af5c..0000000 --- a/modbus/pymodbus/client/asynchronous/async_io/__init__.py +++ /dev/null @@ -1,890 +0,0 @@ -""" -Asynchronous framework adapter for asyncio. -""" -import socket -import asyncio -import functools -import ssl -from pymodbus.exceptions import ConnectionException -from pymodbus.client.asynchronous.mixins import AsyncModbusClientMixin -from pymodbus.utilities import hexlify_packets -from pymodbus.transaction import FifoTransactionManager -import logging - -_logger = logging.getLogger(__name__) - -DGRAM_TYPE = socket.SocketKind.SOCK_DGRAM - - -class BaseModbusAsyncClientProtocol(AsyncModbusClientMixin): - """ - Asyncio specific implementation of asynchronous modbus client protocol. - """ - - #: Factory that created this instance. - factory = None - transport = None - - async def execute(self, request=None): - """ - Executes requests asynchronously - :param request: - :return: - """ - req = self._execute(request) - resp = await asyncio.wait_for(req, timeout=self._timeout) - return resp - - def connection_made(self, transport): - """ - Called when a connection is made. - - The transport argument is the transport representing the connection. - :param transport: - :return: - """ - self.transport = transport - self._connectionMade() - - if self.factory: - self.factory.protocol_made_connection(self) - - def connection_lost(self, reason): - """ - Called when the connection is lost or closed. - - The argument is either an exception object or None - :param reason: - :return: - """ - self.transport = None - self._connectionLost(reason) - - if self.factory: - self.factory.protocol_lost_connection(self) - - def data_received(self, data): - """ - Called when some data is received. - data is a non-empty bytes object containing the incoming data. - :param data: - :return: - """ - self._dataReceived(data) - - def create_future(self): - """ - Helper function to create asyncio Future object - :return: - """ - return asyncio.Future() - - def resolve_future(self, f, result): - """ - Resolves the completed future and sets the result - :param f: - :param result: - :return: - """ - if not f.done(): - f.set_result(result) - - def raise_future(self, f, exc): - """ - Sets exception of a future if not done - :param f: - :param exc: - :return: - """ - if not f.done(): - f.set_exception(exc) - - def _connectionMade(self): - """ - Called upon a successful client connection. - """ - _logger.debug("Client connected to modbus server") - self._connected = True - - def _connectionLost(self, reason): - """ - Called upon a client disconnect - - :param reason: The reason for the disconnect - """ - _logger.debug( - "Client disconnected from modbus server: %s" % reason) - self._connected = False - for tid in list(self.transaction): - self.raise_future(self.transaction.getTransaction(tid), - ConnectionException( - 'Connection lost during request')) - - @property - def connected(self): - """ - Return connection status. - """ - return self._connected - - def write_transport(self, packet): - return self.transport.write(packet) - - def _execute(self, request, **kwargs): - """ - Starts the producer to send the next request to - consumer.write(Frame(request)) - """ - request.transaction_id = self.transaction.getNextTID() - packet = self.framer.buildPacket(request) - _logger.debug("send: " + hexlify_packets(packet)) - self.write_transport(packet) - return self._buildResponse(request.transaction_id) - - def _dataReceived(self, data): - ''' Get response, check for valid message, decode result - - :param data: The data returned from the server - ''' - _logger.debug("recv: " + hexlify_packets(data)) - unit = self.framer.decode_data(data).get("unit", 0) - self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) - - def _handleResponse(self, reply, **kwargs): - """ - Handle the processed response and link to correct deferred - - :param reply: The reply to process - """ - if reply is not None: - tid = reply.transaction_id - handler = self.transaction.getTransaction(tid) - if handler: - self.resolve_future(handler, reply) - else: - _logger.debug("Unrequested message: " + str(reply)) - - def _buildResponse(self, tid): - """ - Helper method to return a deferred response - for the current request. - - :param tid: The transaction identifier for this response - :returns: A defer linked to the latest request - """ - f = self.create_future() - if not self._connected: - self.raise_future(f, ConnectionException( - 'Client is not connected')) - else: - self.transaction.addTransaction(f, tid) - return f - - def close(self): - self.transport.close() - self._connected = False - - -class ModbusClientProtocol(BaseModbusAsyncClientProtocol, asyncio.Protocol): - """ - Asyncio specific implementation of asynchronous modbus client protocol. - """ - - #: Factory that created this instance. - factory = None - transport = None - - def data_received(self, data): - """ - Called when some data is received. - data is a non-empty bytes object containing the incoming data. - :param data: - :return: - """ - self._dataReceived(data) - - -class ModbusUdpClientProtocol(BaseModbusAsyncClientProtocol, - asyncio.DatagramProtocol): - """ - Asyncio specific implementation of asynchronous modbus udp client protocol. - """ - - #: Factory that created this instance. - factory = None - - def __init__(self, host=None, port=0, **kwargs): - self.host = host - self.port = port - super(self.__class__, self).__init__(**kwargs) - - def datagram_received(self, data, addr): - self._dataReceived(data) - - def write_transport(self, packet): - return self.transport.sendto(packet) - - -class ReconnectingAsyncioModbusTcpClient(object): - """ - Client to connect to modbus device repeatedly over TCP/IP." - """ - #: Minimum delay in milli seconds before reconnect is attempted. - DELAY_MIN_MS = 100 - #: Maximum delay in milli seconds before reconnect is attempted. - DELAY_MAX_MS = 1000 * 60 * 5 - - def __init__(self, protocol_class=None, loop=None, **kwargs): - """ - Initialize ReconnectingAsyncioModbusTcpClient - :param protocol_class: Protocol used to talk to modbus device. - :param loop: Event loop to use - """ - #: Protocol used to talk to modbus device. - self.protocol_class = protocol_class or ModbusClientProtocol - #: Current protocol instance. - self.protocol = None - #: Event loop to use. - self.loop = loop or asyncio.get_event_loop() - self.host = None - self.port = 0 - self.connected = False - #: Reconnect delay in milli seconds. - self.delay_ms = self.DELAY_MIN_MS - self._proto_args = kwargs - - def reset_delay(self): - """ - Resets wait before next reconnect to minimal period. - """ - self.delay_ms = self.DELAY_MIN_MS - - @asyncio.coroutine - def start(self, host, port=502): - """ - Initiates connection to start client - :param host: - :param port: - :return: - """ - # force reconnect if required: - self.stop() - - _logger.debug('Connecting to %s:%s.' % (host, port)) - self.host = host - self.port = port - yield from self._connect() - - def stop(self): - """ - Stops client - :return: - """ - # prevent reconnect: - self.host = None - - if self.connected: - if self.protocol: - if self.protocol.transport: - self.protocol.transport.close() - - def _create_protocol(self): - """ - Factory function to create initialized protocol instance. - """ - protocol = self.protocol_class(**self._proto_args) - protocol.factory = self - return protocol - - @asyncio.coroutine - def _connect(self): - _logger.debug('Connecting.') - try: - yield from self.loop.create_connection(self._create_protocol, - self.host, - self.port) - except Exception as ex: - _logger.warning('Failed to connect: %s' % ex) - asyncio.ensure_future(self._reconnect(), loop=self.loop) - else: - _logger.info('Connected to %s:%s.' % (self.host, self.port)) - self.reset_delay() - - def protocol_made_connection(self, protocol): - """ - Protocol notification of successful connection. - """ - _logger.info('Protocol made connection.') - if not self.connected: - self.connected = True - self.protocol = protocol - else: - _logger.error('Factory protocol connect ' - 'callback called while connected.') - - def protocol_lost_connection(self, protocol): - """ - Protocol notification of lost connection. - """ - if self.connected: - _logger.info('Protocol lost connection.') - if protocol is not self.protocol: - _logger.error('Factory protocol callback called ' - 'from unexpected protocol instance.') - - self.connected = False - self.protocol = None - if self.host: - asyncio.ensure_future(self._reconnect(), loop=self.loop) - else: - _logger.error('Factory protocol disconnect callback called while not connected.') - - @asyncio.coroutine - def _reconnect(self): - _logger.debug('Waiting %d ms before next ' - 'connection attempt.' % self.delay_ms) - yield from asyncio.sleep(self.delay_ms / 1000) - self.delay_ms = min(2 * self.delay_ms, self.DELAY_MAX_MS) - yield from self._connect() - - -class AsyncioModbusTcpClient(object): - """Client to connect to modbus device over TCP/IP.""" - - def __init__(self, host=None, port=502, protocol_class=None, loop=None, **kwargs): - """ - Initializes Asyncio Modbus Tcp Client - :param host: Host IP address - :param port: Port to connect - :param protocol_class: Protocol used to talk to modbus device. - :param loop: Asyncio Event loop - """ - #: Protocol used to talk to modbus device. - self.protocol_class = protocol_class or ModbusClientProtocol - #: Current protocol instance. - self.protocol = None - #: Event loop to use. - self.loop = loop or asyncio.get_event_loop() - - self.host = host - self.port = port - - self.connected = False - self._proto_args = kwargs - - def stop(self): - """ - Stops the client - :return: - """ - if self.connected: - if self.protocol: - if self.protocol.transport: - self.protocol.transport.close() - - def _create_protocol(self): - """ - Factory function to create initialized protocol instance. - """ - protocol = self.protocol_class(**self._proto_args) - protocol.factory = self - return protocol - - @asyncio.coroutine - def connect(self): - """ - Connect and start Async client - :return: - """ - _logger.debug('Connecting.') - try: - yield from self.loop.create_connection(self._create_protocol, - self.host, - self.port) - _logger.info('Connected to %s:%s.' % (self.host, self.port)) - except Exception as ex: - _logger.warning('Failed to connect: %s' % ex) - # asyncio.asynchronous(self._reconnect(), loop=self.loop) - - def protocol_made_connection(self, protocol): - """ - Protocol notification of successful connection. - """ - _logger.info('Protocol made connection.') - if not self.connected: - self.connected = True - self.protocol = protocol - else: - _logger.error('Factory protocol connect ' - 'callback called while connected.') - - def protocol_lost_connection(self, protocol): - """ - Protocol notification of lost connection. - """ - if self.connected: - _logger.info('Protocol lost connection.') - if protocol is not self.protocol: - _logger.error('Factory protocol callback called' - ' from unexpected protocol instance.') - - self.connected = False - self.protocol = None - # if self.host: - # asyncio.asynchronous(self._reconnect(), loop=self.loop) - else: - _logger.error('Factory protocol disconnect' - ' callback called while not connected.') - - -class ReconnectingAsyncioModbusTlsClient(ReconnectingAsyncioModbusTcpClient): - """ - Client to connect to modbus device repeatedly over TLS." - """ - def __init__(self, protocol_class=None, loop=None, framer=None, **kwargs): - """ - Initialize ReconnectingAsyncioModbusTcpClient - :param protocol_class: Protocol used to talk to modbus device. - :param loop: Event loop to use - """ - self.framer = framer - ReconnectingAsyncioModbusTcpClient.__init__(self, protocol_class, loop, **kwargs) - - @asyncio.coroutine - def start(self, host, port=802, sslctx=None, server_hostname=None): - """ - Initiates connection to start client - :param host: - :param port: - :param sslctx: - :param server_hostname: - :return: - """ - self.sslctx = sslctx - if self.sslctx is None: - self.sslctx = ssl.create_default_context() - # According to MODBUS/TCP Security Protocol Specification, it is - # TLSv2 at least - self.sslctx.options |= ssl.OP_NO_TLSv1_1 - self.sslctx.options |= ssl.OP_NO_TLSv1 - self.sslctx.options |= ssl.OP_NO_SSLv3 - self.sslctx.options |= ssl.OP_NO_SSLv2 - self.server_hostname = server_hostname - yield from ReconnectingAsyncioModbusTcpClient.start(self, host, port) - - @asyncio.coroutine - def _connect(self): - _logger.debug('Connecting.') - try: - yield from self.loop.create_connection(self._create_protocol, - self.host, - self.port, - ssl=self.sslctx, - server_hostname=self.server_hostname) - except Exception as ex: - _logger.warning('Failed to connect: %s' % ex) - asyncio.ensure_future(self._reconnect(), loop=self.loop) - else: - _logger.info('Connected to %s:%s.' % (self.host, self.port)) - self.reset_delay() - - def _create_protocol(self): - """ - Factory function to create initialized protocol instance. - """ - protocol = self.protocol_class(framer=self.framer, **self._proto_args) - protocol.transaction = FifoTransactionManager(self) - protocol.factory = self - return protocol - -class ReconnectingAsyncioModbusUdpClient(object): - """ - Client to connect to modbus device repeatedly over UDP. - """ - - #: Reconnect delay in milli seconds. - delay_ms = 0 - - #: Maximum delay in milli seconds before reconnect is attempted. - DELAY_MAX_MS = 1000 * 60 * 5 - - def __init__(self, protocol_class=None, loop=None, **kwargs): - """ - Initializes ReconnectingAsyncioModbusUdpClient - :param protocol_class: Protocol used to talk to modbus device. - :param loop: Asyncio Event loop - """ - #: Protocol used to talk to modbus device. - self.protocol_class = protocol_class or ModbusUdpClientProtocol - #: Current protocol instance. - self.protocol = None - #: Event loop to use. - self.loop = loop or asyncio.get_event_loop() - - self.host = None - self.port = 0 - - self.connected = False - self._proto_args = kwargs - self.reset_delay() - - def reset_delay(self): - """ - Resets wait before next reconnect to minimal period. - """ - self.delay_ms = 100 - - @asyncio.coroutine - def start(self, host, port=502): - """ - Start reconnecting asynchronous udp client - :param host: Host IP to connect - :param port: Host port to connect - :return: - """ - # force reconnect if required: - self.stop() - - _logger.debug('Connecting to %s:%s.' % (host, port)) - - # getaddrinfo returns a list of tuples - # - [(family, type, proto, canonname, sockaddr),] - # We want sockaddr which is a (ip, port) tuple - # udp needs ip addresses, not hostnames - addrinfo = yield from self.loop.getaddrinfo(host, - port, - type=DGRAM_TYPE) - self.host, self.port = addrinfo[0][-1] - - yield from self._connect() - - def stop(self): - """ - Stops connection and prevents reconnect - :return: - """ - # prevent reconnect: - self.host = None - - if self.connected: - if self.protocol: - if self.protocol.transport: - self.protocol.transport.close() - - def _create_protocol(self, host=None, port=0): - """ - Factory function to create initialized protocol instance. - """ - protocol = self.protocol_class(**self._proto_args) - protocol.host = host - protocol.port = port - protocol.factory = self - return protocol - - @asyncio.coroutine - def _connect(self): - _logger.debug('Connecting.') - try: - yield from self.loop.create_datagram_endpoint( - functools.partial(self._create_protocol, - host=self.host, - port=self.port), - remote_addr=(self.host, self.port) - ) - _logger.info('Connected to %s:%s.' % (self.host, self.port)) - except Exception as ex: - _logger.warning('Failed to connect: %s' % ex) - asyncio.ensure_future(self._reconnect(), loop=self.loop) - - def protocol_made_connection(self, protocol): - """ - Protocol notification of successful connection. - """ - _logger.info('Protocol made connection.') - if not self.connected: - self.connected = True - self.protocol = protocol - else: - _logger.error('Factory protocol connect callback ' - 'called while connected.') - - def protocol_lost_connection(self, protocol): - """ - Protocol notification of lost connection. - """ - if self.connected: - _logger.info('Protocol lost connection.') - if protocol is not self.protocol: - _logger.error('Factory protocol callback called ' - 'from unexpected protocol instance.') - - self.connected = False - self.protocol = None - if self.host: - asyncio.ensure_future(self._reconnect(), loop=self.loop) - else: - _logger.error('Factory protocol disconnect ' - 'callback called while not connected.') - - @asyncio.coroutine - def _reconnect(self): - _logger.debug('Waiting %d ms before next ' - 'connection attempt.' % self.delay_ms) - yield from asyncio.sleep(self.delay_ms / 1000) - self.delay_ms = min(2 * self.delay_ms, self.DELAY_MAX_MS) - yield from self._connect() - - -class AsyncioModbusUdpClient(object): - """ - Client to connect to modbus device over UDP. - """ - - def __init__(self, host=None, port=502, protocol_class=None, loop=None, **kwargs): - """ - Initializes Asyncio Modbus UDP Client - :param host: Host IP address - :param port: Port to connect - :param protocol_class: Protocol used to talk to modbus device. - :param loop: Asyncio Event loop - """ - #: Protocol used to talk to modbus device. - self.protocol_class = protocol_class or ModbusUdpClientProtocol - #: Current protocol instance. - self.protocol = None - #: Event loop to use. - self.loop = loop or asyncio.get_event_loop() - - self.host = host - self.port = port - - self.connected = False - self._proto_args = kwargs - - def stop(self): - """ - Stops connection - :return: - """ - # prevent reconnect: - # self.host = None - - if self.connected: - if self.protocol: - if self.protocol.transport: - self.protocol.transport.close() - - def _create_protocol(self, host=None, port=0): - """ - Factory function to create initialized protocol instance. - """ - protocol = self.protocol_class(**self._proto_args) - protocol.host = host - protocol.port = port - protocol.factory = self - return protocol - - @asyncio.coroutine - def connect(self): - _logger.debug('Connecting.') - try: - addrinfo = yield from self.loop.getaddrinfo( - self.host, - self.port, - type=DGRAM_TYPE) - _host, _port = addrinfo[0][-1] - yield from self.loop.create_datagram_endpoint( - functools.partial(self._create_protocol, - host=_host, port=_port), - remote_addr=(self.host, self.port) - ) - _logger.info('Connected to %s:%s.' % (self.host, self.port)) - except Exception as ex: - _logger.warning('Failed to connect: %s' % ex) - # asyncio.asynchronous(self._reconnect(), loop=self.loop) - - def protocol_made_connection(self, protocol): - """ - Protocol notification of successful connection. - """ - _logger.info('Protocol made connection.') - if not self.connected: - self.connected = True - self.protocol = protocol - else: - _logger.error('Factory protocol connect ' - 'callback called while connected.') - - def protocol_lost_connection(self, protocol): - """ - Protocol notification of lost connection. - """ - if self.connected: - _logger.info('Protocol lost connection.') - if protocol is not self.protocol: - _logger.error('Factory protocol callback ' - 'called from unexpected protocol instance.') - - self.connected = False - self.protocol = None - # if self.host: - # asyncio.asynchronous(self._reconnect(), loop=self.loop) - else: - _logger.error('Factory protocol disconnect ' - 'callback called while not connected.') - - -class AsyncioModbusSerialClient(object): - """ - Client to connect to modbus device over serial. - """ - transport = None - framer = None - - def __init__(self, port, protocol_class=None, framer=None, loop=None, - baudrate=9600, bytesize=8, parity='N', stopbits=1, **serial_kwargs): - """ - Initializes Asyncio Modbus Serial Client - :param port: Port to connect - :param protocol_class: Protocol used to talk to modbus device. - :param framer: Framer to use - :param loop: Asyncio Event loop - """ - #: Protocol used to talk to modbus device. - self.protocol_class = protocol_class or ModbusClientProtocol - #: Current protocol instance. - self.protocol = None - #: Event loop to use. - self.loop = loop or asyncio.get_event_loop() - self.port = port - self.baudrate = baudrate - self.bytesize = bytesize - self.parity = parity - self.stopbits = stopbits - self.framer = framer - self._extra_serial_kwargs = serial_kwargs - self._connected_event = asyncio.Event() - - def stop(self): - """ - Stops connection - :return: - """ - if self._connected: - if self.protocol: - if self.protocol.transport: - self.protocol.transport.close() - - def _create_protocol(self): - protocol = self.protocol_class(framer=self.framer) - protocol.factory = self - return protocol - - @property - def _connected(self): - return self._connected_event.is_set() - - @asyncio.coroutine - def connect(self): - """ - Connect Async client - :return: - """ - _logger.debug('Connecting.') - try: - from serial_asyncio import create_serial_connection - - yield from create_serial_connection( - self.loop, self._create_protocol, self.port, baudrate=self.baudrate, - bytesize=self.bytesize, stopbits=self.stopbits, parity=self.parity, **self._extra_serial_kwargs - ) - yield from self._connected_event.wait() - _logger.info('Connected to %s', self.port) - except Exception as ex: - _logger.warning('Failed to connect: %s', ex) - - def protocol_made_connection(self, protocol): - """ - Protocol notification of successful connection. - """ - _logger.info('Protocol made connection.') - if not self._connected: - self._connected_event.set() - self.protocol = protocol - else: - _logger.error('Factory protocol connect ' - 'callback called while connected.') - - def protocol_lost_connection(self, protocol): - """ - Protocol notification of lost connection. - """ - if self._connected: - _logger.info('Protocol lost connection.') - if protocol is not self.protocol: - _logger.error('Factory protocol callback called' - ' from unexpected protocol instance.') - - self._connected_event.clear() - self.protocol = None - # if self.host: - # asyncio.asynchronous(self._reconnect(), loop=self.loop) - else: - _logger.error('Factory protocol disconnect callback ' - 'called while not connected.') - - -@asyncio.coroutine -def init_tcp_client(proto_cls, loop, host, port, **kwargs): - """ - Helper function to initialize tcp client - :param proto_cls: - :param loop: - :param host: - :param port: - :param kwargs: - :return: - """ - client = ReconnectingAsyncioModbusTcpClient(protocol_class=proto_cls, - loop=loop, **kwargs) - yield from client.start(host, port) - return client - - -@asyncio.coroutine -def init_tls_client(proto_cls, loop, host, port, sslctx=None, - server_hostname=None, framer=None, **kwargs): - """ - Helper function to initialize tcp client - :param proto_cls: - :param loop: - :param host: - :param port: - :param sslctx: - :param server_hostname: - :param framer: - :param kwargs: - :return: - """ - client = ReconnectingAsyncioModbusTlsClient(protocol_class=proto_cls, - loop=loop, framer=framer, - **kwargs) - yield from client.start(host, port, sslctx, server_hostname) - return client - - -@asyncio.coroutine -def init_udp_client(proto_cls, loop, host, port, **kwargs): - """ - Helper function to initialize UDP client - :param proto_cls: - :param loop: - :param host: - :param port: - :param kwargs: - :return: - """ - client = ReconnectingAsyncioModbusUdpClient(protocol_class=proto_cls, - loop=loop, **kwargs) - yield from client.start(host, port) - return client diff --git a/modbus/pymodbus/client/asynchronous/deprecated/__init__.py b/modbus/pymodbus/client/asynchronous/deprecated/__init__.py deleted file mode 100644 index d891071..0000000 --- a/modbus/pymodbus/client/asynchronous/deprecated/__init__.py +++ /dev/null @@ -1,47 +0,0 @@ -import warnings -warnings.simplefilter('always', DeprecationWarning) - -WARNING = """ -Usage of '{}' is deprecated from 2.0.0 and will be removed in future releases. -Use the new Async Modbus Client implementation based on Twisted, tornado -and asyncio ------------------------------------------------------------------------- - -Example run:: - - from pymodbus.client.asynchronous import schedulers - - # Import The clients - - from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient as Client - from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as Client - from pymodbus.client.asynchronous.udp import AsyncModbusUDPClient as Client - - # For tornado based asynchronous client use - event_loop, future = Client(schedulers.IO_LOOP, port=5020) - - # For twisted based asynchronous client use - event_loop, deferred = Client(schedulers.REACTOR, port=5020) - - # For asyncio based asynchronous client use - event_loop, client = Client(schedulers.ASYNC_IO, port=5020) - - # Here event_loop is a thread which would control the backend and future is - # a Future/deffered object which would be used to - # add call backs to run asynchronously. - - # The Actual client could be accessed with future.result() with Tornado - # and future.result when using twisted - - # For asyncio the actual client is returned and event loop is asyncio loop - -Refer: -https://pymodbus.readthedocs.io/en/dev/source/example/async_twisted_client.html -https://pymodbus.readthedocs.io/en/dev/source/example/async_tornado_client.html -https://pymodbus.readthedocs.io/en/dev/source/example/async_asyncio_client.html - -""" - - -def deprecated(name): # pragma: no cover - warnings.warn(WARNING.format(name), DeprecationWarning) diff --git a/modbus/pymodbus/client/asynchronous/deprecated/asynchronous.py b/modbus/pymodbus/client/asynchronous/deprecated/asynchronous.py deleted file mode 100644 index bd948ae..0000000 --- a/modbus/pymodbus/client/asynchronous/deprecated/asynchronous.py +++ /dev/null @@ -1,231 +0,0 @@ -""" -Implementation of a Modbus Client Using Twisted --------------------------------------------------- - -Example run:: - - from twisted.internet import reactor, protocol - from pymodbus.client.asynchronous import ModbusClientProtocol - - def printResult(result): - print "Result: %d" % result.bits[0] - - def process(client): - result = client.write_coil(1, True) - result.addCallback(printResult) - reactor.callLater(1, reactor.stop) - - defer = protocol.ClientCreator(reactor, ModbusClientProtocol - ).connectTCP("localhost", 502) - defer.addCallback(process) - -Another example:: - - from twisted.internet import reactor - from pymodbus.client.asynchronous import ModbusClientFactory - - def process(): - factory = reactor.connectTCP("localhost", 502, ModbusClientFactory()) - reactor.stop() - - if __name__ == "__main__": - reactor.callLater(1, process) - reactor.run() -""" -import logging -from pymodbus.factory import ClientDecoder -from pymodbus.exceptions import ConnectionException -from pymodbus.transaction import ModbusSocketFramer -from pymodbus.transaction import FifoTransactionManager -from pymodbus.transaction import DictTransactionManager -from pymodbus.client.common import ModbusClientMixin -from pymodbus.client.asynchronous.deprecated import deprecated -from twisted.internet import defer, protocol -from twisted.python.failure import Failure - - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Connected Client Protocols -# --------------------------------------------------------------------------- # -class ModbusClientProtocol(protocol.Protocol, ModbusClientMixin): # pragma: no cover - """ - This represents the base modbus client protocol. All the application - layer code is deferred to a higher level wrapper. - """ - - def __init__(self, framer=None, **kwargs): - """ Initializes the framer module - - :param framer: The framer to use for the protocol - """ - deprecated(self.__class__.__name__) - self._connected = False - self.framer = framer or ModbusSocketFramer(ClientDecoder()) - if isinstance(self.framer, type): - # Framer class not instance - self.framer = self.framer(ClientDecoder(), client=None) - if isinstance(self.framer, ModbusSocketFramer): - self.transaction = DictTransactionManager(self, **kwargs) - else: - self.transaction = FifoTransactionManager(self, **kwargs) - - def connectionMade(self): - """ Called upon a successful client connection. - """ - _logger.debug("Client connected to modbus server") - self._connected = True - - def connectionLost(self, reason): - """ Called upon a client disconnect - - :param reason: The reason for the disconnect - """ - _logger.debug("Client disconnected from modbus server: %s" % reason) - self._connected = False - for tid in list(self.transaction): - self.transaction.getTransaction(tid).errback(Failure( - ConnectionException('Connection lost during request'))) - - def dataReceived(self, data): - """ Get response, check for valid message, decode result - - :param data: The data returned from the server - """ - unit = self.framer.decode_data(data).get("uid", 0) - self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) - - def execute(self, request): - """ Starts the producer to send the next request to - consumer.write(Frame(request)) - """ - request.transaction_id = self.transaction.getNextTID() - packet = self.framer.buildPacket(request) - self.transport.write(packet) - return self._buildResponse(request.transaction_id) - - def _handleResponse(self, reply): - """ Handle the processed response and link to correct deferred - - :param reply: The reply to process - """ - if reply is not None: - tid = reply.transaction_id - handler = self.transaction.getTransaction(tid) - if handler: - handler.callback(reply) - else: - _logger.debug("Unrequested message: " + str(reply)) - - def _buildResponse(self, tid): - """ Helper method to return a deferred response - for the current request. - - :param tid: The transaction identifier for this response - :returns: A defer linked to the latest request - """ - if not self._connected: - return defer.fail(Failure( - ConnectionException('Client is not connected'))) - - d = defer.Deferred() - self.transaction.addTransaction(d, tid) - return d - - # ---------------------------------------------------------------------- # - # Extra Functions - # ---------------------------------------------------------------------- # - # if send_failed: - # if self.retry > 0: - # deferLater(clock, self.delay, send, message) - # self.retry -= 1 - - -# --------------------------------------------------------------------------- # -# Not Connected Client Protocol -# --------------------------------------------------------------------------- # -class ModbusUdpClientProtocol(protocol.DatagramProtocol, ModbusClientMixin): # pragma: no cover - """ - This represents the base modbus client protocol. All the application - layer code is deferred to a higher level wrapper. - """ - - def __init__(self, framer=None, **kwargs): - """ Initializes the framer module - - :param framer: The framer to use for the protocol - """ - deprecated(self.__class__.__name__) - self.framer = framer or ModbusSocketFramer(ClientDecoder()) - if isinstance(self.framer, ModbusSocketFramer): - self.transaction = DictTransactionManager(self, **kwargs) - else: self.transaction = FifoTransactionManager(self, **kwargs) - - def datagramReceived(self, data, params): - """ Get response, check for valid message, decode result - - :param data: The data returned from the server - :param params: The host parameters sending the datagram - """ - _logger.debug("Datagram from: %s:%d" % params) - unit = self.framer.decode_data(data).get("uid", 0) - self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) - - def execute(self, request): - """ Starts the producer to send the next request to - consumer.write(Frame(request)) - """ - request.transaction_id = self.transaction.getNextTID() - packet = self.framer.buildPacket(request) - self.transport.write(packet) - return self._buildResponse(request.transaction_id) - - def _handleResponse(self, reply): - """ Handle the processed response and link to correct deferred - - :param reply: The reply to process - """ - if reply is not None: - tid = reply.transaction_id - handler = self.transaction.getTransaction(tid) - if handler: - handler.callback(reply) - else: _logger.debug("Unrequested message: " + str(reply)) - - def _buildResponse(self, tid): - """ Helper method to return a deferred response - for the current request. - - :param tid: The transaction identifier for this response - :returns: A defer linked to the latest request - """ - d = defer.Deferred() - self.transaction.addTransaction(d, tid) - return d - - -# --------------------------------------------------------------------------- # -# Client Factories -# --------------------------------------------------------------------------- # -class ModbusClientFactory(protocol.ReconnectingClientFactory): # pragma: no cover - """ Simple client protocol factory """ - - protocol = ModbusClientProtocol - - def __init__(self): - deprecated(self.__class__.__name__) - protocol.ReconnectingClientFactory.__init__(self) - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = [ - "ModbusClientProtocol", "ModbusUdpClientProtocol", "ModbusClientFactory" -] diff --git a/modbus/pymodbus/client/asynchronous/factory/__init__.py b/modbus/pymodbus/client/asynchronous/factory/__init__.py deleted file mode 100644 index 5a456ce..0000000 --- a/modbus/pymodbus/client/asynchronous/factory/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from __future__ import absolute_import, unicode_literals \ No newline at end of file diff --git a/modbus/pymodbus/client/asynchronous/factory/serial.py b/modbus/pymodbus/client/asynchronous/factory/serial.py deleted file mode 100644 index 62b719c..0000000 --- a/modbus/pymodbus/client/asynchronous/factory/serial.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -Factory to create asynchronous serial clients based on twisted/tornado/asyncio -""" -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging -import time -from pymodbus.client.asynchronous import schedulers -from pymodbus.client.asynchronous.thread import EventLoopThread - -LOGGER = logging.getLogger(__name__) - - -def reactor_factory(port, framer, **kwargs): - """ - Factory to create twisted serial asynchronous client - :param port: Serial port - :param framer: Modbus Framer - :param kwargs: - :return: event_loop_thread and twisted serial client - """ - from twisted.internet import reactor - from twisted.internet.serialport import SerialPort - from twisted.internet.protocol import ClientFactory - from pymodbus.factory import ClientDecoder - - class SerialClientFactory(ClientFactory): - def __init__(self, framer, proto_cls): - ''' Remember things necessary for building a protocols ''' - self.proto_cls = proto_cls - self.framer = framer - - def buildProtocol(self): - ''' Create a protocol and start the reading cycle ''' - proto = self.proto_cls(self.framer) - proto.factory = self - return proto - - class SerialModbusClient(SerialPort): - - def __init__(self, framer, *args, **kwargs): - ''' Setup the client and start listening on the serial port - - :param factory: The factory to build clients with - ''' - self.decoder = ClientDecoder() - proto_cls = kwargs.pop("proto_cls", None) - proto = SerialClientFactory(framer, proto_cls).buildProtocol() - SerialPort.__init__(self, proto, *args, **kwargs) - - proto = EventLoopThread("reactor", reactor.run, reactor.stop, - installSignalHandlers=0) - ser_client = SerialModbusClient(framer, port, reactor, **kwargs) - - return proto, ser_client - - -def io_loop_factory(port=None, framer=None, **kwargs): - """ - Factory to create Tornado based asynchronous serial clients - :param port: Serial port - :param framer: Modbus Framer - :param kwargs: - :return: event_loop_thread and tornado future - """ - - from tornado.ioloop import IOLoop - from pymodbus.client.asynchronous.tornado import (AsyncModbusSerialClient as - Client) - - ioloop = IOLoop() - protocol = EventLoopThread("ioloop", ioloop.start, ioloop.stop) - protocol.start() - client = Client(port=port, framer=framer, ioloop=ioloop, **kwargs) - - future = client.connect() - - return protocol, future - - -def async_io_factory(port=None, framer=None, **kwargs): - """ - Factory to create asyncio based asynchronous serial clients - :param port: Serial port - :param framer: Modbus Framer - :param kwargs: Serial port options - :return: asyncio event loop and serial client - """ - import asyncio - from pymodbus.client.asynchronous.async_io import (ModbusClientProtocol, - AsyncioModbusSerialClient) - loop = kwargs.pop("loop", None) or asyncio.get_event_loop() - proto_cls = kwargs.pop("proto_cls", None) or ModbusClientProtocol - - try: - from serial_asyncio import create_serial_connection - except ImportError: - LOGGER.critical("pyserial-asyncio is not installed, " - "install with 'pip install pyserial-asyncio") - import sys - sys.exit(1) - - client = AsyncioModbusSerialClient(port, proto_cls, framer, loop, **kwargs) - coro = client.connect() - if loop.is_running(): - future = asyncio.run_coroutine_threadsafe(coro, loop=loop) - future.result() - else: - loop.run_until_complete(coro) - return loop, client - - -def get_factory(scheduler): - """ - Gets protocol factory based on the backend scheduler being used - :param scheduler: REACTOR/IO_LOOP/ASYNC_IO - :return: - """ - if scheduler == schedulers.REACTOR: - return reactor_factory - elif scheduler == schedulers.IO_LOOP: - return io_loop_factory - elif scheduler == schedulers.ASYNC_IO: - return async_io_factory - else: - LOGGER.warning("Allowed Schedulers: {}, {}, {}".format( - schedulers.REACTOR, schedulers.IO_LOOP, schedulers.ASYNC_IO - )) - raise Exception("Invalid Scheduler '{}'".format(scheduler)) diff --git a/modbus/pymodbus/client/asynchronous/factory/tcp.py b/modbus/pymodbus/client/asynchronous/factory/tcp.py deleted file mode 100644 index 2509cad..0000000 --- a/modbus/pymodbus/client/asynchronous/factory/tcp.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Factory to create asynchronous tcp clients based on twisted/tornado/asyncio -""" -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging - -from pymodbus.client.asynchronous import schedulers -from pymodbus.client.asynchronous.thread import EventLoopThread -from pymodbus.constants import Defaults - -LOGGER = logging.getLogger(__name__) - - -def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, - source_address=None, timeout=None, **kwargs): - """ - Factory to create twisted tcp asynchronous client - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: event_loop_thread and twisted_deferred - """ - from twisted.internet import reactor, protocol - from pymodbus.client.asynchronous.twisted import ModbusTcpClientProtocol - - deferred = protocol.ClientCreator( - reactor, ModbusTcpClientProtocol - ).connectTCP(host, port, timeout=timeout, bindAddress=source_address) - - callback = kwargs.get("callback") - errback = kwargs.get("errback") - - if callback: - deferred.addCallback(callback) - - if errback: - deferred.addErrback(errback) - - protocol = EventLoopThread("reactor", reactor.run, reactor.stop, - installSignalHandlers=0) - protocol.start() - - return protocol, deferred - - -def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, - source_address=None, timeout=None, **kwargs): - """ - Factory to create Tornado based asynchronous tcp clients - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: event_loop_thread and tornado future - """ - from tornado.ioloop import IOLoop - from pymodbus.client.asynchronous.tornado import AsyncModbusTCPClient as \ - Client - - ioloop = IOLoop() - protocol = EventLoopThread("ioloop", ioloop.start, ioloop.stop) - protocol.start() - - client = Client(host=host, port=port, framer=framer, - source_address=source_address, - timeout=timeout, ioloop=ioloop, **kwargs) - - future = client.connect() - - return protocol, future - - -def async_io_factory(host="127.0.0.1", port=Defaults.Port, **kwargs): - """ - Factory to create asyncio based asynchronous tcp clients - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: asyncio event loop and tcp client - """ - import asyncio - from pymodbus.client.asynchronous.async_io import init_tcp_client - loop = kwargs.pop("loop", None) or asyncio.new_event_loop() - proto_cls = kwargs.pop("proto_cls", None) - if not loop.is_running(): - asyncio.set_event_loop(loop) - cor = init_tcp_client(proto_cls, loop, host, port, **kwargs) - client = loop.run_until_complete(asyncio.gather(cor))[0] - else: - cor = init_tcp_client(proto_cls, loop, host, port, **kwargs) - future = asyncio.run_coroutine_threadsafe(cor, loop=loop) - client = future.result() - - return loop, client - - -def get_factory(scheduler): - """ - Gets protocol factory based on the backend scheduler being used - :param scheduler: REACTOR/IO_LOOP/ASYNC_IO - :return - """ - if scheduler == schedulers.REACTOR: - return reactor_factory - elif scheduler == schedulers.IO_LOOP: - return io_loop_factory - elif scheduler == schedulers.ASYNC_IO: - return async_io_factory - else: - LOGGER.warning("Allowed Schedulers: {}, {}, {}".format( - schedulers.REACTOR, schedulers.IO_LOOP, schedulers.ASYNC_IO - )) - raise Exception("Invalid Scheduler '{}'".format(scheduler)) diff --git a/modbus/pymodbus/client/asynchronous/factory/tls.py b/modbus/pymodbus/client/asynchronous/factory/tls.py deleted file mode 100644 index 652bec7..0000000 --- a/modbus/pymodbus/client/asynchronous/factory/tls.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -Factory to create asynchronous tls clients based on asyncio -""" -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging - -from pymodbus.client.asynchronous import schedulers -from pymodbus.client.asynchronous.thread import EventLoopThread -from pymodbus.constants import Defaults - -LOGGER = logging.getLogger(__name__) - -def async_io_factory(host="127.0.0.1", port=Defaults.TLSPort, sslctx=None, - server_hostname=None, framer=None, **kwargs): - """ - Factory to create asyncio based asynchronous tls clients - :param host: Host IP address - :param port: Port - :param sslctx: The SSLContext to use for TLS (default None and auto create) - :param server_hostname: Target server's name matched for certificate - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: asyncio event loop and tcp client - """ - import asyncio - from pymodbus.client.asynchronous.async_io import init_tls_client - loop = kwargs.pop("loop", None) or asyncio.new_event_loop() - proto_cls = kwargs.pop("proto_cls", None) - if not loop.is_running(): - asyncio.set_event_loop(loop) - cor = init_tls_client(proto_cls, loop, host, port, sslctx, server_hostname, - framer, **kwargs) - client = loop.run_until_complete(asyncio.gather(cor))[0] - else: - cor = init_tls_client(proto_cls, loop, host, port, sslctx, server_hostname, - framer, **kwargs) - future = asyncio.run_coroutine_threadsafe(cor, loop=loop) - client = future.result() - - return loop, client - - -def get_factory(scheduler): - """ - Gets protocol factory based on the backend scheduler being used - :param scheduler: ASYNC_IO - :return - """ - if scheduler == schedulers.ASYNC_IO: - return async_io_factory - else: - LOGGER.warning("Allowed Schedulers: {}".format( - schedulers.ASYNC_IO - )) - raise Exception("Invalid Scheduler '{}'".format(scheduler)) diff --git a/modbus/pymodbus/client/asynchronous/factory/udp.py b/modbus/pymodbus/client/asynchronous/factory/udp.py deleted file mode 100644 index 0745eb5..0000000 --- a/modbus/pymodbus/client/asynchronous/factory/udp.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging - -from pymodbus.client.asynchronous import schedulers -from pymodbus.client.asynchronous.thread import EventLoopThread -from pymodbus.constants import Defaults - -LOGGER = logging.getLogger(__name__) - - -def reactor_factory(host="127.0.0.1", port=Defaults.Port, framer=None, - source_address=None, timeout=None, **kwargs): - """ - Factory to create twisted udp asynchronous client - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: event_loop_thread and twisted_deferred - """ - raise NotImplementedError() - - -def io_loop_factory(host="127.0.0.1", port=Defaults.Port, framer=None, - source_address=None, timeout=None, **kwargs): - """ - Factory to create Tornado based asynchronous udp clients - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: event_loop_thread and tornado future - """ - from tornado.ioloop import IOLoop - from pymodbus.client.asynchronous.tornado import AsyncModbusUDPClient as \ - Client - - client = Client(host=host, port=port, framer=framer, - source_address=source_address, - timeout=timeout, **kwargs) - protocol = EventLoopThread("ioloop", IOLoop.current().start, - IOLoop.current().stop) - protocol.start() - future = client.connect() - - return protocol, future - - -def async_io_factory(host="127.0.0.1", port=Defaults.Port, **kwargs): - """ - Factory to create asyncio based asynchronous udp clients - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer - :param source_address: Bind address - :param timeout: Timeout in seconds - :param kwargs: - :return: asyncio event loop and udp client - """ - import asyncio - from pymodbus.client.asynchronous.async_io import init_udp_client - loop = kwargs.pop("loop", None) or asyncio.get_event_loop() - proto_cls = kwargs.pop("proto_cls", None) - cor = init_udp_client(proto_cls, loop, host, port, **kwargs) - if not loop.is_running(): - client = loop.run_until_complete(asyncio.gather(cor))[0] - else: - client = asyncio.run_coroutine_threadsafe(cor, loop=loop) - client = client.result() - return loop, client - - -def get_factory(scheduler): - """ - Gets protocol factory based on the backend scheduler being used - :param scheduler: REACTOR/IO_LOOP/ASYNC_IO - :return - """ - if scheduler == schedulers.REACTOR: - return reactor_factory - elif scheduler == schedulers.IO_LOOP: - return io_loop_factory - elif scheduler == schedulers.ASYNC_IO: - return async_io_factory - else: - LOGGER.warning("Allowed Schedulers: {}, {}, {}".format( - schedulers.REACTOR, schedulers.IO_LOOP, schedulers.ASYNC_IO - )) - raise Exception("Invalid Scheduler '{}'".format(scheduler)) - diff --git a/modbus/pymodbus/client/asynchronous/mixins.py b/modbus/pymodbus/client/asynchronous/mixins.py deleted file mode 100644 index f5704e4..0000000 --- a/modbus/pymodbus/client/asynchronous/mixins.py +++ /dev/null @@ -1,75 +0,0 @@ -import logging -from pymodbus.client.sync import BaseModbusClient -# from pymodbus.bit_read_message import * -# from pymodbus.bit_write_message import * -# from pymodbus.register_read_message import * -# from pymodbus.register_write_message import * -# from pymodbus.diag_message import * -# from pymodbus.file_message import * -# from pymodbus.other_message import * -from pymodbus.constants import Defaults - -from pymodbus.factory import ClientDecoder -from pymodbus.transaction import ModbusSocketFramer - - -LOGGER = logging.getLogger(__name__) - - -class BaseAsyncModbusClient(BaseModbusClient): - """ - This represents the base ModbusAsyncClient. - """ - - def __init__(self, framer=None, timeout=2, **kwargs): - """ Initializes the framer module - - :param framer: The framer to use for the protocol. Default: - ModbusSocketFramer - :type framer: pymodbus.transaction.ModbusSocketFramer - """ - self._connected = False - self._timeout = timeout - - super(BaseAsyncModbusClient, self).__init__( - framer or ModbusSocketFramer(ClientDecoder()), **kwargs - ) - - -class AsyncModbusClientMixin(BaseAsyncModbusClient): - """ - Async Modbus client mixing for UDP and TCP clients - """ - def __init__(self, host="127.0.0.1", port=Defaults.Port, framer=None, - source_address=None, timeout=None, **kwargs): - """ - Initializes a Modbus TCP/UDP asynchronous client - :param host: Host IP address - :param port: Port - :param framer: Framer to use - :param source_address: Specific to underlying client being used - :param timeout: Timeout in seconds - :param kwargs: Extra arguments - """ - super(AsyncModbusClientMixin, self).__init__(framer=framer, **kwargs) - self.host = host - self.port = port - self.source_address = source_address or ("", 0) - self._timeout = timeout if timeout is not None else Defaults.Timeout - - -class AsyncModbusSerialClientMixin(BaseAsyncModbusClient): - """ - Async Modbus Serial Client Mixing - """ - def __init__(self, framer=None, port=None, **kwargs): - """ - Initializes a Async Modbus Serial Client - :param framer: Modbus Framer - :param port: Serial port to use - :param kwargs: Extra arguments if any - """ - super(AsyncModbusSerialClientMixin, self).__init__(framer=framer) - self.port = port - self.serial_settings = kwargs - diff --git a/modbus/pymodbus/client/asynchronous/schedulers/__init__.py b/modbus/pymodbus/client/asynchronous/schedulers/__init__.py deleted file mode 100644 index 5917375..0000000 --- a/modbus/pymodbus/client/asynchronous/schedulers/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -""" -Backend schedulers to use with generic Async clients -""" -from __future__ import unicode_literals - - -REACTOR = "reactor" -IO_LOOP = "io_loop" -ASYNC_IO = "async_io" diff --git a/modbus/pymodbus/client/asynchronous/serial.py b/modbus/pymodbus/client/asynchronous/serial.py deleted file mode 100644 index 5d2e2e8..0000000 --- a/modbus/pymodbus/client/asynchronous/serial.py +++ /dev/null @@ -1,76 +0,0 @@ -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging -from pymodbus.client.asynchronous.factory.serial import get_factory -from pymodbus.transaction import ModbusRtuFramer, ModbusAsciiFramer, ModbusBinaryFramer, ModbusSocketFramer -from pymodbus.factory import ClientDecoder -from pymodbus.exceptions import ParameterException -from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION -from pymodbus.client.asynchronous.schedulers import ASYNC_IO - -logger = logging.getLogger(__name__) - - -class AsyncModbusSerialClient(object): - """ - Actual Async Serial Client to be used. - - To use do:: - - from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient - """ - @classmethod - def _framer(cls, method): - """ - Returns the requested framer - - :method: The serial framer to instantiate - :returns: The requested serial framer - """ - method = method.lower() - if method == 'ascii': - return ModbusAsciiFramer(ClientDecoder()) - elif method == 'rtu': - return ModbusRtuFramer(ClientDecoder()) - elif method == 'binary': - return ModbusBinaryFramer(ClientDecoder()) - elif method == 'socket': - return ModbusSocketFramer(ClientDecoder()) - - raise ParameterException("Invalid framer method requested") - - def __new__(cls, scheduler, method, port, **kwargs): - """ - Scheduler to use: - - reactor (Twisted) - - io_loop (Tornado) - - async_io (asyncio) - The methods to connect are:: - - - ascii - - rtu - - binary - : param scheduler: Backend to use - :param method: The method to use for connection - :param port: The serial port to attach to - :param stopbits: The number of stop bits to use - :param bytesize: The bytesize of the serial messages - :param parity: Which kind of parity to use - :param baudrate: The baud rate to use for the serial device - :param timeout: The timeout between serial requests (default 3s) - :param scheduler: - :param method: - :param port: - :param kwargs: - :return: - """ - if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) - and scheduler == ASYNC_IO): - logger.critical("ASYNCIO is supported only on python3") - import sys - sys.exit(1) - factory_class = get_factory(scheduler) - framer = cls._framer(method) - yieldable = factory_class(framer=framer, port=port, **kwargs) - return yieldable diff --git a/modbus/pymodbus/client/asynchronous/tcp.py b/modbus/pymodbus/client/asynchronous/tcp.py deleted file mode 100644 index 45b7cfd..0000000 --- a/modbus/pymodbus/client/asynchronous/tcp.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging -from pymodbus.client.asynchronous.factory.tcp import get_factory -from pymodbus.constants import Defaults -from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION -from pymodbus.client.asynchronous.schedulers import ASYNC_IO - -logger = logging.getLogger(__name__) - - -class AsyncModbusTCPClient(object): - """ - Actual Async Serial Client to be used. - - To use do:: - - from pymodbus.client.asynchronous.tcp import AsyncModbusTCPClient - """ - def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.Port, - framer=None, source_address=None, timeout=None, **kwargs): - """ - Scheduler to use: - - reactor (Twisted) - - io_loop (Tornado) - - async_io (asyncio) - :param scheduler: Backend to use - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer to use - :param source_address: source address specific to underlying backend - :param timeout: Time out in seconds - :param kwargs: Other extra args specific to Backend being used - :return: - """ - if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) - and scheduler == ASYNC_IO): - logger.critical("ASYNCIO is supported only on python3") - import sys - sys.exit(1) - factory_class = get_factory(scheduler) - yieldable = factory_class(host=host, port=port, framer=framer, - source_address=source_address, - timeout=timeout, **kwargs) - return yieldable - diff --git a/modbus/pymodbus/client/asynchronous/thread.py b/modbus/pymodbus/client/asynchronous/thread.py deleted file mode 100644 index 3b1aee5..0000000 --- a/modbus/pymodbus/client/asynchronous/thread.py +++ /dev/null @@ -1,54 +0,0 @@ -from __future__ import unicode_literals -from __future__ import absolute_import - -from threading import Thread - -import logging - -LOGGER = logging.getLogger(__name__) - - -class EventLoopThread(object): - """ - Event loop controlling the backend event loops (io_loop for tornado, - reactor for twisted and event_loop for Asyncio) - """ - def __init__(self, name, start, stop, *args, **kwargs): - """ - Initialize Event loop thread - :param name: Name of the event loop - :param start: Start method to start the backend event loop - :param stop: Stop method to stop the backend event loop - :param args: - :param kwargs: - """ - self._name = name - self._start_loop = start - self._stop_loop = stop - self._args = args - self._kwargs = kwargs - self._event_loop = Thread(name=self._name, target=self._start) - self._event_loop.daemon = True - - def _start(self): - """ - Starts the backend event loop - :return: - """ - self._start_loop(*self._args, **self._kwargs) - - def start(self): - """ - Starts the backend event loop - :return: - """ - LOGGER.info("Starting Event Loop: 'PyModbus_{}'".format(self._name)) - self._event_loop.start() - - def stop(self): - """ - Stops the backend event loop - :return: - """ - LOGGER.info("Stopping Event Loop: 'PyModbus_{}'".format(self._name)) - self._stop_loop() diff --git a/modbus/pymodbus/client/asynchronous/tls.py b/modbus/pymodbus/client/asynchronous/tls.py deleted file mode 100644 index d2412ff..0000000 --- a/modbus/pymodbus/client/asynchronous/tls.py +++ /dev/null @@ -1,52 +0,0 @@ -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging -from pymodbus.client.asynchronous.factory.tls import get_factory -from pymodbus.constants import Defaults -from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION -from pymodbus.client.asynchronous.schedulers import ASYNC_IO -from pymodbus.factory import ClientDecoder -from pymodbus.transaction import ModbusTlsFramer - -logger = logging.getLogger(__name__) - - -class AsyncModbusTLSClient(object): - """ - Actual Async TLS Client to be used. - - To use do:: - - from pymodbus.client.asynchronous.tls import AsyncModbusTLSClient - """ - def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.TLSPort, - framer=None, sslctx=None, server_hostname=None, - source_address=None, timeout=None, **kwargs): - """ - Scheduler to use: - - async_io (asyncio) - :param scheduler: Backend to use - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer to use - :param sslctx: The SSLContext to use for TLS (default None and auto create) - :param server_hostname: Target server's name matched for certificate - :param source_address: source address specific to underlying backend - :param timeout: Time out in seconds - :param kwargs: Other extra args specific to Backend being used - :return: - """ - if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) - and scheduler == ASYNC_IO): - logger.critical("ASYNCIO is supported only on python3") - import sys - sys.exit(1) - framer = framer or ModbusTlsFramer(ClientDecoder()) - factory_class = get_factory(scheduler) - yieldable = factory_class(host=host, port=port, sslctx=sslctx, - server_hostname=server_hostname, - framer=framer, source_address=source_address, - timeout=timeout, **kwargs) - return yieldable - diff --git a/modbus/pymodbus/client/asynchronous/tornado/__init__.py b/modbus/pymodbus/client/asynchronous/tornado/__init__.py deleted file mode 100644 index 8eb61f4..0000000 --- a/modbus/pymodbus/client/asynchronous/tornado/__init__.py +++ /dev/null @@ -1,498 +0,0 @@ -""" -Asynchronous framework adapter for tornado. -""" -from __future__ import unicode_literals - -import abc - -import logging - -import time -import socket -from serial import Serial -from tornado import gen -from tornado.concurrent import Future -from tornado.ioloop import IOLoop -from tornado.iostream import IOStream -from tornado.iostream import BaseIOStream - -from pymodbus.client.asynchronous.mixins import (AsyncModbusClientMixin, - AsyncModbusSerialClientMixin) - -from pymodbus.compat import byte2int -from pymodbus.exceptions import (ConnectionException, - ModbusIOException, - TimeOutException) -from pymodbus.utilities import (hexlify_packets, - ModbusTransactionState) -from pymodbus.constants import Defaults - - -LOGGER = logging.getLogger(__name__) - - -class BaseTornadoClient(AsyncModbusClientMixin): - """ - Base Tornado client - """ - stream = None - io_loop = None - - def __init__(self, *args, **kwargs): - """ - Initializes BaseTornadoClient. - ioloop to be passed as part of kwargs ('ioloop') - :param args: - :param kwargs: - """ - self.io_loop = kwargs.pop("ioloop", None) - super(BaseTornadoClient, self).__init__(*args, **kwargs) - - @abc.abstractmethod - def get_socket(self): - """ - return instance of the socket to connect to - """ - - @gen.coroutine - def connect(self): - """ - Connect to the socket identified by host and port - - :returns: Future - :rtype: tornado.concurrent.Future - """ - conn = self.get_socket() - self.stream = IOStream(conn, io_loop=self.io_loop or IOLoop.current()) - self.stream.connect((self.host, self.port)) - self.stream.read_until_close(None, - streaming_callback=self.on_receive) - self._connected = True - LOGGER.debug("Client connected") - - raise gen.Return(self) - - def on_receive(self, *args): - """ - On data recieve call back - :param args: data received - :return: - """ - data = args[0] if len(args) > 0 else None - - if not data: - return - LOGGER.debug("recv: " + hexlify_packets(data)) - unit = self.framer.decode_data(data).get("unit", 0) - self.framer.processIncomingPacket(data, self._handle_response, unit=unit) - - def execute(self, request=None): - """ - Executes a transaction - :param request: - :return: - """ - request.transaction_id = self.transaction.getNextTID() - packet = self.framer.buildPacket(request) - LOGGER.debug("send: " + hexlify_packets(packet)) - self.stream.write(packet) - return self._build_response(request.transaction_id) - - def _handle_response(self, reply, **kwargs): - """ - Handle response received - :param reply: - :param kwargs: - :return: - """ - if reply is not None: - tid = reply.transaction_id - future = self.transaction.getTransaction(tid) - if future: - future.set_result(reply) - else: - LOGGER.debug("Unrequested message: {}".format(reply)) - - def _build_response(self, tid): - """ - Builds a future response - :param tid: - :return: - """ - f = Future() - - if not self._connected: - f.set_exception(ConnectionException("Client is not connected")) - return f - - self.transaction.addTransaction(f, tid) - return f - - def close(self): - """ - Closes the underlying IOStream - """ - LOGGER.debug("Client disconnected") - if self.stream: - self.stream.close_fd() - - self.stream = None - self._connected = False - - -class BaseTornadoSerialClient(AsyncModbusSerialClientMixin): - """ - Base Tonado serial client - """ - stream = None - io_loop = None - - def __init__(self, *args, **kwargs): - """ - Initializes BaseTornadoSerialClient. - ioloop to be passed as part of kwargs ('ioloop') - :param args: - :param kwargs: - """ - self.io_loop = kwargs.pop("ioloop", None) - super(BaseTornadoSerialClient, self).__init__(*args, **kwargs) - - @abc.abstractmethod - def get_socket(self): - """ - return instance of the socket to connect to - """ - - def on_receive(self, *args): - # Will be handled ine execute method - pass - - def execute(self, request=None): - """ - Executes a transaction - :param request: Request to be written on to the bus - :return: - """ - request.transaction_id = self.transaction.getNextTID() - - def callback(*args): - LOGGER.debug("in callback - {}".format(request.transaction_id)) - while True: - waiting = self.stream.connection.in_waiting - if waiting: - data = self.stream.connection.read(waiting) - LOGGER.debug( - "recv: " + hexlify_packets(data)) - unit = self.framer.decode_data(data).get("uid", 0) - self.framer.processIncomingPacket( - data, - self._handle_response, - unit, - tid=request.transaction_id - ) - break - - packet = self.framer.buildPacket(request) - LOGGER.debug("send: " + hexlify_packets(packet)) - self.stream.write(packet, callback=callback) - f = self._build_response(request.transaction_id) - return f - - def _handle_response(self, reply, **kwargs): - """ - Handles a received response and updates a future - :param reply: Reply received - :param kwargs: - :return: - """ - if reply is not None: - tid = reply.transaction_id - future = self.transaction.getTransaction(tid) - if future: - future.set_result(reply) - else: - LOGGER.debug("Unrequested message: {}".format(reply)) - - def _build_response(self, tid): - """ - Prepare for a response, returns a future - :param tid: - :return: Future - """ - f = Future() - - if not self._connected: - f.set_exception(ConnectionException("Client is not connected")) - return f - - self.transaction.addTransaction(f, tid) - return f - - def close(self): - """ - Closes the underlying IOStream - """ - LOGGER.debug("Client disconnected") - if self.stream: - self.stream.close_fd() - - self.stream = None - self._connected = False - - -class SerialIOStream(BaseIOStream): - """ - Serial IO Stream class to control and handle serial connections - over tornado - """ - def __init__(self, connection, *args, **kwargs): - """ - Initializes Serial IO Stream - :param connection: serial object - :param args: - :param kwargs: - """ - self.connection = connection - super(SerialIOStream, self).__init__(*args, **kwargs) - - def fileno(self): - """ - Returns serial fd - :return: - """ - return self.connection.fileno() - - def close_fd(self): - """ - Closes a serial Fd - :return: - """ - if self.connection: - self.connection.close() - self.connection = None - - def read_from_fd(self): - """ - Reads from a fd - :return: - """ - try: - chunk = self.connection.readline() - except Exception: - return None - - return chunk - - def write_to_fd(self, data): - """ - Writes to a fd - :param data: - :return: - """ - try: - return self.connection.write(data) - except Exception as e: - LOGGER.error(e) - - -class AsyncModbusSerialClient(BaseTornadoSerialClient): - """ - Tornado based asynchronous serial client - """ - def __init__(self, *args, **kwargs): - """ - Initializes AsyncModbusSerialClient. - :param args: - :param kwargs: - """ - self.state = ModbusTransactionState.IDLE - self.timeout = kwargs.get('timeout', Defaults.Timeout) - self.baudrate = kwargs.get('baudrate', Defaults.Baudrate) - if self.baudrate > 19200: - self.silent_interval = 1.75 / 1000 # ms - else: - self._t0 = float((1 + 8 + 2)) / self.baudrate - self.silent_interval = 3.5 * self._t0 - self.silent_interval = round(self.silent_interval, 6) - self.last_frame_end = 0.0 - super(AsyncModbusSerialClient, self).__init__(*args, **kwargs) - - def get_socket(self): - """ - Creates Pyserial object - :return: serial object - """ - return Serial(port=self.port, **self.serial_settings) - - @gen.coroutine - def connect(self): - """Connect to the socket identified by host and port - - :returns: Future - :rtype: tornado.concurrent.Future - """ - conn = self.get_socket() - if self.io_loop is None: - self.io_loop = IOLoop.current() - try: - self.stream = SerialIOStream(conn, io_loop=self.io_loop) - except Exception as e: - LOGGER.exception(e) - - self._connected = True - LOGGER.debug("Client connected") - - raise gen.Return(self) - - def execute(self, request): - """ - Executes a transaction - :param request: Request to be written on to the bus - :return: - """ - request.transaction_id = self.transaction.getNextTID() - - def _clear_timer(): - """ - Clear serial waiting timeout - """ - if self.timeout_handle: - self.io_loop.remove_timeout(self.timeout_handle) - self.timeout_handle = None - - def _on_timeout(): - """ - Got timeout while waiting data from serial port - """ - LOGGER.warning("serial receive timeout") - _clear_timer() - if self.stream: - self.io_loop.remove_handler(self.stream.fileno()) - self.framer.resetFrame() - transaction = self.transaction.getTransaction(request.transaction_id) - if transaction: - transaction.set_exception(TimeOutException()) - - def _on_write_done(*args): - """ - Set up reader part after sucessful write to the serial - """ - LOGGER.debug("frame sent, waiting for a reply") - self.last_frame_end = round(time.time(), 6) - self.state = ModbusTransactionState.WAITING_FOR_REPLY - self.io_loop.add_handler(self.stream.fileno(), _on_receive, IOLoop.READ) - - def _on_fd_error(fd, *args): - _clear_timer() - self.io_loop.remove_handler(fd) - self.close() - self.transaction.getTransaction(request.transaction_id).set_exception(ModbusIOException(*args)) - - def _on_receive(fd, events): - """ - New data in serial buffer to read or serial port closed - """ - if events & IOLoop.ERROR: - _on_fd_error(fd) - return - - try: - waiting = self.stream.connection.in_waiting - if waiting: - data = self.stream.connection.read(waiting) - LOGGER.debug( - "recv: " + hexlify_packets(data)) - self.last_frame_end = round(time.time(), 6) - except OSError as ex: - _on_fd_error(fd, ex) - return - - self.framer.addToFrame(data) - - # check if we have regular frame or modbus exception - fcode = self.framer.decode_data(self.framer.getRawFrame()).get("fcode", 0) - if fcode and ( - (fcode > 0x80 and len(self.framer.getRawFrame()) == exception_response_length) - or - (len(self.framer.getRawFrame()) == expected_response_length) - ): - _clear_timer() - self.io_loop.remove_handler(fd) - self.state = ModbusTransactionState.IDLE - self.framer.processIncomingPacket( - b'', # already sent via addToFrame() - self._handle_response, - 0, # don't care when `single=True` - single=True, - tid=request.transaction_id - ) - - packet = self.framer.buildPacket(request) - f = self._build_response(request.transaction_id) - - response_pdu_size = request.get_response_pdu_size() - expected_response_length = self.transaction._calculate_response_length(response_pdu_size) - LOGGER.debug("expected_response_length = %d", expected_response_length) - - exception_response_length = self.transaction._calculate_exception_length() # TODO: calculate once - - if self.timeout: - self.timeout_handle = self.io_loop.add_timeout(time.time() + self.timeout, _on_timeout) - self._sendPacket(packet, callback=_on_write_done) - - return f - - def _sendPacket(self, message, callback): - """ - Sends packets on the bus with 3.5char delay between frames - :param message: Message to be sent over the bus - :return: - """ - @gen.coroutine - def sleep(timeout): - yield gen.sleep(timeout) - - try: - waiting = self.stream.connection.in_waiting - if waiting: - result = self.stream.connection.read(waiting) - LOGGER.info( - "Cleanup recv buffer before send: " + hexlify_packets(result)) - except OSError as e: - self.transaction.getTransaction( - message.transaction_id).set_exception(ModbusIOException(e)) - return - - start = time.time() - if self.last_frame_end: - waittime = self.last_frame_end + self.silent_interval - start - if waittime > 0: - LOGGER.debug("Waiting for 3.5 char before next send - %f ms", waittime) - sleep(waittime) - - self.state = ModbusTransactionState.SENDING - LOGGER.debug("send: " + hexlify_packets(message)) - self.stream.write(message, callback) - -class AsyncModbusTCPClient(BaseTornadoClient): - """ - Tornado based Async tcp client - """ - def get_socket(self): - """ - Creates socket object - :return: socket - """ - return socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) - - -class AsyncModbusUDPClient(BaseTornadoClient): - """ - Tornado based Async UDP client - """ - def get_socket(self): - """ - Create socket object - :return: socket - """ - return socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) \ No newline at end of file diff --git a/modbus/pymodbus/client/asynchronous/twisted/__init__.py b/modbus/pymodbus/client/asynchronous/twisted/__init__.py deleted file mode 100644 index f86cae3..0000000 --- a/modbus/pymodbus/client/asynchronous/twisted/__init__.py +++ /dev/null @@ -1,251 +0,0 @@ -""" -Implementation of a Modbus Client Using Twisted --------------------------------------------------- - -Example run:: - - from twisted.internet import reactor, protocol - from pymodbus.client.asynchronous import ModbusClientProtocol - - def printResult(result): - print "Result: %d" % result.bits[0] - - def process(client): - result = client.write_coil(1, True) - result.addCallback(printResult) - reactor.callLater(1, reactor.stop) - - defer = protocol.ClientCreator(reactor, ModbusClientProtocol - ).connectTCP("localhost", 502) - defer.addCallback(process) - -Another example:: - - from twisted.internet import reactor - from pymodbus.client.asynchronous import ModbusClientFactory - - def process(): - factory = reactor.connectTCP("localhost", 502, ModbusClientFactory()) - reactor.stop() - - if __name__ == "__main__": - reactor.callLater(1, process) - reactor.run() -""" -from __future__ import unicode_literals -from twisted.internet import defer, protocol - -from pymodbus.exceptions import ConnectionException -from pymodbus.factory import ClientDecoder -from pymodbus.client.asynchronous.mixins import AsyncModbusClientMixin -from pymodbus.transaction import FifoTransactionManager, DictTransactionManager -from pymodbus.transaction import ModbusSocketFramer, ModbusRtuFramer -from pymodbus.utilities import hexlify_packets -from twisted.python.failure import Failure - - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Connected Client Protocols -# --------------------------------------------------------------------------- # -class ModbusClientProtocol(protocol.Protocol, - AsyncModbusClientMixin): - """ - This represents the base modbus client protocol. All the application - layer code is deferred to a higher level wrapper. - """ - framer = None - - def __init__(self, framer=None, **kwargs): - self._connected = False - self.framer = framer or ModbusSocketFramer(ClientDecoder()) - if isinstance(self.framer, type): - # Framer class not instance - self.framer = self.framer(ClientDecoder(), client=None) - if isinstance(self.framer, ModbusSocketFramer): - self.transaction = DictTransactionManager(self, **kwargs) - else: - self.transaction = FifoTransactionManager(self, **kwargs) - - def connectionMade(self): - """ - Called upon a successful client connection. - """ - _logger.debug("Client connected to modbus server") - self._connected = True - - def connectionLost(self, reason=None): - """ - Called upon a client disconnect - - :param reason: The reason for the disconnect - """ - _logger.debug("Client disconnected from modbus server: %s" % reason) - self._connected = False - for tid in list(self.transaction): - self.transaction.getTransaction(tid).errback(Failure( - ConnectionException('Connection lost during request'))) - - def dataReceived(self, data): - """ - Get response, check for valid message, decode result - - :param data: The data returned from the server - """ - unit = self.framer.decode_data(data).get("unit", 0) - self.framer.processIncomingPacket(data, self._handleResponse, - unit=unit) - - def execute(self, request): - """ - Starts the producer to send the next request to - consumer.write(Frame(request)) - """ - request.transaction_id = self.transaction.getNextTID() - packet = self.framer.buildPacket(request) - _logger.debug("send: " + hexlify_packets(packet)) - self.transport.write(packet) - return self._buildResponse(request.transaction_id) - - def _handleResponse(self, reply, **kwargs): - """ - Handle the processed response and link to correct deferred - - :param reply: The reply to process - """ - if reply is not None: - tid = reply.transaction_id - handler = self.transaction.getTransaction(tid) - if handler: - handler.callback(reply) - else: - _logger.debug("Unrequested message: " + str(reply)) - - def _buildResponse(self, tid): - """ - Helper method to return a deferred response - for the current request. - - :param tid: The transaction identifier for this response - :returns: A defer linked to the latest request - """ - if not self._connected: - return defer.fail(Failure( - ConnectionException('Client is not connected'))) - - d = defer.Deferred() - self.transaction.addTransaction(d, tid) - return d - - def close(self): - """ - Closes underlying transport layer ,essentially closing the client - :return: - """ - if self.transport and hasattr(self.transport, "close"): - self.transport.close() - self._connected = False - - -class ModbusTcpClientProtocol(ModbusClientProtocol): - """ - Async TCP Client protocol based on twisted. - - Default framer: ModbusSocketFramer - """ - framer = ModbusSocketFramer(ClientDecoder()) - - -class ModbusSerClientProtocol(ModbusClientProtocol): - """ - Async Serial Client protocol based on twisted - - Default framer: ModbusRtuFramer - """ - def __init__(self, framer=None, **kwargs): - framer = framer or ModbusRtuFramer(ClientDecoder()) - super(ModbusSerClientProtocol, self).__init__(framer, **kwargs) - - -# --------------------------------------------------------------------------- # -# Not Connected Client Protocol -# --------------------------------------------------------------------------- # -class ModbusUdpClientProtocol(protocol.DatagramProtocol, - AsyncModbusClientMixin): - """ - This represents the base modbus client protocol. All the application - layer code is deferred to a higher level wrapper. - """ - - def datagramReceived(self, data, params): - """ - Get response, check for valid message, decode result - - :param data: The data returned from the server - :param params: The host parameters sending the datagram - """ - _logger.debug("Datagram from: %s:%d" % params) - unit = self.framer.decode_data(data).get("uid", 0) - self.framer.processIncomingPacket(data, self._handleResponse, unit=unit) - - def execute(self, request): - """ - Starts the producer to send the next request to - consumer.write(Frame(request)) - """ - request.transaction_id = self.transaction.getNextTID() - packet = self.framer.buildPacket(request) - self.transport.write(packet, (self.host, self.port)) - return self._buildResponse(request.transaction_id) - - def _handleResponse(self, reply, **kwargs): - """ - Handle the processed response and link to correct deferred - - :param reply: The reply to process - """ - if reply is not None: - tid = reply.transaction_id - handler = self.transaction.getTransaction(tid) - if handler: - handler.callback(reply) - else: _logger.debug("Unrequested message: " + str(reply)) - - def _buildResponse(self, tid): - """ - Helper method to return a deferred response - for the current request. - - :param tid: The transaction identifier for this response - :returns: A defer linked to the latest request - """ - d = defer.Deferred() - self.transaction.addTransaction(d, tid) - return d - - -# --------------------------------------------------------------------------- # -# Client Factories -# --------------------------------------------------------------------------- # -class ModbusClientFactory(protocol.ReconnectingClientFactory): - """ Simple client protocol factory """ - - protocol = ModbusClientProtocol - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = [ - "ModbusClientProtocol", - "ModbusUdpClientProtocol", - "ModbusClientFactory" -] - diff --git a/modbus/pymodbus/client/asynchronous/udp.py b/modbus/pymodbus/client/asynchronous/udp.py deleted file mode 100644 index a111d96..0000000 --- a/modbus/pymodbus/client/asynchronous/udp.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import unicode_literals -from __future__ import absolute_import - -import logging -from pymodbus.constants import Defaults -from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION -from pymodbus.client.asynchronous.schedulers import ASYNC_IO -from pymodbus.client.asynchronous.factory.udp import get_factory - -logger = logging.getLogger(__name__) - - -class AsyncModbusUDPClient(object): - """ - Actual Async UDP Client to be used. - - To use do:: - - from pymodbus.client.asynchronous.tcp import AsyncModbusUDPClient - """ - def __new__(cls, scheduler, host="127.0.0.1", port=Defaults.Port, - framer=None, source_address=None, timeout=None, **kwargs): - """ - Scheduler to use: - - reactor (Twisted) - - io_loop (Tornado) - - async_io (asyncio) - :param scheduler: Backend to use - :param host: Host IP address - :param port: Port - :param framer: Modbus Framer to use - :param source_address: source address specific to underlying backend - :param timeout: Time out in seconds - :param kwargs: Other extra args specific to Backend being used - :return: - """ - if (not (IS_PYTHON3 and PYTHON_VERSION >= (3, 4)) - and scheduler == ASYNC_IO): - logger.critical("ASYNCIO is supported only on python3") - import sys - sys.exit(1) - factory_class = get_factory(scheduler) - yieldable = factory_class(host=host, port=port, framer=framer, - source_address=source_address, - timeout=timeout, **kwargs) - return yieldable - diff --git a/modbus/pymodbus/client/common.py b/modbus/pymodbus/client/common.py deleted file mode 100644 index ea3c981..0000000 --- a/modbus/pymodbus/client/common.py +++ /dev/null @@ -1,155 +0,0 @@ -''' -Modbus Client Common ----------------------------------- - -This is a common client mixin that can be used by -both the synchronous and asynchronous clients to -simplify the interface. -''' -from pymodbus.bit_read_message import * -from pymodbus.bit_write_message import * -from pymodbus.register_read_message import * -from pymodbus.register_write_message import * -from pymodbus.diag_message import * -from pymodbus.file_message import * -from pymodbus.other_message import * - -from pymodbus.utilities import ModbusTransactionState - - -class ModbusClientMixin(object): - ''' - This is a modbus client mixin that provides additional factory - methods for all the current modbus methods. This can be used - instead of the normal pattern of:: - - # instead of this - client = ModbusClient(...) - request = ReadCoilsRequest(1,10) - response = client.execute(request) - - # now like this - client = ModbusClient(...) - response = client.read_coils(1, 10) - ''' - state = ModbusTransactionState.IDLE - last_frame_end = 0 - silent_interval = 0 - - def read_coils(self, address, count=1, **kwargs): - ''' - - :param address: The starting address to read from - :param count: The number of coils to read - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = ReadCoilsRequest(address, count, **kwargs) - return self.execute(request) - - def read_discrete_inputs(self, address, count=1, **kwargs): - ''' - - :param address: The starting address to read from - :param count: The number of discretes to read - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = ReadDiscreteInputsRequest(address, count, **kwargs) - return self.execute(request) - - def write_coil(self, address, value, **kwargs): - ''' - - :param address: The starting address to write to - :param value: The value to write to the specified address - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = WriteSingleCoilRequest(address, value, **kwargs) - return self.execute(request) - - def write_coils(self, address, values, **kwargs): - ''' - - :param address: The starting address to write to - :param values: The values to write to the specified address - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = WriteMultipleCoilsRequest(address, values, **kwargs) - return self.execute(request) - - def write_register(self, address, value, **kwargs): - ''' - - :param address: The starting address to write to - :param value: The value to write to the specified address - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = WriteSingleRegisterRequest(address, value, **kwargs) - return self.execute(request) - - def write_registers(self, address, values, **kwargs): - ''' - - :param address: The starting address to write to - :param values: The values to write to the specified address - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = WriteMultipleRegistersRequest(address, values, **kwargs) - return self.execute(request) - - def read_holding_registers(self, address, count=1, **kwargs): - ''' - - :param address: The starting address to read from - :param count: The number of registers to read - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = ReadHoldingRegistersRequest(address, count, **kwargs) - return self.execute(request) - - def read_input_registers(self, address, count=1, **kwargs): - ''' - - :param address: The starting address to read from - :param count: The number of registers to read - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = ReadInputRegistersRequest(address, count, **kwargs) - return self.execute(request) - - def readwrite_registers(self, *args, **kwargs): - ''' - - :param read_address: The address to start reading from - :param read_count: The number of registers to read from address - :param write_address: The address to start writing to - :param write_registers: The registers to write to the specified address - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = ReadWriteMultipleRegistersRequest(*args, **kwargs) - return self.execute(request) - - def mask_write_register(self, *args, **kwargs): - ''' - - :param address: The address of the register to write - :param and_mask: The and bitmask to apply to the register address - :param or_mask: The or bitmask to apply to the register address - :param unit: The slave unit this request is targeting - :returns: A deferred response handle - ''' - request = MaskWriteRegisterRequest(*args, **kwargs) - return self.execute(request) - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ 'ModbusClientMixin' ] diff --git a/modbus/pymodbus/client/sync.py b/modbus/pymodbus/client/sync.py deleted file mode 100644 index 571ba0f..0000000 --- a/modbus/pymodbus/client/sync.py +++ /dev/null @@ -1,779 +0,0 @@ -import socket -import select -import serial -import time -import ssl -import sys -from functools import partial -from pymodbus.constants import Defaults -from pymodbus.utilities import hexlify_packets, ModbusTransactionState -from pymodbus.factory import ClientDecoder -from pymodbus.exceptions import NotImplementedException, ParameterException -from pymodbus.exceptions import ConnectionException -from pymodbus.transaction import FifoTransactionManager -from pymodbus.transaction import DictTransactionManager -from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer -from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer -from pymodbus.transaction import ModbusTlsFramer -from pymodbus.client.common import ModbusClientMixin - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - -# --------------------------------------------------------------------------- # -# The Synchronous Clients -# --------------------------------------------------------------------------- # - - -class BaseModbusClient(ModbusClientMixin): - """ - Inteface for a modbus synchronous client. Defined here are all the - methods for performing the related request methods. Derived classes - simply need to implement the transport methods and set the correct - framer. - """ - def __init__(self, framer, **kwargs): - """ Initialize a client instance - - :param framer: The modbus framer implementation to use - """ - self.framer = framer - self.transaction = DictTransactionManager(self, **kwargs) - self._debug = False - self._debugfd = None - self.broadcast_enable = kwargs.get('broadcast_enable', Defaults.broadcast_enable) - - # ----------------------------------------------------------------------- # - # Client interface - # ----------------------------------------------------------------------- # - def connect(self): - """ Connect to the modbus remote host - - :returns: True if connection succeeded, False otherwise - """ - raise NotImplementedException("Method not implemented by derived class") - - def close(self): - """ Closes the underlying socket connection - """ - pass - - def is_socket_open(self): - """ - Check whether the underlying socket/serial is open or not. - - :returns: True if socket/serial is open, False otherwise - """ - raise NotImplementedException( - "is_socket_open() not implemented by {}".format(self.__str__()) - ) - - def send(self, request): - if self.state != ModbusTransactionState.RETRYING: - _logger.debug("New Transaction state 'SENDING'") - self.state = ModbusTransactionState.SENDING - return self._send(request) - - def _send(self, request): - """ Sends data on the underlying socket - - :param request: The encoded request to send - :return: The number of bytes written - """ - raise NotImplementedException("Method not implemented by derived class") - - def recv(self, size): - return self._recv(size) - - def _recv(self, size): - """ Reads data from the underlying descriptor - - :param size: The number of bytes to read - :return: The bytes read - """ - raise NotImplementedException("Method not implemented by derived class") - - # ----------------------------------------------------------------------- # - # Modbus client methods - # ----------------------------------------------------------------------- # - def execute(self, request=None): - """ - :param request: The request to process - :returns: The result of the request execution - """ - if not self.connect(): - raise ConnectionException("Failed to connect[%s]" % (self.__str__())) - return self.transaction.execute(request) - - # ----------------------------------------------------------------------- # - # The magic methods - # ----------------------------------------------------------------------- # - def __enter__(self): - """ Implement the client with enter block - - :returns: The current instance of the client - """ - if not self.connect(): - raise ConnectionException("Failed to connect[%s]" % (self.__str__())) - return self - - def __exit__(self, klass, value, traceback): - """ Implement the client with exit block """ - self.close() - - def idle_time(self): - """ - Bus Idle Time to initiate next transaction - :return: time stamp - """ - if self.last_frame_end is None or self.silent_interval is None: - return 0 - return self.last_frame_end + self.silent_interval - - def debug_enabled(self): - """ - Returns a boolean indicating if debug is enabled. - """ - return self._debug - - def set_debug(self, debug): - """ - Sets the current debug flag. - """ - self._debug = debug - - def trace(self, writeable): - if writeable: - self.set_debug(True) - self._debugfd = writeable - - def _dump(self, data, direction): - fd = self._debugfd if self._debugfd else sys.stdout - try: - fd.write(hexlify_packets(data)) - except Exception as e: - _logger.debug(hexlify_packets(data)) - _logger.exception(e) - - def register(self, function): - """ - Registers a function and sub function class with the decoder - :param function: Custom function class to register - :return: - """ - self.framer.decoder.register(function) - - def __str__(self): - """ Builds a string representation of the connection - - :returns: The string representation - """ - return "Null Transport" - - -# --------------------------------------------------------------------------- # -# Modbus TCP Client Transport Implementation -# --------------------------------------------------------------------------- # -class ModbusTcpClient(BaseModbusClient): - """ Implementation of a modbus tcp client - """ - - def __init__(self, host='127.0.0.1', port=Defaults.Port, - framer=ModbusSocketFramer, **kwargs): - """ Initialize a client instance - - :param host: The host to connect to (default 127.0.0.1) - :param port: The modbus port to connect to (default 502) - :param source_address: The source address tuple to bind to (default ('', 0)) - :param timeout: The timeout to use for this socket (default Defaults.Timeout) - :param framer: The modbus framer to use (default ModbusSocketFramer) - - .. note:: The host argument will accept ipv4 and ipv6 hosts - """ - self.host = host - self.port = port - self.source_address = kwargs.get('source_address', ('', 0)) - self.socket = None - self.timeout = kwargs.get('timeout', Defaults.Timeout) - BaseModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs) - - def connect(self): - """ Connect to the modbus tcp server - - :returns: True if connection succeeded, False otherwise - """ - if self.socket: - return True - try: - self.socket = socket.create_connection( - (self.host, self.port), - timeout=self.timeout, - source_address=self.source_address) - _logger.debug("Connection to Modbus server established. " - "Socket {}".format(self.socket.getsockname())) - except socket.error as msg: - _logger.error('Connection to (%s, %s) ' - 'failed: %s' % (self.host, self.port, msg)) - self.close() - return self.socket is not None - - def close(self): - """ Closes the underlying socket connection - """ - if self.socket: - self.socket.close() - self.socket = None - - def _check_read_buffer(self, recv_size=None): - time_ = time.time() - end = time_ + self.timeout - data = None - ready = select.select([self.socket], [], [], end - time_) - if ready[0]: - data = self.socket.recv(1024) - return data - - def _send(self, request): - """ Sends data on the underlying socket - - :param request: The encoded request to send - :return: The number of bytes written - """ - if not self.socket: - raise ConnectionException(self.__str__()) - if self.state == ModbusTransactionState.RETRYING: - data = self._check_read_buffer() - if data: - return data - - if request: - return self.socket.send(request) - return 0 - - def _recv(self, size): - """ Reads data from the underlying descriptor - - :param size: The number of bytes to read - :return: The bytes read if the peer sent a response, or a zero-length - response if no data packets were received from the client at - all. - :raises: ConnectionException if the socket is not initialized, or the - peer either has closed the connection before this method is - invoked or closes it before sending any data before timeout. - """ - if not self.socket: - raise ConnectionException(self.__str__()) - - # socket.recv(size) waits until it gets some data from the host but - # not necessarily the entire response that can be fragmented in - # many packets. - # To avoid the splitted responses to be recognized as invalid - # messages and to be discarded, loops socket.recv until full data - # is received or timeout is expired. - # If timeout expires returns the read data, also if its length is - # less than the expected size. - self.socket.setblocking(0) - - timeout = self.timeout - - # If size isn't specified read up to 4096 bytes at a time. - if size is None: - recv_size = 4096 - else: - recv_size = size - - data = [] - data_length = 0 - time_ = time.time() - end = time_ + timeout - while recv_size > 0: - ready = select.select([self.socket], [], [], end - time_) - if ready[0]: - recv_data = self.socket.recv(recv_size) - if recv_data == b'': - return self._handle_abrupt_socket_close( - size, data, time.time() - time_) - data.append(recv_data) - data_length += len(recv_data) - time_ = time.time() - - # If size isn't specified continue to read until timeout expires. - if size: - recv_size = size - data_length - - # Timeout is reduced also if some data has been received in order - # to avoid infinite loops when there isn't an expected response - # size and the slave sends noisy data continuously. - if time_ > end: - break - - return b"".join(data) - - def _handle_abrupt_socket_close(self, size, data, duration): - """ Handle unexpected socket close by remote end - - Intended to be invoked after determining that the remote end - has unexpectedly closed the connection, to clean up and handle - the situation appropriately. - - :param size: The number of bytes that was attempted to read - :param data: The actual data returned - :param duration: Duration from the read was first attempted - until it was determined that the remote closed the - socket - :return: The more than zero bytes read from the remote end - :raises: ConnectionException If the remote end didn't send any - data at all before closing the connection. - """ - self.close() - readsize = ("read of %s bytes" % size if size - else "unbounded read") - msg = ("%s: Connection unexpectedly closed " - "%.6f seconds into %s" % (self, duration, readsize)) - if data: - result = b"".join(data) - msg += " after returning %s bytes" % len(result) - _logger.warning(msg) - return result - msg += " without response from unit before it closed connection" - raise ConnectionException(msg) - - def is_socket_open(self): - return True if self.socket is not None else False - - def __str__(self): - """ Builds a string representation of the connection - - :returns: The string representation - """ - return "ModbusTcpClient(%s:%s)" % (self.host, self.port) - - def __repr__(self): - return ( - "<{} at {} socket={self.socket}, ipaddr={self.host}, " - "port={self.port}, timeout={self.timeout}>" - ).format(self.__class__.__name__, hex(id(self)), self=self) - -# --------------------------------------------------------------------------- # -# Modbus TLS Client Transport Implementation -# --------------------------------------------------------------------------- # - - -class ModbusTlsClient(ModbusTcpClient): - """ Implementation of a modbus tls client - """ - - def __init__(self, host='localhost', port=Defaults.TLSPort, sslctx=None, - framer=ModbusTlsFramer, **kwargs): - """ Initialize a client instance - - :param host: The host to connect to (default localhost) - :param port: The modbus port to connect to (default 802) - :param sslctx: The SSLContext to use for TLS (default None and auto create) - :param source_address: The source address tuple to bind to (default ('', 0)) - :param timeout: The timeout to use for this socket (default Defaults.Timeout) - :param framer: The modbus framer to use (default ModbusSocketFramer) - - .. note:: The host argument will accept ipv4 and ipv6 hosts - """ - self.sslctx = sslctx - if self.sslctx is None: - self.sslctx = ssl.create_default_context() - # According to MODBUS/TCP Security Protocol Specification, it is - # TLSv2 at least - self.sslctx.options |= ssl.OP_NO_TLSv1_1 - self.sslctx.options |= ssl.OP_NO_TLSv1 - self.sslctx.options |= ssl.OP_NO_SSLv3 - self.sslctx.options |= ssl.OP_NO_SSLv2 - ModbusTcpClient.__init__(self, host, port, framer, **kwargs) - - def connect(self): - """ Connect to the modbus tls server - - :returns: True if connection succeeded, False otherwise - """ - if self.socket: return True - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.bind(self.source_address) - self.socket = self.sslctx.wrap_socket(sock, server_side=False, - server_hostname=self.host) - self.socket.settimeout(self.timeout) - self.socket.connect((self.host, self.port)) - except socket.error as msg: - _logger.error('Connection to (%s, %s) ' - 'failed: %s' % (self.host, self.port, msg)) - self.close() - return self.socket is not None - - def _recv(self, size): - """ Reads data from the underlying descriptor - - :param size: The number of bytes to read - :return: The bytes read - """ - if not self.socket: - raise ConnectionException(self.__str__()) - - # socket.recv(size) waits until it gets some data from the host but - # not necessarily the entire response that can be fragmented in - # many packets. - # To avoid the splitted responses to be recognized as invalid - # messages and to be discarded, loops socket.recv until full data - # is received or timeout is expired. - # If timeout expires returns the read data, also if its length is - # less than the expected size. - timeout = self.timeout - - # If size isn't specified read 1 byte at a time. - if size is None: - recv_size = 1 - else: - recv_size = size - - data = b'' - time_ = time.time() - end = time_ + timeout - while recv_size > 0: - data += self.socket.recv(recv_size) - time_ = time.time() - - # If size isn't specified continue to read until timeout expires. - if size: - recv_size = size - len(data) - - # Timeout is reduced also if some data has been received in order - # to avoid infinite loops when there isn't an expected response - # size and the slave sends noisy data continuously. - if time_ > end: - break - - return data - - def __str__(self): - """ Builds a string representation of the connection - - :returns: The string representation - """ - return "ModbusTlsClient(%s:%s)" % (self.host, self.port) - - def __repr__(self): - return ( - "<{} at {} socket={self.socket}, ipaddr={self.host}, " - "port={self.port}, sslctx={self.sslctx}, timeout={self.timeout}>" - ).format(self.__class__.__name__, hex(id(self)), self=self) - - -# --------------------------------------------------------------------------- # -# Modbus UDP Client Transport Implementation -# --------------------------------------------------------------------------- # - - -class ModbusUdpClient(BaseModbusClient): - """ Implementation of a modbus udp client - """ - - def __init__(self, host='127.0.0.1', port=Defaults.Port, - framer=ModbusSocketFramer, **kwargs): - """ Initialize a client instance - - :param host: The host to connect to (default 127.0.0.1) - :param port: The modbus port to connect to (default 502) - :param framer: The modbus framer to use (default ModbusSocketFramer) - :param timeout: The timeout to use for this socket (default None) - """ - self.host = host - self.port = port - self.socket = None - self.timeout = kwargs.get('timeout', None) - BaseModbusClient.__init__(self, framer(ClientDecoder(), self), **kwargs) - - @classmethod - def _get_address_family(cls, address): - """ A helper method to get the correct address family - for a given address. - - :param address: The address to get the af for - :returns: AF_INET for ipv4 and AF_INET6 for ipv6 - """ - try: - _ = socket.inet_pton(socket.AF_INET6, address) - except socket.error: # not a valid ipv6 address - return socket.AF_INET - return socket.AF_INET6 - - def connect(self): - """ Connect to the modbus tcp server - - :returns: True if connection succeeded, False otherwise - """ - if self.socket: - return True - try: - family = ModbusUdpClient._get_address_family(self.host) - self.socket = socket.socket(family, socket.SOCK_DGRAM) - self.socket.settimeout(self.timeout) - except socket.error as ex: - _logger.error('Unable to create udp socket %s' % ex) - self.close() - return self.socket is not None - - def close(self): - """ Closes the underlying socket connection - """ - self.socket = None - - def _send(self, request): - """ Sends data on the underlying socket - - :param request: The encoded request to send - :return: The number of bytes written - """ - if not self.socket: - raise ConnectionException(self.__str__()) - if request: - return self.socket.sendto(request, (self.host, self.port)) - return 0 - - def _recv(self, size): - """ Reads data from the underlying descriptor - - :param size: The number of bytes to read - :return: The bytes read - """ - if not self.socket: - raise ConnectionException(self.__str__()) - return self.socket.recvfrom(size)[0] - - def is_socket_open(self): - if self.socket: - return True - return self.connect() - - def __str__(self): - """ Builds a string representation of the connection - - :returns: The string representation - """ - return "ModbusUdpClient(%s:%s)" % (self.host, self.port) - - def __repr__(self): - return ( - "<{} at {} socket={self.socket}, ipaddr={self.host}, " - "port={self.port}, timeout={self.timeout}>" - ).format(self.__class__.__name__, hex(id(self)), self=self) - -# --------------------------------------------------------------------------- # -# Modbus Serial Client Transport Implementation -# --------------------------------------------------------------------------- # - - -class ModbusSerialClient(BaseModbusClient): - """ Implementation of a modbus serial client - """ - state = ModbusTransactionState.IDLE - inter_char_timeout = 0 - silent_interval = 0 - - def __init__(self, method='ascii', **kwargs): - """ Initialize a serial client instance - - The methods to connect are:: - - - ascii - - rtu - - binary - - :param method: The method to use for connection - :param port: The serial port to attach to - :param stopbits: The number of stop bits to use - :param bytesize: The bytesize of the serial messages - :param parity: Which kind of parity to use - :param baudrate: The baud rate to use for the serial device - :param timeout: The timeout between serial requests (default 3s) - :param strict: Use Inter char timeout for baudrates <= 19200 (adhere - to modbus standards) - :param handle_local_echo: Handle local echo of the USB-to-RS485 adaptor - """ - self.method = method - self.socket = None - BaseModbusClient.__init__(self, self.__implementation(method, self), - **kwargs) - - self.port = kwargs.get('port', 0) - self.stopbits = kwargs.get('stopbits', Defaults.Stopbits) - self.bytesize = kwargs.get('bytesize', Defaults.Bytesize) - self.parity = kwargs.get('parity', Defaults.Parity) - self.baudrate = kwargs.get('baudrate', Defaults.Baudrate) - self.timeout = kwargs.get('timeout', Defaults.Timeout) - self._strict = kwargs.get("strict", False) - self.last_frame_end = None - self.handle_local_echo = kwargs.get("handle_local_echo", False) - if self.method == "rtu": - if self.baudrate > 19200: - self.silent_interval = 1.75 / 1000 # ms - else: - self._t0 = float((1 + 8 + 2)) / self.baudrate - self.inter_char_timeout = 1.5 * self._t0 - self.silent_interval = 3.5 * self._t0 - self.silent_interval = round(self.silent_interval, 6) - - @staticmethod - def __implementation(method, client): - """ Returns the requested framer - - :method: The serial framer to instantiate - :returns: The requested serial framer - """ - method = method.lower() - if method == 'ascii': - return ModbusAsciiFramer(ClientDecoder(), client) - elif method == 'rtu': - return ModbusRtuFramer(ClientDecoder(), client) - elif method == 'binary': - return ModbusBinaryFramer(ClientDecoder(), client) - elif method == 'socket': - return ModbusSocketFramer(ClientDecoder(), client) - raise ParameterException("Invalid framer method requested") - - def connect(self): - """ Connect to the modbus serial server - - :returns: True if connection succeeded, False otherwise - """ - import serial - if self.socket: - return True - try: - self.socket = serial.serial_for_url(self.port, - timeout=self.timeout, - bytesize=self.bytesize, - stopbits=self.stopbits, - baudrate=self.baudrate, - parity=self.parity) - if self.method == "rtu": - if self._strict: - self.socket.interCharTimeout = self.inter_char_timeout - self.last_frame_end = None - except serial.SerialException as msg: - _logger.error(msg) - self.close() - return self.socket is not None - - def close(self): - """ Closes the underlying socket connection - """ - if self.socket: - self.socket.close() - self.socket = None - - def _in_waiting(self): - in_waiting = ("in_waiting" if hasattr( - self.socket, "in_waiting") else "inWaiting") - - if in_waiting == "in_waiting": - waitingbytes = getattr(self.socket, in_waiting) - else: - waitingbytes = getattr(self.socket, in_waiting)() - return waitingbytes - - def _send(self, request): - """ Sends data on the underlying socket - - If receive buffer still holds some data then flush it. - - Sleep if last send finished less than 3.5 character - times ago. - - :param request: The encoded request to send - :return: The number of bytes written - """ - if not self.socket: - raise ConnectionException(self.__str__()) - if request: - try: - waitingbytes = self._in_waiting() - if waitingbytes: - result = self.socket.read(waitingbytes) - if self.state == ModbusTransactionState.RETRYING: - _logger.debug("Sending available data in recv " - "buffer {}".format( - hexlify_packets(result))) - return result - if _logger.isEnabledFor(logging.WARNING): - _logger.warning("Cleanup recv buffer before " - "send: " + hexlify_packets(result)) - except NotImplementedError: - pass - if self.state != ModbusTransactionState.SENDING: - _logger.debug("New Transaction state 'SENDING'") - self.state = ModbusTransactionState.SENDING - size = self.socket.write(request) - return size - return 0 - - def _wait_for_data(self): - size = 0 - more_data = False - if self.timeout is not None and self.timeout != 0: - condition = partial(lambda start, timeout: - (time.time() - start) <= timeout, - timeout=self.timeout) - else: - condition = partial(lambda dummy1, dummy2: True, dummy2=None) - start = time.time() - while condition(start): - avaialble = self._in_waiting() - if (more_data and not avaialble) or (more_data and avaialble == size): - break - if avaialble and avaialble != size: - more_data = True - size = avaialble - time.sleep(0.01) - return size - - def _recv(self, size): - """ Reads data from the underlying descriptor - - :param size: The number of bytes to read - :return: The bytes read - """ - if not self.socket: - raise ConnectionException(self.__str__()) - if size is None: - size = self._wait_for_data() - result = self.socket.read(size) - return result - - def is_socket_open(self): - if self.socket: - if hasattr(self.socket, "is_open"): - return self.socket.is_open - else: - return self.socket.isOpen() - return False - - def __str__(self): - """ Builds a string representation of the connection - - :returns: The string representation - """ - return "ModbusSerialClient(%s baud[%s])" % (self.method, self.baudrate) - - def __repr__(self): - return ( - "<{} at {} socket={self.socket}, method={self.method}, " - "timeout={self.timeout}>" - ).format(self.__class__.__name__, hex(id(self)), self=self) - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = [ - "ModbusTcpClient", "ModbusTlsClient", "ModbusUdpClient", "ModbusSerialClient" -] diff --git a/modbus/pymodbus/client/sync_diag.py b/modbus/pymodbus/client/sync_diag.py deleted file mode 100644 index e2e4129..0000000 --- a/modbus/pymodbus/client/sync_diag.py +++ /dev/null @@ -1,167 +0,0 @@ -import socket -import logging -import time - -from pymodbus.constants import Defaults -from pymodbus.client.sync import ModbusTcpClient -from pymodbus.transaction import ModbusSocketFramer -from pymodbus.exceptions import ConnectionException - -_logger = logging.getLogger(__name__) - -LOG_MSGS = { - 'conn_msg': 'Connecting to modbus device %s', - 'connfail_msg': 'Connection to (%s, %s) failed: %s', - 'discon_msg': 'Disconnecting from modbus device %s', - 'timelimit_read_msg': - 'Modbus device read took %.4f seconds, ' - 'returned %s bytes in timelimit read', - 'timeout_msg': - 'Modbus device timeout after %.4f seconds, ' - 'returned %s bytes %s', - 'delay_msg': - 'Modbus device read took %.4f seconds, ' - 'returned %s bytes of %s expected', - 'read_msg': - 'Modbus device read took %.4f seconds, ' - 'returned %s bytes of %s expected', - 'unexpected_dc_msg': '%s %s'} - - -class ModbusTcpDiagClient(ModbusTcpClient): - """ - Variant of pymodbus.client.sync.ModbusTcpClient with additional - logging to diagnose network issues. - - The following events are logged: - - +---------+-----------------------------------------------------------------+ - | Level | Events | - +=========+=================================================================+ - | ERROR | Failure to connect to modbus unit; unexpected disconnect by | - | | modbus unit | - +---------+-----------------------------------------------------------------+ - | WARNING | Timeout on normal read; read took longer than warn_delay_limit | - +---------+-----------------------------------------------------------------+ - | INFO | Connection attempt to modbus unit; disconnection from modbus | - | | unit; each time limited read | - +---------+-----------------------------------------------------------------+ - | DEBUG | Normal read with timing information | - +---------+-----------------------------------------------------------------+ - - Reads are differentiated between "normal", which reads a specified number of - bytes, and "time limited", which reads all data for a duration equal to the - timeout period configured for this instance. - """ - - # pylint: disable=no-member - - def __init__(self, host='127.0.0.1', port=Defaults.Port, - framer=ModbusSocketFramer, **kwargs): - """ Initialize a client instance - - The keys of LOG_MSGS can be used in kwargs to customize the messages. - - :param host: The host to connect to (default 127.0.0.1) - :param port: The modbus port to connect to (default 502) - :param source_address: The source address tuple to bind to (default ('', 0)) - :param timeout: The timeout to use for this socket (default Defaults.Timeout) - :param warn_delay_limit: Log reads that take longer than this as warning. - Default True sets it to half of "timeout". None never logs these as - warning, 0 logs everything as warning. - :param framer: The modbus framer to use (default ModbusSocketFramer) - - .. note:: The host argument will accept ipv4 and ipv6 hosts - """ - self.warn_delay_limit = kwargs.get('warn_delay_limit', True) - super(ModbusTcpDiagClient, self).__init__(host, port, framer, **kwargs) - if self.warn_delay_limit is True: - self.warn_delay_limit = self.timeout / 2 - - # Set logging messages, defaulting to LOG_MSGS - for (k, v) in LOG_MSGS.items(): - self.__dict__[k] = kwargs.get(k, v) - - def connect(self): - """ Connect to the modbus tcp server - - :returns: True if connection succeeded, False otherwise - """ - if self.socket: - return True - try: - _logger.info(self.conn_msg, self) - self.socket = socket.create_connection( - (self.host, self.port), - timeout=self.timeout, - source_address=self.source_address) - except socket.error as msg: - _logger.error(self.connfail_msg, self.host, self.port, msg) - self.close() - return self.socket is not None - - def close(self): - """ Closes the underlying socket connection - """ - if self.socket: - _logger.info(self.discon_msg, self) - self.socket.close() - self.socket = None - - def _recv(self, size): - try: - start = time.time() - - result = super(ModbusTcpDiagClient, self)._recv(size) - - delay = time.time() - start - if self.warn_delay_limit is not None and delay >= self.warn_delay_limit: - self._log_delayed_response(len(result), size, delay) - elif not size: - _logger.debug(self.timelimit_read_msg, delay, len(result)) - else: - _logger.debug(self.read_msg, delay, len(result), size) - - return result - except ConnectionException as ex: - # Only log actual network errors, "if not self.socket" then it's a internal code issue - if 'Connection unexpectedly closed' in ex.string: - _logger.error(self.unexpected_dc_msg, self, ex) - raise ex - - def _log_delayed_response(self, result_len, size, delay): - if not size and result_len > 0: - _logger.info(self.timelimit_read_msg, delay, result_len) - elif (result_len == 0 or (size and result_len < size)) and delay >= self.timeout: - read_type = ("of %i expected" % size) if size else "in timelimit read" - _logger.warning(self.timeout_msg, delay, result_len, read_type) - else: - _logger.warning(self.delay_msg, delay, result_len, size) - - def __str__(self): - """ Builds a string representation of the connection - - :returns: The string representation - """ - return "ModbusTcpDiagClient(%s:%s)" % (self.host, self.port) - - -def get_client(): - """ Returns an appropriate client based on logging level - - This will be ModbusTcpDiagClient by default, or the parent class - if the log level is such that the diagnostic client will not log - anything. - - :returns: ModbusTcpClient or a child class thereof - """ - return ModbusTcpDiagClient if _logger.isEnabledFor(logging.ERROR) else ModbusTcpClient - - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - -__all__ = [ - "ModbusTcpDiagClient", "get_client" -] diff --git a/modbus/pymodbus/compat.py b/modbus/pymodbus/compat.py deleted file mode 100644 index 33e37db..0000000 --- a/modbus/pymodbus/compat.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -Python 2.x/3.x Compatibility Layer -------------------------------------------------- - -This is mostly based on the jinja2 compat code: - - Some py2/py3 compatibility support based on a stripped down - version of six so we don't have to depend on a specific version - of it. - - :copyright: Copyright 2013 by the Jinja team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" -import sys -import six - -# --------------------------------------------------------------------------- # -# python version checks -# --------------------------------------------------------------------------- # -PYTHON_VERSION = sys.version_info -IS_PYTHON2 = six.PY2 -IS_PYTHON3 = six.PY3 -IS_PYPY = hasattr(sys, 'pypy_translation_info') -IS_JYTHON = sys.platform.startswith('java') - -# --------------------------------------------------------------------------- # -# python > 3.3 compatibility layer -# --------------------------------------------------------------------------- # -# ----------------------------------------------------------------------- # -# portable builtins -# ----------------------------------------------------------------------- # -int2byte = six.int2byte -unichr = six.unichr -range_type = six.moves.range -text_type = six.string_types -string_types = six.string_types -iterkeys = six.iterkeys -itervalues = six.itervalues -iteritems = six.iteritems -get_next = six.next -unicode_string = six.u - -NativeStringIO = six.StringIO -ifilter = six.moves.filter -imap = six.moves.map -izip = six.moves.zip -intern = six.moves.intern - -if not IS_PYTHON2: - # ----------------------------------------------------------------------- # - # module renames - # ----------------------------------------------------------------------- # - import socketserver - # #609 monkey patch for socket server memory leaks - # Refer https://bugs.python.org/issue37193 - socketserver.ThreadingMixIn.daemon_threads = True - # ----------------------------------------------------------------------- # - # decorators - # ----------------------------------------------------------------------- # - implements_to_string = lambda x: x - - byte2int = lambda b: b - if PYTHON_VERSION >= (3, 4): - def is_installed(module): - import importlib.util - found = importlib.util.find_spec(module) - return found - else: - def is_installed(module): - import importlib - found = importlib.find_loader(module) - return found -# --------------------------------------------------------------------------- # -# python > 2.5 compatability layer -# --------------------------------------------------------------------------- # -else: - byte2int = six.byte2int - # ----------------------------------------------------------------------- # - # module renames - - # ----------------------------------------------------------------------- # - import SocketServer as socketserver - - # ----------------------------------------------------------------------- # - # decorators - # ----------------------------------------------------------------------- # - def implements_to_string(klass): - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda x: x.__unicode__().encode('utf-8') - return klass - - def is_installed(module): - import imp - try: - imp.find_module(module) - return True - except ImportError: - return False \ No newline at end of file diff --git a/modbus/pymodbus/constants.py b/modbus/pymodbus/constants.py deleted file mode 100644 index 175df03..0000000 --- a/modbus/pymodbus/constants.py +++ /dev/null @@ -1,268 +0,0 @@ -''' -Constants For Modbus Server/Client ----------------------------------- - -This is the single location for storing default -values for the servers and clients. -''' -from pymodbus.interfaces import Singleton - - -class Defaults(Singleton): - ''' A collection of modbus default values - - .. attribute:: Port - - The default modbus tcp server port (502) - - .. attribute:: TLSPort - - The default modbus tcp over tls server port (802) - - - .. attribute:: Backoff - - The default exponential backoff delay (0.3 seconds) - - .. attribute:: Retries - - The default number of times a client should retry the given - request before failing (3) - - .. attribute:: RetryOnEmpty - - A flag indicating if a transaction should be retried in the - case that an empty response is received. This is useful for - slow clients that may need more time to process a request. - - .. attribute:: RetryOnInvalid - - A flag indicating if a transaction should be retried in the - case that an invalid response is received. - - .. attribute:: Timeout - - The default amount of time a client should wait for a request - to be processed (3 seconds) - - .. attribute:: Reconnects - - The default number of times a client should attempt to reconnect - before deciding the server is down (0) - - .. attribute:: TransactionId - - The starting transaction identifier number (0) - - .. attribute:: ProtocolId - - The modbus protocol id. Currently this is set to 0 in all - but proprietary implementations. - - .. attribute:: UnitId - - The modbus slave addrss. Currently this is set to 0x00 which - means this request should be broadcast to all the slave devices - (really means that all the devices should respons). - - .. attribute:: Baudrate - - The speed at which the data is transmitted over the serial line. - This defaults to 19200. - - .. attribute:: Parity - - The type of checksum to use to verify data integrity. This can be - on of the following:: - - - (E)ven - 1 0 1 0 | P(0) - - (O)dd - 1 0 1 0 | P(1) - - (N)one - 1 0 1 0 | no parity - - This defaults to (N)one. - - .. attribute:: Bytesize - - The number of bits in a byte of serial data. This can be one of - 5, 6, 7, or 8. This defaults to 8. - - .. attribute:: Stopbits - - The number of bits sent after each character in a message to - indicate the end of the byte. This defaults to 1. - - .. attribute:: ZeroMode - - Indicates if the slave datastore should use indexing at 0 or 1. - More about this can be read in section 4.4 of the modbus specification. - - .. attribute:: IgnoreMissingSlaves - - In case a request is made to a missing slave, this defines if an error - should be returned or simply ignored. This is useful for the case of a - serial server emulater where a request to a non-existant slave on a bus - will never respond. The client in this case will simply timeout. - - .. attribute:: broadcast_enable - - When False unit_id 0 will be treated as any other unit_id. When True and - the unit_id is 0 the server will execute all requests on all server - contexts and not respond and the client will skip trying to receive a - response. Default value False does not conform to Modbus spec but maintains - legacy behavior for existing pymodbus users. - - ''' - Port = 502 - TLSPort = 802 - Backoff = 0.3 - Retries = 3 - RetryOnEmpty = False - RetryOnInvalid = False - Timeout = 3 - Reconnects = 0 - TransactionId = 0 - ProtocolId = 0 - UnitId = 0x00 - Baudrate = 19200 - Parity = 'N' - Bytesize = 8 - Stopbits = 1 - ZeroMode = False - IgnoreMissingSlaves = False - ReadSize = 1024 - broadcast_enable = False - -class ModbusStatus(Singleton): - ''' - These represent various status codes in the modbus - protocol. - - .. attribute:: Waiting - - This indicates that a modbus device is currently - waiting for a given request to finish some running task. - - .. attribute:: Ready - - This indicates that a modbus device is currently - free to perform the next request task. - - .. attribute:: On - - This indicates that the given modbus entity is on - - .. attribute:: Off - - This indicates that the given modbus entity is off - - .. attribute:: SlaveOn - - This indicates that the given modbus slave is running - - .. attribute:: SlaveOff - - This indicates that the given modbus slave is not running - ''' - Waiting = 0xffff - Ready = 0x0000 - On = 0xff00 - Off = 0x0000 - SlaveOn = 0xff - SlaveOff = 0x00 - - -class Endian(Singleton): - ''' An enumeration representing the various byte endianess. - - .. attribute:: Auto - - This indicates that the byte order is chosen by the - current native environment. - - .. attribute:: Big - - This indicates that the bytes are in little endian format - - .. attribute:: Little - - This indicates that the bytes are in big endian format - - .. note:: I am simply borrowing the format strings from the - python struct module for my convenience. - ''' - Auto = '@' - Big = '>' - Little = '<' - - -class ModbusPlusOperation(Singleton): - ''' Represents the type of modbus plus request - - .. attribute:: GetStatistics - - Operation requesting that the current modbus plus statistics - be returned in the response. - - .. attribute:: ClearStatistics - - Operation requesting that the current modbus plus statistics - be cleared and not returned in the response. - ''' - GetStatistics = 0x0003 - ClearStatistics = 0x0004 - - -class DeviceInformation(Singleton): - ''' Represents what type of device information to read - - .. attribute:: Basic - - This is the basic (required) device information to be returned. - This includes VendorName, ProductCode, and MajorMinorRevision - code. - - .. attribute:: Regular - - In addition to basic data objects, the device provides additional - and optional identification and description data objects. All of - the objects of this category are defined in the standard but their - implementation is optional. - - .. attribute:: Extended - - In addition to regular data objects, the device provides additional - and optional identification and description private data about the - physical device itself. All of these data are device dependent. - - .. attribute:: Specific - - Request to return a single data object. - ''' - Basic = 0x01 - Regular = 0x02 - Extended = 0x03 - Specific = 0x04 - - -class MoreData(Singleton): - ''' Represents the more follows condition - - .. attribute:: Nothing - - This indiates that no more objects are going to be returned. - - .. attribute:: KeepReading - - This indicates that there are more objects to be returned. - ''' - Nothing = 0x00 - KeepReading = 0xFF - -#---------------------------------------------------------------------------# -# Exported Identifiers -#---------------------------------------------------------------------------# -__all__ = [ - "Defaults", "ModbusStatus", "Endian", - "ModbusPlusOperation", - "DeviceInformation", "MoreData", -] diff --git a/modbus/pymodbus/datastore/__init__.py b/modbus/pymodbus/datastore/__init__.py deleted file mode 100644 index a981d4b..0000000 --- a/modbus/pymodbus/datastore/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from pymodbus.datastore.store import ModbusSequentialDataBlock -from pymodbus.datastore.store import ModbusSparseDataBlock -from pymodbus.datastore.context import ModbusSlaveContext -from pymodbus.datastore.context import ModbusServerContext - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "ModbusSequentialDataBlock", "ModbusSparseDataBlock", - "ModbusSlaveContext", "ModbusServerContext", -] diff --git a/modbus/pymodbus/datastore/context.py b/modbus/pymodbus/datastore/context.py deleted file mode 100644 index 7ba7bf4..0000000 --- a/modbus/pymodbus/datastore/context.py +++ /dev/null @@ -1,184 +0,0 @@ -from pymodbus.exceptions import ParameterException, NoSuchSlaveException -from pymodbus.interfaces import IModbusSlaveContext -from pymodbus.datastore.store import ModbusSequentialDataBlock -from pymodbus.constants import Defaults -from pymodbus.compat import iteritems, itervalues - -#---------------------------------------------------------------------------# -# Logging -#---------------------------------------------------------------------------# -import logging -_logger = logging.getLogger(__name__) - - -#---------------------------------------------------------------------------# -# Slave Contexts -#---------------------------------------------------------------------------# -class ModbusSlaveContext(IModbusSlaveContext): - ''' - This creates a modbus data model with each data access - stored in its own personal block - ''' - - def __init__(self, *args, **kwargs): - ''' Initializes the datastores, defaults to fully populated - sequential data blocks if none are passed in. - - :param kwargs: Each element is a ModbusDataBlock - - 'di' - Discrete Inputs initializer - 'co' - Coils initializer - 'hr' - Holding Register initializer - 'ir' - Input Registers iniatializer - ''' - self.store = dict() - self.store['d'] = kwargs.get('di', ModbusSequentialDataBlock.create()) - self.store['c'] = kwargs.get('co', ModbusSequentialDataBlock.create()) - self.store['i'] = kwargs.get('ir', ModbusSequentialDataBlock.create()) - self.store['h'] = kwargs.get('hr', ModbusSequentialDataBlock.create()) - self.zero_mode = kwargs.get('zero_mode', Defaults.ZeroMode) - - def __str__(self): - ''' Returns a string representation of the context - - :returns: A string representation of the context - ''' - return "Modbus Slave Context" - - def reset(self): - ''' Resets all the datastores to their default values ''' - for datastore in itervalues(self.store): - datastore.reset() - - def validate(self, fx, address, count=1): - ''' Validates the request to make sure it is in range - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to test - :returns: True if the request in within range, False otherwise - ''' - if not self.zero_mode: - address = address + 1 - _logger.debug("validate: fc-[%d] address-%d: count-%d" % (fx, address, - count)) - return self.store[self.decode(fx)].validate(address, count) - - def getValues(self, fx, address, count=1): - ''' Get `count` values from datastore - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - ''' - if not self.zero_mode: - address = address + 1 - _logger.debug("getValues fc-[%d] address-%d: count-%d" % (fx, address, - count)) - return self.store[self.decode(fx)].getValues(address, count) - - def setValues(self, fx, address, values): - ''' Sets the datastore with the supplied values - - :param fx: The function we are working with - :param address: The starting address - :param values: The new values to be set - ''' - if not self.zero_mode: - address = address + 1 - _logger.debug("setValues[%d] %d:%d" % (fx, address, len(values))) - self.store[self.decode(fx)].setValues(address, values) - - def register(self, fc, fx, datablock=None): - """ - Registers a datablock with the slave context - :param fc: function code (int) - :param fx: string representation of function code (e.g 'cf' ) - :param datablock: datablock to associate with this function code - :return: - """ - self.store[fx] = datablock or ModbusSequentialDataBlock.create() - self._IModbusSlaveContext__fx_mapper[fc] = fx - - -class ModbusServerContext(object): - ''' This represents a master collection of slave contexts. - If single is set to true, it will be treated as a single - context so every unit-id returns the same context. If single - is set to false, it will be interpreted as a collection of - slave contexts. - ''' - - def __init__(self, slaves=None, single=True): - ''' Initializes a new instance of a modbus server context. - - :param slaves: A dictionary of client contexts - :param single: Set to true to treat this as a single context - ''' - self.single = single - self._slaves = slaves or {} - if self.single: - self._slaves = {Defaults.UnitId: self._slaves} - - def __iter__(self): - ''' Iterater over the current collection of slave - contexts. - - :returns: An iterator over the slave contexts - ''' - return iteritems(self._slaves) - - def __contains__(self, slave): - ''' Check if the given slave is in this list - - :param slave: slave The slave to check for existence - :returns: True if the slave exists, False otherwise - ''' - if self.single and self._slaves: - return True - else: - return slave in self._slaves - - def __setitem__(self, slave, context): - ''' Used to set a new slave context - - :param slave: The slave context to set - :param context: The new context to set for this slave - ''' - if self.single: - slave = Defaults.UnitId - if 0xf7 >= slave >= 0x00: - self._slaves[slave] = context - else: - raise NoSuchSlaveException('slave index :{} ' - 'out of range'.format(slave)) - - def __delitem__(self, slave): - ''' Wrapper used to access the slave context - - :param slave: The slave context to remove - ''' - if not self.single and (0xf7 >= slave >= 0x00): - del self._slaves[slave] - else: - raise NoSuchSlaveException('slave index: {} ' - 'out of range'.format(slave)) - - def __getitem__(self, slave): - ''' Used to get access to a slave context - - :param slave: The slave context to get - :returns: The requested slave context - ''' - if self.single: - slave = Defaults.UnitId - if slave in self._slaves: - return self._slaves.get(slave) - else: - raise NoSuchSlaveException("slave - {} does not exist, " - "or is out of range".format(slave)) - - def slaves(self): - # Python3 now returns keys() as iterable - return list(self._slaves.keys()) diff --git a/modbus/pymodbus/datastore/database/__init__.py b/modbus/pymodbus/datastore/database/__init__.py deleted file mode 100644 index dbb2609..0000000 --- a/modbus/pymodbus/datastore/database/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from pymodbus.datastore.database.sql_datastore import SqlSlaveContext -from pymodbus.datastore.database.redis_datastore import RedisSlaveContext - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = ["SqlSlaveContext", "RedisSlaveContext"] diff --git a/modbus/pymodbus/datastore/database/redis_datastore.py b/modbus/pymodbus/datastore/database/redis_datastore.py deleted file mode 100644 index 98bf5be..0000000 --- a/modbus/pymodbus/datastore/database/redis_datastore.py +++ /dev/null @@ -1,243 +0,0 @@ -import redis -from pymodbus.interfaces import IModbusSlaveContext -from pymodbus.utilities import pack_bitstring, unpack_bitstring - -#---------------------------------------------------------------------------# -# Logging -#---------------------------------------------------------------------------# -import logging -_logger = logging.getLogger(__name__) - - -#---------------------------------------------------------------------------# -# Context -#---------------------------------------------------------------------------# -class RedisSlaveContext(IModbusSlaveContext): - ''' - This is a modbus slave context using redis as a backing - store. - ''' - - def __init__(self, **kwargs): - ''' Initializes the datastores - - :param host: The host to connect to - :param port: The port to connect to - :param prefix: A prefix for the keys - ''' - host = kwargs.get('host', 'localhost') - port = kwargs.get('port', 6379) - self.prefix = kwargs.get('prefix', 'pymodbus') - self.client = kwargs.get('client', redis.Redis(host=host, port=port)) - self._build_mapping() - - def __str__(self): - ''' Returns a string representation of the context - - :returns: A string representation of the context - ''' - return "Redis Slave Context %s" % self.client - - def reset(self): - ''' Resets all the datastores to their default values ''' - self.client.flushall() - - def validate(self, fx, address, count=1): - ''' Validates the request to make sure it is in range - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to test - :returns: True if the request in within range, False otherwise - ''' - address = address + 1 # section 4.4 of specification - _logger.debug("validate[%d] %d:%d" % (fx, address, count)) - return self._val_callbacks[self.decode(fx)](address, count) - - def getValues(self, fx, address, count=1): - ''' Get `count` values from datastore - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - ''' - address = address + 1 # section 4.4 of specification - _logger.debug("getValues[%d] %d:%d" % (fx, address, count)) - return self._get_callbacks[self.decode(fx)](address, count) - - def setValues(self, fx, address, values): - ''' Sets the datastore with the supplied values - - :param fx: The function we are working with - :param address: The starting address - :param values: The new values to be set - ''' - address = address + 1 # section 4.4 of specification - _logger.debug("setValues[%d] %d:%d" % (fx, address, len(values))) - self._set_callbacks[self.decode(fx)](address, values) - - #--------------------------------------------------------------------------# - # Redis Helper Methods - #--------------------------------------------------------------------------# - def _get_prefix(self, key): - ''' This is a helper to abstract getting bit values - - :param key: The key prefix to use - :returns: The key prefix to redis - ''' - return "%s:%s" % (self.prefix, key) - - def _build_mapping(self): - ''' - A quick helper method to build the function - code mapper. - ''' - self._val_callbacks = { - 'd': lambda o, c: self._val_bit('d', o, c), - 'c': lambda o, c: self._val_bit('c', o, c), - 'h': lambda o, c: self._val_reg('h', o, c), - 'i': lambda o, c: self._val_reg('i', o, c), - } - self._get_callbacks = { - 'd': lambda o, c: self._get_bit('d', o, c), - 'c': lambda o, c: self._get_bit('c', o, c), - 'h': lambda o, c: self._get_reg('h', o, c), - 'i': lambda o, c: self._get_reg('i', o, c), - } - self._set_callbacks = { - 'd': lambda o, v: self._set_bit('d', o, v), - 'c': lambda o, v: self._set_bit('c', o, v), - 'h': lambda o, v: self._set_reg('h', o, v), - 'i': lambda o, v: self._set_reg('i', o, v), - } - - #--------------------------------------------------------------------------# - # Redis discrete implementation - #--------------------------------------------------------------------------# - _bit_size = 16 - _bit_default = '\x00' * (_bit_size % 8) - - def _get_bit_values(self, key, offset, count): - ''' This is a helper to abstract getting bit values - - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - ''' - key = self._get_prefix(key) - s = divmod(offset, self._bit_size)[0] - e = divmod(offset + count, self._bit_size)[0] - - request = ('%s:%s' % (key, v) for v in range(s, e + 1)) - response = self.client.mget(request) - return response - - def _val_bit(self, key, offset, count): - ''' Validates that the given range is currently set in redis. - If any of the keys return None, then it is invalid. - - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - ''' - response = self._get_bit_values(key, offset, count) - return True if None not in response else False - - def _get_bit(self, key, offset, count): - ''' - - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - ''' - response = self._get_bit_values(key, offset, count) - response = (r or self._bit_default for r in response) - result = ''.join(response) - result = unpack_bitstring(result) - return result[offset:offset + count] - - def _set_bit(self, key, offset, values): - ''' - - :param key: The key prefix to use - :param offset: The address offset to start at - :param values: The values to set - ''' - count = len(values) - s = divmod(offset, self._bit_size)[0] - e = divmod(offset + count, self._bit_size)[0] - value = pack_bitstring(values) - - current = self._get_bit_values(key, offset, count) - current = (r or self._bit_default for r in current) - current = ''.join(current) - current = current[0:offset] + value.decode('utf-8') + current[offset + count:] - final = (current[s:s + self._bit_size] for s in range(0, count, self._bit_size)) - - key = self._get_prefix(key) - request = ('%s:%s' % (key, v) for v in range(s, e + 1)) - request = dict(zip(request, final)) - self.client.mset(request) - - #--------------------------------------------------------------------------# - # Redis register implementation - #--------------------------------------------------------------------------# - _reg_size = 16 - _reg_default = '\x00' * (_reg_size % 8) - - def _get_reg_values(self, key, offset, count): - ''' This is a helper to abstract getting register values - - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - ''' - key = self._get_prefix(key) - #s = divmod(offset, self.__reg_size)[0] - #e = divmod(offset+count, self.__reg_size)[0] - - #request = ('%s:%s' % (key, v) for v in range(s, e + 1)) - request = ('%s:%s' % (key, v) for v in range(offset, count + 1)) - response = self.client.mget(request) - return response - - def _val_reg(self, key, offset, count): - ''' Validates that the given range is currently set in redis. - If any of the keys return None, then it is invalid. - - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - ''' - response = self._get_reg_values(key, offset, count) - return None not in response - - def _get_reg(self, key, offset, count): - ''' - - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - ''' - response = self._get_reg_values(key, offset, count) - response = [r or self._reg_default for r in response] - return response[offset:offset + count] - - def _set_reg(self, key, offset, values): - ''' - - :param key: The key prefix to use - :param offset: The address offset to start at - :param values: The values to set - ''' - count = len(values) - #s = divmod(offset, self.__reg_size) - #e = divmod(offset+count, self.__reg_size) - - #current = self.__get_reg_values(key, offset, count) - - key = self._get_prefix(key) - request = ('%s:%s' % (key, v) for v in range(offset, count + 1)) - request = dict(zip(request, values)) - self.client.mset(request) diff --git a/modbus/pymodbus/datastore/database/sql_datastore.py b/modbus/pymodbus/datastore/database/sql_datastore.py deleted file mode 100644 index 769dab7..0000000 --- a/modbus/pymodbus/datastore/database/sql_datastore.py +++ /dev/null @@ -1,184 +0,0 @@ -import sqlalchemy -import sqlalchemy.types as sqltypes -from sqlalchemy.sql import and_ -from sqlalchemy.schema import UniqueConstraint -from sqlalchemy.sql.expression import bindparam - -from pymodbus.exceptions import NotImplementedException -from pymodbus.interfaces import IModbusSlaveContext - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Context -# --------------------------------------------------------------------------- # -class SqlSlaveContext(IModbusSlaveContext): - """ - This creates a modbus data model with each data access - stored in its own personal block - """ - - def __init__(self, *args, **kwargs): - """ Initializes the datastores - - :param kwargs: Each element is a ModbusDataBlock - """ - self.table = kwargs.get('table', 'pymodbus') - self.database = kwargs.get('database', 'sqlite:///pymodbus.db') - self._db_create(self.table, self.database) - - def __str__(self): - """ Returns a string representation of the context - - :returns: A string representation of the context - """ - return "Modbus Slave Context" - - def reset(self): - """ Resets all the datastores to their default values """ - self._metadata.drop_all() - self._db_create(self.table, self.database) - - def validate(self, fx, address, count=1): - """ Validates the request to make sure it is in range - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to test - :returns: True if the request in within range, False otherwise - """ - address = address + 1 # section 4.4 of specification - _logger.debug("validate[%d] %d:%d" % (fx, address, count)) - return self._validate(self.decode(fx), address, count) - - def getValues(self, fx, address, count=1): - """ Get `count` values from datastore - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - """ - address = address + 1 # section 4.4 of specification - _logger.debug("get-values[%d] %d:%d" % (fx, address, count)) - return self._get(self.decode(fx), address, count) - - def setValues(self, fx, address, values, update=True): - """ Sets the datastore with the supplied values - - :param fx: The function we are working with - :param address: The starting address - :param values: The new values to be set - :param update: Update existing register in the db - """ - address = address + 1 # section 4.4 of specification - _logger.debug("set-values[%d] %d:%d" % (fx, address, len(values))) - if update: - self._update(self.decode(fx), address, values) - else: - self._set(self.decode(fx), address, values) - - # ----------------------------------------------------------------------- # - # Sqlite Helper Methods - # ----------------------------------------------------------------------- # - def _db_create(self, table, database): - """ A helper method to initialize the database and handles - - :param table: The table name to create - :param database: The database uri to use - """ - self._engine = sqlalchemy.create_engine(database, echo=False) - self._metadata = sqlalchemy.MetaData(self._engine) - self._table = sqlalchemy.Table(table, self._metadata, - sqlalchemy.Column('type', sqltypes.String(1)), - sqlalchemy.Column('index', sqltypes.Integer), - sqlalchemy.Column('value', sqltypes.Integer), - UniqueConstraint('type', 'index', name='key')) - self._table.create(checkfirst=True) - self._connection = self._engine.connect() - - def _get(self, type, offset, count): - """ - :param type: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - :returns: The resulting values - """ - query = self._table.select(and_( - self._table.c.type == type, - self._table.c.index >= offset, - self._table.c.index <= offset + count - 1) - ) - query = query.order_by(self._table.c.index.asc()) - result = self._connection.execute(query).fetchall() - return [row.value for row in result] - - def _build_set(self, type, offset, values, prefix=''): - """ A helper method to generate the sql update context - - :param type: The key prefix to use - :param offset: The address offset to start at - :param values: The values to set - :param prefix: Prefix fields index and type, defaults to empty string - """ - result = [] - for index, value in enumerate(values): - result.append({ - prefix + 'type': type, - prefix + 'index': offset + index, - 'value': value - }) - return result - - def _check(self, type, offset, values): - result = self._get(type, offset, count=1) - return False if len(result) > 0 else True - - def _set(self, type, offset, values): - """ - - :param key: The type prefix to use - :param offset: The address offset to start at - :param values: The values to set - """ - if self._check(type, offset, values): - context = self._build_set(type, offset, values) - query = self._table.insert() - result = self._connection.execute(query, context) - return result.rowcount == len(values) - else: - return False - - def _update(self, type, offset, values): - """ - - :param type: The type prefix to use - :param offset: The address offset to start at - :param values: The values to set - """ - context = self._build_set(type, offset, values, prefix='x_') - query = self._table.update().values(value='value') - query = query.where(and_( - self._table.c.type == bindparam('x_type'), - self._table.c.index == bindparam('x_index'))) - result = self._connection.execute(query, context) - return result.rowcount == len(values) - - def _validate(self, type, offset, count): - """ - :param key: The key prefix to use - :param offset: The address offset to start at - :param count: The number of bits to read - :returns: The result of the validation - """ - query = self._table.select(and_( - self._table.c.type == type, - self._table.c.index >= offset, - self._table.c.index <= offset + count - 1)) - result = self._connection.execute(query).fetchall() - return len(result) == count diff --git a/modbus/pymodbus/datastore/remote.py b/modbus/pymodbus/datastore/remote.py deleted file mode 100644 index 17161cc..0000000 --- a/modbus/pymodbus/datastore/remote.py +++ /dev/null @@ -1,108 +0,0 @@ -from pymodbus.exceptions import NotImplementedException -from pymodbus.interfaces import IModbusSlaveContext - -#---------------------------------------------------------------------------# -# Logging -#---------------------------------------------------------------------------# -import logging -_logger = logging.getLogger(__name__) - - -#---------------------------------------------------------------------------# -# Context -#---------------------------------------------------------------------------# -class RemoteSlaveContext(IModbusSlaveContext): - ''' TODO - This creates a modbus data model that connects to - a remote device (depending on the client used) - ''' - - def __init__(self, client, unit=None): - ''' Initializes the datastores - - :param client: The client to retrieve values with - :param unit: Unit ID of the remote slave - ''' - self._client = client - self.unit = unit - self.__build_mapping() - - def reset(self): - ''' Resets all the datastores to their default values ''' - raise NotImplementedException() - - def validate(self, fx, address, count=1): - ''' Validates the request to make sure it is in range - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to test - :returns: True if the request in within range, False otherwise - ''' - _logger.debug("validate[%d] %d:%d" % (fx, address, count)) - result = self.__get_callbacks[self.decode(fx)](address, count) - return not result.isError() - - def getValues(self, fx, address, count=1): - ''' Get `count` values from datastore - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - ''' - # TODO deal with deferreds - _logger.debug("get values[%d] %d:%d" % (fx, address, count)) - result = self.__get_callbacks[self.decode(fx)](address, count) - return self.__extract_result(self.decode(fx), result) - - def setValues(self, fx, address, values): - ''' Sets the datastore with the supplied values - - :param fx: The function we are working with - :param address: The starting address - :param values: The new values to be set - ''' - # TODO deal with deferreds - _logger.debug("set values[%d] %d:%d" % (fx, address, len(values))) - self.__set_callbacks[self.decode(fx)](address, values) - - def __str__(self): - ''' Returns a string representation of the context - - :returns: A string representation of the context - ''' - return "Remote Slave Context(%s)" % self._client - - def __build_mapping(self): - ''' - A quick helper method to build the function - code mapper. - ''' - kwargs = {} - if self.unit: - kwargs["unit"] = self.unit - self.__get_callbacks = { - 'd': lambda a, c: self._client.read_discrete_inputs(a, c, **kwargs), - 'c': lambda a, c: self._client.read_coils(a, c, **kwargs), - 'h': lambda a, c: self._client.read_holding_registers(a, c, **kwargs), - 'i': lambda a, c: self._client.read_input_registers(a, c, **kwargs), - } - self.__set_callbacks = { - 'd': lambda a, v: self._client.write_coils(a, v, **kwargs), - 'c': lambda a, v: self._client.write_coils(a, v, **kwargs), - 'h': lambda a, v: self._client.write_registers(a, v, **kwargs), - 'i': lambda a, v: self._client.write_registers(a, v, **kwargs), - } - - def __extract_result(self, fx, result): - ''' A helper method to extract the values out of - a response. TODO make this consistent (values?) - ''' - if not result.isError(): - if fx in ['d', 'c']: - return result.bits - if fx in ['h', 'i']: - return result.registers - else: - return result diff --git a/modbus/pymodbus/datastore/store.py b/modbus/pymodbus/datastore/store.py deleted file mode 100644 index 3f45767..0000000 --- a/modbus/pymodbus/datastore/store.py +++ /dev/null @@ -1,313 +0,0 @@ -""" -Modbus Server Datastore -------------------------- - -For each server, you will create a ModbusServerContext and pass -in the default address space for each data access. The class -will create and manage the data. - -Further modification of said data accesses should be performed -with [get,set][access]Values(address, count) - -Datastore Implementation -------------------------- - -There are two ways that the server datastore can be implemented. -The first is a complete range from 'address' start to 'count' -number of indecies. This can be thought of as a straight array:: - - data = range(1, 1 + count) - [1,2,3,...,count] - -The other way that the datastore can be implemented (and how -many devices implement it) is a associate-array:: - - data = {1:'1', 3:'3', ..., count:'count'} - [1,3,...,count] - -The difference between the two is that the latter will allow -arbitrary gaps in its datastore while the former will not. -This is seen quite commonly in some modbus implementations. -What follows is a clear example from the field: - -Say a company makes two devices to monitor power usage on a rack. -One works with three-phase and the other with a single phase. The -company will dictate a modbus data mapping such that registers:: - - n: phase 1 power - n+1: phase 2 power - n+2: phase 3 power - -Using this, layout, the first device will implement n, n+1, and n+2, -however, the second device may set the latter two values to 0 or -will simply not implmented the registers thus causing a single read -or a range read to fail. - -I have both methods implemented, and leave it up to the user to change -based on their preference. -""" -from pymodbus.exceptions import NotImplementedException, ParameterException -from pymodbus.compat import iteritems, iterkeys, itervalues, get_next - -#---------------------------------------------------------------------------# -# Logging -#---------------------------------------------------------------------------# -import logging -_logger = logging.getLogger(__name__) - - -#---------------------------------------------------------------------------# -# Datablock Storage -#---------------------------------------------------------------------------# -class BaseModbusDataBlock(object): - ''' - Base class for a modbus datastore - - Derived classes must create the following fields: - @address The starting address point - @defult_value The default value of the datastore - @values The actual datastore values - - Derived classes must implemented the following methods: - validate(self, address, count=1) - getValues(self, address, count=1) - setValues(self, address, values) - ''' - - def default(self, count, value=False): - ''' Used to initialize a store to one value - - :param count: The number of fields to set - :param value: The default value to set to the fields - ''' - self.default_value = value - self.values = [self.default_value] * count - self.address = 0x00 - - def reset(self): - ''' Resets the datastore to the initialized default value ''' - self.values = [self.default_value] * len(self.values) - - def validate(self, address, count=1): - ''' Checks to see if the request is in range - - :param address: The starting address - :param count: The number of values to test for - :returns: True if the request in within range, False otherwise - ''' - raise NotImplementedException("Datastore Address Check") - - def getValues(self, address, count=1): - ''' Returns the requested values from the datastore - - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - ''' - raise NotImplementedException("Datastore Value Retrieve") - - def setValues(self, address, values): - ''' Returns the requested values from the datastore - - :param address: The starting address - :param values: The values to store - ''' - raise NotImplementedException("Datastore Value Retrieve") - - def __str__(self): - ''' Build a representation of the datastore - - :returns: A string representation of the datastore - ''' - return "DataStore(%d, %d)" % (len(self.values), self.default_value) - - def __iter__(self): - ''' Iterater over the data block data - - :returns: An iterator of the data block data - ''' - if isinstance(self.values, dict): - return iteritems(self.values) - return enumerate(self.values, self.address) - - -class ModbusSequentialDataBlock(BaseModbusDataBlock): - ''' Creates a sequential modbus datastore ''' - - def __init__(self, address, values): - ''' Initializes the datastore - - :param address: The starting address of the datastore - :param values: Either a list or a dictionary of values - ''' - self.address = address - if hasattr(values, '__iter__'): - self.values = list(values) - else: - self.values = [values] - self.default_value = self.values[0].__class__() - - @classmethod - def create(klass): - ''' Factory method to create a datastore with the - full address space initialized to 0x00 - - :returns: An initialized datastore - ''' - return klass(0x00, [0x00] * 65536) - - def validate(self, address, count=1): - ''' Checks to see if the request is in range - - :param address: The starting address - :param count: The number of values to test for - :returns: True if the request in within range, False otherwise - ''' - result = (self.address <= address) - result &= ((self.address + len(self.values)) >= (address + count)) - return result - - def getValues(self, address, count=1): - ''' Returns the requested values of the datastore - - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - ''' - start = address - self.address - return self.values[start:start + count] - - def setValues(self, address, values): - ''' Sets the requested values of the datastore - - :param address: The starting address - :param values: The new values to be set - ''' - if not isinstance(values, list): - values = [values] - start = address - self.address - self.values[start:start + len(values)] = values - - -class ModbusSparseDataBlock(BaseModbusDataBlock): - """ - Creates a sparse modbus datastore - - E.g Usage. - sparse = ModbusSparseDataBlock({10: [3, 5, 6, 8], 30: 1, 40: [0]*20}) - - This would create a datablock with 3 blocks starting at - offset 10 with length 4 , 30 with length 1 and 40 with length 20 - - sparse = ModbusSparseDataBlock([10]*100) - Creates a sparse datablock of length 100 starting at offset 0 and default value of 10 - - sparse = ModbusSparseDataBlock() --> Create Empty datablock - sparse.setValues(0, [10]*10) --> Add block 1 at offset 0 with length 10 (default value 10) - sparse.setValues(30, [20]*5) --> Add block 2 at offset 30 with length 5 (default value 20) - - if mutable is set to True during initialization, the datablock can not be altered with - setValues (new datablocks can not be added) - """ - - def __init__(self, values=None, mutable=True): - """ - Initializes a sparse datastore. Will only answer to addresses - registered, either initially here, or later via setValues() - - :param values: Either a list or a dictionary of values - :param mutable: The data-block can be altered later with setValues(i.e add more blocks) - - If values are list , This is as good as sequential datablock. - Values as dictionary should be in {offset: } format, if values - is a list, a sparse datablock is created starting at offset with the length of values. - If values is a integer, then the value is set for the corresponding offset. - - """ - self.values = {} - self._process_values(values) - self.mutable = mutable - self.default_value = self.values.copy() - self.address = get_next(iterkeys(self.values), None) - - @classmethod - def create(klass, values=None): - ''' Factory method to create sparse datastore. - Use setValues to initialize registers. - - :param values: Either a list or a dictionary of values - :returns: An initialized datastore - ''' - return klass(values) - - def reset(self): - ''' Reset the store to the initially provided defaults''' - self.values = self.default_value.copy() - - def validate(self, address, count=1): - ''' Checks to see if the request is in range - - :param address: The starting address - :param count: The number of values to test for - :returns: True if the request in within range, False otherwise - ''' - if count == 0: - return False - handle = set(range(address, address + count)) - return handle.issubset(set(iterkeys(self.values))) - - def getValues(self, address, count=1): - ''' Returns the requested values of the datastore - - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - ''' - return [self.values[i] for i in range(address, address + count)] - - def _process_values(self, values): - def _process_as_dict(values): - for idx, val in iteritems(values): - if isinstance(val, (list, tuple)): - for i, v in enumerate(val): - self.values[idx + i] = v - else: - self.values[idx] = int(val) - if isinstance(values, dict): - _process_as_dict(values) - return - if hasattr(values, '__iter__'): - values = dict(enumerate(values)) - elif values is None: - values = {} # Must make a new dict here per instance - else: - raise ParameterException("Values for datastore must " - "be a list or dictionary") - _process_as_dict(values) - - def setValues(self, address, values, use_as_default=False): - ''' Sets the requested values of the datastore - - :param address: The starting address - :param values: The new values to be set - :param use_as_default: Use the values as default - ''' - if isinstance(values, dict): - new_offsets = list(set(list(values.keys())) - set(list(self.values.keys()))) - if new_offsets and not self.mutable: - raise ParameterException("Offsets {} not " - "in range".format(new_offsets)) - self._process_values(values) - else: - if not isinstance(values, list): - values = [values] - for idx, val in enumerate(values): - if address+idx not in self.values and not self.mutable: - raise ParameterException("Offset {} not " - "in range".format(address+idx)) - self.values[address + idx] = val - if not self.address: - self.address = get_next(iterkeys(self.values), None) - if use_as_default: - for idx, val in iteritems(self.values): - self.default_value[idx] = val diff --git a/modbus/pymodbus/device.py b/modbus/pymodbus/device.py deleted file mode 100644 index 829abe7..0000000 --- a/modbus/pymodbus/device.py +++ /dev/null @@ -1,626 +0,0 @@ -""" -Modbus Device Controller -------------------------- - -These are the device management handlers. They should be -maintained in the server context and the various methods -should be inserted in the correct locations. -""" -from pymodbus.constants import DeviceInformation -from pymodbus.interfaces import Singleton -from pymodbus.utilities import dict_property -from pymodbus.compat import iteritems, itervalues, izip, int2byte - -from collections import OrderedDict - -#---------------------------------------------------------------------------# -# Network Access Control -#---------------------------------------------------------------------------# -class ModbusAccessControl(Singleton): - ''' - This is a simple implementation of a Network Management System table. - Its purpose is to control access to the server (if it is used). - We assume that if an entry is in the table, it is allowed accesses to - resources. However, if the host does not appear in the table (all - unknown hosts) its connection will simply be closed. - - Since it is a singleton, only one version can possible exist and all - instances pull from here. - ''' - __nmstable = [ - "127.0.0.1", - ] - - def __iter__(self): - ''' Iterater over the network access table - - :returns: An iterator of the network access table - ''' - return self.__nmstable.__iter__() - - def __contains__(self, host): - ''' Check if a host is allowed to access resources - - :param host: The host to check - ''' - return host in self.__nmstable - - def add(self, host): - ''' Add allowed host(s) from the NMS table - - :param host: The host to add - ''' - if not isinstance(host, list): - host = [host] - for entry in host: - if entry not in self.__nmstable: - self.__nmstable.append(entry) - - def remove(self, host): - ''' Remove allowed host(s) from the NMS table - - :param host: The host to remove - ''' - if not isinstance(host, list): - host = [host] - for entry in host: - if entry in self.__nmstable: - self.__nmstable.remove(entry) - - def check(self, host): - ''' Check if a host is allowed to access resources - - :param host: The host to check - ''' - return host in self.__nmstable - - -#---------------------------------------------------------------------------# -# Modbus Plus Statistics -#---------------------------------------------------------------------------# -class ModbusPlusStatistics(object): - ''' - This is used to maintain the current modbus plus statistics count. As of - right now this is simply a stub to complete the modbus implementation. - For more information, see the modbus implementation guide page 87. - ''' - - __data = OrderedDict({ - 'node_type_id' : [0x00] * 2, # 00 - 'software_version_number' : [0x00] * 2, # 01 - 'network_address' : [0x00] * 2, # 02 - 'mac_state_variable' : [0x00] * 2, # 03 - 'peer_status_code' : [0x00] * 2, # 04 - 'token_pass_counter' : [0x00] * 2, # 05 - 'token_rotation_time' : [0x00] * 2, # 06 - - 'program_master_token_failed' : [0x00], # 07 hi - 'data_master_token_failed' : [0x00], # 07 lo - 'program_master_token_owner' : [0x00], # 08 hi - 'data_master_token_owner' : [0x00], # 08 lo - 'program_slave_token_owner' : [0x00], # 09 hi - 'data_slave_token_owner' : [0x00], # 09 lo - 'data_slave_command_transfer' : [0x00], # 10 hi - '__unused_10_lowbit' : [0x00], # 10 lo - - 'program_slave_command_transfer' : [0x00], # 11 hi - 'program_master_rsp_transfer' : [0x00], # 11 lo - 'program_slave_auto_logout' : [0x00], # 12 hi - 'program_master_connect_status' : [0x00], # 12 lo - 'receive_buffer_dma_overrun' : [0x00], # 13 hi - 'pretransmit_deferral_error' : [0x00], # 13 lo - 'frame_size_error' : [0x00], # 14 hi - 'repeated_command_received' : [0x00], # 14 lo - 'receiver_alignment_error' : [0x00], # 15 hi - 'receiver_collision_abort_error' : [0x00], # 15 lo - 'bad_packet_length_error' : [0x00], # 16 hi - 'receiver_crc_error' : [0x00], # 16 lo - 'transmit_buffer_dma_underrun' : [0x00], # 17 hi - 'bad_link_address_error' : [0x00], # 17 lo - - 'bad_mac_function_code_error' : [0x00], # 18 hi - 'internal_packet_length_error' : [0x00], # 18 lo - 'communication_failed_error' : [0x00], # 19 hi - 'communication_retries' : [0x00], # 19 lo - 'no_response_error' : [0x00], # 20 hi - 'good_receive_packet' : [0x00], # 20 lo - 'unexpected_path_error' : [0x00], # 21 hi - 'exception_response_error' : [0x00], # 21 lo - 'forgotten_transaction_error' : [0x00], # 22 hi - 'unexpected_response_error' : [0x00], # 22 lo - - 'active_station_bit_map' : [0x00] * 8, # 23-26 - 'token_station_bit_map' : [0x00] * 8, # 27-30 - 'global_data_bit_map' : [0x00] * 8, # 31-34 - 'receive_buffer_use_bit_map' : [0x00] * 8, # 35-37 - 'data_master_output_path' : [0x00] * 8, # 38-41 - 'data_slave_input_path' : [0x00] * 8, # 42-45 - 'program_master_outptu_path' : [0x00] * 8, # 46-49 - 'program_slave_input_path' : [0x00] * 8, # 50-53 - }) - - def __init__(self): - ''' - Initialize the modbus plus statistics with the default - information. - ''' - self.reset() - - def __iter__(self): - ''' Iterater over the statistics - - :returns: An iterator of the modbus plus statistics - ''' - return iteritems(self.__data) - - def reset(self): - ''' This clears all of the modbus plus statistics - ''' - for key in self.__data: - self.__data[key] = [0x00] * len(self.__data[key]) - - def summary(self): - ''' Returns a summary of the modbus plus statistics - - :returns: 54 16-bit words representing the status - ''' - return itervalues(self.__data) - - def encode(self): - ''' Returns a summary of the modbus plus statistics - - :returns: 54 16-bit words representing the status - ''' - total, values = [], sum(self.__data.values(), []) - for c in range(0, len(values), 2): - total.append((values[c] << 8) | values[c+1]) - return total - - -#---------------------------------------------------------------------------# -# Device Information Control -#---------------------------------------------------------------------------# -class ModbusDeviceIdentification(object): - ''' - This is used to supply the device identification - for the readDeviceIdentification function - - For more information read section 6.21 of the modbus - application protocol. - ''' - __data = { - 0x00: '', # VendorName - 0x01: '', # ProductCode - 0x02: '', # MajorMinorRevision - 0x03: '', # VendorUrl - 0x04: '', # ProductName - 0x05: '', # ModelName - 0x06: '', # UserApplicationName - 0x07: '', # reserved - 0x08: '', # reserved - # 0x80 -> 0xFF are private - } - - __names = [ - 'VendorName', - 'ProductCode', - 'MajorMinorRevision', - 'VendorUrl', - 'ProductName', - 'ModelName', - 'UserApplicationName', - ] - - def __init__(self, info=None): - ''' - Initialize the datastore with the elements you need. - (note acceptable range is [0x00-0x06,0x80-0xFF] inclusive) - - :param information: A dictionary of {int:string} of values - ''' - if isinstance(info, dict): - for key in info: - if (0x06 >= key >= 0x00) or (0xFF >= key >= 0x80): - self.__data[key] = info[key] - - def __iter__(self): - ''' Iterater over the device information - - :returns: An iterator of the device information - ''' - return iteritems(self.__data) - - def summary(self): - ''' Return a summary of the main items - - :returns: An dictionary of the main items - ''' - return dict(zip(self.__names, itervalues(self.__data))) - - def update(self, value): - ''' Update the values of this identity - using another identify as the value - - :param value: The value to copy values from - ''' - self.__data.update(value) - - def __setitem__(self, key, value): - ''' Wrapper used to access the device information - - :param key: The register to set - :param value: The new value for referenced register - ''' - if key not in [0x07, 0x08]: - self.__data[key] = value - - def __getitem__(self, key): - ''' Wrapper used to access the device information - - :param key: The register to read - ''' - return self.__data.setdefault(key, '') - - def __str__(self): - ''' Build a representation of the device - - :returns: A string representation of the device - ''' - return "DeviceIdentity" - - #-------------------------------------------------------------------------# - # Properties - #-------------------------------------------------------------------------# - VendorName = dict_property(lambda s: s.__data, 0) - ProductCode = dict_property(lambda s: s.__data, 1) - MajorMinorRevision = dict_property(lambda s: s.__data, 2) - VendorUrl = dict_property(lambda s: s.__data, 3) - ProductName = dict_property(lambda s: s.__data, 4) - ModelName = dict_property(lambda s: s.__data, 5) - UserApplicationName = dict_property(lambda s: s.__data, 6) - - -class DeviceInformationFactory(Singleton): - ''' This is a helper factory that really just hides - some of the complexity of processing the device information - requests (function code 0x2b 0x0e). - ''' - - __lookup = { - DeviceInformation.Basic: lambda c, r, i: c.__gets(r, list(range(i, 0x03))), - DeviceInformation.Regular: lambda c, r, i: c.__gets(r, list(range(i, 0x07)) - if c.__get(r, i)[i] else list(range(0, 0x07))), - DeviceInformation.Extended: lambda c, r, i: c.__gets(r, - [x for x in range(i, 0x100) if x not in range(0x07, 0x80)] - if c.__get(r, i)[i] else - [x for x in range(0, 0x100) if x not in range(0x07, 0x80)]), - DeviceInformation.Specific: lambda c, r, i: c.__get(r, i), - } - - @classmethod - def get(cls, control, read_code=DeviceInformation.Basic, object_id=0x00): - ''' Get the requested device data from the system - - :param control: The control block to pull data from - :param read_code: The read code to process - :param object_id: The specific object_id to read - :returns: The requested data (id, length, value) - ''' - identity = control.Identity - return cls.__lookup[read_code](cls, identity, object_id) - - @classmethod - def __get(cls, identity, object_id): - ''' Read a single object_id from the device information - - :param identity: The identity block to pull data from - :param object_id: The specific object id to read - :returns: The requested data (id, length, value) - ''' - return { object_id:identity[object_id] } - - @classmethod - def __gets(cls, identity, object_ids): - ''' Read multiple object_ids from the device information - - :param identity: The identity block to pull data from - :param object_ids: The specific object ids to read - :returns: The requested data (id, length, value) - ''' - return dict((oid, identity[oid]) for oid in object_ids if identity[oid]) - - -#---------------------------------------------------------------------------# -# Counters Handler -#---------------------------------------------------------------------------# -class ModbusCountersHandler(object): - ''' - This is a helper class to simplify the properties for the counters:: - - 0x0B 1 Return Bus Message Count - - Quantity of messages that the remote - device has detected on the communications system since its - last restart, clear counters operation, or power-up. Messages - with bad CRC are not taken into account. - - 0x0C 2 Return Bus Communication Error Count - - Quantity of CRC errors encountered by the remote device since its - last restart, clear counters operation, or power-up. In case of - an error detected on the character level, (overrun, parity error), - or in case of a message length < 3 bytes, the receiving device is - not able to calculate the CRC. In such cases, this counter is - also incremented. - - 0x0D 3 Return Slave Exception Error Count - - Quantity of MODBUS exception error detected by the remote device - since its last restart, clear counters operation, or power-up. It - comprises also the error detected in broadcast messages even if an - exception message is not returned in this case. - Exception errors are described and listed in "MODBUS Application - Protocol Specification" document. - - 0xOE 4 Return Slave Message Count - - Quantity of messages addressed to the remote device, including - broadcast messages, that the remote device has processed since its - last restart, clear counters operation, or power-up. - - 0x0F 5 Return Slave No Response Count - - Quantity of messages received by the remote device for which it - returned no response (neither a normal response nor an exception - response), since its last restart, clear counters operation, or - power-up. Then, this counter counts the number of broadcast - messages it has received. - - 0x10 6 Return Slave NAK Count - - Quantity of messages addressed to the remote device for which it - returned a Negative Acknowledge (NAK) exception response, since - its last restart, clear counters operation, or power-up. Exception - responses are described and listed in "MODBUS Application Protocol - Specification" document. - - 0x11 7 Return Slave Busy Count - - Quantity of messages addressed to the remote device for which it - returned a Slave Device Busy exception response, since its last - restart, clear counters operation, or power-up. Exception - responses are described and listed in "MODBUS Application - Protocol Specification" document. - - 0x12 8 Return Bus Character Overrun Count - - Quantity of messages addressed to the remote device that it could - not handle due to a character overrun condition, since its last - restart, clear counters operation, or power-up. A character - overrun is caused by data characters arriving at the port faster - than they can. - - .. note:: I threw the event counter in here for convinience - ''' - __data = dict([(i, 0x0000) for i in range(9)]) - __names = [ - 'BusMessage', - 'BusCommunicationError', - 'SlaveExceptionError', - 'SlaveMessage', - 'SlaveNoResponse', - 'SlaveNAK', - 'SlaveBusy', - 'BusCharacterOverrun' - 'Event ' - ] - - def __iter__(self): - ''' Iterater over the device counters - - :returns: An iterator of the device counters - ''' - return izip(self.__names, itervalues(self.__data)) - - def update(self, values): - ''' Update the values of this identity - using another identify as the value - - :param values: The value to copy values from - ''' - for k, v in iteritems(values): - v += self.__getattribute__(k) - self.__setattr__(k, v) - - def reset(self): - ''' This clears all of the system counters - ''' - self.__data = dict([(i, 0x0000) for i in range(9)]) - - def summary(self): - ''' Returns a summary of the counters current status - - :returns: A byte with each bit representing each counter - ''' - count, result = 0x01, 0x00 - for i in itervalues(self.__data): - if i != 0x00: result |= count - count <<= 1 - return result - - #-------------------------------------------------------------------------# - # Properties - #-------------------------------------------------------------------------# - BusMessage = dict_property(lambda s: s.__data, 0) - BusCommunicationError = dict_property(lambda s: s.__data, 1) - BusExceptionError = dict_property(lambda s: s.__data, 2) - SlaveMessage = dict_property(lambda s: s.__data, 3) - SlaveNoResponse = dict_property(lambda s: s.__data, 4) - SlaveNAK = dict_property(lambda s: s.__data, 5) - SlaveBusy = dict_property(lambda s: s.__data, 6) - BusCharacterOverrun = dict_property(lambda s: s.__data, 7) - Event = dict_property(lambda s: s.__data, 8) - - -#---------------------------------------------------------------------------# -# Main server control block -#---------------------------------------------------------------------------# -class ModbusControlBlock(Singleton): - ''' - This is a global singleton that controls all system information - - All activity should be logged here and all diagnostic requests - should come from here. - ''' - - __mode = 'ASCII' - __diagnostic = [False] * 16 - __instance = None - __listen_only = False - __delimiter = '\r' - __counters = ModbusCountersHandler() - __identity = ModbusDeviceIdentification() - __plus = ModbusPlusStatistics() - __events = [] - - #-------------------------------------------------------------------------# - # Magic - #-------------------------------------------------------------------------# - def __str__(self): - ''' Build a representation of the control block - - :returns: A string representation of the control block - ''' - return "ModbusControl" - - def __iter__(self): - ''' Iterater over the device counters - - :returns: An iterator of the device counters - ''' - return self.__counters.__iter__() - - #-------------------------------------------------------------------------# - # Events - #-------------------------------------------------------------------------# - def addEvent(self, event): - ''' Adds a new event to the event log - - :param event: A new event to add to the log - ''' - self.__events.insert(0, event) - self.__events = self.__events[0:64] # chomp to 64 entries - self.Counter.Event += 1 - - def getEvents(self): - ''' Returns an encoded collection of the event log. - - :returns: The encoded events packet - ''' - events = [event.encode() for event in self.__events] - return b''.join(events) - - def clearEvents(self): - ''' Clears the current list of events - ''' - self.__events = [] - - #-------------------------------------------------------------------------# - # Other Properties - #-------------------------------------------------------------------------# - Identity = property(lambda s: s.__identity) - Counter = property(lambda s: s.__counters) - Events = property(lambda s: s.__events) - Plus = property(lambda s: s.__plus) - - def reset(self): - ''' This clears all of the system counters and the - diagnostic register - ''' - self.__events = [] - self.__counters.reset() - self.__diagnostic = [False] * 16 - - #-------------------------------------------------------------------------# - # Listen Properties - #-------------------------------------------------------------------------# - def _setListenOnly(self, value): - ''' This toggles the listen only status - - :param value: The value to set the listen status to - ''' - self.__listen_only = bool(value) - - ListenOnly = property(lambda s: s.__listen_only, _setListenOnly) - - #-------------------------------------------------------------------------# - # Mode Properties - #-------------------------------------------------------------------------# - def _setMode(self, mode): - ''' This toggles the current serial mode - - :param mode: The data transfer method in (RTU, ASCII) - ''' - if mode in ['ASCII', 'RTU']: - self.__mode = mode - - Mode = property(lambda s: s.__mode, _setMode) - - #-------------------------------------------------------------------------# - # Delimiter Properties - #-------------------------------------------------------------------------# - def _setDelimiter(self, char): - ''' This changes the serial delimiter character - - :param char: The new serial delimiter character - ''' - if isinstance(char, str): - self.__delimiter = char.encode() - if isinstance(char, bytes): - self.__delimiter = char - elif isinstance(char, int): - self.__delimiter = int2byte(char) - - Delimiter = property(lambda s: s.__delimiter, _setDelimiter) - - #-------------------------------------------------------------------------# - # Diagnostic Properties - #-------------------------------------------------------------------------# - def setDiagnostic(self, mapping): - ''' This sets the value in the diagnostic register - - :param mapping: Dictionary of key:value pairs to set - ''' - for entry in iteritems(mapping): - if entry[0] >= 0 and entry[0] < len(self.__diagnostic): - self.__diagnostic[entry[0]] = (entry[1] != 0) - - def getDiagnostic(self, bit): - ''' This gets the value in the diagnostic register - - :param bit: The bit to get - :returns: The current value of the requested bit - ''' - try: - if bit and bit >= 0 and bit < len(self.__diagnostic): - return self.__diagnostic[bit] - except Exception: - return None - - def getDiagnosticRegister(self): - ''' This gets the entire diagnostic register - - :returns: The diagnostic register collection - ''' - return self.__diagnostic - -#---------------------------------------------------------------------------# -# Exported Identifiers -#---------------------------------------------------------------------------# -__all__ = [ - "ModbusAccessControl", - "ModbusPlusStatistics", - "ModbusDeviceIdentification", - "DeviceInformationFactory", - "ModbusControlBlock" -] diff --git a/modbus/pymodbus/diag_message.py b/modbus/pymodbus/diag_message.py deleted file mode 100644 index b76782c..0000000 --- a/modbus/pymodbus/diag_message.py +++ /dev/null @@ -1,790 +0,0 @@ -''' -Diagnostic Record Read/Write ------------------------------- - -These need to be tied into a the current server context -or linked to the appropriate data -''' -import struct - -from pymodbus.constants import ModbusStatus, ModbusPlusOperation -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.device import ModbusControlBlock -from pymodbus.exceptions import NotImplementedException -from pymodbus.utilities import pack_bitstring - -_MCB = ModbusControlBlock() - - -#---------------------------------------------------------------------------# -# Diagnostic Function Codes Base Classes -# diagnostic 08, 00-18,20 -#---------------------------------------------------------------------------# -# TODO Make sure all the data is decoded from the response -#---------------------------------------------------------------------------# -class DiagnosticStatusRequest(ModbusRequest): - ''' - This is a base class for all of the diagnostic request functions - ''' - function_code = 0x08 - _rtu_frame_size = 8 - - def __init__(self, **kwargs): - ''' - Base initializer for a diagnostic request - ''' - ModbusRequest.__init__(self, **kwargs) - self.message = None - - def encode(self): - ''' - Base encoder for a diagnostic response - we encode the data set in self.message - - :returns: The encoded packet - ''' - packet = struct.pack('>H', self.sub_function_code) - if self.message is not None: - if isinstance(self.message, str): - packet += self.message.encode() - elif isinstance(self.message, bytes): - packet += self.message - elif isinstance(self.message, list): - for piece in self.message: - packet += struct.pack('>H', piece) - elif isinstance(self.message, int): - packet += struct.pack('>H', self.message) - return packet - - def decode(self, data): - ''' Base decoder for a diagnostic request - - :param data: The data to decode into the function code - ''' - self.sub_function_code, self.message = struct.unpack('>HH', data) - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Sub function code (2 byte) + Data (2 * N bytes) - :return: - """ - if not isinstance(self.message,list): - self.message = [self.message] - return 1 + 2 + 2 * len(self.message) - - -class DiagnosticStatusResponse(ModbusResponse): - ''' - This is a base class for all of the diagnostic response functions - - It works by performing all of the encoding and decoding of variable - data and lets the higher classes define what extra data to append - and how to execute a request - ''' - function_code = 0x08 - _rtu_frame_size = 8 - - def __init__(self, **kwargs): - ''' - Base initializer for a diagnostic response - ''' - ModbusResponse.__init__(self, **kwargs) - self.message = None - - def encode(self): - ''' - Base encoder for a diagnostic response - we encode the data set in self.message - - :returns: The encoded packet - ''' - packet = struct.pack('>H', self.sub_function_code) - if self.message is not None: - if isinstance(self.message, str): - packet += self.message.encode() - elif isinstance(self.message, bytes): - packet += self.message - elif isinstance(self.message, list): - for piece in self.message: - packet += struct.pack('>H', piece) - elif isinstance(self.message, int): - packet += struct.pack('>H', self.message) - return packet - - def decode(self, data): - ''' Base decoder for a diagnostic response - - :param data: The data to decode into the function code - ''' - word_len = len(data)//2 - if len(data) % 2: - word_len += 1 - data = data + b'0' - data = struct.unpack('>' + 'H'*word_len, data) - self.sub_function_code, self.message = data[0], data[1:] - - -class DiagnosticStatusSimpleRequest(DiagnosticStatusRequest): - ''' - A large majority of the diagnostic functions are simple - status request functions. They work by sending 0x0000 - as data and their function code and they are returned - 2 bytes of data. - - If a function inherits this, they only need to implement - the execute method - ''' - - def __init__(self, data=0x0000, **kwargs): - ''' - General initializer for a simple diagnostic request - - The data defaults to 0x0000 if not provided as over half - of the functions require it. - - :param data: The data to send along with the request - ''' - DiagnosticStatusRequest.__init__(self, **kwargs) - self.message = data - - def execute(self, *args): - ''' Base function to raise if not implemented ''' - raise NotImplementedException("Diagnostic Message Has No Execute Method") - - -class DiagnosticStatusSimpleResponse(DiagnosticStatusResponse): - ''' - A large majority of the diagnostic functions are simple - status request functions. They work by sending 0x0000 - as data and their function code and they are returned - 2 bytes of data. - ''' - - def __init__(self, data=0x0000, **kwargs): - ''' General initializer for a simple diagnostic response - - :param data: The resulting data to return to the client - ''' - DiagnosticStatusResponse.__init__(self, **kwargs) - self.message = data - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 00 -#---------------------------------------------------------------------------# -class ReturnQueryDataRequest(DiagnosticStatusRequest): - ''' - The data passed in the request data field is to be returned (looped back) - in the response. The entire response message should be identical to the - request. - ''' - sub_function_code = 0x0000 - - def __init__(self, message=0x0000, **kwargs): - ''' Initializes a new instance of the request - - :param message: The message to send to loopback - ''' - DiagnosticStatusRequest.__init__(self, **kwargs) - if isinstance(message, list): - self.message = message - else: - self.message = [message] - - def execute(self, *args): - ''' Executes the loopback request (builds the response) - - :returns: The populated loopback response message - ''' - return ReturnQueryDataResponse(self.message) - - -class ReturnQueryDataResponse(DiagnosticStatusResponse): - ''' - The data passed in the request data field is to be returned (looped back) - in the response. The entire response message should be identical to the - request. - ''' - sub_function_code = 0x0000 - - def __init__(self, message=0x0000, **kwargs): - ''' Initializes a new instance of the response - - :param message: The message to loopback - ''' - DiagnosticStatusResponse.__init__(self, **kwargs) - if isinstance(message, list): - self.message = message - else: self.message = [message] - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 01 -#---------------------------------------------------------------------------# -class RestartCommunicationsOptionRequest(DiagnosticStatusRequest): - ''' - The remote device serial line port must be initialized and restarted, and - all of its communications event counters are cleared. If the port is - currently in Listen Only Mode, no response is returned. This function is - the only one that brings the port out of Listen Only Mode. If the port is - not currently in Listen Only Mode, a normal response is returned. This - occurs before the restart is executed. - ''' - sub_function_code = 0x0001 - - def __init__(self, toggle=False, **kwargs): - ''' Initializes a new request - - :param toggle: Set to True to toggle, False otherwise - ''' - DiagnosticStatusRequest.__init__(self, **kwargs) - if toggle: - self.message = [ModbusStatus.On] - else: self.message = [ModbusStatus.Off] - - def execute(self, *args): - ''' Clear event log and restart - - :returns: The initialized response message - ''' - #if _MCB.ListenOnly: - return RestartCommunicationsOptionResponse(self.message) - -class RestartCommunicationsOptionResponse(DiagnosticStatusResponse): - ''' - The remote device serial line port must be initialized and restarted, and - all of its communications event counters are cleared. If the port is - currently in Listen Only Mode, no response is returned. This function is - the only one that brings the port out of Listen Only Mode. If the port is - not currently in Listen Only Mode, a normal response is returned. This - occurs before the restart is executed. - ''' - sub_function_code = 0x0001 - - def __init__(self, toggle=False, **kwargs): - ''' Initializes a new response - - :param toggle: Set to True if we toggled, False otherwise - ''' - DiagnosticStatusResponse.__init__(self, **kwargs) - if toggle: - self.message = [ModbusStatus.On] - else: self.message = [ModbusStatus.Off] - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 02 -#---------------------------------------------------------------------------# -class ReturnDiagnosticRegisterRequest(DiagnosticStatusSimpleRequest): - ''' - The contents of the remote device's 16-bit diagnostic register are - returned in the response - ''' - sub_function_code = 0x0002 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - #if _MCB.isListenOnly(): - register = pack_bitstring(_MCB.getDiagnosticRegister()) - return ReturnDiagnosticRegisterResponse(register) - - -class ReturnDiagnosticRegisterResponse(DiagnosticStatusSimpleResponse): - ''' - The contents of the remote device's 16-bit diagnostic register are - returned in the response - ''' - sub_function_code = 0x0002 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 03 -#---------------------------------------------------------------------------# -class ChangeAsciiInputDelimiterRequest(DiagnosticStatusSimpleRequest): - ''' - The character 'CHAR' passed in the request data field becomes the end of - message delimiter for future messages (replacing the default LF - character). This function is useful in cases of a Line Feed is not - required at the end of ASCII messages. - ''' - sub_function_code = 0x0003 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - char = (self.message & 0xff00) >> 8 - _MCB.Delimiter = char - return ChangeAsciiInputDelimiterResponse(self.message) - - -class ChangeAsciiInputDelimiterResponse(DiagnosticStatusSimpleResponse): - ''' - The character 'CHAR' passed in the request data field becomes the end of - message delimiter for future messages (replacing the default LF - character). This function is useful in cases of a Line Feed is not - required at the end of ASCII messages. - ''' - sub_function_code = 0x0003 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 04 -#---------------------------------------------------------------------------# -class ForceListenOnlyModeRequest(DiagnosticStatusSimpleRequest): - ''' - Forces the addressed remote device to its Listen Only Mode for MODBUS - communications. This isolates it from the other devices on the network, - allowing them to continue communicating without interruption from the - addressed remote device. No response is returned. - ''' - sub_function_code = 0x0004 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - _MCB.ListenOnly = True - return ForceListenOnlyModeResponse() - - -class ForceListenOnlyModeResponse(DiagnosticStatusResponse): - ''' - Forces the addressed remote device to its Listen Only Mode for MODBUS - communications. This isolates it from the other devices on the network, - allowing them to continue communicating without interruption from the - addressed remote device. No response is returned. - - This does not send a response - ''' - sub_function_code = 0x0004 - should_respond = False - - def __init__(self, **kwargs): - ''' Initializer to block a return response - ''' - DiagnosticStatusResponse.__init__(self, **kwargs) - self.message = [] - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 10 -#---------------------------------------------------------------------------# -class ClearCountersRequest(DiagnosticStatusSimpleRequest): - ''' - The goal is to clear ll counters and the diagnostic register. - Also, counters are cleared upon power-up - ''' - sub_function_code = 0x000A - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - _MCB.reset() - return ClearCountersResponse(self.message) - - -class ClearCountersResponse(DiagnosticStatusSimpleResponse): - ''' - The goal is to clear ll counters and the diagnostic register. - Also, counters are cleared upon power-up - ''' - sub_function_code = 0x000A - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 11 -#---------------------------------------------------------------------------# -class ReturnBusMessageCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of messages that the - remote device has detected on the communications systems since its last - restart, clear counters operation, or power-up - ''' - sub_function_code = 0x000B - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.BusMessage - return ReturnBusMessageCountResponse(count) - - -class ReturnBusMessageCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages that the - remote device has detected on the communications systems since its last - restart, clear counters operation, or power-up - ''' - sub_function_code = 0x000B - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 12 -#---------------------------------------------------------------------------# -class ReturnBusCommunicationErrorCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of CRC errors encountered - by the remote device since its last restart, clear counter operation, or - power-up - ''' - sub_function_code = 0x000C - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.BusCommunicationError - return ReturnBusCommunicationErrorCountResponse(count) - - -class ReturnBusCommunicationErrorCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of CRC errors encountered - by the remote device since its last restart, clear counter operation, or - power-up - ''' - sub_function_code = 0x000C - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 13 -#---------------------------------------------------------------------------# -class ReturnBusExceptionErrorCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of modbus exception - responses returned by the remote device since its last restart, - clear counters operation, or power-up - ''' - sub_function_code = 0x000D - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.BusExceptionError - return ReturnBusExceptionErrorCountResponse(count) - - -class ReturnBusExceptionErrorCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of modbus exception - responses returned by the remote device since its last restart, - clear counters operation, or power-up - ''' - sub_function_code = 0x000D - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 14 -#---------------------------------------------------------------------------# -class ReturnSlaveMessageCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of messages addressed to the - remote device, or broadcast, that the remote device has processed since - its last restart, clear counters operation, or power-up - ''' - sub_function_code = 0x000E - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.SlaveMessage - return ReturnSlaveMessageCountResponse(count) - - -class ReturnSlaveMessageCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages addressed to the - remote device, or broadcast, that the remote device has processed since - its last restart, clear counters operation, or power-up - ''' - sub_function_code = 0x000E - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 15 -#---------------------------------------------------------------------------# -class ReturnSlaveNoResponseCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of messages addressed to the - remote device, or broadcast, that the remote device has processed since - its last restart, clear counters operation, or power-up - ''' - sub_function_code = 0x000F - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.SlaveNoResponse - return ReturnSlaveNoReponseCountResponse(count) - - -class ReturnSlaveNoReponseCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages addressed to the - remote device, or broadcast, that the remote device has processed since - its last restart, clear counters operation, or power-up - ''' - sub_function_code = 0x000F - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 16 -#---------------------------------------------------------------------------# -class ReturnSlaveNAKCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of messages addressed to the - remote device for which it returned a Negative Acknowledge (NAK) exception - response, since its last restart, clear counters operation, or power-up. - Exception responses are described and listed in section 7 . - ''' - sub_function_code = 0x0010 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.SlaveNAK - return ReturnSlaveNAKCountResponse(count) - - -class ReturnSlaveNAKCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages addressed to the - remote device for which it returned a Negative Acknowledge (NAK) exception - response, since its last restart, clear counters operation, or power-up. - Exception responses are described and listed in section 7. - ''' - sub_function_code = 0x0010 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 17 -#---------------------------------------------------------------------------# -class ReturnSlaveBusyCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of messages addressed to the - remote device for which it returned a Slave Device Busy exception response, - since its last restart, clear counters operation, or power-up. - ''' - sub_function_code = 0x0011 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.SlaveBusy - return ReturnSlaveBusyCountResponse(count) - - -class ReturnSlaveBusyCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages addressed to the - remote device for which it returned a Slave Device Busy exception response, - since its last restart, clear counters operation, or power-up. - ''' - sub_function_code = 0x0011 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 18 -#---------------------------------------------------------------------------# -class ReturnSlaveBusCharacterOverrunCountRequest(DiagnosticStatusSimpleRequest): - ''' - The response data field returns the quantity of messages addressed to the - remote device that it could not handle due to a character overrun condition, - since its last restart, clear counters operation, or power-up. A character - overrun is caused by data characters arriving at the port faster than they - can be stored, or by the loss of a character due to a hardware malfunction. - ''' - sub_function_code = 0x0012 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.BusCharacterOverrun - return ReturnSlaveBusCharacterOverrunCountResponse(count) - - -class ReturnSlaveBusCharacterOverrunCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages addressed to the - remote device that it could not handle due to a character overrun condition, - since its last restart, clear counters operation, or power-up. A character - overrun is caused by data characters arriving at the port faster than they - can be stored, or by the loss of a character due to a hardware malfunction. - ''' - sub_function_code = 0x0012 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 19 -#---------------------------------------------------------------------------# -class ReturnIopOverrunCountRequest(DiagnosticStatusSimpleRequest): - ''' - An IOP overrun is caused by data characters arriving at the port - faster than they can be stored, or by the loss of a character due - to a hardware malfunction. This function is specific to the 884. - ''' - sub_function_code = 0x0013 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - count = _MCB.Counter.BusCharacterOverrun - return ReturnIopOverrunCountResponse(count) - - -class ReturnIopOverrunCountResponse(DiagnosticStatusSimpleResponse): - ''' - The response data field returns the quantity of messages - addressed to the slave that it could not handle due to an 884 - IOP overrun condition, since its last restart, clear counters - operation, or power-up. - ''' - sub_function_code = 0x0013 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 20 -#---------------------------------------------------------------------------# -class ClearOverrunCountRequest(DiagnosticStatusSimpleRequest): - ''' - Clears the overrun error counter and reset the error flag - - An error flag should be cleared, but nothing else in the - specification mentions is, so it is ignored. - ''' - sub_function_code = 0x0014 - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - _MCB.Counter.BusCharacterOverrun = 0x0000 - return ClearOverrunCountResponse(self.message) - - -class ClearOverrunCountResponse(DiagnosticStatusSimpleResponse): - ''' - Clears the overrun error counter and reset the error flag - ''' - sub_function_code = 0x0014 - - -#---------------------------------------------------------------------------# -# Diagnostic Sub Code 21 -#---------------------------------------------------------------------------# -class GetClearModbusPlusRequest(DiagnosticStatusSimpleRequest): - ''' - In addition to the Function code (08) and Subfunction code - (00 15 hex) in the query, a two-byte Operation field is used - to specify either a 'Get Statistics' or a 'Clear Statistics' - operation. The two operations are exclusive - the 'Get' - operation cannot clear the statistics, and the 'Clear' - operation does not return statistics prior to clearing - them. Statistics are also cleared on power-up of the slave - device. - ''' - sub_function_code = 0x0015 - - def __init__(self, **kwargs): - super(GetClearModbusPlusRequest, self).__init__(**kwargs) - - def get_response_pdu_size(self): - """ - Returns a series of 54 16-bit words (108 bytes) in the data field of the response - (this function differs from the usual two-byte length of the data field). The data - contains the statistics for the Modbus Plus peer processor in the slave device. - Func_code (1 byte) + Sub function code (2 byte) + Operation (2 byte) + Data (108 bytes) - :return: - """ - if self.message == ModbusPlusOperation.GetStatistics: - data = 2 + 108 # byte count(2) + data (54*2) - else: - data = 0 - return 1 + 2 + 2 + 2+ data - - def execute(self, *args): - ''' Execute the diagnostic request on the given device - - :returns: The initialized response message - ''' - message = None # the clear operation does not return info - if self.message == ModbusPlusOperation.ClearStatistics: - _MCB.Plus.reset() - message = self.message - else: - message = [self.message] - message += _MCB.Plus.encode() - return GetClearModbusPlusResponse(message) - - def encode(self): - ''' - Base encoder for a diagnostic response - we encode the data set in self.message - - :returns: The encoded packet - ''' - packet = struct.pack('>H', self.sub_function_code) - packet += struct.pack('>H', self.message) - return packet - - -class GetClearModbusPlusResponse(DiagnosticStatusSimpleResponse): - ''' - Returns a series of 54 16-bit words (108 bytes) in the data field - of the response (this function differs from the usual two-byte - length of the data field). The data contains the statistics for - the Modbus Plus peer processor in the slave device. - ''' - sub_function_code = 0x0015 - - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "DiagnosticStatusRequest", "DiagnosticStatusResponse", - "ReturnQueryDataRequest", "ReturnQueryDataResponse", - "RestartCommunicationsOptionRequest", "RestartCommunicationsOptionResponse", - "ReturnDiagnosticRegisterRequest", "ReturnDiagnosticRegisterResponse", - "ChangeAsciiInputDelimiterRequest", "ChangeAsciiInputDelimiterResponse", - "ForceListenOnlyModeRequest", "ForceListenOnlyModeResponse", - "ClearCountersRequest", "ClearCountersResponse", - "ReturnBusMessageCountRequest", "ReturnBusMessageCountResponse", - "ReturnBusCommunicationErrorCountRequest", "ReturnBusCommunicationErrorCountResponse", - "ReturnBusExceptionErrorCountRequest", "ReturnBusExceptionErrorCountResponse", - "ReturnSlaveMessageCountRequest", "ReturnSlaveMessageCountResponse", - "ReturnSlaveNoResponseCountRequest", "ReturnSlaveNoReponseCountResponse", - "ReturnSlaveNAKCountRequest", "ReturnSlaveNAKCountResponse", - "ReturnSlaveBusyCountRequest", "ReturnSlaveBusyCountResponse", - "ReturnSlaveBusCharacterOverrunCountRequest", "ReturnSlaveBusCharacterOverrunCountResponse", - "ReturnIopOverrunCountRequest", "ReturnIopOverrunCountResponse", - "ClearOverrunCountRequest", "ClearOverrunCountResponse", - "GetClearModbusPlusRequest", "GetClearModbusPlusResponse", -] diff --git a/modbus/pymodbus/events.py b/modbus/pymodbus/events.py deleted file mode 100644 index c3ed367..0000000 --- a/modbus/pymodbus/events.py +++ /dev/null @@ -1,197 +0,0 @@ -''' -Modbus Remote Events ------------------------------------------------------------- - -An event byte returned by the Get Communications Event Log function -can be any one of four types. The type is defined by bit 7 -(the high-order bit) in each byte. It may be further defined by bit 6. -''' -from pymodbus.exceptions import NotImplementedException -from pymodbus.exceptions import ParameterException -from pymodbus.utilities import pack_bitstring, unpack_bitstring - - -class ModbusEvent(object): - - def encode(self): - ''' Encodes the status bits to an event message - - :returns: The encoded event message - ''' - raise NotImplementedException() - - def decode(self, event): - ''' Decodes the event message to its status bits - - :param event: The event to decode - ''' - raise NotImplementedException() - - -class RemoteReceiveEvent(ModbusEvent): - ''' Remote device MODBUS Receive Event - - The remote device stores this type of event byte when a query message - is received. It is stored before the remote device processes the message. - This event is defined by bit 7 set to logic '1'. The other bits will be - set to a logic '1' if the corresponding condition is TRUE. The bit layout - is:: - - Bit Contents - ---------------------------------- - 0 Not Used - 2 Not Used - 3 Not Used - 4 Character Overrun - 5 Currently in Listen Only Mode - 6 Broadcast Receive - 7 1 - ''' - - def __init__(self, **kwargs): - ''' Initialize a new event instance - ''' - self.overrun = kwargs.get('overrun', False) - self.listen = kwargs.get('listen', False) - self.broadcast = kwargs.get('broadcast', False) - - def encode(self): - ''' Encodes the status bits to an event message - - :returns: The encoded event message - ''' - bits = [False] * 3 - bits += [self.overrun, self.listen, self.broadcast, True] - packet = pack_bitstring(bits) - return packet - - def decode(self, event): - ''' Decodes the event message to its status bits - - :param event: The event to decode - ''' - bits = unpack_bitstring(event) - self.overrun = bits[4] - self.listen = bits[5] - self.broadcast = bits[6] - - -class RemoteSendEvent(ModbusEvent): - ''' Remote device MODBUS Send Event - - The remote device stores this type of event byte when it finishes - processing a request message. It is stored if the remote device - returned a normal or exception response, or no response. - - This event is defined by bit 7 set to a logic '0', with bit 6 set to a '1'. - The other bits will be set to a logic '1' if the corresponding - condition is TRUE. The bit layout is:: - - Bit Contents - ----------------------------------------------------------- - 0 Read Exception Sent (Exception Codes 1-3) - 1 Slave Abort Exception Sent (Exception Code 4) - 2 Slave Busy Exception Sent (Exception Codes 5-6) - 3 Slave Program NAK Exception Sent (Exception Code 7) - 4 Write Timeout Error Occurred - 5 Currently in Listen Only Mode - 6 1 - 7 0 - ''' - - def __init__(self, **kwargs): - ''' Initialize a new event instance - ''' - self.read = kwargs.get('read', False) - self.slave_abort = kwargs.get('slave_abort', False) - self.slave_busy = kwargs.get('slave_busy', False) - self.slave_nak = kwargs.get('slave_nak', False) - self.write_timeout = kwargs.get('write_timeout', False) - self.listen = kwargs.get('listen', False) - - def encode(self): - ''' Encodes the status bits to an event message - - :returns: The encoded event message - ''' - bits = [self.read, self.slave_abort, self.slave_busy, - self.slave_nak, self.write_timeout, self.listen] - bits += [True, False] - packet = pack_bitstring(bits) - return packet - - def decode(self, event): - ''' Decodes the event message to its status bits - - :param event: The event to decode - ''' - # todo fix the start byte count - bits = unpack_bitstring(event) - self.read = bits[0] - self.slave_abort = bits[1] - self.slave_busy = bits[2] - self.slave_nak = bits[3] - self.write_timeout = bits[4] - self.listen = bits[5] - - -class EnteredListenModeEvent(ModbusEvent): - ''' Remote device Entered Listen Only Mode - - The remote device stores this type of event byte when it enters - the Listen Only Mode. The event is defined by a content of 04 hex. - ''' - - value = 0x04 - __encoded = b'\x04' - - def encode(self): - ''' Encodes the status bits to an event message - - :returns: The encoded event message - ''' - return self.__encoded - - def decode(self, event): - ''' Decodes the event message to its status bits - - :param event: The event to decode - ''' - if event != self.__encoded: - raise ParameterException('Invalid decoded value') - - -class CommunicationRestartEvent(ModbusEvent): - ''' Remote device Initiated Communication Restart - - The remote device stores this type of event byte when its communications - port is restarted. The remote device can be restarted by the Diagnostics - function (code 08), with sub-function Restart Communications Option - (code 00 01). - - That function also places the remote device into a 'Continue on Error' - or 'Stop on Error' mode. If the remote device is placed into 'Continue on - Error' mode, the event byte is added to the existing event log. If the - remote device is placed into 'Stop on Error' mode, the byte is added to - the log and the rest of the log is cleared to zeros. - - The event is defined by a content of zero. - ''' - - value = 0x00 - __encoded = b'\x00' - - def encode(self): - ''' Encodes the status bits to an event message - - :returns: The encoded event message - ''' - return self.__encoded - - def decode(self, event): - ''' Decodes the event message to its status bits - - :param event: The event to decode - ''' - if event != self.__encoded: - raise ParameterException('Invalid decoded value') diff --git a/modbus/pymodbus/exceptions.py b/modbus/pymodbus/exceptions.py deleted file mode 100644 index 0a4b0f6..0000000 --- a/modbus/pymodbus/exceptions.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -Pymodbus Exceptions --------------------- - -Custom exceptions to be used in the Modbus code. -""" - - -class ModbusException(Exception): - """ Base modbus exception """ - - def __init__(self, string): - """ Initialize the exception - :param string: The message to append to the error - """ - self.string = string - - def __str__(self): - return 'Modbus Error: %s' % self.string - - def isError(self): - """Error""" - return True - - -class ModbusIOException(ModbusException): - """ Error resulting from data i/o """ - - def __init__(self, string="", function_code=None): - """ Initialize the exception - :param string: The message to append to the error - """ - self.fcode = function_code - self.message = "[Input/Output] %s" % string - ModbusException.__init__(self, self.message) - - -class ParameterException(ModbusException): - """ Error resulting from invalid parameter """ - - def __init__(self, string=""): - """ Initialize the exception - - :param string: The message to append to the error - """ - message = "[Invalid Parameter] %s" % string - ModbusException.__init__(self, message) - - -class NoSuchSlaveException(ModbusException): - """ Error resulting from making a request to a slave - that does not exist """ - - def __init__(self, string=""): - """ Initialize the exception - - :param string: The message to append to the error - """ - message = "[No Such Slave] %s" % string - ModbusException.__init__(self, message) - - -class NotImplementedException(ModbusException): - """ Error resulting from not implemented function """ - - def __init__(self, string=""): - """ Initialize the exception - :param string: The message to append to the error - """ - message = "[Not Implemented] %s" % string - ModbusException.__init__(self, message) - - -class ConnectionException(ModbusException): - """ Error resulting from a bad connection """ - - def __init__(self, string=""): - """ Initialize the exception - - :param string: The message to append to the error - """ - message = "[Connection] %s" % string - ModbusException.__init__(self, message) - - -class InvalidMessageReceivedException(ModbusException): - """ - Error resulting from invalid response received or decoded - """ - - def __init__(self, string=""): - """ Initialize the exception - - :param string: The message to append to the error - """ - message = "[Invalid Message] %s" % string - ModbusException.__init__(self, message) - - -class MessageRegisterException(ModbusException): - """ - Error resulting from failing to register a custom message request/response - """ - def __init__(self, string=""): - message = '[Error registering message] %s' % string - ModbusException.__init__(self, message) - - -class TimeOutException(ModbusException): - """ Error resulting from modbus response timeout """ - - def __init__(self, string=""): - """ Initialize the exception - - :param string: The message to append to the error - """ - message = "[Timeout] %s" % string - ModbusException.__init__(self, message) - - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # -__all__ = [ - "ModbusException", "ModbusIOException", - "ParameterException", "NotImplementedException", - "ConnectionException", "NoSuchSlaveException", - "InvalidMessageReceivedException", - "MessageRegisterException", "TimeOutException" -] diff --git a/modbus/pymodbus/factory.py b/modbus/pymodbus/factory.py deleted file mode 100644 index ad2dff1..0000000 --- a/modbus/pymodbus/factory.py +++ /dev/null @@ -1,312 +0,0 @@ -""" -Modbus Request/Response Decoder Factories -------------------------------------------- - -The following factories make it easy to decode request/response messages. -To add a new request/response pair to be decodeable by the library, simply -add them to the respective function lookup table (order doesn't matter, but -it does help keep things organized). - -Regardless of how many functions are added to the lookup, O(1) behavior is -kept as a result of a pre-computed lookup dictionary. -""" - -from pymodbus.pdu import IllegalFunctionRequest -from pymodbus.pdu import ExceptionResponse -from pymodbus.pdu import ModbusRequest, ModbusResponse -from pymodbus.pdu import ModbusExceptions as ecode -from pymodbus.interfaces import IModbusDecoder -from pymodbus.exceptions import ModbusException, MessageRegisterException -from pymodbus.bit_read_message import * -from pymodbus.bit_write_message import * -from pymodbus.diag_message import * -from pymodbus.file_message import * -from pymodbus.other_message import * -from pymodbus.mei_message import * -from pymodbus.register_read_message import * -from pymodbus.register_write_message import * -from pymodbus.compat import byte2int - - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Server Decoder -# --------------------------------------------------------------------------- # -class ServerDecoder(IModbusDecoder): - """ Request Message Factory (Server) - - To add more implemented functions, simply add them to the list - """ - __function_table = [ - ReadHoldingRegistersRequest, - ReadDiscreteInputsRequest, - ReadInputRegistersRequest, - ReadCoilsRequest, - WriteMultipleCoilsRequest, - WriteMultipleRegistersRequest, - WriteSingleRegisterRequest, - WriteSingleCoilRequest, - ReadWriteMultipleRegistersRequest, - DiagnosticStatusRequest, - ReadExceptionStatusRequest, - GetCommEventCounterRequest, - GetCommEventLogRequest, - ReportSlaveIdRequest, - ReadFileRecordRequest, - WriteFileRecordRequest, - MaskWriteRegisterRequest, - ReadFifoQueueRequest, - ReadDeviceInformationRequest, - ] - __sub_function_table = [ - ReturnQueryDataRequest, - RestartCommunicationsOptionRequest, - ReturnDiagnosticRegisterRequest, - ChangeAsciiInputDelimiterRequest, - ForceListenOnlyModeRequest, - ClearCountersRequest, - ReturnBusMessageCountRequest, - ReturnBusCommunicationErrorCountRequest, - ReturnBusExceptionErrorCountRequest, - ReturnSlaveMessageCountRequest, - ReturnSlaveNoResponseCountRequest, - ReturnSlaveNAKCountRequest, - ReturnSlaveBusyCountRequest, - ReturnSlaveBusCharacterOverrunCountRequest, - ReturnIopOverrunCountRequest, - ClearOverrunCountRequest, - GetClearModbusPlusRequest, - ReadDeviceInformationRequest, - ] - - def __init__(self): - """ Initializes the client lookup tables - """ - functions = set(f.function_code for f in self.__function_table) - self.__lookup = dict([(f.function_code, f) for f in self.__function_table]) - self.__sub_lookup = dict((f, {}) for f in functions) - for f in self.__sub_function_table: - self.__sub_lookup[f.function_code][f.sub_function_code] = f - - def decode(self, message): - """ Wrapper to decode a request packet - - :param message: The raw modbus request packet - :return: The decoded modbus message or None if error - """ - try: - return self._helper(message) - except ModbusException as er: - _logger.warning("Unable to decode request %s" % er) - return None - - def lookupPduClass(self, function_code): - """ Use `function_code` to determine the class of the PDU. - - :param function_code: The function code specified in a frame. - :returns: The class of the PDU that has a matching `function_code`. - """ - return self.__lookup.get(function_code, ExceptionResponse) - - def _helper(self, data): - """ - This factory is used to generate the correct request object - from a valid request packet. This decodes from a list of the - currently implemented request types. - - :param data: The request packet to decode - :returns: The decoded request or illegal function request object - """ - function_code = byte2int(data[0]) - request = self.__lookup.get(function_code, lambda: None)() - if not request: - _logger.debug("Factory Request[%d]" % function_code) - request = IllegalFunctionRequest(function_code) - else: - fc_string = "%s: %s" % ( - str(self.__lookup[function_code]).split('.')[-1].rstrip( - "'>"), - function_code - ) - _logger.debug("Factory Request[%s]" % fc_string) - request.decode(data[1:]) - - if hasattr(request, 'sub_function_code'): - lookup = self.__sub_lookup.get(request.function_code, {}) - subtype = lookup.get(request.sub_function_code, None) - if subtype: request.__class__ = subtype - - return request - - def register(self, function=None): - """ - Registers a function and sub function class with the decoder - :param function: Custom function class to register - :return: - """ - if function and not issubclass(function, ModbusRequest): - raise MessageRegisterException("'{}' is Not a valid Modbus Message" - ". Class needs to be derived from " - "`pymodbus.pdu.ModbusRequest` " - "".format( - function.__class__.__name__ - )) - self.__lookup[function.function_code] = function - if hasattr(function, "sub_function_code"): - if function.function_code not in self.__sub_lookup: - self.__sub_lookup[function.function_code] = dict() - self.__sub_lookup[function.function_code][ - function.sub_function_code] = function - - -# --------------------------------------------------------------------------- # -# Client Decoder -# --------------------------------------------------------------------------- # -class ClientDecoder(IModbusDecoder): - """ Response Message Factory (Client) - - To add more implemented functions, simply add them to the list - """ - __function_table = [ - ReadHoldingRegistersResponse, - ReadDiscreteInputsResponse, - ReadInputRegistersResponse, - ReadCoilsResponse, - WriteMultipleCoilsResponse, - WriteMultipleRegistersResponse, - WriteSingleRegisterResponse, - WriteSingleCoilResponse, - ReadWriteMultipleRegistersResponse, - DiagnosticStatusResponse, - ReadExceptionStatusResponse, - GetCommEventCounterResponse, - GetCommEventLogResponse, - ReportSlaveIdResponse, - ReadFileRecordResponse, - WriteFileRecordResponse, - MaskWriteRegisterResponse, - ReadFifoQueueResponse, - ReadDeviceInformationResponse, - ] - __sub_function_table = [ - ReturnQueryDataResponse, - RestartCommunicationsOptionResponse, - ReturnDiagnosticRegisterResponse, - ChangeAsciiInputDelimiterResponse, - ForceListenOnlyModeResponse, - ClearCountersResponse, - ReturnBusMessageCountResponse, - ReturnBusCommunicationErrorCountResponse, - ReturnBusExceptionErrorCountResponse, - ReturnSlaveMessageCountResponse, - ReturnSlaveNoReponseCountResponse, - ReturnSlaveNAKCountResponse, - ReturnSlaveBusyCountResponse, - ReturnSlaveBusCharacterOverrunCountResponse, - ReturnIopOverrunCountResponse, - ClearOverrunCountResponse, - GetClearModbusPlusResponse, - ReadDeviceInformationResponse, - ] - - def __init__(self): - """ Initializes the client lookup tables - """ - functions = set(f.function_code for f in self.__function_table) - self.__lookup = dict([(f.function_code, f) - for f in self.__function_table]) - self.__sub_lookup = dict((f, {}) for f in functions) - for f in self.__sub_function_table: - self.__sub_lookup[f.function_code][f.sub_function_code] = f - - def lookupPduClass(self, function_code): - """ Use `function_code` to determine the class of the PDU. - - :param function_code: The function code specified in a frame. - :returns: The class of the PDU that has a matching `function_code`. - """ - return self.__lookup.get(function_code, ExceptionResponse) - - def decode(self, message): - """ Wrapper to decode a response packet - - :param message: The raw packet to decode - :return: The decoded modbus message or None if error - """ - try: - return self._helper(message) - except ModbusException as er: - _logger.error("Unable to decode response %s" % er) - - except Exception as ex: - _logger.error(ex) - return None - - def _helper(self, data): - """ - This factory is used to generate the correct response object - from a valid response packet. This decodes from a list of the - currently implemented request types. - - :param data: The response packet to decode - :returns: The decoded request or an exception response object - """ - fc_string = function_code = byte2int(data[0]) - if function_code in self.__lookup: - fc_string = "%s: %s" % ( - str(self.__lookup[function_code]).split('.')[-1].rstrip("'>"), - function_code - ) - _logger.debug("Factory Response[%s]" % fc_string) - response = self.__lookup.get(function_code, lambda: None)() - if function_code > 0x80: - code = function_code & 0x7f # strip error portion - response = ExceptionResponse(code, ecode.IllegalFunction) - if not response: - raise ModbusException("Unknown response %d" % function_code) - response.decode(data[1:]) - - if hasattr(response, 'sub_function_code'): - lookup = self.__sub_lookup.get(response.function_code, {}) - subtype = lookup.get(response.sub_function_code, None) - if subtype: response.__class__ = subtype - - return response - - def register(self, function=None, sub_function=None, force=False): - """ - Registers a function and sub function class with the decoder - :param function: Custom function class to register - :param sub_function: Custom sub function class to register - :param force: Force update the existing class - :return: - """ - if function and not issubclass(function, ModbusResponse): - raise MessageRegisterException("'{}' is Not a valid Modbus Message" - ". Class needs to be derived from " - "`pymodbus.pdu.ModbusResponse` " - "".format( - function.__class__.__name__ - )) - self.__lookup[function.function_code] = function - if hasattr(function, "sub_function_code"): - if function.function_code not in self.__sub_lookup: - self.__sub_lookup[function.function_code] = dict() - self.__sub_lookup[function.function_code][ - function.sub_function_code] = function - - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = ['ServerDecoder', 'ClientDecoder'] - - diff --git a/modbus/pymodbus/file_message.py b/modbus/pymodbus/file_message.py deleted file mode 100644 index 15fba11..0000000 --- a/modbus/pymodbus/file_message.py +++ /dev/null @@ -1,395 +0,0 @@ -''' -File Record Read/Write Messages -------------------------------- - -Currently none of these messages are implemented -''' -import struct -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.compat import byte2int - - -#---------------------------------------------------------------------------# -# File Record Types -#---------------------------------------------------------------------------# -class FileRecord(object): - ''' Represents a file record and its relevant data. - ''' - - def __init__(self, **kwargs): - ''' Initializes a new instance - - :params reference_type: Defaults to 0x06 (must be) - :params file_number: Indicates which file number we are reading - :params record_number: Indicates which record in the file - :params record_data: The actual data of the record - :params record_length: The length in registers of the record - :params response_length: The length in bytes of the record - ''' - self.reference_type = kwargs.get('reference_type', 0x06) - self.file_number = kwargs.get('file_number', 0x00) - self.record_number = kwargs.get('record_number', 0x00) - self.record_data = kwargs.get('record_data', '') - - self.record_length = kwargs.get('record_length', len(self.record_data) // 2) - self.response_length = kwargs.get('response_length', len(self.record_data) + 1) - - def __eq__(self, relf): - ''' Compares the left object to the right - ''' - return self.reference_type == relf.reference_type \ - and self.file_number == relf.file_number \ - and self.record_number == relf.record_number \ - and self.record_length == relf.record_length \ - and self.record_data == relf.record_data - - def __ne__(self, relf): - ''' Compares the left object to the right - ''' - return not self.__eq__(relf) - - def __repr__(self): - ''' Gives a representation of the file record - ''' - params = (self.file_number, self.record_number, self.record_length) - return 'FileRecord(file=%d, record=%d, length=%d)' % params - - -#---------------------------------------------------------------------------# -# File Requests/Responses -#---------------------------------------------------------------------------# -class ReadFileRecordRequest(ModbusRequest): - ''' - This function code is used to perform a file record read. All request - data lengths are provided in terms of number of bytes and all record - lengths are provided in terms of registers. - - A file is an organization of records. Each file contains 10000 records, - addressed 0000 to 9999 decimal or 0x0000 to 0x270f. For example, record - 12 is addressed as 12. The function can read multiple groups of - references. The groups can be separating (non-contiguous), but the - references within each group must be sequential. Each group is defined - in a seperate 'sub-request' field that contains seven bytes:: - - The reference type: 1 byte (must be 0x06) - The file number: 2 bytes - The starting record number within the file: 2 bytes - The length of the record to be read: 2 bytes - - The quantity of registers to be read, combined with all other fields - in the expected response, must not exceed the allowable length of the - MODBUS PDU: 235 bytes. - ''' - function_code = 0x14 - _rtu_byte_count_pos = 2 - - def __init__(self, records=None, **kwargs): - ''' Initializes a new instance - - :param records: The file record requests to be read - ''' - ModbusRequest.__init__(self, **kwargs) - self.records = records or [] - - def encode(self): - ''' Encodes the request packet - - :returns: The byte encoded packet - ''' - packet = struct.pack('B', len(self.records) * 7) - for record in self.records: - packet += struct.pack('>BHHH', 0x06, record.file_number, - record.record_number, record.record_length) - return packet - - def decode(self, data): - ''' Decodes the incoming request - - :param data: The data to decode into the address - ''' - self.records = [] - byte_count = byte2int(data[0]) - for count in range(1, byte_count, 7): - decoded = struct.unpack('>BHHH', data[count:count+7]) - record = FileRecord(file_number=decoded[1], - record_number=decoded[2], record_length=decoded[3]) - if decoded[0] == 0x06: self.records.append(record) - - def execute(self, context): - ''' Run a read exeception status request against the store - - :param context: The datastore to request from - :returns: The populated response - ''' - # TODO do some new context operation here - # if file number, record number, or address + length - # is too big, return an error. - files = [] - return ReadFileRecordResponse(files) - - -class ReadFileRecordResponse(ModbusResponse): - ''' - The normal response is a series of 'sub-responses,' one for each - 'sub-request.' The byte count field is the total combined count of - bytes in all 'sub-responses.' In addition, each 'sub-response' - contains a field that shows its own byte count. - ''' - function_code = 0x14 - _rtu_byte_count_pos = 2 - - def __init__(self, records=None, **kwargs): - ''' Initializes a new instance - - :param records: The requested file records - ''' - ModbusResponse.__init__(self, **kwargs) - self.records = records or [] - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - total = sum(record.response_length + 1 for record in self.records) - packet = struct.pack('B', total) - for record in self.records: - packet += struct.pack('>BB', 0x06, record.record_length) - packet += record.record_data - return packet - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - count, self.records = 1, [] - byte_count = byte2int(data[0]) - while count < byte_count: - response_length, reference_type = struct.unpack('>BB', data[count:count+2]) - count += response_length + 1 # the count is not included - record = FileRecord(response_length=response_length, - record_data=data[count - response_length + 1:count]) - if reference_type == 0x06: self.records.append(record) - - -class WriteFileRecordRequest(ModbusRequest): - ''' - This function code is used to perform a file record write. All - request data lengths are provided in terms of number of bytes - and all record lengths are provided in terms of the number of 16 - bit words. - ''' - function_code = 0x15 - _rtu_byte_count_pos = 2 - - def __init__(self, records=None, **kwargs): - ''' Initializes a new instance - - :param records: The file record requests to be read - ''' - ModbusRequest.__init__(self, **kwargs) - self.records = records or [] - - def encode(self): - ''' Encodes the request packet - - :returns: The byte encoded packet - ''' - total_length = sum((record.record_length * 2) + 7 for record in self.records) - packet = struct.pack('B', total_length) - for record in self.records: - packet += struct.pack('>BHHH', 0x06, record.file_number, - record.record_number, record.record_length) - packet += record.record_data - return packet - - def decode(self, data): - ''' Decodes the incoming request - - :param data: The data to decode into the address - ''' - count, self.records = 1, [] - byte_count = byte2int(data[0]) - while count < byte_count: - decoded = struct.unpack('>BHHH', data[count:count+7]) - response_length = decoded[3] * 2 - count += response_length + 7 - record = FileRecord(record_length=decoded[3], - file_number=decoded[1], record_number=decoded[2], - record_data=data[count - response_length:count]) - if decoded[0] == 0x06: self.records.append(record) - - def execute(self, context): - ''' Run the write file record request against the context - - :param context: The datastore to request from - :returns: The populated response - ''' - # TODO do some new context operation here - # if file number, record number, or address + length - # is too big, return an error. - return WriteFileRecordResponse(self.records) - - -class WriteFileRecordResponse(ModbusResponse): - ''' - The normal response is an echo of the request. - ''' - function_code = 0x15 - _rtu_byte_count_pos = 2 - - def __init__(self, records=None, **kwargs): - ''' Initializes a new instance - - :param records: The file record requests to be read - ''' - ModbusResponse.__init__(self, **kwargs) - self.records = records or [] - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - total_length = sum((record.record_length * 2) + 7 for record in self.records) - packet = struct.pack('B', total_length) - for record in self.records: - packet += struct.pack('>BHHH', 0x06, record.file_number, - record.record_number, record.record_length) - packet += record.record_data - return packet - - def decode(self, data): - ''' Decodes the incoming request - - :param data: The data to decode into the address - ''' - count, self.records = 1, [] - byte_count = byte2int(data[0]) - while count < byte_count: - decoded = struct.unpack('>BHHH', data[count:count+7]) - response_length = decoded[3] * 2 - count += response_length + 7 - record = FileRecord(record_length=decoded[3], - file_number=decoded[1], record_number=decoded[2], - record_data=data[count - response_length:count]) - if decoded[0] == 0x06: self.records.append(record) - - -class ReadFifoQueueRequest(ModbusRequest): - ''' - This function code allows to read the contents of a First-In-First-Out - (FIFO) queue of register in a remote device. The function returns a - count of the registers in the queue, followed by the queued data. - Up to 32 registers can be read: the count, plus up to 31 queued data - registers. - - The queue count register is returned first, followed by the queued data - registers. The function reads the queue contents, but does not clear - them. - ''' - function_code = 0x18 - _rtu_frame_size = 6 - - def __init__(self, address=0x0000, **kwargs): - ''' Initializes a new instance - - :param address: The fifo pointer address (0x0000 to 0xffff) - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - self.values = [] # this should be added to the context - - def encode(self): - ''' Encodes the request packet - - :returns: The byte encoded packet - ''' - return struct.pack('>H', self.address) - - def decode(self, data): - ''' Decodes the incoming request - - :param data: The data to decode into the address - ''' - self.address = struct.unpack('>H', data)[0] - - def execute(self, context): - ''' Run a read exeception status request against the store - - :param context: The datastore to request from - :returns: The populated response - ''' - if not (0x0000 <= self.address <= 0xffff): - return self.doException(merror.IllegalValue) - if len(self.values) > 31: - return self.doException(merror.IllegalValue) - # TODO pull the values from some context - return ReadFifoQueueResponse(self.values) - - -class ReadFifoQueueResponse(ModbusResponse): - ''' - In a normal response, the byte count shows the quantity of bytes to - follow, including the queue count bytes and value register bytes - (but not including the error check field). The queue count is the - quantity of data registers in the queue (not including the count register). - - If the queue count exceeds 31, an exception response is returned with an - error code of 03 (Illegal Data Value). - ''' - function_code = 0x18 - - @classmethod - def calculateRtuFrameSize(cls, buffer): - ''' Calculates the size of the message - - :param buffer: A buffer containing the data that have been received. - :returns: The number of bytes in the response. - ''' - hi_byte = byte2int(buffer[2]) - lo_byte = byte2int(buffer[3]) - return (hi_byte << 16) + lo_byte + 6 - - def __init__(self, values=None, **kwargs): - ''' Initializes a new instance - - :param values: The list of values of the fifo to return - ''' - ModbusResponse.__init__(self, **kwargs) - self.values = values or [] - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - length = len(self.values) * 2 - packet = struct.pack('>HH', 2 + length, length) - for value in self.values: - packet += struct.pack('>H', value) - return packet - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - self.values = [] - _, count = struct.unpack('>HH', data[0:4]) - for index in range(0, count - 4): - idx = 4 + index * 2 - self.values.append(struct.unpack('>H', data[idx:idx + 2])[0]) - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "FileRecord", - "ReadFileRecordRequest", "ReadFileRecordResponse", - "WriteFileRecordRequest", "WriteFileRecordResponse", - "ReadFifoQueueRequest", "ReadFifoQueueResponse", -] diff --git a/modbus/pymodbus/framer/__init__.py b/modbus/pymodbus/framer/__init__.py deleted file mode 100644 index 4859d01..0000000 --- a/modbus/pymodbus/framer/__init__.py +++ /dev/null @@ -1,49 +0,0 @@ -from pymodbus.interfaces import IModbusFramer -import struct - -# Unit ID, Function Code -BYTE_ORDER = '>' -FRAME_HEADER = 'BB' - -# Transaction Id, Protocol ID, Length, Unit ID, Function Code -SOCKET_FRAME_HEADER = BYTE_ORDER + 'HHH' + FRAME_HEADER - -# Function Code -TLS_FRAME_HEADER = BYTE_ORDER + 'B' - -class ModbusFramer(IModbusFramer): - """ - Base Framer class - """ - - def _validate_unit_id(self, units, single): - """ - Validates if the received data is valid for the client - :param units: list of unit id for which the transaction is valid - :param single: Set to true to treat this as a single context - :return: """ - - if single: - return True - else: - if 0 in units or 0xFF in units: - # Handle Modbus TCP unit identifier (0x00 0r 0xFF) - # in asynchronous requests - return True - return self._header['uid'] in units - - def sendPacket(self, message): - """ - Sends packets on the bus with 3.5char delay between frames - :param message: Message to be sent over the bus - :return: - """ - return self.client.send(message) - - def recvPacket(self, size): - """ - Receives packet from the bus with specified len - :param size: Number of bytes to read - :return: - """ - return self.client.recv(size) diff --git a/modbus/pymodbus/framer/ascii_framer.py b/modbus/pymodbus/framer/ascii_framer.py deleted file mode 100644 index a88f649..0000000 --- a/modbus/pymodbus/framer/ascii_framer.py +++ /dev/null @@ -1,208 +0,0 @@ -import struct -from binascii import b2a_hex, a2b_hex - -from pymodbus.exceptions import ModbusIOException -from pymodbus.utilities import checkLRC, computeLRC -from pymodbus.framer import ModbusFramer, FRAME_HEADER, BYTE_ORDER - - -ASCII_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Modbus ASCII Message -# --------------------------------------------------------------------------- # -class ModbusAsciiFramer(ModbusFramer): - """ - Modbus ASCII Frame Controller:: - - [ Start ][Address ][ Function ][ Data ][ LRC ][ End ] - 1c 2c 2c Nc 2c 2c - - * data can be 0 - 2x252 chars - * end is '\\r\\n' (Carriage return line feed), however the line feed - character can be changed via a special command - * start is ':' - - This framer is used for serial transmission. Unlike the RTU protocol, - the data in this framer is transferred in plain text ascii. - """ - - def __init__(self, decoder, client=None): - """ Initializes a new instance of the framer - - :param decoder: The decoder implementation to use - """ - self._buffer = b'' - self._header = {'lrc': '0000', 'len': 0, 'uid': 0x00} - self._hsize = 0x02 - self._start = b':' - self._end = b"\r\n" - self.decoder = decoder - self.client = client - - # ----------------------------------------------------------------------- # - # Private Helper Functions - # ----------------------------------------------------------------------- # - def decode_data(self, data): - if len(data) > 1: - uid = int(data[1:3], 16) - fcode = int(data[3:5], 16) - return dict(unit=uid, fcode=fcode) - return dict() - - def checkFrame(self): - """ Check and decode the next frame - - :returns: True if we successful, False otherwise - """ - start = self._buffer.find(self._start) - if start == -1: - return False - if start > 0: # go ahead and skip old bad data - self._buffer = self._buffer[start:] - start = 0 - - end = self._buffer.find(self._end) - if end != -1: - self._header['len'] = end - self._header['uid'] = int(self._buffer[1:3], 16) - self._header['lrc'] = int(self._buffer[end - 2:end], 16) - data = a2b_hex(self._buffer[start + 1:end - 2]) - return checkLRC(data, self._header['lrc']) - return False - - def advanceFrame(self): - """ Skip over the current framed message - This allows us to skip over the current message after we have processed - it or determined that it contains an error. It also has to reset the - current frame header handle - """ - self._buffer = self._buffer[self._header['len'] + 2:] - self._header = {'lrc': '0000', 'len': 0, 'uid': 0x00} - - def isFrameReady(self): - """ Check if we should continue decode logic - This is meant to be used in a while loop in the decoding phase to let - the decoder know that there is still data in the buffer. - - :returns: True if ready, False otherwise - """ - return len(self._buffer) > 1 - - def addToFrame(self, message): - """ Add the next message to the frame buffer - This should be used before the decoding while loop to add the received - data to the buffer handle. - - :param message: The most recent packet - """ - self._buffer += message - - def getFrame(self): - """ Get the next frame from the buffer - - :returns: The frame data or '' - """ - start = self._hsize + 1 - end = self._header['len'] - 2 - buffer = self._buffer[start:end] - if end > 0: - return a2b_hex(buffer) - return b'' - - def resetFrame(self): - """ Reset the entire message frame. - This allows us to skip ovver errors that may be in the stream. - It is hard to know if we are simply out of sync or if there is - an error in the stream as we have no way to check the start or - end of the message (python just doesn't have the resolution to - check for millisecond delays). - """ - self._buffer = b'' - self._header = {'lrc': '0000', 'len': 0, 'uid': 0x00} - - def populateResult(self, result): - """ Populates the modbus result header - - The serial packets do not have any header information - that is copied. - - :param result: The response packet - """ - result.unit_id = self._header['uid'] - - # ----------------------------------------------------------------------- # - # Public Member Functions - # ----------------------------------------------------------------------- # - def processIncomingPacket(self, data, callback, unit, **kwargs): - """ - The new packet processing pattern - - This takes in a new request packet, adds it to the current - packet stream, and performs framing on it. That is, checks - for complete messages, and once found, will process all that - exist. This handles the case when we read N + 1 or 1 // N - messages at a time instead of 1. - - The processed and decoded messages are pushed to the callback - function to process and send. - - :param data: The new packet data - :param callback: The function to send results to - :param unit: Process if unit id matches, ignore otherwise (could be a - list of unit ids (server) or single unit id(client/server)) - :param single: True or False (If True, ignore unit address validation) - - """ - if not isinstance(unit, (list, tuple)): - unit = [unit] - single = kwargs.get('single', False) - self.addToFrame(data) - while self.isFrameReady(): - if self.checkFrame(): - if self._validate_unit_id(unit, single): - frame = self.getFrame() - result = self.decoder.decode(frame) - if result is None: - raise ModbusIOException("Unable to decode response") - self.populateResult(result) - self.advanceFrame() - callback(result) # defer this - else: - _logger.error("Not a valid unit id - {}, " - "ignoring!!".format(self._header['uid'])) - self.resetFrame() - else: - break - - def buildPacket(self, message): - """ Creates a ready to send modbus packet - Built off of a modbus request/response - - :param message: The request/response to send - :return: The encoded packet - """ - encoded = message.encode() - buffer = struct.pack(ASCII_FRAME_HEADER, message.unit_id, - message.function_code) - checksum = computeLRC(encoded + buffer) - - packet = bytearray() - params = (message.unit_id, message.function_code) - packet.extend(self._start) - packet.extend(('%02x%02x' % params).encode()) - packet.extend(b2a_hex(encoded)) - packet.extend(('%02x' % checksum).encode()) - packet.extend(self._end) - return bytes(packet).upper() - - -# __END__ - diff --git a/modbus/pymodbus/framer/binary_framer.py b/modbus/pymodbus/framer/binary_framer.py deleted file mode 100644 index ba6655b..0000000 --- a/modbus/pymodbus/framer/binary_framer.py +++ /dev/null @@ -1,227 +0,0 @@ -import struct -from pymodbus.exceptions import ModbusIOException -from pymodbus.utilities import checkCRC, computeCRC -from pymodbus.framer import ModbusFramer, FRAME_HEADER, BYTE_ORDER - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - -BINARY_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER - -# --------------------------------------------------------------------------- # -# Modbus Binary Message -# --------------------------------------------------------------------------- # - - -class ModbusBinaryFramer(ModbusFramer): - """ - Modbus Binary Frame Controller:: - - [ Start ][Address ][ Function ][ Data ][ CRC ][ End ] - 1b 1b 1b Nb 2b 1b - - * data can be 0 - 2x252 chars - * end is '}' - * start is '{' - - The idea here is that we implement the RTU protocol, however, - instead of using timing for message delimiting, we use start - and end of message characters (in this case { and }). Basically, - this is a binary framer. - - The only case we have to watch out for is when a message contains - the { or } characters. If we encounter these characters, we - simply duplicate them. Hopefully we will not encounter those - characters that often and will save a little bit of bandwitch - without a real-time system. - - Protocol defined by jamod.sourceforge.net. - """ - - def __init__(self, decoder, client=None): - """ Initializes a new instance of the framer - - :param decoder: The decoder implementation to use - """ - self._buffer = b'' - self._header = {'crc': 0x0000, 'len': 0, 'uid': 0x00} - self._hsize = 0x01 - self._start = b'\x7b' # { - self._end = b'\x7d' # } - self._repeat = [b'}'[0], b'{'[0]] # python3 hack - self.decoder = decoder - self.client = client - - # ----------------------------------------------------------------------- # - # Private Helper Functions - # ----------------------------------------------------------------------- # - def decode_data(self, data): - if len(data) > self._hsize: - uid = struct.unpack('>B', data[1:2])[0] - fcode = struct.unpack('>B', data[2:3])[0] - return dict(unit=uid, fcode=fcode) - return dict() - - def checkFrame(self): - """ Check and decode the next frame - - :returns: True if we are successful, False otherwise - """ - start = self._buffer.find(self._start) - if start == -1: - return False - if start > 0: # go ahead and skip old bad data - self._buffer = self._buffer[start:] - - end = self._buffer.find(self._end) - if end != -1: - self._header['len'] = end - self._header['uid'] = struct.unpack('>B', self._buffer[1:2])[0] - self._header['crc'] = struct.unpack('>H', self._buffer[end - 2:end])[0] - data = self._buffer[start + 1:end - 2] - return checkCRC(data, self._header['crc']) - return False - - def advanceFrame(self): - """ Skip over the current framed message - This allows us to skip over the current message after we have processed - it or determined that it contains an error. It also has to reset the - current frame header handle - """ - self._buffer = self._buffer[self._header['len'] + 2:] - self._header = {'crc':0x0000, 'len':0, 'uid':0x00} - - def isFrameReady(self): - """ Check if we should continue decode logic - This is meant to be used in a while loop in the decoding phase to let - the decoder know that there is still data in the buffer. - - :returns: True if ready, False otherwise - """ - return len(self._buffer) > 1 - - def addToFrame(self, message): - """ Add the next message to the frame buffer - This should be used before the decoding while loop to add the received - data to the buffer handle. - - :param message: The most recent packet - """ - self._buffer += message - - def getFrame(self): - """ Get the next frame from the buffer - - :returns: The frame data or '' - """ - start = self._hsize + 1 - end = self._header['len'] - 2 - buffer = self._buffer[start:end] - if end > 0: - return buffer - return b'' - - def populateResult(self, result): - """ Populates the modbus result header - - The serial packets do not have any header information - that is copied. - - :param result: The response packet - """ - result.unit_id = self._header['uid'] - - # ----------------------------------------------------------------------- # - # Public Member Functions - # ----------------------------------------------------------------------- # - def processIncomingPacket(self, data, callback, unit, **kwargs): - """ - The new packet processing pattern - - This takes in a new request packet, adds it to the current - packet stream, and performs framing on it. That is, checks - for complete messages, and once found, will process all that - exist. This handles the case when we read N + 1 or 1 // N - messages at a time instead of 1. - - The processed and decoded messages are pushed to the callback - function to process and send. - - :param data: The new packet data - :param callback: The function to send results to - :param unit: Process if unit id matches, ignore otherwise (could be a - list of unit ids (server) or single unit id(client/server) - :param single: True or False (If True, ignore unit address validation) - - """ - self.addToFrame(data) - if not isinstance(unit, (list, tuple)): - unit = [unit] - single = kwargs.get('single', False) - while self.isFrameReady(): - if self.checkFrame(): - if self._validate_unit_id(unit, single): - result = self.decoder.decode(self.getFrame()) - if result is None: - raise ModbusIOException("Unable to decode response") - self.populateResult(result) - self.advanceFrame() - callback(result) # defer or push to a thread? - else: - _logger.debug("Not a valid unit id - {}, " - "ignoring!!".format(self._header['uid'])) - self.resetFrame() - break - - else: - _logger.debug("Frame check failed, ignoring!!") - self.resetFrame() - break - - def buildPacket(self, message): - """ Creates a ready to send modbus packet - - :param message: The request/response to send - :returns: The encoded packet - """ - data = self._preflight(message.encode()) - packet = struct.pack(BINARY_FRAME_HEADER, - message.unit_id, - message.function_code) + data - packet += struct.pack(">H", computeCRC(packet)) - packet = self._start + packet + self._end - return packet - - def _preflight(self, data): - """ - Preflight buffer test - - This basically scans the buffer for start and end - tags and if found, escapes them. - - :param data: The message to escape - :returns: the escaped packet - """ - array = bytearray() - for d in data: - if d in self._repeat: - array.append(d) - array.append(d) - return bytes(array) - - def resetFrame(self): - """ Reset the entire message frame. - This allows us to skip ovver errors that may be in the stream. - It is hard to know if we are simply out of sync or if there is - an error in the stream as we have no way to check the start or - end of the message (python just doesn't have the resolution to - check for millisecond delays). - """ - self._buffer = b'' - self._header = {'crc': 0x0000, 'len': 0, 'uid': 0x00} - - -# __END__ diff --git a/modbus/pymodbus/framer/rtu_framer.py b/modbus/pymodbus/framer/rtu_framer.py deleted file mode 100644 index 6c246ba..0000000 --- a/modbus/pymodbus/framer/rtu_framer.py +++ /dev/null @@ -1,334 +0,0 @@ -import struct -import time - -from pymodbus.exceptions import ModbusIOException -from pymodbus.exceptions import InvalidMessageReceivedException -from pymodbus.utilities import checkCRC, computeCRC -from pymodbus.utilities import hexlify_packets, ModbusTransactionState -from pymodbus.compat import byte2int -from pymodbus.framer import ModbusFramer, FRAME_HEADER, BYTE_ORDER - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - -RTU_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER - - -# --------------------------------------------------------------------------- # -# Modbus RTU Message -# --------------------------------------------------------------------------- # -class ModbusRtuFramer(ModbusFramer): - """ - Modbus RTU Frame controller:: - - [ Start Wait ] [Address ][ Function Code] [ Data ][ CRC ][ End Wait ] - 3.5 chars 1b 1b Nb 2b 3.5 chars - - Wait refers to the amount of time required to transmit at least x many - characters. In this case it is 3.5 characters. Also, if we receive a - wait of 1.5 characters at any point, we must trigger an error message. - Also, it appears as though this message is little endian. The logic is - simplified as the following:: - - block-on-read: - read until 3.5 delay - check for errors - decode - - The following table is a listing of the baud wait times for the specified - baud rates:: - - ------------------------------------------------------------------ - Baud 1.5c (18 bits) 3.5c (38 bits) - ------------------------------------------------------------------ - 1200 13333.3 us 31666.7 us - 4800 3333.3 us 7916.7 us - 9600 1666.7 us 3958.3 us - 19200 833.3 us 1979.2 us - 38400 416.7 us 989.6 us - ------------------------------------------------------------------ - 1 Byte = start + 8 bits + parity + stop = 11 bits - (1/Baud)(bits) = delay seconds - """ - - def __init__(self, decoder, client=None): - """ Initializes a new instance of the framer - - :param decoder: The decoder factory implementation to use - """ - self._buffer = b'' - self._header = {'uid': 0x00, 'len': 0, 'crc': b'\x00\x00'} - self._hsize = 0x01 - self._end = b'\x0d\x0a' - self._min_frame_size = 4 - self.decoder = decoder - self.client = client - - # ----------------------------------------------------------------------- # - # Private Helper Functions - # ----------------------------------------------------------------------- # - def decode_data(self, data): - if len(data) > self._hsize: - uid = byte2int(data[0]) - fcode = byte2int(data[1]) - return dict(unit=uid, fcode=fcode) - return dict() - - def checkFrame(self): - """ - Check if the next frame is available. - Return True if we were successful. - - 1. Populate header - 2. Discard frame if UID does not match - """ - try: - self.populateHeader() - frame_size = self._header['len'] - data = self._buffer[:frame_size - 2] - crc = self._header['crc'] - crc_val = (byte2int(crc[0]) << 8) + byte2int(crc[1]) - return checkCRC(data, crc_val) - except (IndexError, KeyError, struct.error): - return False - - def advanceFrame(self): - """ - Skip over the current framed message - This allows us to skip over the current message after we have processed - it or determined that it contains an error. It also has to reset the - current frame header handle - """ - - self._buffer = self._buffer[self._header['len']:] - _logger.debug("Frame advanced, resetting header!!") - self._header = {'uid': 0x00, 'len': 0, 'crc': b'\x00\x00'} - - def resetFrame(self): - """ - Reset the entire message frame. - This allows us to skip over errors that may be in the stream. - It is hard to know if we are simply out of sync or if there is - an error in the stream as we have no way to check the start or - end of the message (python just doesn't have the resolution to - check for millisecond delays). - """ - _logger.debug("Resetting frame - Current Frame in " - "buffer - {}".format(hexlify_packets(self._buffer))) - self._buffer = b'' - self._header = {'uid': 0x00, 'len': 0, 'crc': b'\x00\x00'} - - def isFrameReady(self): - """ - Check if we should continue decode logic - This is meant to be used in a while loop in the decoding phase to let - the decoder know that there is still data in the buffer. - - :returns: True if ready, False otherwise - """ - if len(self._buffer) <= self._hsize: - return False - - try: - # Frame is ready only if populateHeader() successfully populates crc field which finishes RTU frame - # Otherwise, if buffer is not yet long enough, populateHeader() raises IndexError - self.populateHeader() - except IndexError: - return False - - return True - - def populateHeader(self, data=None): - """ - Try to set the headers `uid`, `len` and `crc`. - - This method examines `self._buffer` and writes meta - information into `self._header`. - - Beware that this method will raise an IndexError if - `self._buffer` is not yet long enough. - """ - data = data if data is not None else self._buffer - self._header['uid'] = byte2int(data[0]) - func_code = byte2int(data[1]) - pdu_class = self.decoder.lookupPduClass(func_code) - size = pdu_class.calculateRtuFrameSize(data) - self._header['len'] = size - - if len(data) < size: - # crc yet not available - raise IndexError - self._header['crc'] = data[size - 2:size] - - def addToFrame(self, message): - """ - This should be used before the decoding while loop to add the received - data to the buffer handle. - - :param message: The most recent packet - """ - self._buffer += message - - def getFrame(self): - """ - Get the next frame from the buffer - - :returns: The frame data or '' - """ - start = self._hsize - end = self._header['len'] - 2 - buffer = self._buffer[start:end] - if end > 0: - _logger.debug("Getting Frame - {}".format(hexlify_packets(buffer))) - return buffer - return b'' - - def populateResult(self, result): - """ - Populates the modbus result header - - The serial packets do not have any header information - that is copied. - - :param result: The response packet - """ - result.unit_id = self._header['uid'] - result.transaction_id = self._header['uid'] - - # ----------------------------------------------------------------------- # - # Public Member Functions - # ----------------------------------------------------------------------- # - def processIncomingPacket(self, data, callback, unit, **kwargs): - """ - The new packet processing pattern - - This takes in a new request packet, adds it to the current - packet stream, and performs framing on it. That is, checks - for complete messages, and once found, will process all that - exist. This handles the case when we read N + 1 or 1 // N - messages at a time instead of 1. - - The processed and decoded messages are pushed to the callback - function to process and send. - - :param data: The new packet data - :param callback: The function to send results to - :param unit: Process if unit id matches, ignore otherwise (could be a - list of unit ids (server) or single unit id(client/server) - :param single: True or False (If True, ignore unit address validation) - - """ - if not isinstance(unit, (list, tuple)): - unit = [unit] - self.addToFrame(data) - single = kwargs.get("single", False) - if self.isFrameReady(): - if self.checkFrame(): - if self._validate_unit_id(unit, single): - self._process(callback) - else: - _logger.debug("Not a valid unit id - {}, " - "ignoring!!".format(self._header['uid'])) - self.resetFrame() - else: - _logger.debug("Frame check failed, ignoring!!") - self.resetFrame() - else: - _logger.debug("Frame - [{}] not ready".format(data)) - - def buildPacket(self, message): - """ - Creates a ready to send modbus packet - - :param message: The populated request/response to send - """ - data = message.encode() - packet = struct.pack(RTU_FRAME_HEADER, - message.unit_id, - message.function_code) + data - packet += struct.pack(">H", computeCRC(packet)) - message.transaction_id = message.unit_id # Ensure that transaction is actually the unit id for serial comms - return packet - - def sendPacket(self, message): - """ - Sends packets on the bus with 3.5char delay between frames - :param message: Message to be sent over the bus - :return: - """ - start = time.time() - timeout = start + self.client.timeout - while self.client.state != ModbusTransactionState.IDLE: - if self.client.state == ModbusTransactionState.TRANSACTION_COMPLETE: - ts = round(time.time(), 6) - _logger.debug("Changing state to IDLE - Last Frame End - {}, " - "Current Time stamp - {}".format( - self.client.last_frame_end, ts) - ) - - if self.client.last_frame_end: - idle_time = self.client.idle_time() - if round(ts - idle_time, 6) <= self.client.silent_interval: - _logger.debug("Waiting for 3.5 char before next " - "send - {} ms".format( - self.client.silent_interval * 1000) - ) - time.sleep(self.client.silent_interval) - else: - # Recovering from last error ?? - time.sleep(self.client.silent_interval) - self.client.state = ModbusTransactionState.IDLE - elif self.client.state == ModbusTransactionState.RETRYING: - # Simple lets settle down!!! - # To check for higher baudrates - time.sleep(self.client.timeout) - break - else: - if time.time() > timeout: - _logger.debug("Spent more time than the read time out, " - "resetting the transaction to IDLE") - self.client.state = ModbusTransactionState.IDLE - else: - _logger.debug("Sleeping") - time.sleep(self.client.silent_interval) - size = self.client.send(message) - self.client.last_frame_end = round(time.time(), 6) - return size - - def recvPacket(self, size): - """ - Receives packet from the bus with specified len - :param size: Number of bytes to read - :return: - """ - result = self.client.recv(size) - self.client.last_frame_end = round(time.time(), 6) - return result - - def _process(self, callback, error=False): - """ - Process incoming packets irrespective error condition - """ - data = self.getRawFrame() if error else self.getFrame() - result = self.decoder.decode(data) - if result is None: - raise ModbusIOException("Unable to decode request") - elif error and result.function_code < 0x80: - raise InvalidMessageReceivedException(result) - else: - self.populateResult(result) - self.advanceFrame() - callback(result) # defer or push to a thread? - - def getRawFrame(self): - """ - Returns the complete buffer - """ - _logger.debug("Getting Raw Frame - " - "{}".format(hexlify_packets(self._buffer))) - return self._buffer - -# __END__ diff --git a/modbus/pymodbus/framer/socket_framer.py b/modbus/pymodbus/framer/socket_framer.py deleted file mode 100644 index e671897..0000000 --- a/modbus/pymodbus/framer/socket_framer.py +++ /dev/null @@ -1,217 +0,0 @@ -import struct -from pymodbus.exceptions import ModbusIOException -from pymodbus.exceptions import InvalidMessageReceivedException -from pymodbus.utilities import hexlify_packets -from pymodbus.framer import ModbusFramer, SOCKET_FRAME_HEADER - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - -# --------------------------------------------------------------------------- # -# Modbus TCP Message -# --------------------------------------------------------------------------- # - - -class ModbusSocketFramer(ModbusFramer): - """ Modbus Socket Frame controller - - Before each modbus TCP message is an MBAP header which is used as a - message frame. It allows us to easily separate messages as follows:: - - [ MBAP Header ] [ Function Code] [ Data ] \ - [ tid ][ pid ][ length ][ uid ] - 2b 2b 2b 1b 1b Nb - - while len(message) > 0: - tid, pid, length`, uid = struct.unpack(">HHHB", message) - request = message[0:7 + length - 1`] - message = [7 + length - 1:] - - * length = uid + function code + data - * The -1 is to account for the uid byte - """ - - def __init__(self, decoder, client=None): - """ Initializes a new instance of the framer - - :param decoder: The decoder factory implementation to use - """ - self._buffer = b'' - self._header = {'tid': 0, 'pid': 0, 'len': 0, 'uid': 0} - self._hsize = 0x07 - self.decoder = decoder - self.client = client - - # ----------------------------------------------------------------------- # - # Private Helper Functions - # ----------------------------------------------------------------------- # - def checkFrame(self): - """ - Check and decode the next frame Return true if we were successful - """ - if self.isFrameReady(): - (self._header['tid'], self._header['pid'], - self._header['len'], self._header['uid']) = struct.unpack( - '>HHHB', self._buffer[0:self._hsize]) - - # someone sent us an error? ignore it - if self._header['len'] < 2: - self.advanceFrame() - # we have at least a complete message, continue - elif len(self._buffer) - self._hsize + 1 >= self._header['len']: - return True - # we don't have enough of a message yet, wait - return False - - def advanceFrame(self): - """ Skip over the current framed message - This allows us to skip over the current message after we have processed - it or determined that it contains an error. It also has to reset the - current frame header handle - """ - length = self._hsize + self._header['len'] - 1 - self._buffer = self._buffer[length:] - self._header = {'tid': 0, 'pid': 0, 'len': 0, 'uid': 0} - - def isFrameReady(self): - """ Check if we should continue decode logic - This is meant to be used in a while loop in the decoding phase to let - the decoder factory know that there is still data in the buffer. - - :returns: True if ready, False otherwise - """ - return len(self._buffer) > self._hsize - - def addToFrame(self, message): - """ Adds new packet data to the current frame buffer - - :param message: The most recent packet - """ - self._buffer += message - - def getFrame(self): - """ Return the next frame from the buffered data - - :returns: The next full frame buffer - """ - length = self._hsize + self._header['len'] - 1 - return self._buffer[self._hsize:length] - - def populateResult(self, result): - """ - Populates the modbus result with the transport specific header - information (pid, tid, uid, checksum, etc) - - :param result: The response packet - """ - result.transaction_id = self._header['tid'] - result.protocol_id = self._header['pid'] - result.unit_id = self._header['uid'] - - # ----------------------------------------------------------------------- # - # Public Member Functions - # ----------------------------------------------------------------------- # - def decode_data(self, data): - if len(data) > self._hsize: - tid, pid, length, uid, fcode = struct.unpack(SOCKET_FRAME_HEADER, - data[0:self._hsize+1]) - return dict(tid=tid, pid=pid, length=length, unit=uid, fcode=fcode) - return dict() - - def processIncomingPacket(self, data, callback, unit, **kwargs): - """ - The new packet processing pattern - - This takes in a new request packet, adds it to the current - packet stream, and performs framing on it. That is, checks - for complete messages, and once found, will process all that - exist. This handles the case when we read N + 1 or 1 // N - messages at a time instead of 1. - - The processed and decoded messages are pushed to the callback - function to process and send. - - :param data: The new packet data - :param callback: The function to send results to - :param unit: Process if unit id matches, ignore otherwise (could be a - list of unit ids (server) or single unit id(client/server) - :param single: True or False (If True, ignore unit address validation) - :return: - """ - if not isinstance(unit, (list, tuple)): - unit = [unit] - single = kwargs.get("single", False) - _logger.debug("Processing: " + hexlify_packets(data)) - self.addToFrame(data) - while True: - if self.isFrameReady(): - if self.checkFrame(): - if self._validate_unit_id(unit, single): - self._process(callback) - else: - _logger.debug("Not a valid unit id - {}, " - "ignoring!!".format(self._header['uid'])) - self.resetFrame() - else: - _logger.debug("Frame check failed, ignoring!!") - self.resetFrame() - else: - if len(self._buffer): - # Possible error ??? - if self._header['len'] < 2: - self._process(callback, error=True) - break - - def _process(self, callback, error=False): - """ - Process incoming packets irrespective error condition - """ - data = self.getRawFrame() if error else self.getFrame() - result = self.decoder.decode(data) - if result is None: - raise ModbusIOException("Unable to decode request") - elif error and result.function_code < 0x80: - raise InvalidMessageReceivedException(result) - else: - self.populateResult(result) - self.advanceFrame() - callback(result) # defer or push to a thread? - - def resetFrame(self): - """ - Reset the entire message frame. - This allows us to skip ovver errors that may be in the stream. - It is hard to know if we are simply out of sync or if there is - an error in the stream as we have no way to check the start or - end of the message (python just doesn't have the resolution to - check for millisecond delays). - """ - self._buffer = b'' - self._header = {'tid': 0, 'pid': 0, 'len': 0, 'uid': 0} - - def getRawFrame(self): - """ - Returns the complete buffer - """ - return self._buffer - - def buildPacket(self, message): - """ Creates a ready to send modbus packet - - :param message: The populated request/response to send - """ - data = message.encode() - packet = struct.pack(SOCKET_FRAME_HEADER, - message.transaction_id, - message.protocol_id, - len(data) + 2, - message.unit_id, - message.function_code) - packet += data - return packet - - -# __END__ diff --git a/modbus/pymodbus/framer/tls_framer.py b/modbus/pymodbus/framer/tls_framer.py deleted file mode 100644 index 33bd48d..0000000 --- a/modbus/pymodbus/framer/tls_framer.py +++ /dev/null @@ -1,185 +0,0 @@ -import struct -from pymodbus.exceptions import ModbusIOException -from pymodbus.exceptions import InvalidMessageReceivedException -from pymodbus.utilities import hexlify_packets -from pymodbus.framer import ModbusFramer, TLS_FRAME_HEADER - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - -# --------------------------------------------------------------------------- # -# Modbus TLS Message -# --------------------------------------------------------------------------- # - - -class ModbusTlsFramer(ModbusFramer): - """ Modbus TLS Frame controller - - No prefix MBAP header before decrypted PDU is used as a message frame for - Modbus Security Application Protocol. It allows us to easily separate - decrypted messages which is PDU as follows: - - [ Function Code] [ Data ] - 1b Nb - """ - - def __init__(self, decoder, client=None): - """ Initializes a new instance of the framer - - :param decoder: The decoder factory implementation to use - """ - self._buffer = b'' - self._header = {} - self._hsize = 0x0 - self.decoder = decoder - self.client = client - - # ----------------------------------------------------------------------- # - # Private Helper Functions - # ----------------------------------------------------------------------- # - def checkFrame(self): - """ - Check and decode the next frame Return true if we were successful - """ - if self.isFrameReady(): - # we have at least a complete message, continue - if len(self._buffer) - self._hsize >= 1: - return True - # we don't have enough of a message yet, wait - return False - - def advanceFrame(self): - """ Skip over the current framed message - This allows us to skip over the current message after we have processed - it or determined that it contains an error. It also has to reset the - current frame header handle - """ - self._buffer = b'' - self._header = {} - - def isFrameReady(self): - """ Check if we should continue decode logic - This is meant to be used in a while loop in the decoding phase to let - the decoder factory know that there is still data in the buffer. - - :returns: True if ready, False otherwise - """ - return len(self._buffer) > self._hsize - - def addToFrame(self, message): - """ Adds new packet data to the current frame buffer - - :param message: The most recent packet - """ - self._buffer += message - - def getFrame(self): - """ Return the next frame from the buffered data - - :returns: The next full frame buffer - """ - return self._buffer[self._hsize:] - - def populateResult(self, result): - """ - Populates the modbus result with the transport specific header - information (no header before PDU in decrypted message) - - :param result: The response packet - """ - return - - # ----------------------------------------------------------------------- # - # Public Member Functions - # ----------------------------------------------------------------------- # - def decode_data(self, data): - if len(data) > self._hsize: - (fcode,) = struct.unpack(TLS_FRAME_HEADER, data[0:self._hsize+1]) - return dict(fcode=fcode) - return dict() - - def processIncomingPacket(self, data, callback, unit, **kwargs): - """ - The new packet processing pattern - - This takes in a new request packet, adds it to the current - packet stream, and performs framing on it. That is, checks - for complete messages, and once found, will process all that - exist. This handles the case when we read N + 1 or 1 // N - messages at a time instead of 1. - - The processed and decoded messages are pushed to the callback - function to process and send. - - :param data: The new packet data - :param callback: The function to send results to - :param unit: Process if unit id matches, ignore otherwise (could be a - list of unit ids (server) or single unit id(client/server) - :param single: True or False (If True, ignore unit address validation) - :return: - """ - if not isinstance(unit, (list, tuple)): - unit = [unit] - # no unit id for Modbus Security Application Protocol - single = kwargs.get("single", True) - _logger.debug("Processing: " + hexlify_packets(data)) - self.addToFrame(data) - - if self.isFrameReady(): - if self.checkFrame(): - if self._validate_unit_id(unit, single): - self._process(callback) - else: - _logger.debug("Not in valid unit id - {}, " - "ignoring!!".format(unit)) - self.resetFrame() - else: - _logger.debug("Frame check failed, ignoring!!") - self.resetFrame() - - def _process(self, callback, error=False): - """ - Process incoming packets irrespective error condition - """ - data = self.getRawFrame() if error else self.getFrame() - result = self.decoder.decode(data) - if result is None: - raise ModbusIOException("Unable to decode request") - elif error and result.function_code < 0x80: - raise InvalidMessageReceivedException(result) - else: - self.populateResult(result) - self.advanceFrame() - callback(result) # defer or push to a thread? - - def resetFrame(self): - """ - Reset the entire message frame. - This allows us to skip ovver errors that may be in the stream. - It is hard to know if we are simply out of sync or if there is - an error in the stream as we have no way to check the start or - end of the message (python just doesn't have the resolution to - check for millisecond delays). - """ - self._buffer = b'' - - def getRawFrame(self): - """ - Returns the complete buffer - """ - return self._buffer - - def buildPacket(self, message): - """ Creates a ready to send modbus packet - - :param message: The populated request/response to send - """ - data = message.encode() - packet = struct.pack(TLS_FRAME_HEADER, message.function_code) - packet += data - return packet - -# __END__ diff --git a/modbus/pymodbus/interfaces.py b/modbus/pymodbus/interfaces.py deleted file mode 100644 index 49e6939..0000000 --- a/modbus/pymodbus/interfaces.py +++ /dev/null @@ -1,248 +0,0 @@ -""" -Pymodbus Interfaces ---------------------- - -A collection of base classes that are used throughout -the pymodbus library. -""" -from pymodbus.exceptions import (NotImplementedException, - MessageRegisterException) - - -# --------------------------------------------------------------------------- # -# Generic -# --------------------------------------------------------------------------- # -class Singleton(object): - """ - Singleton base class - http://mail.python.org/pipermail/python-list/2007-July/450681.html - """ - def __new__(cls, *args, **kwargs): - """ Create a new instance - """ - if '_inst' not in vars(cls): - cls._inst = object.__new__(cls) - return cls._inst - - -# --------------------------------------------------------------------------- # -# Project Specific -# --------------------------------------------------------------------------- # -class IModbusDecoder(object): - """ Modbus Decoder Base Class - - This interface must be implemented by a modbus message - decoder factory. These factories are responsible for - abstracting away converting a raw packet into a request / response - message object. - """ - - def decode(self, message): - """ Wrapper to decode a given packet - - :param message: The raw modbus request packet - :return: The decoded modbus message or None if error - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def lookupPduClass(self, function_code): - """ Use `function_code` to determine the class of the PDU. - - :param function_code: The function code specified in a frame. - :returns: The class of the PDU that has a matching `function_code`. - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def register(self, function=None): - """ - Registers a function and sub function class with the decoder - :param function: Custom function class to register - :return: - """ - raise NotImplementedException( - "Method not implemented by derived class") - - -class IModbusFramer(object): - """ - A framer strategy interface. The idea is that we abstract away all the - detail about how to detect if a current message frame exists, decoding - it, sending it, etc so that we can plug in a new Framer object (tcp, - rtu, ascii). - """ - - def checkFrame(self): - """ Check and decode the next frame - - :returns: True if we successful, False otherwise - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def advanceFrame(self): - """ Skip over the current framed message - This allows us to skip over the current message after we have processed - it or determined that it contains an error. It also has to reset the - current frame header handle - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def addToFrame(self, message): - """ Add the next message to the frame buffer - - This should be used before the decoding while loop to add the received - data to the buffer handle. - - :param message: The most recent packet - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def isFrameReady(self): - """ Check if we should continue decode logic - - This is meant to be used in a while loop in the decoding phase to let - the decoder know that there is still data in the buffer. - - :returns: True if ready, False otherwise - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def getFrame(self): - """ Get the next frame from the buffer - - :returns: The frame data or '' - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def populateResult(self, result): - """ Populates the modbus result with current frame header - - We basically copy the data back over from the current header - to the result header. This may not be needed for serial messages. - - :param result: The response packet - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def processIncomingPacket(self, data, callback): - """ The new packet processing pattern - - This takes in a new request packet, adds it to the current - packet stream, and performs framing on it. That is, checks - for complete messages, and once found, will process all that - exist. This handles the case when we read N + 1 or 1 / N - messages at a time instead of 1. - - The processed and decoded messages are pushed to the callback - function to process and send. - - :param data: The new packet data - :param callback: The function to send results to - """ - raise NotImplementedException( - "Method not implemented by derived class") - - def buildPacket(self, message): - """ Creates a ready to send modbus packet - - The raw packet is built off of a fully populated modbus - request / response message. - - :param message: The request/response to send - :returns: The built packet - """ - raise NotImplementedException( - "Method not implemented by derived class") - - -class IModbusSlaveContext(object): - """ - Interface for a modbus slave data context - - Derived classes must implemented the following methods: - reset(self) - validate(self, fx, address, count=1) - getValues(self, fx, address, count=1) - setValues(self, fx, address, values) - """ - __fx_mapper = {2: 'd', 4: 'i'} - __fx_mapper.update([(i, 'h') for i in [3, 6, 16, 22, 23]]) - __fx_mapper.update([(i, 'c') for i in [1, 5, 15]]) - - def decode(self, fx): - """ Converts the function code to the datastore to - - :param fx: The function we are working with - :returns: one of [d(iscretes),i(nputs),h(olding),c(oils) - """ - return self.__fx_mapper[fx] - - def reset(self): - """ Resets all the datastores to their default values - """ - raise NotImplementedException("Context Reset") - - def validate(self, fx, address, count=1): - """ Validates the request to make sure it is in range - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to test - :returns: True if the request in within range, False otherwise - """ - raise NotImplementedException("validate context values") - - def getValues(self, fx, address, count=1): - """ Get `count` values from datastore - - :param fx: The function we are working with - :param address: The starting address - :param count: The number of values to retrieve - :returns: The requested values from a:a+c - """ - raise NotImplementedException("get context values") - - def setValues(self, fx, address, values): - """ Sets the datastore with the supplied values - - :param fx: The function we are working with - :param address: The starting address - :param values: The new values to be set - """ - raise NotImplementedException("set context values") - - -class IPayloadBuilder(object): - """ - This is an interface to a class that can build a payload - for a modbus register write command. It should abstract - the codec for encoding data to the required format - (bcd, binary, char, etc). - """ - - def build(self): - """ Return the payload buffer as a list - - This list is two bytes per element and can - thus be treated as a list of registers. - - :returns: The payload buffer as a list - """ - raise NotImplementedException("set context values") - - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # -__all__ = [ - 'Singleton', - 'IModbusDecoder', 'IModbusFramer', 'IModbusSlaveContext', - 'IPayloadBuilder', -] diff --git a/modbus/pymodbus/internal/__init__.py b/modbus/pymodbus/internal/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/modbus/pymodbus/internal/ptwisted.py b/modbus/pymodbus/internal/ptwisted.py deleted file mode 100644 index fdd15a6..0000000 --- a/modbus/pymodbus/internal/ptwisted.py +++ /dev/null @@ -1,42 +0,0 @@ -''' -A collection of twisted utility code -''' -from pymodbus.compat import IS_PYTHON2, IS_PYTHON3 -if IS_PYTHON2: - from twisted.cred import portal, checkers - from twisted.conch import manhole, manhole_ssh - from twisted.conch.insults import insults - -#---------------------------------------------------------------------------# -# Logging -#---------------------------------------------------------------------------# -import logging -_logger = logging.getLogger(__name__) - - -#---------------------------------------------------------------------------# -# Twisted Helper Methods -#---------------------------------------------------------------------------# -def InstallManagementConsole(namespace, users={'admin': 'admin'}, port=503): - ''' Helper method to start an ssh management console - for the modbus server. - - :param namespace: The data to constrain the server to - :param users: The users to login with - :param port: The port to host the server on - ''' - if IS_PYTHON3: - raise NotImplemented("This code currently doesn't work on python3") - from twisted.internet import reactor - - def build_protocol(): - p = insults.ServerProtocol(manhole.ColoredManhole, namespace) - return p - - r = manhole_ssh.TerminalRealm() - r.chainedProtocolFactory = build_protocol - c = checkers.InMemoryUsernamePasswordDatabaseDontUse(**users) - p = portal.Portal(r, [c]) - factory = manhole_ssh.ConchFactory(p) - reactor.listenTCP(port, factory) - diff --git a/modbus/pymodbus/mei_message.py b/modbus/pymodbus/mei_message.py deleted file mode 100644 index 44f70ed..0000000 --- a/modbus/pymodbus/mei_message.py +++ /dev/null @@ -1,213 +0,0 @@ -''' -Encapsulated Interface (MEI) Transport Messages ------------------------------------------------ - -''' -import struct -from pymodbus.constants import DeviceInformation, MoreData -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.device import ModbusControlBlock -from pymodbus.device import DeviceInformationFactory -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.compat import iteritems, byte2int, IS_PYTHON3 - -_MCB = ModbusControlBlock() - - -class _OutOfSpaceException(Exception): - # This exception exists here as a simple, local way to manage response - # length control for the only MODBUS command which requires it under - # standard, non-error conditions. It and the structures associated with - # it should ideally be refactored and applied to all responses, however, - # since a Client can make requests which result in disallowed conditions, - # such as, for instance, requesting a register read of more registers - # than will fit in a single PDU. As per the specification, the PDU is - # restricted to 253 bytes, irrespective of the transport used. - # - # See Page 5/50 of MODBUS Application Protocol Specification V1.1b3. - def __init__(self, oid): - self.oid = oid - - -#---------------------------------------------------------------------------# -# Read Device Information -#---------------------------------------------------------------------------# -class ReadDeviceInformationRequest(ModbusRequest): - ''' - This function code allows reading the identification and additional - information relative to the physical and functional description of a - remote device, only. - - The Read Device Identification interface is modeled as an address space - composed of a set of addressable data elements. The data elements are - called objects and an object Id identifies them. - ''' - function_code = 0x2b - sub_function_code = 0x0e - _rtu_frame_size = 7 - - def __init__(self, read_code=None, object_id=0x00, **kwargs): - ''' Initializes a new instance - - :param read_code: The device information read code - :param object_id: The object to read from - ''' - ModbusRequest.__init__(self, **kwargs) - self.read_code = read_code or DeviceInformation.Basic - self.object_id = object_id - - def encode(self): - ''' Encodes the request packet - - :returns: The byte encoded packet - ''' - packet = struct.pack('>BBB', self.sub_function_code, - self.read_code, self.object_id) - return packet - - def decode(self, data): - ''' Decodes data part of the message. - - :param data: The incoming data - ''' - params = struct.unpack('>BBB', data) - self.sub_function_code, self.read_code, self.object_id = params - - def execute(self, context): - ''' Run a read exeception status request against the store - - :param context: The datastore to request from - :returns: The populated response - ''' - if not (0x00 <= self.object_id <= 0xff): - return self.doException(merror.IllegalValue) - if not (0x00 <= self.read_code <= 0x04): - return self.doException(merror.IllegalValue) - - information = DeviceInformationFactory.get(_MCB, - self.read_code, self.object_id) - return ReadDeviceInformationResponse(self.read_code, information) - - def __str__(self): - ''' Builds a representation of the request - - :returns: The string representation of the request - ''' - params = (self.read_code, self.object_id) - return "ReadDeviceInformationRequest(%d,%d)" % params - - -class ReadDeviceInformationResponse(ModbusResponse): - ''' - ''' - function_code = 0x2b - sub_function_code = 0x0e - - @classmethod - def calculateRtuFrameSize(cls, buffer): - ''' Calculates the size of the message - - :param buffer: A buffer containing the data that have been received. - :returns: The number of bytes in the response. - ''' - size = 8 # skip the header information - count = byte2int(buffer[7]) - - while count > 0: - _, object_length = struct.unpack('>BB', buffer[size:size+2]) - size += object_length + 2 - count -= 1 - return size + 2 - - def __init__(self, read_code=None, information=None, **kwargs): - ''' Initializes a new instance - - :param read_code: The device information read code - :param information: The requested information request - ''' - ModbusResponse.__init__(self, **kwargs) - self.read_code = read_code or DeviceInformation.Basic - self.information = information or {} - self.number_of_objects = 0 - self.conformity = 0x83 # I support everything right now - self.next_object_id = 0x00 - self.more_follows = MoreData.Nothing - self.space_left = None - - def _encode_object(self, object_id, data): - self.space_left -= (2 + len(data)) - if self.space_left <= 0: - raise _OutOfSpaceException(object_id) - encoded_obj = struct.pack('>BB', object_id, len(data)) - if IS_PYTHON3: - if isinstance(data, bytes): - encoded_obj += data - else: - encoded_obj += data.encode() - else: - encoded_obj += data.encode() - self.number_of_objects += 1 - return encoded_obj - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - packet = struct.pack('>BBB', self.sub_function_code, - self.read_code, self.conformity) - self.space_left = 253 - 6 - objects = b'' - try: - for (object_id, data) in iteritems(self.information): - 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 e: - self.next_object_id = e.oid - self.more_follows = MoreData.KeepReading - - packet += struct.pack('>BBB', self.more_follows, self.next_object_id, - self.number_of_objects) - packet += objects - return packet - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - params = struct.unpack('>BBBBBB', data[0:6]) - self.sub_function_code, self.read_code = params[0:2] - self.conformity, self.more_follows = params[2:4] - self.next_object_id, self.number_of_objects = params[4:6] - self.information, count = {}, 6 # skip the header information - - 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.keys(): - self.information[object_id] = data[count-object_length:count] - else: - if isinstance(self.information[object_id], list): - self.information[object_id].append(data[count-object_length:count]) - else: - self.information[object_id] = [self.information[object_id], - data[count - object_length:count]] - - def __str__(self): - ''' Builds a representation of the response - - :returns: The string representation of the response - ''' - return "ReadDeviceInformationResponse(%d)" % self.read_code - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "ReadDeviceInformationRequest", "ReadDeviceInformationResponse", -] diff --git a/modbus/pymodbus/other_message.py b/modbus/pymodbus/other_message.py deleted file mode 100644 index 31e6734..0000000 --- a/modbus/pymodbus/other_message.py +++ /dev/null @@ -1,453 +0,0 @@ -''' -Diagnostic record read/write - -Currently not all implemented -''' -import struct -from pymodbus.constants import ModbusStatus -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.device import ModbusControlBlock, DeviceInformationFactory -from pymodbus.compat import byte2int, int2byte - -_MCB = ModbusControlBlock() - - -#---------------------------------------------------------------------------# -# TODO Make these only work on serial -#---------------------------------------------------------------------------# -class ReadExceptionStatusRequest(ModbusRequest): - ''' - This function code is used to read the contents of eight Exception Status - outputs in a remote device. The function provides a simple method for - accessing this information, because the Exception Output references are - known (no output reference is needed in the function). - ''' - function_code = 0x07 - _rtu_frame_size = 4 - - def __init__(self, **kwargs): - ''' Initializes a new instance - ''' - ModbusRequest.__init__(self, **kwargs) - - def encode(self): - ''' Encodes the message - ''' - return b'' - - def decode(self, data): - ''' Decodes data part of the message. - - :param data: The incoming data - ''' - pass - - def execute(self, context=None): - ''' Run a read exeception status request against the store - - :returns: The populated response - ''' - status = _MCB.Counter.summary() - return ReadExceptionStatusResponse(status) - - def __str__(self): - ''' Builds a representation of the request - - :returns: The string representation of the request - ''' - return "ReadExceptionStatusRequest(%d)" % (self.function_code) - - -class ReadExceptionStatusResponse(ModbusResponse): - ''' - The normal response contains the status of the eight Exception Status - outputs. The outputs are packed into one data byte, with one bit - per output. The status of the lowest output reference is contained - in the least significant bit of the byte. The contents of the eight - Exception Status outputs are device specific. - ''' - function_code = 0x07 - _rtu_frame_size = 5 - - def __init__(self, status=0x00, **kwargs): - ''' Initializes a new instance - - :param status: The status response to report - ''' - ModbusResponse.__init__(self, **kwargs) - self.status = status - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - return struct.pack('>B', self.status) - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - self.status = byte2int(data[0]) - - def __str__(self): - ''' Builds a representation of the response - - :returns: The string representation of the response - ''' - arguments = (self.function_code, self.status) - return "ReadExceptionStatusResponse(%d, %s)" % arguments - -# Encapsulate interface transport 43, 14 -# CANopen general reference 43, 13 - - -#---------------------------------------------------------------------------# -# TODO Make these only work on serial -#---------------------------------------------------------------------------# -class GetCommEventCounterRequest(ModbusRequest): - ''' - This function code is used to get a status word and an event count from - the remote device's communication event counter. - - By fetching the current count before and after a series of messages, a - client can determine whether the messages were handled normally by the - remote device. - - The device's event counter is incremented once for each successful - message completion. It is not incremented for exception responses, - poll commands, or fetch event counter commands. - - The event counter can be reset by means of the Diagnostics function - (code 08), with a subfunction of Restart Communications Option - (code 00 01) or Clear Counters and Diagnostic Register (code 00 0A). - ''' - function_code = 0x0b - _rtu_frame_size = 4 - - def __init__(self, **kwargs): - ''' Initializes a new instance - ''' - ModbusRequest.__init__(self, **kwargs) - - def encode(self): - ''' Encodes the message - ''' - return b'' - - def decode(self, data): - ''' Decodes data part of the message. - - :param data: The incoming data - ''' - pass - - def execute(self, context=None): - ''' Run a read exeception status request against the store - - :returns: The populated response - ''' - status = _MCB.Counter.Event - return GetCommEventCounterResponse(status) - - def __str__(self): - ''' Builds a representation of the request - - :returns: The string representation of the request - ''' - return "GetCommEventCounterRequest(%d)" % (self.function_code) - - -class GetCommEventCounterResponse(ModbusResponse): - ''' - The normal response contains a two-byte status word, and a two-byte - event count. The status word will be all ones (FF FF hex) if a - previously-issued program command is still being processed by the - remote device (a busy condition exists). Otherwise, the status word - will be all zeros. - ''' - function_code = 0x0b - _rtu_frame_size = 8 - - def __init__(self, count=0x0000, **kwargs): - ''' Initializes a new instance - - :param count: The current event counter value - ''' - ModbusResponse.__init__(self, **kwargs) - self.count = count - self.status = True # this means we are ready, not waiting - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - if self.status: ready = ModbusStatus.Ready - else: ready = ModbusStatus.Waiting - return struct.pack('>HH', ready, self.count) - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - ready, self.count = struct.unpack('>HH', data) - self.status = (ready == ModbusStatus.Ready) - - def __str__(self): - ''' Builds a representation of the response - - :returns: The string representation of the response - ''' - arguments = (self.function_code, self.count, self.status) - return "GetCommEventCounterResponse(%d, %d, %d)" % arguments - - -#---------------------------------------------------------------------------# -# TODO Make these only work on serial -#---------------------------------------------------------------------------# -class GetCommEventLogRequest(ModbusRequest): - ''' - This function code is used to get a status word, event count, message - count, and a field of event bytes from the remote device. - - The status word and event counts are identical to that returned by - the Get Communications Event Counter function (11, 0B hex). - - The message counter contains the quantity of messages processed by the - remote device since its last restart, clear counters operation, or - power-up. This count is identical to that returned by the Diagnostic - function (code 08), sub-function Return Bus Message Count (code 11, - 0B hex). - - The event bytes field contains 0-64 bytes, with each byte corresponding - to the status of one MODBUS send or receive operation for the remote - device. The remote device enters the events into the field in - chronological order. Byte 0 is the most recent event. Each new byte - flushes the oldest byte from the field. - ''' - function_code = 0x0c - _rtu_frame_size = 4 - - def __init__(self, **kwargs): - ''' Initializes a new instance - ''' - ModbusRequest.__init__(self, **kwargs) - - def encode(self): - ''' Encodes the message - ''' - return b'' - - def decode(self, data): - ''' Decodes data part of the message. - - :param data: The incoming data - ''' - pass - - def execute(self, context=None): - ''' Run a read exeception status request against the store - - :returns: The populated response - ''' - results = { - 'status' : True, - 'message_count' : _MCB.Counter.BusMessage, - 'event_count' : _MCB.Counter.Event, - 'events' : _MCB.getEvents(), - } - return GetCommEventLogResponse(**results) - - def __str__(self): - ''' Builds a representation of the request - - :returns: The string representation of the request - ''' - return "GetCommEventLogRequest(%d)" % self.function_code - - -class GetCommEventLogResponse(ModbusResponse): - ''' - The normal response contains a two-byte status word field, - a two-byte event count field, a two-byte message count field, - and a field containing 0-64 bytes of events. A byte count - field defines the total length of the data in these four field - ''' - function_code = 0x0c - _rtu_byte_count_pos = 2 - - def __init__(self, **kwargs): - ''' Initializes a new instance - - :param status: The status response to report - :param message_count: The current message count - :param event_count: The current event count - :param events: The collection of events to send - ''' - ModbusResponse.__init__(self, **kwargs) - self.status = kwargs.get('status', True) - self.message_count = kwargs.get('message_count', 0) - self.event_count = kwargs.get('event_count', 0) - self.events = kwargs.get('events', []) - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - if self.status: ready = ModbusStatus.Ready - else: 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) - packet += b''.join(struct.pack('>B', e) for e in self.events) - return packet - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - length = byte2int(data[0]) - status = struct.unpack('>H', data[1:3])[0] - self.status = (status == ModbusStatus.Ready) - self.event_count = struct.unpack('>H', data[3:5])[0] - self.message_count = struct.unpack('>H', data[5:7])[0] - - self.events = [] - for e in range(7, length + 1): - self.events.append(byte2int(data[e])) - - def __str__(self): - ''' Builds a representation of the response - - :returns: The string representation of the response - ''' - arguments = (self.function_code, self.status, self.message_count, self.event_count) - return "GetCommEventLogResponse(%d, %d, %d, %d)" % arguments - - -#---------------------------------------------------------------------------# -# TODO Make these only work on serial -#---------------------------------------------------------------------------# -class ReportSlaveIdRequest(ModbusRequest): - ''' - This function code is used to read the description of the type, the - current status, and other information specific to a remote device. - ''' - function_code = 0x11 - _rtu_frame_size = 4 - - def __init__(self, **kwargs): - ''' Initializes a new instance - ''' - ModbusRequest.__init__(self, **kwargs) - - def encode(self): - ''' Encodes the message - ''' - return b'' - - def decode(self, data): - ''' Decodes data part of the message. - - :param data: The incoming data - ''' - pass - - def execute(self, context=None): - ''' Run a report slave id request against the store - - :returns: The populated response - ''' - reportSlaveIdData = None - if context: - reportSlaveIdData = getattr(context, 'reportSlaveIdData', None) - if not reportSlaveIdData: - information = DeviceInformationFactory.get(_MCB) - identifier = "-".join(information.values()).encode() - identifier = identifier or b'Pymodbus' - reportSlaveIdData = identifier - return ReportSlaveIdResponse(reportSlaveIdData) - - def __str__(self): - ''' Builds a representation of the request - - :returns: The string representation of the request - ''' - return "ReportSlaveIdRequest(%d)" % self.function_code - - -class ReportSlaveIdResponse(ModbusResponse): - ''' - The format of a normal response is shown in the following example. - The data contents are specific to each type of device. - ''' - function_code = 0x11 - _rtu_byte_count_pos = 2 - - def __init__(self, identifier=b'\x00', status=True, **kwargs): - ''' Initializes a new instance - - :param identifier: The identifier of the slave - :param status: The status response to report - ''' - ModbusResponse.__init__(self, **kwargs) - self.identifier = identifier - self.status = status - self.byte_count = None - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - if self.status: - status = ModbusStatus.SlaveOn - else: - status = ModbusStatus.SlaveOff - length = len(self.identifier) + 1 - packet = int2byte(length) - packet += self.identifier # we assume it is already encoded - packet += int2byte(status) - return packet - - def decode(self, data): - ''' Decodes a the response - - Since the identifier is device dependent, we just return the - raw value that a user can decode to whatever it should be. - - :param data: The packet data to decode - ''' - self.byte_count = byte2int(data[0]) - self.identifier = data[1:self.byte_count + 1] - status = byte2int(data[-1]) - self.status = status == ModbusStatus.SlaveOn - - def __str__(self): - ''' Builds a representation of the response - - :returns: The string representation of the response - ''' - arguments = (self.function_code, self.identifier, self.status) - return "ReportSlaveIdResponse(%s, %s, %s)" % arguments - -#---------------------------------------------------------------------------# -# TODO Make these only work on serial -#---------------------------------------------------------------------------# -# report device identification 43, 14 - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "ReadExceptionStatusRequest", "ReadExceptionStatusResponse", - "GetCommEventCounterRequest", "GetCommEventCounterResponse", - "GetCommEventLogRequest", "GetCommEventLogResponse", - "ReportSlaveIdRequest", "ReportSlaveIdResponse", -] diff --git a/modbus/pymodbus/payload.py b/modbus/pymodbus/payload.py deleted file mode 100644 index ee7da48..0000000 --- a/modbus/pymodbus/payload.py +++ /dev/null @@ -1,509 +0,0 @@ -""" -Modbus Payload Builders ------------------------- - -A collection of utilities for building and decoding -modbus messages payloads. - - -""" -from struct import pack, unpack -from pymodbus.interfaces import IPayloadBuilder -from pymodbus.constants import Endian -from pymodbus.utilities import pack_bitstring -from pymodbus.utilities import unpack_bitstring -from pymodbus.utilities import make_byte_string -from pymodbus.exceptions import ParameterException -from pymodbus.compat import unicode_string, IS_PYTHON3, PYTHON_VERSION -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -WC = { - "b": 1, - "h": 2, - "e": 2, - "i": 4, - "l": 4, - "q": 8, - "f": 4, - "d": 8 -} - - -class BinaryPayloadBuilder(IPayloadBuilder): - """ - A utility that helps build payload messages to be - written with the various modbus messages. It really is just - a simple wrapper around the struct module, however it saves - time looking up the format strings. What follows is a simple - example:: - - builder = BinaryPayloadBuilder(byteorder=Endian.Little) - builder.add_8bit_uint(1) - builder.add_16bit_uint(2) - payload = builder.build() - """ - - def __init__(self, payload=None, byteorder=Endian.Little, - wordorder=Endian.Big, repack=False): - """ Initialize a new instance of the payload builder - - :param payload: Raw binary payload data to initialize with - :param byteorder: The endianess of the bytes in the words - :param wordorder: The endianess of the word (when wordcount is >= 2) - :param repack: Repack the provided payload based on BO - """ - self._payload = payload or [] - self._byteorder = byteorder - self._wordorder = wordorder - self._repack = repack - - def _pack_words(self, fstring, value): - """ - Packs Words based on the word order and byte order - - # ---------------------------------------------- # - # pack in to network ordered value # - # unpack in to network ordered unsigned integer # - # Change Word order if little endian word order # - # Pack values back based on correct byte order # - # ---------------------------------------------- # - - :param value: Value to be packed - :return: - """ - value = pack("!{}".format(fstring), value) - wc = WC.get(fstring.lower())//2 - up = "!{}H".format(wc) - payload = unpack(up, value) - - if self._wordorder == Endian.Little: - payload = list(reversed(payload)) - - fstring = self._byteorder + "H" - payload = [pack(fstring, word) for word in payload] - payload = b''.join(payload) - - return payload - - def to_string(self): - """ Return the payload buffer as a string - - :returns: The payload buffer as a string - """ - return b''.join(self._payload) - - def __str__(self): - """ Return the payload buffer as a string - - :returns: The payload buffer as a string - """ - return self.to_string().decode('utf-8') - - def reset(self): - """ Reset the payload buffer - """ - self._payload = [] - - def to_registers(self): - """ Convert the payload buffer into a register - layout that can be used as a context block. - - :returns: The register layout to use as a block - """ - # fstring = self._byteorder+'H' - fstring = '!H' - payload = self.build() - if self._repack: - payload = [unpack(self._byteorder+"H", value)[0] for value in payload] - else: - payload = [unpack(fstring, value)[0] for value in payload] - _logger.debug(payload) - return payload - - def to_coils(self): - """Convert the payload buffer into a coil - layout that can be used as a context block. - - :returns: The coil layout to use as a block - """ - payload = self.to_registers() - coils = [bool(int(bit)) for reg - in payload for bit in format(reg, '016b')] - return coils - - def build(self): - """ Return the payload buffer as a list - - This list is two bytes per element and can - thus be treated as a list of registers. - - :returns: The payload buffer as a list - """ - string = self.to_string() - length = len(string) - string = string + (b'\x00' * (length % 2)) - return [string[i:i+2] for i in range(0, length, 2)] - - def add_bits(self, values): - """ Adds a collection of bits to be encoded - - If these are less than a multiple of eight, - they will be left padded with 0 bits to make - it so. - - :param value: The value to add to the buffer - """ - value = pack_bitstring(values) - self._payload.append(value) - - def add_8bit_uint(self, value): - """ Adds a 8 bit unsigned int to the buffer - - :param value: The value to add to the buffer - """ - fstring = self._byteorder + 'B' - self._payload.append(pack(fstring, value)) - - def add_16bit_uint(self, value): - """ Adds a 16 bit unsigned int to the buffer - - :param value: The value to add to the buffer - """ - fstring = self._byteorder + 'H' - self._payload.append(pack(fstring, value)) - - def add_32bit_uint(self, value): - """ Adds a 32 bit unsigned int to the buffer - - :param value: The value to add to the buffer - """ - fstring = 'I' - # fstring = self._byteorder + 'I' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - - def add_64bit_uint(self, value): - """ Adds a 64 bit unsigned int to the buffer - - :param value: The value to add to the buffer - """ - fstring = 'Q' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - - def add_8bit_int(self, value): - """ Adds a 8 bit signed int to the buffer - - :param value: The value to add to the buffer - """ - fstring = self._byteorder + 'b' - self._payload.append(pack(fstring, value)) - - def add_16bit_int(self, value): - """ Adds a 16 bit signed int to the buffer - - :param value: The value to add to the buffer - """ - fstring = self._byteorder + 'h' - self._payload.append(pack(fstring, value)) - - def add_32bit_int(self, value): - """ Adds a 32 bit signed int to the buffer - - :param value: The value to add to the buffer - """ - fstring = 'i' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - - def add_64bit_int(self, value): - """ Adds a 64 bit signed int to the buffer - - :param value: The value to add to the buffer - """ - fstring = 'q' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - - def add_16bit_float(self, value): - """ Adds a 16 bit float to the buffer - - :param value: The value to add to the buffer - """ - if IS_PYTHON3 and PYTHON_VERSION.minor >= 6: - fstring = 'e' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - else: - _logger.warning("float16 only supported on python3.6 and above!!!") - - def add_32bit_float(self, value): - """ Adds a 32 bit float to the buffer - - :param value: The value to add to the buffer - """ - fstring = 'f' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - - def add_64bit_float(self, value): - """ Adds a 64 bit float(double) to the buffer - - :param value: The value to add to the buffer - """ - fstring = 'd' - p_string = self._pack_words(fstring, value) - self._payload.append(p_string) - - def add_string(self, value): - """ Adds a string to the buffer - - :param value: The value to add to the buffer - """ - value = make_byte_string(value) - fstring = self._byteorder + str(len(value)) + 's' - self._payload.append(pack(fstring, value)) - - -class BinaryPayloadDecoder(object): - """ - A utility that helps decode payload messages from a modbus - response message. It really is just a simple wrapper around - the struct module, however it saves time looking up the format - strings. What follows is a simple example:: - - decoder = BinaryPayloadDecoder(payload) - first = decoder.decode_8bit_uint() - second = decoder.decode_16bit_uint() - """ - - def __init__(self, payload, byteorder=Endian.Little, wordorder=Endian.Big): - """ Initialize a new payload decoder - - :param payload: The payload to decode with - :param byteorder: The endianess of the payload - :param wordorder: The endianess of the word (when wordcount is >= 2) - """ - self._payload = payload - self._pointer = 0x00 - self._byteorder = byteorder - self._wordorder = wordorder - - @classmethod - def fromRegisters(klass, registers, byteorder=Endian.Little, - wordorder=Endian.Big): - """ Initialize a payload decoder with the result of - reading a collection of registers from a modbus device. - - The registers are treated as a list of 2 byte values. - We have to do this because of how the data has already - been decoded by the rest of the library. - - :param registers: The register results to initialize with - :param byteorder: The Byte order of each word - :param wordorder: The endianess of the word (when wordcount is >= 2) - :returns: An initialized PayloadDecoder - """ - _logger.debug(registers) - if isinstance(registers, list): # repack into flat binary - payload = b''.join(pack('!H', x) for x in registers) - return klass(payload, byteorder, wordorder) - raise ParameterException('Invalid collection of registers supplied') - - @classmethod - def bit_chunks(cls, coils, size=8): - chunks = [coils[i: i + size] for i in range(0, len(coils), size)] - return chunks - - @classmethod - def fromCoils(klass, coils, byteorder=Endian.Little, wordorder=Endian.Big): - """ Initialize a payload decoder with the result of - reading a collection of coils from a modbus device. - - The coils are treated as a list of bit(boolean) values. - - :param coils: The coil results to initialize with - :param byteorder: The endianess of the payload - :returns: An initialized PayloadDecoder - """ - if isinstance(coils, list): - payload = b'' - padding = len(coils) % 8 - if padding: # Pad zero's - extra = [False] * padding - coils = extra + coils - chunks = klass.bit_chunks(coils) - for chunk in chunks: - payload += pack_bitstring(chunk[::-1]) - return klass(payload, byteorder) - raise ParameterException('Invalid collection of coils supplied') - - def _unpack_words(self, fstring, handle): - """ - Un Packs Words based on the word order and byte order - - # ---------------------------------------------- # - # Unpack in to network ordered unsigned integer # - # Change Word order if little endian word order # - # Pack values back based on correct byte order # - # ---------------------------------------------- # - :param handle: Value to be unpacked - :return: - """ - handle = make_byte_string(handle) - wc = WC.get(fstring.lower()) // 2 - up = "!{}H".format(wc) - handle = unpack(up, handle) - if self._wordorder == Endian.Little: - handle = list(reversed(handle)) - - # Repack as unsigned Integer - pk = self._byteorder + 'H' - handle = [pack(pk, p) for p in handle] - _logger.debug(handle) - handle = b''.join(handle) - return handle - - def reset(self): - """ Reset the decoder pointer back to the start - """ - self._pointer = 0x00 - - def decode_8bit_uint(self): - """ Decodes a 8 bit unsigned int from the buffer - """ - self._pointer += 1 - fstring = self._byteorder + 'B' - handle = self._payload[self._pointer - 1:self._pointer] - handle = make_byte_string(handle) - return unpack(fstring, handle)[0] - - def decode_bits(self): - """ Decodes a byte worth of bits from the buffer - """ - self._pointer += 1 - # fstring = self._endian + 'B' - handle = self._payload[self._pointer - 1:self._pointer] - handle = make_byte_string(handle) - return unpack_bitstring(handle) - - def decode_16bit_uint(self): - """ Decodes a 16 bit unsigned int from the buffer - """ - self._pointer += 2 - fstring = self._byteorder + 'H' - handle = self._payload[self._pointer - 2:self._pointer] - handle = make_byte_string(handle) - return unpack(fstring, handle)[0] - - def decode_32bit_uint(self): - """ Decodes a 32 bit unsigned int from the buffer - """ - self._pointer += 4 - fstring = 'I' - # fstring = 'I' - handle = self._payload[self._pointer - 4:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - - def decode_64bit_uint(self): - """ Decodes a 64 bit unsigned int from the buffer - """ - self._pointer += 8 - fstring = 'Q' - handle = self._payload[self._pointer - 8:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - - def decode_8bit_int(self): - """ Decodes a 8 bit signed int from the buffer - """ - self._pointer += 1 - fstring = self._byteorder + 'b' - handle = self._payload[self._pointer - 1:self._pointer] - handle = make_byte_string(handle) - return unpack(fstring, handle)[0] - - def decode_16bit_int(self): - """ Decodes a 16 bit signed int from the buffer - """ - self._pointer += 2 - fstring = self._byteorder + 'h' - handle = self._payload[self._pointer - 2:self._pointer] - handle = make_byte_string(handle) - return unpack(fstring, handle)[0] - - def decode_32bit_int(self): - """ Decodes a 32 bit signed int from the buffer - """ - self._pointer += 4 - fstring = 'i' - handle = self._payload[self._pointer - 4:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - - def decode_64bit_int(self): - """ Decodes a 64 bit signed int from the buffer - """ - self._pointer += 8 - fstring = 'q' - handle = self._payload[self._pointer - 8:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - - def decode_16bit_float(self): - """ Decodes a 16 bit float from the buffer - """ - if IS_PYTHON3 and PYTHON_VERSION.minor >= 6: - self._pointer += 2 - fstring = 'e' - handle = self._payload[self._pointer - 2:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - else: - _logger.warning("float16 only supported on python3.6 and above!!!") - - def decode_32bit_float(self): - """ Decodes a 32 bit float from the buffer - """ - self._pointer += 4 - fstring = 'f' - handle = self._payload[self._pointer - 4:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - - def decode_64bit_float(self): - """ Decodes a 64 bit float(double) from the buffer - """ - self._pointer += 8 - fstring = 'd' - handle = self._payload[self._pointer - 8:self._pointer] - handle = self._unpack_words(fstring, handle) - return unpack("!"+fstring, handle)[0] - - def decode_string(self, size=1): - """ Decodes a string from the buffer - - :param size: The size of the string to decode - """ - self._pointer += size - s = self._payload[self._pointer - size:self._pointer] - return s - - def skip_bytes(self, nbytes): - """ Skip n bytes in the buffer - - :param nbytes: The number of bytes to skip - """ - self._pointer += nbytes - return None - -#---------------------------------------------------------------------------# -# Exported Identifiers -#---------------------------------------------------------------------------# -__all__ = ["BinaryPayloadBuilder", "BinaryPayloadDecoder"] diff --git a/modbus/pymodbus/pdu.py b/modbus/pymodbus/pdu.py deleted file mode 100644 index 2c7c55d..0000000 --- a/modbus/pymodbus/pdu.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Contains base classes for modbus request/response/error packets -""" -from pymodbus.interfaces import Singleton -from pymodbus.exceptions import NotImplementedException -from pymodbus.constants import Defaults -from pymodbus.utilities import rtuFrameSize -from pymodbus.compat import iteritems, int2byte, byte2int - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Base PDU's -# --------------------------------------------------------------------------- # -class ModbusPDU(object): - """ - Base class for all Modbus messages - - .. attribute:: transaction_id - - This value is used to uniquely identify a request - response pair. It can be implemented as a simple counter - - .. attribute:: protocol_id - - This is a constant set at 0 to indicate Modbus. It is - put here for ease of expansion. - - .. attribute:: unit_id - - This is used to route the request to the correct child. In - the TCP modbus, it is used for routing (or not used at all. However, - for the serial versions, it is used to specify which child to perform - the requests against. The value 0x00 represents the broadcast address - (also 0xff). - - .. attribute:: check - - This is used for LRC/CRC in the serial modbus protocols - - .. attribute:: skip_encode - - This is used when the message payload has already been encoded. - Generally this will occur when the PayloadBuilder is being used - to create a complicated message. By setting this to True, the - request will pass the currently encoded message through instead - of encoding it again. - """ - - def __init__(self, **kwargs): - """ Initializes the base data for a modbus request """ - self.transaction_id = kwargs.get('transaction', Defaults.TransactionId) - self.protocol_id = kwargs.get('protocol', Defaults.ProtocolId) - self.unit_id = kwargs.get('unit', Defaults.UnitId) - self.skip_encode = kwargs.get('skip_encode', False) - self.check = 0x0000 - - def encode(self): - """ Encodes the message - - :raises: A not implemented exception - """ - raise NotImplementedException() - - def decode(self, data): - """ Decodes data part of the message. - - :param data: is a string object - :raises: A not implemented exception - """ - raise NotImplementedException() - - @classmethod - def calculateRtuFrameSize(cls, buffer): - """ Calculates the size of a PDU. - - :param buffer: A buffer containing the data that have been received. - :returns: The number of bytes in the PDU. - """ - if hasattr(cls, '_rtu_frame_size'): - return cls._rtu_frame_size - elif hasattr(cls, '_rtu_byte_count_pos'): - return rtuFrameSize(buffer, cls._rtu_byte_count_pos) - else: raise NotImplementedException( - "Cannot determine RTU frame size for %s" % cls.__name__) - - -class ModbusRequest(ModbusPDU): - """ Base class for a modbus request PDU """ - - def __init__(self, **kwargs): - """ Proxy to the lower level initializer """ - ModbusPDU.__init__(self, **kwargs) - - def doException(self, exception): - """ Builds an error response based on the function - - :param exception: The exception to return - :raises: An exception response - """ - exc = ExceptionResponse(self.function_code, exception) - _logger.error(exc) - return exc - - -class ModbusResponse(ModbusPDU): - """ Base class for a modbus response PDU - - .. attribute:: should_respond - - A flag that indicates if this response returns a result back - to the client issuing the request - - .. attribute:: _rtu_frame_size - - Indicates the size of the modbus rtu response used for - calculating how much to read. - """ - - should_respond = True - - def __init__(self, **kwargs): - """ Proxy to the lower level initializer """ - ModbusPDU.__init__(self, **kwargs) - - def isError(self): - """Checks if the error is a success or failure""" - return self.function_code > 0x80 - - -# --------------------------------------------------------------------------- # -# Exception PDU's -# --------------------------------------------------------------------------- # -class ModbusExceptions(Singleton): - """ - An enumeration of the valid modbus exceptions - """ - IllegalFunction = 0x01 - IllegalAddress = 0x02 - IllegalValue = 0x03 - SlaveFailure = 0x04 - Acknowledge = 0x05 - SlaveBusy = 0x06 - MemoryParityError = 0x08 - GatewayPathUnavailable = 0x0A - GatewayNoResponse = 0x0B - - @classmethod - def decode(cls, code): - """ Given an error code, translate it to a - string error name. - - :param code: The code number to translate - """ - values = dict((v, k) for k, v in iteritems(cls.__dict__) - if not k.startswith('__') and not callable(v)) - return values.get(code, None) - - -class ExceptionResponse(ModbusResponse): - """ Base class for a modbus exception PDU """ - ExceptionOffset = 0x80 - _rtu_frame_size = 5 - - def __init__(self, function_code, exception_code=None, **kwargs): - """ Initializes the modbus exception response - - :param function_code: The function to build an exception response for - :param exception_code: The specific modbus exception to return - """ - ModbusResponse.__init__(self, **kwargs) - self.original_code = function_code - self.function_code = function_code | self.ExceptionOffset - self.exception_code = exception_code - - def encode(self): - """ Encodes a modbus exception response - - :returns: The encoded exception packet - """ - return int2byte(self.exception_code) - - def decode(self, data): - """ Decodes a modbus exception response - - :param data: The packet data to decode - """ - self.exception_code = byte2int(data[0]) - - def __str__(self): - """ Builds a representation of an exception response - - :returns: The string representation of an exception response - """ - message = ModbusExceptions.decode(self.exception_code) - parameters = (self.function_code, self.original_code, message) - return "Exception Response(%d, %d, %s)" % parameters - - -class IllegalFunctionRequest(ModbusRequest): - """ - Defines the Modbus slave exception type 'Illegal Function' - This exception code is returned if the slave:: - - - does not implement the function code **or** - - is not in a state that allows it to process the function - """ - ErrorCode = 1 - - def __init__(self, function_code, **kwargs): - """ Initializes a IllegalFunctionRequest - - :param function_code: The function we are erroring on - """ - ModbusRequest.__init__(self, **kwargs) - self.function_code = function_code - - def decode(self, data): - """ This is here so this failure will run correctly - - :param data: Not used - """ - pass - - def execute(self, context): - """ Builds an illegal function request error response - - :param context: The current context for the message - :returns: The error response packet - """ - return ExceptionResponse(self.function_code, self.ErrorCode) - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = [ - 'ModbusRequest', 'ModbusResponse', 'ModbusExceptions', - 'ExceptionResponse', 'IllegalFunctionRequest', -] - diff --git a/modbus/pymodbus/register_read_message.py b/modbus/pymodbus/register_read_message.py deleted file mode 100644 index 1c406ea..0000000 --- a/modbus/pymodbus/register_read_message.py +++ /dev/null @@ -1,359 +0,0 @@ -''' -Register Reading Request/Response ---------------------------------- -''' -import struct -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.compat import int2byte, byte2int - - -class ReadRegistersRequestBase(ModbusRequest): - ''' - Base class for reading a modbus register - ''' - _rtu_frame_size = 8 - - def __init__(self, address, count, **kwargs): - ''' Initializes a new instance - - :param address: The address to start the read from - :param count: The number of registers to read - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - self.count = count - - def encode(self): - ''' Encodes the request packet - - :return: The encoded packet - ''' - return struct.pack('>HH', self.address, self.count) - - def decode(self, data): - ''' Decode a register request packet - - :param data: The request to decode - ''' - self.address, self.count = struct.unpack('>HH', data) - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) - :return: - """ - return 1 + 1 + 2 * self.count - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "ReadRegisterRequest (%d,%d)" % (self.address, self.count) - - -class ReadRegistersResponseBase(ModbusResponse): - ''' - Base class for responsing to a modbus register read - ''' - - _rtu_byte_count_pos = 2 - - def __init__(self, values, **kwargs): - ''' Initializes a new instance - - :param values: The values to write to - ''' - ModbusResponse.__init__(self, **kwargs) - self.registers = values or [] - - def encode(self): - ''' Encodes the response packet - - :returns: The encoded packet - ''' - result = int2byte(len(self.registers) * 2) - for register in self.registers: - result += struct.pack('>H', register) - return result - - def decode(self, data): - ''' Decode a register response packet - - :param data: The request to decode - ''' - byte_count = byte2int(data[0]) - self.registers = [] - for i in range(1, byte_count + 1, 2): - self.registers.append(struct.unpack('>H', data[i:i + 2])[0]) - - def getRegister(self, index): - ''' Get the requested register - - :param index: The indexed register to retrieve - :returns: The request register - ''' - return self.registers[index] - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "%s (%d)" % (self.__class__.__name__, len(self.registers)) - - -class ReadHoldingRegistersRequest(ReadRegistersRequestBase): - ''' - This function code is used to read the contents of a contiguous block - of holding registers in a remote device. The Request PDU specifies the - starting register address and the number of registers. In the PDU - Registers are addressed starting at zero. Therefore registers numbered - 1-16 are addressed as 0-15. - ''' - function_code = 3 - - def __init__(self, address=None, count=None, **kwargs): - ''' Initializes a new instance of the request - - :param address: The starting address to read from - :param count: The number of registers to read from address - ''' - ReadRegistersRequestBase.__init__(self, address, count, **kwargs) - - def execute(self, context): - ''' Run a read holding request against a datastore - - :param context: The datastore to request from - :returns: An initialized response, exception message otherwise - ''' - if not (1 <= self.count <= 0x7d): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, self.count): - return self.doException(merror.IllegalAddress) - values = context.getValues(self.function_code, self.address, self.count) - return ReadHoldingRegistersResponse(values) - - -class ReadHoldingRegistersResponse(ReadRegistersResponseBase): - ''' - This function code is used to read the contents of a contiguous block - of holding registers in a remote device. The Request PDU specifies the - starting register address and the number of registers. In the PDU - Registers are addressed starting at zero. Therefore registers numbered - 1-16 are addressed as 0-15. - ''' - function_code = 3 - - def __init__(self, values=None, **kwargs): - ''' Initializes a new response instance - - :param values: The resulting register values - ''' - ReadRegistersResponseBase.__init__(self, values, **kwargs) - - -class ReadInputRegistersRequest(ReadRegistersRequestBase): - ''' - This function code is used to read from 1 to approx. 125 contiguous - input registers in a remote device. The Request PDU specifies the - starting register address and the number of registers. In the PDU - Registers are addressed starting at zero. Therefore input registers - numbered 1-16 are addressed as 0-15. - ''' - function_code = 4 - - def __init__(self, address=None, count=None, **kwargs): - ''' Initializes a new instance of the request - - :param address: The starting address to read from - :param count: The number of registers to read from address - ''' - ReadRegistersRequestBase.__init__(self, address, count, **kwargs) - - def execute(self, context): - ''' Run a read input request against a datastore - - :param context: The datastore to request from - :returns: An initialized response, exception message otherwise - ''' - if not (1 <= self.count <= 0x7d): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, self.count): - return self.doException(merror.IllegalAddress) - values = context.getValues(self.function_code, self.address, self.count) - return ReadInputRegistersResponse(values) - - -class ReadInputRegistersResponse(ReadRegistersResponseBase): - ''' - This function code is used to read from 1 to approx. 125 contiguous - input registers in a remote device. The Request PDU specifies the - starting register address and the number of registers. In the PDU - Registers are addressed starting at zero. Therefore input registers - numbered 1-16 are addressed as 0-15. - ''' - function_code = 4 - - def __init__(self, values=None, **kwargs): - ''' Initializes a new response instance - - :param values: The resulting register values - ''' - ReadRegistersResponseBase.__init__(self, values, **kwargs) - - -class ReadWriteMultipleRegistersRequest(ModbusRequest): - ''' - This function code performs a combination of one read operation and one - write operation in a single MODBUS transaction. The write - operation is performed before the read. - - Holding registers are addressed starting at zero. Therefore holding - registers 1-16 are addressed in the PDU as 0-15. - - The request specifies the starting address and number of holding - registers to be read as well as the starting address, number of holding - registers, and the data to be written. The byte count specifies the - number of bytes to follow in the write data field." - ''' - function_code = 23 - _rtu_byte_count_pos = 10 - - def __init__(self, **kwargs): - ''' Initializes a new request message - - :param read_address: The address to start reading from - :param read_count: The number of registers to read from address - :param write_address: The address to start writing to - :param write_registers: The registers to write to the specified address - ''' - ModbusRequest.__init__(self, **kwargs) - self.read_address = kwargs.get('read_address', 0x00) - self.read_count = kwargs.get('read_count', 0) - self.write_address = kwargs.get('write_address', 0x00) - self.write_registers = kwargs.get('write_registers', None) - if not hasattr(self.write_registers, '__iter__'): - self.write_registers = [self.write_registers] - self.write_count = len(self.write_registers) - self.write_byte_count = self.write_count * 2 - - def encode(self): - ''' Encodes the request packet - - :returns: The encoded packet - ''' - result = struct.pack('>HHHHB', - self.read_address, self.read_count, \ - self.write_address, self.write_count, self.write_byte_count) - for register in self.write_registers: - result += struct.pack('>H', register) - return result - - def decode(self, data): - ''' Decode the register request packet - - :param data: The request to decode - ''' - self.read_address, self.read_count, \ - self.write_address, self.write_count, \ - self.write_byte_count = struct.unpack('>HHHHB', data[:9]) - self.write_registers = [] - for i in range(9, self.write_byte_count + 9, 2): - register = struct.unpack('>H', data[i:i + 2])[0] - self.write_registers.append(register) - - def execute(self, context): - ''' Run a write single register request against a datastore - - :param context: The datastore to request from - :returns: An initialized response, exception message otherwise - ''' - if not (1 <= self.read_count <= 0x07d): - return self.doException(merror.IllegalValue) - if not (1 <= self.write_count <= 0x079): - return self.doException(merror.IllegalValue) - if (self.write_byte_count != self.write_count * 2): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.write_address, - self.write_count): - return self.doException(merror.IllegalAddress) - if not context.validate(self.function_code, self.read_address, - self.read_count): - return self.doException(merror.IllegalAddress) - context.setValues(self.function_code, self.write_address, - self.write_registers) - registers = context.getValues(self.function_code, self.read_address, - self.read_count) - return ReadWriteMultipleRegistersResponse(registers) - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Byte Count(1 byte) + 2 * Quantity of Coils (n Bytes) - :return: - """ - return 1 + 1 + 2 * self.read_count - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - params = (self.read_address, self.read_count, self.write_address, - self.write_count) - return "ReadWriteNRegisterRequest R(%d,%d) W(%d,%d)" % params - - -class ReadWriteMultipleRegistersResponse(ModbusResponse): - ''' - The normal response contains the data from the group of registers that - were read. The byte count field specifies the quantity of bytes to - follow in the read data field. - ''' - function_code = 23 - _rtu_byte_count_pos = 2 - - def __init__(self, values=None, **kwargs): - ''' Initializes a new instance - - :param values: The register values to write - ''' - ModbusResponse.__init__(self, **kwargs) - self.registers = values or [] - - def encode(self): - ''' Encodes the response packet - - :returns: The encoded packet - ''' - result = int2byte(len(self.registers) * 2) - for register in self.registers: - result += struct.pack('>H', register) - return result - - def decode(self, data): - ''' Decode the register response packet - - :param data: The response to decode - ''' - bytecount = byte2int(data[0]) - for i in range(1, bytecount, 2): - self.registers.append(struct.unpack('>H', data[i:i + 2])[0]) - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "ReadWriteNRegisterResponse (%d)" % len(self.registers) - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "ReadHoldingRegistersRequest", "ReadHoldingRegistersResponse", - "ReadInputRegistersRequest", "ReadInputRegistersResponse", - "ReadWriteMultipleRegistersRequest", "ReadWriteMultipleRegistersResponse", -] diff --git a/modbus/pymodbus/register_write_message.py b/modbus/pymodbus/register_write_message.py deleted file mode 100644 index 3a128ab..0000000 --- a/modbus/pymodbus/register_write_message.py +++ /dev/null @@ -1,354 +0,0 @@ -''' -Register Writing Request/Response Messages -------------------------------------------- -''' -import struct -from pymodbus.pdu import ModbusRequest -from pymodbus.pdu import ModbusResponse -from pymodbus.pdu import ModbusExceptions as merror - - -class WriteSingleRegisterRequest(ModbusRequest): - ''' - This function code is used to write a single holding register in a - remote device. - - The Request PDU specifies the address of the register to - be written. Registers are addressed starting at zero. Therefore register - numbered 1 is addressed as 0. - ''' - function_code = 6 - _rtu_frame_size = 8 - - def __init__(self, address=None, value=None, **kwargs): - ''' Initializes a new instance - - :param address: The address to start writing add - :param value: The values to write - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - self.value = value - - def encode(self): - ''' Encode a write single register packet packet request - - :returns: The encoded packet - ''' - packet = struct.pack('>H', self.address) - if self.skip_encode: - packet += self.value - else: - packet += struct.pack('>H', self.value) - return packet - - def decode(self, data): - ''' Decode a write single register packet packet request - - :param data: The request to decode - ''' - self.address, self.value = struct.unpack('>HH', data) - - def execute(self, context): - ''' Run a write single register request against a datastore - - :param context: The datastore to request from - :returns: An initialized response, exception message otherwise - ''' - if not (0 <= self.value <= 0xffff): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, 1): - return self.doException(merror.IllegalAddress) - - context.setValues(self.function_code, self.address, [self.value]) - values = context.getValues(self.function_code, self.address, 1) - return WriteSingleRegisterResponse(self.address, values[0]) - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Register Address(2 byte) + Register Value (2 bytes) - :return: - """ - return 1 + 2 + 2 - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - return "WriteRegisterRequest %d" % self.address - - -class WriteSingleRegisterResponse(ModbusResponse): - ''' - The normal response is an echo of the request, returned after the - register contents have been written. - ''' - function_code = 6 - _rtu_frame_size = 8 - - def __init__(self, address=None, value=None, **kwargs): - ''' Initializes a new instance - - :param address: The address to start writing add - :param value: The values to write - ''' - ModbusResponse.__init__(self, **kwargs) - self.address = address - self.value = value - - def encode(self): - ''' Encode a write single register packet packet request - - :returns: The encoded packet - ''' - return struct.pack('>HH', self.address, self.value) - - def decode(self, data): - ''' Decode a write single register packet packet request - - :param data: The request to decode - ''' - self.address, self.value = struct.unpack('>HH', data) - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - params = (self.address, self.value) - return "WriteRegisterResponse %d => %d" % params - - -#---------------------------------------------------------------------------# -# Write Multiple Registers -#---------------------------------------------------------------------------# -class WriteMultipleRegistersRequest(ModbusRequest): - ''' - This function code is used to write a block of contiguous registers (1 - to approx. 120 registers) in a remote device. - - The requested written values are specified in the request data field. - Data is packed as two bytes per register. - ''' - function_code = 16 - _rtu_byte_count_pos = 6 - _pdu_length = 5 #func + adress1 + adress2 + outputQuant1 + outputQuant2 - - def __init__(self, address=None, values=None, **kwargs): - ''' Initializes a new instance - - :param address: The address to start writing to - :param values: The values to write - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - if values is None: - values = [] - elif not hasattr(values, '__iter__'): - values = [values] - self.values = values - self.count = len(self.values) - self.byte_count = self.count * 2 - - def encode(self): - ''' Encode a write single register packet packet request - - :returns: The encoded packet - ''' - packet = struct.pack('>HHB', self.address, self.count, self.byte_count) - if self.skip_encode: - return packet + b''.join(self.values) - - for value in self.values: - packet += struct.pack('>H', value) - - return packet - - def decode(self, data): - ''' Decode a write single register packet packet request - - :param data: The request to decode - ''' - self.address, self.count, \ - self.byte_count = struct.unpack('>HHB', data[:5]) - self.values = [] # reset - for idx in range(5, (self.count * 2) + 5, 2): - self.values.append(struct.unpack('>H', data[idx:idx + 2])[0]) - - def execute(self, context): - ''' Run a write single register request against a datastore - - :param context: The datastore to request from - :returns: An initialized response, exception message otherwise - ''' - if not (1 <= self.count <= 0x07b): - return self.doException(merror.IllegalValue) - if (self.byte_count != self.count * 2): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, self.count): - return self.doException(merror.IllegalAddress) - - context.setValues(self.function_code, self.address, self.values) - return WriteMultipleRegistersResponse(self.address, self.count) - - def get_response_pdu_size(self): - """ - Func_code (1 byte) + Starting Address (2 byte) + Quantity of Reggisters (2 Bytes) - :return: - """ - return 1 + 2 + 2 - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - params = (self.address, self.count) - return "WriteMultipleRegisterRequest %d => %d" % params - - -class WriteMultipleRegistersResponse(ModbusResponse): - ''' - "The normal response returns the function code, starting address, and - quantity of registers written. - ''' - function_code = 16 - _rtu_frame_size = 8 - - def __init__(self, address=None, count=None, **kwargs): - ''' Initializes a new instance - - :param address: The address to start writing to - :param count: The number of registers to write to - ''' - ModbusResponse.__init__(self, **kwargs) - self.address = address - self.count = count - - def encode(self): - ''' Encode a write single register packet packet request - - :returns: The encoded packet - ''' - return struct.pack('>HH', self.address, self.count) - - def decode(self, data): - ''' Decode a write single register packet packet request - - :param data: The request to decode - ''' - self.address, self.count = struct.unpack('>HH', data) - - def __str__(self): - ''' Returns a string representation of the instance - - :returns: A string representation of the instance - ''' - params = (self.address, self.count) - return "WriteMultipleRegisterResponse (%d,%d)" % params - -class MaskWriteRegisterRequest(ModbusRequest): - ''' - This function code is used to modify the contents of a specified holding - register using a combination of an AND mask, an OR mask, and the - register's current contents. The function can be used to set or clear - individual bits in the register. - ''' - function_code = 0x16 - _rtu_frame_size = 10 - - def __init__(self, address=0x0000, and_mask=0xffff, or_mask=0x0000, - **kwargs): - ''' Initializes a new instance - - :param address: The mask pointer address (0x0000 to 0xffff) - :param and_mask: The and bitmask to apply to the register address - :param or_mask: The or bitmask to apply to the register address - ''' - ModbusRequest.__init__(self, **kwargs) - self.address = address - self.and_mask = and_mask - self.or_mask = or_mask - - def encode(self): - ''' Encodes the request packet - - :returns: The byte encoded packet - ''' - return struct.pack('>HHH', self.address, self.and_mask, - self.or_mask) - - def decode(self, data): - ''' Decodes the incoming request - - :param data: The data to decode into the address - ''' - self.address, self.and_mask, self.or_mask = struct.unpack('>HHH', - data) - - def execute(self, context): - ''' Run a mask write register request against the store - - :param context: The datastore to request from - :returns: The populated response - ''' - if not (0x0000 <= self.and_mask <= 0xffff): - return self.doException(merror.IllegalValue) - if not (0x0000 <= self.or_mask <= 0xffff): - return self.doException(merror.IllegalValue) - if not context.validate(self.function_code, self.address, 1): - return self.doException(merror.IllegalAddress) - values = context.getValues(self.function_code, self.address, 1)[0] - values = ((values & self.and_mask) | self.or_mask) - context.setValues(self.function_code, self.address, [values]) - return MaskWriteRegisterResponse(self.address, self.and_mask, - self.or_mask) - - -class MaskWriteRegisterResponse(ModbusResponse): - ''' - The normal response is an echo of the request. The response is returned - after the register has been written. - ''' - function_code = 0x16 - _rtu_frame_size = 10 - - def __init__(self, address=0x0000, and_mask=0xffff, or_mask=0x0000, - **kwargs): - ''' Initializes a new instance - - :param address: The mask pointer address (0x0000 to 0xffff) - :param and_mask: The and bitmask applied to the register address - :param or_mask: The or bitmask applied to the register address - ''' - ModbusResponse.__init__(self, **kwargs) - self.address = address - self.and_mask = and_mask - self.or_mask = or_mask - - def encode(self): - ''' Encodes the response - - :returns: The byte encoded message - ''' - return struct.pack('>HHH', self.address, self.and_mask, - self.or_mask) - - def decode(self, data): - ''' Decodes a the response - - :param data: The packet data to decode - ''' - self.address, self.and_mask, self.or_mask = struct.unpack('>HHH', - data) - - -#---------------------------------------------------------------------------# -# Exported symbols -#---------------------------------------------------------------------------# -__all__ = [ - "WriteSingleRegisterRequest", "WriteSingleRegisterResponse", - "WriteMultipleRegistersRequest", "WriteMultipleRegistersResponse", - "MaskWriteRegisterRequest", "MaskWriteRegisterResponse" -] diff --git a/modbus/pymodbus/repl/README.md b/modbus/pymodbus/repl/README.md deleted file mode 100644 index b4f192f..0000000 --- a/modbus/pymodbus/repl/README.md +++ /dev/null @@ -1,315 +0,0 @@ -# Pymodbus REPL - -## Dependencies - -Depends on [prompt_toolkit](https://python-prompt-toolkit.readthedocs.io/en/stable/index.html) and [click](http://click.pocoo.org/6/quickstart/) - -Install dependencies -``` -$ pip install click prompt_toolkit --upgrade -``` - -Or -Install pymodbus with repl support -``` -$ pip install pymodbus[repl] --upgrade -``` - -## Usage Instructions -RTU and TCP are supported as of now - -``` -✗ pymodbus.console --help -Usage: pymodbus.console [OPTIONS] COMMAND [ARGS]... - -Options: - --version Show the version and exit. - --verbose Verbose logs - --broadcast-support Support broadcast messages - --help Show this message and exit. - -Commands: - serial - tcp - - -``` -TCP Options - -``` -✗ pymodbus.console tcp --help -Usage: pymodbus.console tcp [OPTIONS] - -Options: - --host TEXT Modbus TCP IP - --port INTEGER Modbus TCP port - --framer TEXT Override the default packet framer tcp|rtu - --help Show this message and exit. - -``` - -SERIAL Options -``` -✗ pymodbus.console serial --help -Usage: pymodbus.console serial [OPTIONS] - -Options: - --method TEXT Modbus Serial Mode (rtu/ascii) - --port TEXT Modbus RTU port - --baudrate INTEGER Modbus RTU serial baudrate to use. Defaults to 9600 - --bytesize [5|6|7|8] Modbus RTU serial Number of data bits. Possible - values: FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS. - Defaults to 8 - - --parity [N|E|O|M|S] Modbus RTU serial parity. Enable parity checking. - Possible values: PARITY_NONE, PARITY_EVEN, PARITY_ODD - PARITY_MARK, PARITY_SPACE. Default to 'N' - - --stopbits [1|1.5|2] Modbus RTU serial stop bits. Number of stop bits. - Possible values: STOPBITS_ONE, - STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to '1' - - --xonxoff INTEGER Modbus RTU serial xonxoff. Enable software flow - control.Defaults to 0 - - --rtscts INTEGER Modbus RTU serial rtscts. Enable hardware (RTS/CTS) - flow control. Defaults to 0 - - --dsrdtr INTEGER Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR) - flow control. Defaults to 0 - - --timeout FLOAT Modbus RTU serial read timeout. Defaults to 0.025 sec - --write-timeout FLOAT Modbus RTU serial write timeout. Defaults to 2 sec - --help Show this message and exit. -``` - -To view all available commands type `help` - -TCP -``` -$ pymodbus.console tcp --host 192.168.128.126 --port 5020 - -> help -Available commands: -client.change_ascii_input_delimiter Diagnostic sub command, Change message delimiter for future requests. -client.clear_counters Diagnostic sub command, Clear all counters and diag registers. -client.clear_overrun_count Diagnostic sub command, Clear over run counter. -client.close Closes the underlying socket connection -client.connect Connect to the modbus tcp server -client.debug_enabled Returns a boolean indicating if debug is enabled. -client.force_listen_only_mode Diagnostic sub command, Forces the addressed remote device to its Listen Only Mode. -client.get_clear_modbus_plus Diagnostic sub command, Get or clear stats of remote modbus plus device. -client.get_com_event_counter Read status word and an event count from the remote device's communication event counter. -client.get_com_event_log Read status word, event count, message count, and a field of event bytes from the remote device. -client.host Read Only! -client.idle_time Bus Idle Time to initiate next transaction -client.is_socket_open Check whether the underlying socket/serial is open or not. -client.last_frame_end Read Only! -client.mask_write_register Mask content of holding register at `address` with `and_mask` and `or_mask`. -client.port Read Only! -client.read_coils Reads `count` coils from a given slave starting at `address`. -client.read_device_information Read the identification and additional information of remote slave. -client.read_discrete_inputs Reads `count` number of discrete inputs starting at offset `address`. -client.read_exception_status Read the contents of eight Exception Status outputs in a remote device. -client.read_holding_registers Read `count` number of holding registers starting at `address`. -client.read_input_registers Read `count` number of input registers starting at `address`. -client.readwrite_registers Read `read_count` number of holding registers starting at `read_address` and write `write_registers` starting at `write_address`. -client.report_slave_id Report information about remote slave ID. -client.restart_comm_option Diagnostic sub command, initialize and restart remote devices serial interface and clear all of its communications event counters . -client.return_bus_com_error_count Diagnostic sub command, Return count of CRC errors received by remote slave. -client.return_bus_exception_error_count Diagnostic sub command, Return count of Modbus exceptions returned by remote slave. -client.return_bus_message_count Diagnostic sub command, Return count of message detected on bus by remote slave. -client.return_diagnostic_register Diagnostic sub command, Read 16-bit diagnostic register. -client.return_iop_overrun_count Diagnostic sub command, Return count of iop overrun errors by remote slave. -client.return_query_data Diagnostic sub command , Loop back data sent in response. -client.return_slave_bus_char_overrun_count Diagnostic sub command, Return count of messages not handled by remote slave due to character overrun condition. -client.return_slave_busy_count Diagnostic sub command, Return count of server busy exceptions sent by remote slave. -client.return_slave_message_count Diagnostic sub command, Return count of messages addressed to remote slave. -client.return_slave_no_ack_count Diagnostic sub command, Return count of NO ACK exceptions sent by remote slave. -client.return_slave_no_response_count Diagnostic sub command, Return count of No responses by remote slave. -client.silent_interval Read Only! -client.state Read Only! -client.timeout Read Only! -client.write_coil Write `value` to coil at `address`. -client.write_coils Write `value` to coil at `address`. -client.write_register Write `value` to register at `address`. -client.write_registers Write list of `values` to registers starting at `address`. -``` - -SERIAL -``` -$ pymodbus.console serial --port /dev/ttyUSB0 --baudrate 19200 --timeout 2 -> help -Available commands: -client.baudrate Read Only! -client.bytesize Read Only! -client.change_ascii_input_delimiter Diagnostic sub command, Change message delimiter for future requests. -client.clear_counters Diagnostic sub command, Clear all counters and diag registers. -client.clear_overrun_count Diagnostic sub command, Clear over run counter. -client.close Closes the underlying socket connection -client.connect Connect to the modbus serial server -client.debug_enabled Returns a boolean indicating if debug is enabled. -client.force_listen_only_mode Diagnostic sub command, Forces the addressed remote device to its Listen Only Mode. -client.get_baudrate Serial Port baudrate. -client.get_bytesize Number of data bits. -client.get_clear_modbus_plus Diagnostic sub command, Get or clear stats of remote modbus plus device. -client.get_com_event_counter Read status word and an event count from the remote device's communication event counter. -client.get_com_event_log Read status word, event count, message count, and a field of event bytes from the remote device. -client.get_parity Enable Parity Checking. -client.get_port Serial Port. -client.get_serial_settings Gets Current Serial port settings. -client.get_stopbits Number of stop bits. -client.get_timeout Serial Port Read timeout. -client.idle_time Bus Idle Time to initiate next transaction -client.inter_char_timeout Read Only! -client.is_socket_open c l i e n t . i s s o c k e t o p e n -client.mask_write_register Mask content of holding register at `address` with `and_mask` and `or_mask`. -client.method Read Only! -client.parity Read Only! -client.port Read Only! -client.read_coils Reads `count` coils from a given slave starting at `address`. -client.read_device_information Read the identification and additional information of remote slave. -client.read_discrete_inputs Reads `count` number of discrete inputs starting at offset `address`. -client.read_exception_status Read the contents of eight Exception Status outputs in a remote device. -client.read_holding_registers Read `count` number of holding registers starting at `address`. -client.read_input_registers Read `count` number of input registers starting at `address`. -client.readwrite_registers Read `read_count` number of holding registers starting at `read_address` and write `write_registers` starting at `write_address`. -client.report_slave_id Report information about remote slave ID. -client.restart_comm_option Diagnostic sub command, initialize and restart remote devices serial interface and clear all of its communications event counters . -client.return_bus_com_error_count Diagnostic sub command, Return count of CRC errors received by remote slave. -client.return_bus_exception_error_count Diagnostic sub command, Return count of Modbus exceptions returned by remote slave. -client.return_bus_message_count Diagnostic sub command, Return count of message detected on bus by remote slave. -client.return_diagnostic_register Diagnostic sub command, Read 16-bit diagnostic register. -client.return_iop_overrun_count Diagnostic sub command, Return count of iop overrun errors by remote slave. -client.return_query_data Diagnostic sub command , Loop back data sent in response. -client.return_slave_bus_char_overrun_count Diagnostic sub command, Return count of messages not handled by remote slave due to character overrun condition. -client.return_slave_busy_count Diagnostic sub command, Return count of server busy exceptions sent by remote slave. -client.return_slave_message_count Diagnostic sub command, Return count of messages addressed to remote slave. -client.return_slave_no_ack_count Diagnostic sub command, Return count of NO ACK exceptions sent by remote slave. -client.return_slave_no_response_count Diagnostic sub command, Return count of No responses by remote slave. -client.set_baudrate Baudrate setter. -client.set_bytesize Byte size setter. -client.set_parity Parity Setter. -client.set_port Serial Port setter. -client.set_stopbits Stop bit setter. -client.set_timeout Read timeout setter. -client.silent_interval Read Only! -client.state Read Only! -client.stopbits Read Only! -client.timeout Read Only! -client.write_coil Write `value` to coil at `address`. -client.write_coils Write `value` to coil at `address`. -client.write_register Write `value` to register at `address`. -client.write_registers Write list of `values` to registers starting at `address`. -result.decode Decode the register response to known formatters. -result.raw Return raw result dict. - -``` - -Every command has auto suggetion on the arguments supported , supply arg and value are to be supplied in `arg=val` format. -``` - -> client.read_holding_registers count=4 address=9 unit=1 -{ - "registers": [ - 60497, - 47134, - 34091, - 15424 - ] -} -``` - -The last result could be accessed with `result.raw` command -``` -> result.raw -{ - "registers": [ - 15626, - 55203, - 28733, - 18368 - ] -} -``` - -For Holding and Input register reads, the decoded value could be viewed with `result.decode` -``` -> result.decode word_order=little byte_order=little formatters=float64 -28.17 - -> -``` - -Client settings could be retrieved and altered as well. -``` -> # For serial settings - -> # Check the serial mode -> client.method -"rtu" - -> client.get_serial_settings -{ - "t1.5": 0.00171875, - "baudrate": 9600, - "read timeout": 0.5, - "port": "/dev/ptyp0", - "t3.5": 0.00401, - "bytesize": 8, - "parity": "N", - "stopbits": 1.0 -} -> client.set_timeout value=1 -null - -> client.get_timeout -1.0 - -> client.get_serial_settings -{ - "t1.5": 0.00171875, - "baudrate": 9600, - "read timeout": 1.0, - "port": "/dev/ptyp0", - "t3.5": 0.00401, - "bytesize": 8, - "parity": "N", - "stopbits": 1.0 -} - -``` - -To Send broadcast requests, use `--broadcast-support` and send requests with unit id as `0`. -`write_coil`, `write_coils`, `write_register`, `write_registers` are supported. - -``` -✗ pymodbus.console --broadcast-support tcp --host 192.168.1.8 --port 5020 - ----------------------------------------------------------------------------- -__________ _____ .___ __________ .__ -\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | | - | ___< | |/ \ / \ / _ \ / __ | | _// __ \\____ \| | - | | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__ - |____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/ - \/ \/ \/ \/ \/ \/|__| - v1.2.0 - [pymodbus, version 2.4.0] ----------------------------------------------------------------------------- - -> client.write_registers address=0 values=10,20,30,40 unit=0 -{ - "broadcasted": true -} - -> client.write_registers address=0 values=10,20,30,40 unit=1 -{ - "address": 0, - "count": 4 -} -``` - -## DEMO - -[![asciicast](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o.png)](https://asciinema.org/a/y1xOk7lm59U1bRBE2N1pDIj2o) -[![asciicast](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI.png)](https://asciinema.org/a/edUqZN77fdjxL2toisiilJNwI) - diff --git a/modbus/pymodbus/repl/__init__.py b/modbus/pymodbus/repl/__init__.py deleted file mode 100644 index ff5581a..0000000 --- a/modbus/pymodbus/repl/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Pymodbus REPL Module. - -Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. - -""" -from __future__ import absolute_import, unicode_literals \ No newline at end of file diff --git a/modbus/pymodbus/repl/client/__init__.py b/modbus/pymodbus/repl/client/__init__.py deleted file mode 100644 index bc4e394..0000000 --- a/modbus/pymodbus/repl/client/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" diff --git a/modbus/pymodbus/repl/client/completer.py b/modbus/pymodbus/repl/client/completer.py deleted file mode 100644 index 426c5b2..0000000 --- a/modbus/pymodbus/repl/client/completer.py +++ /dev/null @@ -1,156 +0,0 @@ -""" -Command Completion for pymodbus REPL. - -Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. - -""" -from __future__ import absolute_import, unicode_literals -from prompt_toolkit.completion import Completer, Completion -from prompt_toolkit.styles import Style -from prompt_toolkit.filters import Condition -from prompt_toolkit.application.current import get_app -from pymodbus.repl.client.helper import get_commands -from pymodbus.compat import string_types - - -@Condition -def has_selected_completion(): - complete_state = get_app().current_buffer.complete_state - return (complete_state is not None and - complete_state.current_completion is not None) - - -style = Style.from_dict({ - 'completion-menu.completion': 'bg:#008888 #ffffff', - 'completion-menu.completion.current': 'bg:#00aaaa #000000', - 'scrollbar.background': 'bg:#88aaaa', - 'scrollbar.button': 'bg:#222222', -}) - - -class CmdCompleter(Completer): - """ - Completer for Pymodbus REPL. - """ - - def __init__(self, client=None, commands=None, ignore_case=True): - """ - - :param client: Modbus Client - :param commands: Commands to be added for Completion (list) - :param ignore_case: Ignore Case while looking up for commands - """ - self._commands = commands or get_commands(client) - self._commands['help'] = "" - self._command_names = self._commands.keys() - self.ignore_case = ignore_case - - @property - def commands(self): - return self._commands - - @property - def command_names(self): - return self._commands.keys() - - def completing_command(self, words, word_before_cursor): - """ - Determine if we are dealing with supported command. - - :param words: Input text broken in to word tokens. - :param word_before_cursor: The current word before the cursor, \ - which might be one or more blank spaces. - :return: - """ - if len(words) == 1 and word_before_cursor != '': - return True - else: - return False - - def completing_arg(self, words, word_before_cursor): - """ - Determine if we are currently completing an argument. - - :param words: The input text broken into word tokens. - :param word_before_cursor: The current word before the cursor, \ - which might be one or more blank spaces. - :return: Specifies whether we are currently completing an arg. - """ - if len(words) > 1 and word_before_cursor != '': - return True - else: - return False - - def arg_completions(self, words, word_before_cursor): - """ - Generates arguments completions based on the input. - - :param words: The input text broken into word tokens. - :param word_before_cursor: The current word before the cursor, \ - which might be one or more blank spaces. - :return: A list of completions. - """ - cmd = words[0].strip() - cmd = self._commands.get(cmd, None) - if cmd: - return cmd - - def _get_completions(self, word, word_before_cursor): - if self.ignore_case: - word_before_cursor = word_before_cursor.lower() - return self.word_matches(word, word_before_cursor) - - def word_matches(self, word, word_before_cursor): - """ - Match the word and word before cursor - - :param words: The input text broken into word tokens. - :param word_before_cursor: The current word before the cursor, \ - which might be one or more blank spaces. - :return: True if matched. - - """ - if self.ignore_case: - word = word.lower() - return word.startswith(word_before_cursor) - - def get_completions(self, document, complete_event): - """ - Get completions for the current scope. - - :param document: An instance of `prompt_toolkit.Document`. - :param complete_event: (Unused). - :return: Yields an instance of `prompt_toolkit.completion.Completion`. - """ - word_before_cursor = document.get_word_before_cursor(WORD=True) - text = document.text_before_cursor.lstrip() - words = document.text.strip().split() - meta = None - commands = [] - if len(words) == 0: - # yield commands - pass - if self.completing_command(words, word_before_cursor): - commands = self._command_names - c_meta = { - k: v.help_text - if not isinstance(v, string_types) - else v for k, v in self._commands.items() - } - meta = lambda x: (x, c_meta.get(x, '')) - else: - if not list(filter(lambda cmd: any(x == cmd for x in words), - self._command_names)): - # yield commands - pass - - if ' ' in text: - command = self.arg_completions(words, word_before_cursor) - commands = list(command.get_completion()) - commands = list(filter(lambda cmd: not(any(cmd in x for x in words)), commands)) - meta = command.get_meta - for a in commands: - if self._get_completions(a, word_before_cursor): - cmd, display_meta = meta(a) if meta else ('', '') - yield Completion(a, -len(word_before_cursor), - display_meta=display_meta) diff --git a/modbus/pymodbus/repl/client/helper.py b/modbus/pymodbus/repl/client/helper.py deleted file mode 100644 index eb7ede6..0000000 --- a/modbus/pymodbus/repl/client/helper.py +++ /dev/null @@ -1,327 +0,0 @@ -""" -Helper Module for REPL actions. - -Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. - -""" -from __future__ import absolute_import, unicode_literals -import json -import pygments -import inspect -from collections import OrderedDict -from pygments.lexers.data import JsonLexer -from prompt_toolkit.formatted_text import PygmentsTokens, HTML -from prompt_toolkit import print_formatted_text - -from pymodbus.payload import BinaryPayloadDecoder, Endian -from pymodbus.compat import PYTHON_VERSION, IS_PYTHON2, string_types, izip - -predicate = inspect.ismethod -if IS_PYTHON2 or PYTHON_VERSION < (3, 3): - argspec = inspect.getargspec -else: - predicate = inspect.isfunction - argspec = inspect.signature - - -FORMATTERS = { - 'int8': 'decode_8bit_int', - 'int16': 'decode_16bit_int', - 'int32': 'decode_32bit_int', - 'int64': 'decode_64bit_int', - 'uint8': 'decode_8bit_uint', - 'uint16': 'decode_16bit_uint', - 'uint32': 'decode_32bit_uint', - 'uint64': 'decode_64bit_int', - 'float16': 'decode_16bit_float', - 'float32': 'decode_32bit_float', - 'float64': 'decode_64bit_float', -} - - -DEFAULT_KWARGS = { - 'unit': 'Slave address' -} - -OTHER_COMMANDS = { - "result.raw": "Show RAW Result", - "result.decode": "Decode register response to known formats", -} -EXCLUDE = ['execute', 'recv', 'send', 'trace', 'set_debug'] -CLIENT_METHODS = [ - 'connect', 'close', 'idle_time', 'is_socket_open', 'get_port', 'set_port', - 'get_stopbits', 'set_stopbits', 'get_bytesize', 'set_bytesize', - 'get_parity', 'set_parity', 'get_baudrate', 'set_baudrate', 'get_timeout', - 'set_timeout', 'get_serial_settings' - -] -CLIENT_ATTRIBUTES = [] - - -class Command(object): - """ - Class representing Commands to be consumed by Completer. - """ - def __init__(self, name, signature, doc, unit=False): - """ - - :param name: Name of the command - :param signature: inspect object - :param doc: Doc string for the command - :param unit: Use unit as additional argument in the command . - """ - self.name = name - self.doc = doc.split("\n") if doc else " ".join(name.split("_")) - self.help_text = self._create_help() - self.param_help = self._create_arg_help() - if signature: - if IS_PYTHON2: - self._params = signature - else: - self._params = signature.parameters - self.args = self.create_completion() - else: - self._params = '' - - if self.name.startswith("client.") and unit: - self.args.update(**DEFAULT_KWARGS) - - def _create_help(self): - doc = filter(lambda d: d, self.doc) - cmd_help = list(filter( - lambda x: not x.startswith(":param") and not x.startswith( - ":return"), doc)) - return " ".join(cmd_help).strip() - - def _create_arg_help(self): - param_dict = {} - params = list(filter(lambda d: d.strip().startswith(":param"), - self.doc)) - for param in params: - param, help = param.split(":param")[1].strip().split(":") - param_dict[param] = help - return param_dict - - def create_completion(self): - """ - Create command completion meta data. - - :return: - """ - words = {} - - def _create(entry, default): - if entry not in ['self', 'kwargs']: - if isinstance(default, (int, string_types)): - entry += "={}".format(default) - return entry - - if IS_PYTHON2: - if not self._params.defaults: - defaults = [None]*len(self._params.args) - else: - defaults = list(self._params.defaults) - missing = len(self._params.args) - len(defaults) - if missing > 1: - defaults.extend([None]*missing) - defaults.insert(0, None) - for arg, default in izip(self._params.args, defaults): - entry = _create(arg, default) - if entry: - entry, meta = self.get_meta(entry) - words[entry] = help - else: - for arg in self._params.values(): - entry = _create(arg.name, arg.default) - if entry: - entry, meta = self.get_meta(entry) - words[entry] = meta - - return words - - def get_completion(self): - """ - Gets a list of completions. - - :return: - """ - return self.args.keys() - - def get_meta(self, cmd): - """ - Get Meta info of a given command. - - :param cmd: Name of command. - :return: Dict containing meta info. - """ - cmd = cmd.strip() - cmd = cmd.split("=")[0].strip() - return cmd, self.param_help.get(cmd, '') - - def __str__(self): - if self.doc: - return "Command {:>50}{:<20}".format(self.name, self.doc) - return "Command {}".format(self.name) - - -def _get_requests(members): - commands = list(filter(lambda x: (x[0] not in EXCLUDE - and x[0] not in CLIENT_METHODS - and callable(x[1])), - members)) - commands = { - "client.{}".format(c[0]): - Command("client.{}".format(c[0]), - argspec(c[1]), inspect.getdoc(c[1]), unit=True) - for c in commands if not c[0].startswith("_") - } - return commands - - -def _get_client_methods(members): - commands = list(filter(lambda x: (x[0] not in EXCLUDE - and x[0] in CLIENT_METHODS), - members)) - commands = { - "client.{}".format(c[0]): - Command("client.{}".format(c[0]), - argspec(c[1]), inspect.getdoc(c[1]), unit=False) - for c in commands if not c[0].startswith("_") - } - return commands - - -def _get_client_properties(members): - global CLIENT_ATTRIBUTES - commands = list(filter(lambda x: not callable(x[1]), members)) - commands = { - "client.{}".format(c[0]): - Command("client.{}".format(c[0]), None, "Read Only!", unit=False) - for c in commands if (not c[0].startswith("_") - and isinstance(c[1], (string_types, int, float))) - } - CLIENT_ATTRIBUTES.extend(list(commands.keys())) - return commands - - -def get_commands(client): - """ - Helper method to retrieve all required methods and attributes of a client \ - object and convert it to commands. - - :param client: Modbus Client object. - :return: - """ - commands = dict() - members = inspect.getmembers(client) - requests = _get_requests(members) - client_methods = _get_client_methods(members) - client_attr = _get_client_properties(members) - - result_commands = inspect.getmembers(Result, predicate=predicate) - result_commands = { - "result.{}".format(c[0]): - Command("result.{}".format(c[0]), argspec(c[1]), - inspect.getdoc(c[1])) - for c in result_commands if (not c[0].startswith("_") - and c[0] != "print_result") - } - commands.update(requests) - commands.update(client_methods) - commands.update(client_attr) - commands.update(result_commands) - return commands - - -class Result(object): - """ - Represent result command. - """ - function_code = None - data = None - - def __init__(self, result): - """ - :param result: Response of a modbus command. - """ - if isinstance(result, dict): # Modbus response - self.function_code = result.pop('function_code', None) - self.data = dict(result) - else: - self.data = result - - def decode(self, formatters, byte_order='big', word_order='big'): - """ - Decode the register response to known formatters. - - :param formatters: int8/16/32/64, uint8/16/32/64, float32/64 - :param byte_order: little/big - :param word_order: little/big - :return: Decoded Value - """ - # Read Holding Registers (3) - # Read Input Registers (4) - # Read Write Registers (23) - if not isinstance(formatters, (list, tuple)): - formatters = [formatters] - - if self.function_code not in [3, 4, 23]: - print_formatted_text( - HTML("Decoder works only for registers!!")) - return - byte_order = (Endian.Little if byte_order.strip().lower() == "little" - else Endian.Big) - word_order = (Endian.Little if word_order.strip().lower() == "little" - else Endian.Big) - decoder = BinaryPayloadDecoder.fromRegisters(self.data.get('registers'), - byteorder=byte_order, - wordorder=word_order) - for formatter in formatters: - formatter = FORMATTERS.get(formatter) - if not formatter: - print_formatted_text( - HTML("Invalid Formatter - {}" - "!!".format(formatter))) - return - decoded = getattr(decoder, formatter)() - self.print_result(decoded) - - def raw(self): - """ - Return raw result dict. - - :return: - """ - self.print_result() - - def _process_dict(self, d): - new_dict = OrderedDict() - for k, v in d.items(): - if isinstance(v, bytes): - v = v.decode('utf-8') - elif isinstance(v, dict): - v = self._process_dict(v) - elif isinstance(v, (list, tuple)): - v = [v1.decode('utf-8') if isinstance(v1, bytes) else v1 - for v1 in v ] - new_dict[k] = v - return new_dict - - def print_result(self, data=None): - """ - Prettu print result object. - - :param data: Data to be printed. - :return: - """ - data = data or self.data - if isinstance(data, dict): - data = self._process_dict(data) - elif isinstance(data, (list, tuple)): - data = [v.decode('utf-8') if isinstance(v, bytes) else v - for v in data] - elif isinstance(data, bytes): - data = data.decode('utf-8') - tokens = list(pygments.lex(json.dumps(data, indent=4), - lexer=JsonLexer())) - print_formatted_text(PygmentsTokens(tokens)) diff --git a/modbus/pymodbus/repl/client/main.py b/modbus/pymodbus/repl/client/main.py deleted file mode 100644 index 8dc5583..0000000 --- a/modbus/pymodbus/repl/client/main.py +++ /dev/null @@ -1,384 +0,0 @@ -""" -Pymodbus REPL Entry point. - -Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. - -""" -from __future__ import absolute_import, unicode_literals -try: - import click -except ImportError: - print("click not installed!! Install with 'pip install click'") - exit(1) -try: - from prompt_toolkit import PromptSession, print_formatted_text -except ImportError: - print("prompt toolkit is not installed!! " - "Install with 'pip install prompt_toolkit --upgrade'") - exit(1) - -from prompt_toolkit.lexers import PygmentsLexer -from prompt_toolkit.styles import Style -from prompt_toolkit.key_binding import KeyBindings - -from pygments.lexers.python import PythonLexer -from prompt_toolkit.formatted_text import HTML -from prompt_toolkit.history import FileHistory -from prompt_toolkit.auto_suggest import AutoSuggestFromHistory -from pymodbus.version import version -from pymodbus.repl.client.completer import CmdCompleter, has_selected_completion -from pymodbus.repl.client.helper import Result, CLIENT_ATTRIBUTES - -click.disable_unicode_literals_warning = True - -TITLE = """ ----------------------------------------------------------------------------- -__________ _____ .___ __________ .__ -\______ \___.__. / \ ____ __| _/ \______ \ ____ ______ | | - | ___< | |/ \ / \ / _ \ / __ | | _// __ \\\____ \| | - | | \___ / Y ( <_> ) /_/ | | | \ ___/| |_> > |__ - |____| / ____\____|__ /\____/\____ | /\ |____|_ /\___ > __/|____/ - \/ \/ \/ \/ \/ \/|__| - v{} - {} ----------------------------------------------------------------------------- -""".format("1.3.0", version) - -log = None - -style = Style.from_dict({ - 'completion-menu.completion': 'bg:#008888 #ffffff', - 'completion-menu.completion.current': 'bg:#00aaaa #000000', - 'scrollbar.background': 'bg:#88aaaa', - 'scrollbar.button': 'bg:#222222', -}) - - -def bottom_toolbar(): - """ - Console toolbar. - :return: - """ - return HTML('Press ' - ' to exit! Type "help" for list of available commands') - - -class CaseInsenstiveChoice(click.Choice): - """ - Case Insensitive choice for click commands and options - """ - def convert(self, value, param, ctx): - """ - Convert args to uppercase for evaluation. - - """ - if value is None: - return None - return super(CaseInsenstiveChoice, self).convert( - value.strip().upper(), param, ctx) - - -class NumericChoice(click.Choice): - """ - Numeric choice for click arguments and options. - """ - def __init__(self, choices, typ): - self.typ = typ - super(NumericChoice, self).__init__(choices) - - def convert(self, value, param, ctx): - # Exact match - if value in self.choices: - return self.typ(value) - - if ctx is not None and ctx.token_normalize_func is not None: - value = ctx.token_normalize_func(value) - for choice in self.casted_choices: - if ctx.token_normalize_func(choice) == value: - return choice - - self.fail('invalid choice: %s. (choose from %s)' % - (value, ', '.join(self.choices)), param, ctx) - - -def cli(client): - kb = KeyBindings() - - @kb.add('c-space') - def _(event): - """ - Initialize autocompletion, or select the next completion. - """ - buff = event.app.current_buffer - if buff.complete_state: - buff.complete_next() - else: - buff.start_completion(select_first=False) - - @kb.add('enter', filter=has_selected_completion) - def _(event): - """ - Makes the enter key work as the tab key only when showing the menu. - """ - - event.current_buffer.complete_state = None - b = event.cli.current_buffer - b.complete_state = None - - def _process_args(args, string=True): - kwargs = {} - execute = True - skip_index = None - for i, arg in enumerate(args): - if i == skip_index: - continue - arg = arg.strip() - if "=" in arg: - a, val = arg.split("=") - if not string: - if "," in val: - val = val.split(",") - val = [int(v) for v in val] - else: - val = int(val) - kwargs[a] = val - else: - a, val = arg, args[i + 1] - try: - if not string: - if "," in val: - val = val.split(",") - val = [int(v) for v in val] - else: - val = int(val) - kwargs[a] = val - skip_index = i + 1 - except TypeError: - click.secho("Error parsing arguments!", - fg='yellow') - execute = False - break - except ValueError: - click.secho("Error parsing argument", - fg='yellow') - execute = False - break - return kwargs, execute - - session = PromptSession(lexer=PygmentsLexer(PythonLexer), - completer=CmdCompleter(client), style=style, - complete_while_typing=True, - bottom_toolbar=bottom_toolbar, - key_bindings=kb, - history=FileHistory('../.pymodhis'), - auto_suggest=AutoSuggestFromHistory()) - click.secho("{}".format(TITLE), fg='green') - result = None - while True: - try: - - text = session.prompt('> ', complete_while_typing=True) - if text.strip().lower() == 'help': - print_formatted_text(HTML("Available commands:")) - for cmd, obj in sorted(session.completer.commands.items()): - if cmd != 'help': - print_formatted_text( - HTML("{:45s}" - "{:100s}" - "".format(cmd, obj.help_text))) - - continue - elif text.strip().lower() == 'exit': - raise EOFError() - elif text.strip().lower().startswith("client."): - try: - text = text.strip().split() - cmd = text[0].split(".")[1] - args = text[1:] - kwargs, execute = _process_args(args, string=False) - if execute: - if text[0] in CLIENT_ATTRIBUTES: - result = Result(getattr(client, cmd)) - else: - result = Result(getattr(client, cmd)(**kwargs)) - result.print_result() - except Exception as e: - click.secho(repr(e), fg='red') - elif text.strip().lower().startswith("result."): - if result: - words = text.lower().split() - if words[0] == 'result.raw': - result.raw() - if words[0] == 'result.decode': - args = words[1:] - kwargs, execute = _process_args(args) - if execute: - result.decode(**kwargs) - except KeyboardInterrupt: - continue # Control-C pressed. Try again. - except EOFError: - break # Control-D pressed. - except Exception as e: # Handle all other exceptions - click.secho(str(e), fg='red') - - click.secho('GoodBye!', fg='blue') - - -@click.group('pymodbus-repl') -@click.version_option(version, message=TITLE) -@click.option("--verbose", is_flag=True, default=False, help="Verbose logs") -@click.option("--broadcast-support", is_flag=True, default=False, - help="Support broadcast messages") -@click.option("--retry-on-empty", is_flag=True, default=False, - help="Retry on empty response") -@click.option("--retry-on-error", is_flag=True, default=False, - help="Retry on error response") -@click.option("--retries", default=3, help="Retry count") -@click.option("--reset-socket/--no-reset-socket", default=True, help="Reset client socket on error") -@click.pass_context -def main(ctx, verbose, broadcast_support, retry_on_empty, - retry_on_error, retries, reset_socket): - if verbose: - global log - import logging - format = ('%(asctime)-15s %(threadName)-15s ' - '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') - log = logging.getLogger('pymodbus') - logging.basicConfig(format=format) - log.setLevel(logging.DEBUG) - ctx.obj = { - "broadcast": broadcast_support, - "retry_on_empty": retry_on_empty, - "retry_on_invalid": retry_on_error, - "retries": retries, - "reset_socket": reset_socket - } - - -@main.command("tcp") -@click.pass_context -@click.option( - "--host", - default='localhost', - help="Modbus TCP IP " -) -@click.option( - "--port", - default=502, - type=int, - help="Modbus TCP port", -) -@click.option( - "--framer", - default='tcp', - type=str, - help="Override the default packet framer tcp|rtu", -) -def tcp(ctx, host, port, framer): - from pymodbus.repl.client.mclient import ModbusTcpClient - kwargs = dict(host=host, port=port) - kwargs.update(**ctx.obj) - if framer == 'rtu': - from pymodbus.framer.rtu_framer import ModbusRtuFramer - kwargs['framer'] = ModbusRtuFramer - client = ModbusTcpClient(**kwargs) - cli(client) - - -@main.command("serial") -@click.pass_context -@click.option( - "--method", - default='rtu', - type=str, - help="Modbus Serial Mode (rtu/ascii)", -) -@click.option( - "--port", - default=None, - type=str, - help="Modbus RTU port", -) -@click.option( - "--baudrate", - help="Modbus RTU serial baudrate to use. Defaults to 9600", - default=9600, - type=int - ) -@click.option( - "--bytesize", - help="Modbus RTU serial Number of data bits. " - "Possible values: FIVEBITS, SIXBITS, SEVENBITS, " - "EIGHTBITS. Defaults to 8", - type=NumericChoice(["5", "6", "7", "8"], int), - default="8" -) -@click.option( - "--parity", - help="Modbus RTU serial parity. " - " Enable parity checking. Possible values: " - "PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, " - "PARITY_SPACE. Default to 'N'", - default='N', - type=CaseInsenstiveChoice(['N', 'E', 'O', 'M', 'S']) -) -@click.option( - "--stopbits", - help="Modbus RTU serial stop bits. " - "Number of stop bits. Possible values: STOPBITS_ONE, " - "STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO. Default to '1'", - default="1", - type=NumericChoice(["1", "1.5", "2"], float), -) -@click.option( - "--xonxoff", - help="Modbus RTU serial xonxoff. Enable software flow control." - "Defaults to 0", - default=0, - type=int -) -@click.option( - "--rtscts", - help="Modbus RTU serial rtscts. Enable hardware (RTS/CTS) flow " - "control. Defaults to 0", - default=0, - type=int -) -@click.option( - "--dsrdtr", - help="Modbus RTU serial dsrdtr. Enable hardware (DSR/DTR) flow " - "control. Defaults to 0", - default=0, - type=int -) -@click.option( - "--timeout", - help="Modbus RTU serial read timeout. Defaults to 0.025 sec", - default=0.25, - type=float -) -@click.option( - "--write-timeout", - help="Modbus RTU serial write timeout. Defaults to 2 sec", - default=2, - type=float -) -def serial(ctx, method, port, baudrate, bytesize, parity, stopbits, xonxoff, - rtscts, dsrdtr, timeout, write_timeout): - from pymodbus.repl.client.mclient import ModbusSerialClient - client = ModbusSerialClient(method=method, - port=port, - baudrate=baudrate, - bytesize=bytesize, - parity=parity, - stopbits=stopbits, - xonxoff=xonxoff, - rtscts=rtscts, - dsrdtr=dsrdtr, - timeout=timeout, - write_timeout=write_timeout, - **ctx.obj) - cli(client) - - -if __name__ == "__main__": - main() diff --git a/modbus/pymodbus/repl/client/mclient.py b/modbus/pymodbus/repl/client/mclient.py deleted file mode 100644 index b84296e..0000000 --- a/modbus/pymodbus/repl/client/mclient.py +++ /dev/null @@ -1,734 +0,0 @@ -""" -Modbus Clients to be used with REPL. - -Copyright (c) 2018 Riptide IO, Inc. All Rights Reserved. - -""" -from __future__ import absolute_import, unicode_literals -import functools -from pymodbus.pdu import ModbusExceptions, ExceptionResponse -from pymodbus.exceptions import ModbusIOException -from pymodbus.client.sync import ModbusSerialClient as _ModbusSerialClient -from pymodbus.client.sync import ModbusTcpClient as _ModbusTcpClient -from pymodbus.mei_message import ReadDeviceInformationRequest -from pymodbus.other_message import (ReadExceptionStatusRequest, - ReportSlaveIdRequest, - GetCommEventCounterRequest, - GetCommEventLogRequest) -from pymodbus.diag_message import ( - ReturnQueryDataRequest, - RestartCommunicationsOptionRequest, - ReturnDiagnosticRegisterRequest, - ChangeAsciiInputDelimiterRequest, - ForceListenOnlyModeRequest, - ClearCountersRequest, - ReturnBusMessageCountRequest, - ReturnBusCommunicationErrorCountRequest, - ReturnBusExceptionErrorCountRequest, - ReturnSlaveMessageCountRequest, - ReturnSlaveNoResponseCountRequest, - ReturnSlaveNAKCountRequest, - ReturnSlaveBusyCountRequest, - ReturnSlaveBusCharacterOverrunCountRequest, - ReturnIopOverrunCountRequest, - ClearOverrunCountRequest, - GetClearModbusPlusRequest) - - -def make_response_dict(resp): - rd = { - 'function_code': resp.function_code, - 'address': resp.address - } - if hasattr(resp, "value"): - rd['value'] = resp.value - elif hasattr(resp, 'values'): - rd['values'] = resp.values - elif hasattr(resp, 'count'): - rd['count'] = resp.count - - return rd - - -def handle_brodcast(func): - @functools.wraps(func) - def _wrapper(*args, **kwargs): - self = args[0] - resp = func(*args, **kwargs) - if kwargs.get("unit") == 0 and self.broadcast_enable: - return { - 'broadcasted': True - } - if not resp.isError(): - return make_response_dict(resp) - else: - return ExtendedRequestSupport._process_exception(resp, **kwargs) - return _wrapper - - -class ExtendedRequestSupport(object): - - @staticmethod - def _process_exception(resp, **kwargs): - unit = kwargs.get("unit") - if unit == 0: - err = { - "message": "Broadcast message, ignoring errors!!!" - } - else: - if isinstance(resp, ExceptionResponse): - err = { - 'original_function_code': "{} ({})".format( - resp.original_code, hex(resp.original_code)), - 'error_function_code': "{} ({})".format( - resp.function_code, hex(resp.function_code)), - 'exception code': resp.exception_code, - 'message': ModbusExceptions.decode(resp.exception_code) - } - elif isinstance(resp, ModbusIOException): - err = { - 'original_function_code': "{} ({})".format( - resp.fcode, hex(resp.fcode)), - 'error': resp.message - } - else: - err = { - 'error': str(resp) - } - return err - - def read_coils(self, address, count=1, **kwargs): - """ - Reads `count` coils from a given slave starting at `address`. - - :param address: The starting address to read from - :param count: The number of coils to read - :param unit: The slave unit this request is targeting - :returns: List of register values - """ - resp = super(ExtendedRequestSupport, self).read_coils(address, - count, **kwargs) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'bits': resp.bits - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def read_discrete_inputs(self, address, count=1, **kwargs): - """ - Reads `count` number of discrete inputs starting at offset `address`. - - :param address: The starting address to read from - :param count: The number of coils to read - :param unit: The slave unit this request is targeting - :return: List of bits - """ - resp = super(ExtendedRequestSupport, - self).read_discrete_inputs(address, count, **kwargs) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'bits': resp.bits - } - else: - return ExtendedRequestSupport._process_exception(resp) - - @handle_brodcast - def write_coil(self, address, value, **kwargs): - """ - Write `value` to coil at `address`. - - :param address: coil offset to write to - :param value: bit value to write - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).write_coil( - address, value, **kwargs) - return resp - - @handle_brodcast - def write_coils(self, address, values, **kwargs): - """ - Write `value` to coil at `address`. - - :param address: coil offset to write to - :param values: list of bit values to write (comma separated) - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).write_coils( - address, values, **kwargs) - return resp - - @handle_brodcast - def write_register(self, address, value, **kwargs): - """ - Write `value` to register at `address`. - - :param address: register offset to write to - :param value: register value to write - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).write_register( - address, value, **kwargs) - return resp - - @handle_brodcast - def write_registers(self, address, values, **kwargs): - """ - Write list of `values` to registers starting at `address`. - - :param address: register offset to write to - :param values: list of register value to write (comma separated) - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).write_registers( - address, values, **kwargs) - return resp - - def read_holding_registers(self, address, count=1, **kwargs): - """ - Read `count` number of holding registers starting at `address`. - - :param address: starting register offset to read from - :param count: Number of registers to read - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).read_holding_registers( - address, count, **kwargs) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'registers': resp.registers - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def read_input_registers(self, address, count=1, **kwargs): - """ - Read `count` number of input registers starting at `address`. - - :param address: starting register offset to read from to - :param count: Number of registers to read - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).read_input_registers( - address, count, **kwargs) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'registers': resp.registers - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def readwrite_registers(self, read_address, read_count, write_address, - write_registers, **kwargs): - """ - Read `read_count` number of holding registers starting at \ - `read_address` and write `write_registers` \ - starting at `write_address`. - - :param read_address: register offset to read from - :param read_count: Number of registers to read - :param write_address: register offset to write to - :param write_registers: List of register values to write (comma separated) - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).readwrite_registers( - read_address=read_address, - read_count=read_count, - write_address=write_address, - write_registers=write_registers, - **kwargs - ) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'registers': resp.registers - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def mask_write_register(self, address=0x0000, - and_mask=0xffff, or_mask=0x0000, **kwargs): - """ - Mask content of holding register at `address` \ - with `and_mask` and `or_mask`. - - :param address: Reference address of register - :param and_mask: And Mask - :param or_mask: OR Mask - :param unit: The slave unit this request is targeting - :return: - """ - resp = super(ExtendedRequestSupport, self).read_input_registers( - address=address, and_mask=and_mask, or_mask=or_mask, **kwargs) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'address': resp.address, - 'and mask': resp.and_mask, - 'or mask': resp.or_mask - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def read_device_information(self, read_code=None, - object_id=0x00, **kwargs): - """ - Read the identification and additional information of remote slave. - - :param read_code: Read Device ID code (0x01/0x02/0x03/0x04) - :param object_id: Identification of the first object to obtain. - :param unit: The slave unit this request is targeting - :return: - """ - request = ReadDeviceInformationRequest(read_code, object_id, **kwargs) - resp = self.execute(request) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'information': resp.information, - 'object count': resp.number_of_objects, - 'conformity': resp.conformity, - 'next object id': resp.next_object_id, - 'more follows': resp.more_follows, - 'space left': resp.space_left - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def report_slave_id(self, **kwargs): - """ - Report information about remote slave ID. - - :param unit: The slave unit this request is targeting - :return: - """ - request = ReportSlaveIdRequest(**kwargs) - resp = self.execute(request) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'identifier': resp.identifier.decode('cp1252'), - 'status': resp.status, - 'byte count': resp.byte_count - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def read_exception_status(self, **kwargs): - """ - Read the contents of eight Exception Status outputs in a remote \ - device. - - :param unit: The slave unit this request is targeting - - :return: - - """ - request = ReadExceptionStatusRequest(**kwargs) - resp = self.execute(request) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'status': resp.status - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def get_com_event_counter(self, **kwargs): - """ - Read status word and an event count from the remote device's \ - communication event counter. - - :param unit: The slave unit this request is targeting - - :return: - - """ - request = GetCommEventCounterRequest(**kwargs) - resp = self.execute(request) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'status': resp.status, - 'count': resp.count - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def get_com_event_log(self, **kwargs): - """ - Read status word, event count, message count, and a field of event - bytes from the remote device. - - :param unit: The slave unit this request is targeting - :return: - """ - request = GetCommEventLogRequest(**kwargs) - resp = self.execute(request) - if not resp.isError(): - return { - 'function_code': resp.function_code, - 'status': resp.status, - 'message count': resp.message_count, - 'event count': resp.event_count, - 'events': resp.events, - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def _execute_diagnostic_request(self, request): - resp = self.execute(request) - if not resp.isError(): - return { - 'function code': resp.function_code, - 'sub function code': resp.sub_function_code, - 'message': resp.message - } - else: - return ExtendedRequestSupport._process_exception(resp) - - def return_query_data(self, message=0, **kwargs): - """ - Diagnostic sub command , Loop back data sent in response. - - :param message: Message to be looped back - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnQueryDataRequest(message, **kwargs) - return self._execute_diagnostic_request(request) - - def restart_comm_option(self, toggle=False, **kwargs): - """ - Diagnostic sub command, initialize and restart remote devices serial \ - interface and clear all of its communications event counters . - - :param toggle: Toggle Status [ON(0xff00)/OFF(0x0000] - :param unit: The slave unit this request is targeting - :return: - """ - request = RestartCommunicationsOptionRequest(toggle, **kwargs) - return self._execute_diagnostic_request(request) - - def return_diagnostic_register(self, data=0, **kwargs): - """ - Diagnostic sub command, Read 16-bit diagnostic register. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnDiagnosticRegisterRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def change_ascii_input_delimiter(self, data=0, **kwargs): - """ - Diagnostic sub command, Change message delimiter for future requests. - - :param data: New delimiter character - :param unit: The slave unit this request is targeting - :return: - """ - request = ChangeAsciiInputDelimiterRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def force_listen_only_mode(self, data=0, **kwargs): - """ - Diagnostic sub command, Forces the addressed remote device to \ - its Listen Only Mode. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ForceListenOnlyModeRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def clear_counters(self, data=0, **kwargs): - """ - Diagnostic sub command, Clear all counters and diag registers. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ClearCountersRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_bus_message_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of message detected on bus \ - by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnBusMessageCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_bus_com_error_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of CRC errors \ - received by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnBusCommunicationErrorCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_bus_exception_error_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of Modbus exceptions \ - returned by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnBusExceptionErrorCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_slave_message_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of messages addressed to \ - remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnSlaveMessageCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_slave_no_response_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of No responses by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnSlaveNoResponseCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_slave_no_ack_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of NO ACK exceptions sent \ - by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnSlaveNAKCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_slave_busy_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of server busy exceptions sent \ - by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnSlaveBusyCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_slave_bus_char_overrun_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of messages not handled \ - by remote slave due to character overrun condition. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnSlaveBusCharacterOverrunCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def return_iop_overrun_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Return count of iop overrun errors \ - by remote slave. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ReturnIopOverrunCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def clear_overrun_count(self, data=0, **kwargs): - """ - Diagnostic sub command, Clear over run counter. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = ClearOverrunCountRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - def get_clear_modbus_plus(self, data=0, **kwargs): - """ - Diagnostic sub command, Get or clear stats of remote \ - modbus plus device. - - :param data: Data field (0x0000) - :param unit: The slave unit this request is targeting - :return: - """ - request = GetClearModbusPlusRequest(data, **kwargs) - return self._execute_diagnostic_request(request) - - -class ModbusSerialClient(ExtendedRequestSupport, _ModbusSerialClient): - def __init__(self, method, **kwargs): - super(ModbusSerialClient, self).__init__(method, **kwargs) - - def get_port(self): - """ - Serial Port. - - :return: Current Serial port - """ - return self.port - - def set_port(self, value): - """ - Serial Port setter. - - :param value: New port - """ - self.port = value - if self.is_socket_open(): - self.close() - - def get_stopbits(self): - """ - Number of stop bits. - - :return: Current Stop bits - """ - return self.stopbits - - def set_stopbits(self, value): - """ - Stop bit setter. - - :param value: Possible values (1, 1.5, 2) - """ - self.stopbits = float(value) - if self.is_socket_open(): - self.close() - - def get_bytesize(self): - """ - Number of data bits. - - :return: Current bytesize - """ - return self.bytesize - - def set_bytesize(self, value): - """ - Byte size setter. - - :param value: Possible values (5, 6, 7, 8) - - """ - self.bytesize = int(value) - if self.is_socket_open(): - self.close() - - def get_parity(self): - """ - Enable Parity Checking. - - :return: Current parity setting - """ - return self.parity - - def set_parity(self, value): - """ - Parity Setter. - - :param value: Possible values ('N', 'E', 'O', 'M', 'S') - """ - self.parity = value - if self.is_socket_open(): - self.close() - - def get_baudrate(self): - """ - Serial Port baudrate. - - :return: Current baudrate - """ - return self.baudrate - - def set_baudrate(self, value): - """ - Baudrate setter. - - :param value: - """ - self.baudrate = int(value) - if self.is_socket_open(): - self.close() - - def get_timeout(self): - """ - Serial Port Read timeout. - - :return: Current read imeout. - """ - return self.timeout - - def set_timeout(self, value): - """ - Read timeout setter. - - :param value: Read Timeout in seconds - """ - self.timeout = float(value) - if self.is_socket_open(): - self.close() - - def get_serial_settings(self): - """ - Gets Current Serial port settings. - - :return: Current Serial settings as dict. - """ - return { - 'baudrate': self.baudrate, - 'port': self.port, - 'parity': self.parity, - 'stopbits': self.stopbits, - 'bytesize': self.bytesize, - 'read timeout': self.timeout, - 't1.5': self.inter_char_timeout, - 't3.5': self.silent_interval - } - - -class ModbusTcpClient(ExtendedRequestSupport, _ModbusTcpClient): - def __init__(self, **kwargs): - super(ModbusTcpClient, self).__init__(**kwargs) diff --git a/modbus/pymodbus/repl/server/__init__.py b/modbus/pymodbus/repl/server/__init__.py deleted file mode 100644 index bc4e394..0000000 --- a/modbus/pymodbus/repl/server/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" diff --git a/modbus/pymodbus/repl/server/cli.py b/modbus/pymodbus/repl/server/cli.py deleted file mode 100644 index 0ab1a96..0000000 --- a/modbus/pymodbus/repl/server/cli.py +++ /dev/null @@ -1,208 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" -import json -import click -import shutil -import logging - -from prompt_toolkit.shortcuts import clear -from prompt_toolkit.shortcuts.progress_bar import formatters -from prompt_toolkit.styles import Style - -from prompt_toolkit import PromptSession, print_formatted_text -from prompt_toolkit.patch_stdout import patch_stdout -from prompt_toolkit.completion import NestedCompleter -from prompt_toolkit.formatted_text import HTML - - -logger = logging.getLogger(__name__) - -TITLE = """ -__________ .______. _________ -\______ \___.__. _____ ____ __| _/\_ |__ __ __ ______ / _____/ ______________ __ ___________ - | ___< | |/ \ / _ \ / __ | | __ \| | \/ ___/ \_____ \_/ __ \_ __ \ \/ // __ \_ __ \\ - | | \___ | Y Y ( <_> ) /_/ | | \_\ \ | /\___ \ / \ ___/| | \/\ /\ ___/| | \/ - |____| / ____|__|_| /\____/\____ | |___ /____//____ > /_______ /\___ >__| \_/ \___ >__| - \/ \/ \/ \/ \/ \/ \/ \/ -""" - -SMALL_TITLE = "Pymodbus server..." -BOTTOM_TOOLBAR = HTML('(MODBUS SERVER) Type "help" ' - 'for list of available commands') -COMMAND_ARGS = ["response_type", "error_code", "delay_by", - "clear_after", "data_len"] -RESPONSE_TYPES = ["normal", "error", "delayed", "empty", "stray"] -COMMANDS = { - "manipulator": { - "response_type": None, - "error_code": None, - "delay_by": None, - "clear_after": None - }, - "exit": None, - "help": None, - "clear": None -} -USAGE = "manipulator response_type=|normal|error|delayed|empty|stray \n" \ - "\tAdditional parameters\n" \ - "\t\terror_code=<int> \n\t\tdelay_by=<in seconds> \n\t\t" \ - "clear_after=<clear after n messages int>" \ - "\n\t\tdata_len=<length of stray data (int)>\n" \ - "\n\tExample usage: \n\t" \ - "1. Send error response 3 for 4 requests\n\t" \ - " manipulator response_type=error error_code=3 clear_after=4\n\t" \ - "2. Delay outgoing response by 5 seconds indefinitely\n\t" \ - " manipulator response_type=delayed delay_by=5\n\t" \ - "3. Send empty response\n\t" \ - " manipulator response_type=empty\n\t" \ - "4. Send stray response of lenght 12 and revert to normal after 2 responses\n\t" \ - " manipulator response_type=stray data_len=11 clear_after=2\n\t" \ - "5. To disable response manipulation\n\t" \ - " manipulator response_type=normal" -COMMAND_HELPS = { - "manipulator": "Manipulate response from server.\nUsage: {}".format(USAGE), - "clear": "Clears screen" - -} - - -STYLE = Style.from_dict({"": "cyan"}) -CUSTOM_FORMATTERS = [ - formatters.Label(suffix=": "), - formatters.Bar(start="|", end="|", sym_a="#", sym_b="#", sym_c="-"), - formatters.Text(" "), - formatters.Text(" "), - formatters.TimeElapsed(), - formatters.Text(" "), - ] - - -def info(message): - if not isinstance(message, str): - message = str(message) - click.secho(message, fg="green") - - -def warning(message): - click.secho(str(message), fg="yellow") - - -def error(message): - click.secho(str(message), fg="red") - - -def get_terminal_width(): - return shutil.get_terminal_size()[0] - - -def print_help(): - print_formatted_text(HTML("Available commands:")) - for cmd, hlp in sorted(COMMAND_HELPS.items()): - print_formatted_text( - HTML("{:45s}{:100s}".format(cmd, hlp)) - ) - - -async def interactive_shell(server): - """ - CLI interactive shell - """ - col = get_terminal_width() - max_len = max([len(t) for t in TITLE.split("\n")]) - if col > max_len: - info(TITLE) - else: - print_formatted_text(HTML(''.format(SMALL_TITLE))) - info("") - completer = NestedCompleter.from_nested_dict(COMMANDS) - session = PromptSession("SERVER > ", - completer=completer, - bottom_toolbar=BOTTOM_TOOLBAR) - - # Run echo loop. Read text from stdin, and reply it back. - while True: - try: - invalid_command = False - result = await session.prompt_async() - if result == "exit": - await server.web_app.shutdown() - break - if result == "help": - print_help() - continue - if result == "clear": - clear() - continue - command = result.split() - if command: - if command[0] not in COMMANDS: - invalid_command = True - if invalid_command: - warning("Invalid command or invalid usage of command - {}".format(command)) - continue - if len(command) == 1: - warning("Usage: '{}'".format(USAGE)) - else: - args = command[1:] - skip_next = False - val_dict = {} - for index, arg in enumerate(args): - if skip_next: - skip_next = False - continue - if "=" in arg: - arg, value = arg.split("=") - else: - if arg in COMMAND_ARGS: - try: - value = args[index+1] - skip_next = True - except IndexError: - error("Missing value " - "for argument - {}".format(arg)) - warning("Usage: '{}'".format(USAGE)) - break - valid = True - if arg == "response_type": - if value not in RESPONSE_TYPES: - warning("Invalid response " - "type request - {}".format(value)) - warning("Choose from {}".format(RESPONSE_TYPES)) - valid = False - elif arg in ["error_code", "delay_by", - "clear_after", "data_len"]: - try: - value = int(value) - except ValueError: - warning("Expected integer " - "value for {}".format(arg)) - valid = False - - if valid: - val_dict[arg] = value - if val_dict: - server.update_manipulator_config(val_dict) - # server.manipulator_config = val_dict - # result = await run_command(tester, *command) - - except (EOFError, KeyboardInterrupt): - return - - -async def main(server): - # with patch_stdout(): - try: - await interactive_shell(server) - finally: - pass - warning("Bye Bye!!!") - - -async def run_repl(server): - await main(server) - - diff --git a/modbus/pymodbus/repl/server/main.py b/modbus/pymodbus/repl/server/main.py deleted file mode 100644 index 8d300f1..0000000 --- a/modbus/pymodbus/repl/server/main.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" -import asyncio -import json -import click -from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION -from pymodbus.framer.socket_framer import ModbusSocketFramer -from pymodbus.server.reactive.main import ( - ReactiveServer, DEFAULT_FRAMER, DEFUALT_HANDLERS) -from pymodbus.server.reactive.default_config import DEFUALT_CONFIG -from pymodbus.repl.server.cli import run_repl - -if IS_PYTHON3 and PYTHON_VERSION > (3, 7): - CANCELLED_ERROR = asyncio.exceptions.CancelledError -else: - CANCELLED_ERROR = asyncio.CancelledError - - -@click.group("ReactiveModbusServer") -@click.option("--host", default="localhost", help="Host address") -@click.option("--web-port", default=8080, help="Web app port") -@click.option("--broadcast-support", is_flag=True, - default=False, help="Support broadcast messages") -@click.option("--repl/--no-repl", is_flag=True, - default=True, help="Enable/Disable repl for server") -@click.option("--verbose", is_flag=True, - help="Run with debug logs enabled for pymodbus") -@click.pass_context -def server(ctx, host, web_port, broadcast_support, repl, verbose): - global logger - import logging - FORMAT = ('%(asctime)-15s %(threadName)-15s' - ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s') - pymodbus_logger = logging.getLogger("pymodbus") - logging.basicConfig(format=FORMAT) - logger = logging.getLogger(__name__) - if verbose: - pymodbus_logger.setLevel(logging.DEBUG) - logger.setLevel(logging.DEBUG) - else: - pymodbus_logger.setLevel(logging.ERROR) - logger.setLevel(logging.ERROR) - - ctx.obj = {"repl": repl, "host": host, "web_port": web_port, - "broadcast": broadcast_support} - - -@server.command("run") -@click.option("--modbus-server", default="tcp", - type=click.Choice(["tcp", "serial", "tls", "udp"], - case_sensitive=False), - help="Modbus server") -@click.option("--modbus-framer", default="socket", - type=click.Choice(["socket", "rtu", "tls", "ascii", "binary"], - case_sensitive=False), - help="Modbus framer to use") -@click.option("--modbus-port", default="5020", help="Modbus port") -@click.option("--modbus-unit-id", default=[1], type=int, - multiple=True, help="Modbus unit id") -@click.option("--modbus-config", type=click.Path(exists=True), - help="Path to additional modbus server config") -@click.option("-r", "--randomize", default=0, help="Randomize every `r` reads." - " 0=never, 1=always, " - "2=every-second-read, " - "and so on. " - "Applicable IR and DI.") -@click.pass_context -def run(ctx, modbus_server, modbus_framer, modbus_port, modbus_unit_id, - modbus_config, randomize): - """ - Run Reactive Modbus server exposing REST endpoint - for response manipulation. - """ - if not IS_PYTHON3: - click.secho("Pymodbus Server REPL not supported on python2", fg="read") - exit(1) - repl = ctx.obj.pop("repl") - web_app_config = ctx.obj - loop = asyncio.get_event_loop() - framer = DEFAULT_FRAMER.get(modbus_framer, ModbusSocketFramer) - if modbus_config: - with open(modbus_config) as f: - modbus_config = json.load(f) - else: - modbus_config = DEFUALT_CONFIG - modbus_config = modbus_config.get(modbus_server, {}) - if modbus_server != "serial": - modbus_port = int(modbus_port) - handler = modbus_config.pop("handler", "ModbusConnectedRequestHandler") - else: - handler = modbus_config.pop("handler", "ModbusSingleRequestHandler") - handler = DEFUALT_HANDLERS.get(handler.strip()) - - modbus_config["handler"] = handler - modbus_config["randomize"] = randomize - app = ReactiveServer.factory(modbus_server, framer, - modbus_port=modbus_port, - unit=modbus_unit_id, - loop=loop, - **web_app_config, **modbus_config) - try: - if repl: - loop.run_until_complete(app.run_async()) - - loop.run_until_complete(run_repl(app)) - loop.run_forever() - else: - app.run() - - except CANCELLED_ERROR: - print("Done!!!!!") - - -if __name__ == '__main__': - server() diff --git a/modbus/pymodbus/server/__init__.py b/modbus/pymodbus/server/__init__.py deleted file mode 100644 index 35350f6..0000000 --- a/modbus/pymodbus/server/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -''' -''' diff --git a/modbus/pymodbus/server/async_io.py b/modbus/pymodbus/server/async_io.py deleted file mode 100644 index adf53a7..0000000 --- a/modbus/pymodbus/server/async_io.py +++ /dev/null @@ -1,963 +0,0 @@ -""" -Implementation of a Threaded Modbus Server ------------------------------------------- - -""" -from binascii import b2a_hex -import serial -from serial_asyncio import create_serial_connection -import ssl -import traceback - -import asyncio -from pymodbus.compat import PYTHON_VERSION -from pymodbus.constants import Defaults -from pymodbus.utilities import hexlify_packets -from pymodbus.factory import ServerDecoder -from pymodbus.datastore import ModbusServerContext -from pymodbus.device import ModbusControlBlock -from pymodbus.device import ModbusDeviceIdentification -from pymodbus.transaction import * -from pymodbus.exceptions import NotImplementedException, NoSuchSlaveException -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.compat import socketserver, byte2int - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Protocol Handlers -# --------------------------------------------------------------------------- # - -class ModbusBaseRequestHandler(asyncio.BaseProtocol): - """ Implements modbus slave wire protocol - This uses the asyncio.Protocol to implement the client handler. - - When a connection is established, the asyncio.Protocol.connection_made - callback is called. This callback will setup the connection and - create and schedule an asyncio.Task and assign it to running_task. - - running_task will be canceled upon connection_lost event. - """ - def __init__(self, owner): - self.server = owner - self.running = False - self.receive_queue = asyncio.Queue() - self.handler_task = None # coroutine to be run on asyncio loop - - def _log_exception(self): - if isinstance(self, ModbusConnectedRequestHandler): - _logger.error( - "Handler for stream [%s:%s] has " - "been canceled" % self.client_address[:2]) - elif isinstance(self, ModbusSingleRequestHandler): - _logger.error( - "Handler for serial port has been cancelled") - else: - sock_name = self.protocol._sock.getsockname() - _logger.error("Handler for UDP socket [%s] has " - "been canceled" % sock_name[1]) - - def connection_made(self, transport): - """ - asyncio.BaseProtocol callback for socket establish - - For streamed protocols (TCP) this will also correspond to an - entire conversation; however for datagram protocols (UDP) this - corresponds to the socket being opened - """ - try: - sockname = transport.get_extra_info('sockname') - if sockname is not None: - _logger.debug( - "Socket [%s:%s] opened" % transport.get_extra_info( - 'sockname')[:2]) - else: - if hasattr(transport, 'serial'): - _logger.debug( - "Serial connection opened on port: {}".format( - transport.serial.port) - ) - self.transport = transport - self.running = True - self.framer = self.server.framer(self.server.decoder, client=None) - - # schedule the connection handler on the event loop - if PYTHON_VERSION >= (3, 7): - self.handler_task = asyncio.create_task(self.handle()) - else: - self.handler_task = asyncio.ensure_future(self.handle()) - except Exception as ex: # pragma: no cover - _logger.error("Datastore unable to fulfill request: " - "%s; %s", ex, traceback.format_exc()) - - def connection_lost(self, exc): - """ - asyncio.BaseProtocol callback for socket tear down - - For streamed protocols any break in the network connection will - be reported here; for datagram protocols, only a teardown of the - socket itself will result in this call. - """ - try: - self.handler_task.cancel() - if exc is None: - self._log_exception() - else: # pragma: no cover - if hasattr(self, "client_address"): # TCP connection - _logger.debug("Client Disconnection {} due " - "to {}".format(*self.client_address, exc)) - - self.running = False - - except Exception as ex: # pragma: no cover - _logger.error("Datastore unable to fulfill request: " - "%s; %s", ex, traceback.format_exc()) - - async def handle(self): - """Asyncio coroutine which represents a single conversation between - the modbus slave and master - - Once the client connection is established, the data chunks will be - fed to this coroutine via the asyncio.Queue object which is fed by - the ModbusBaseRequestHandler class's callback Future. - - This callback future gets data from either - asyncio.DatagramProtocol.datagram_received or - from asyncio.BaseProtocol.data_received. - - This function will execute without blocking in the while-loop and - yield to the asyncio event loop when the frame is exhausted. - As a result, multiple clients can be interleaved without any - interference between them. - - For ModbusConnectedRequestHandler, each connection will be given an - instance of the handle() coroutine and this instance will be put in the - active_connections dict. Calling server_close will individually cancel - each running handle() task. - - For ModbusDisconnectedRequestHandler, a single handle() coroutine will - be started and maintained. Calling server_close will cancel that task. - - """ - reset_frame = False - while self.running: - try: - units = self.server.context.slaves() - # this is an asyncio.Queue await, it will never fail - data = await self._recv_() - if isinstance(data, tuple): - # addr is populated when talking over UDP - data, *addr = data - else: - addr = (None,) # empty tuple - - if not isinstance(units, (list, tuple)): - units = [units] - # if broadcast is enabled make sure to - # process requests to address 0 - if self.server.broadcast_enable: # pragma: no cover - if 0 not in units: - units.append(0) - - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('Handling data: ' + hexlify_packets(data)) - - single = self.server.context.single - self.framer.processIncomingPacket( - data=data, callback=lambda x: self.execute(x, *addr), - unit=units, single=single) - - except asyncio.CancelledError: - # catch and ignore cancelation errors - self._log_exception() - except Exception as e: - # force TCP socket termination as processIncomingPacket - # should handle applicaiton layer errors - # for UDP sockets, simply reset the frame - if isinstance(self, ModbusConnectedRequestHandler): - client_addr = self.client_address[:2] - _logger.error("Unknown exception '{}' on stream {} " - "forcing disconnect".format(e, client_addr)) - self.transport.close() - else: - _logger.error("Unknown error occurred %s" % e) - reset_frame = True # graceful recovery - finally: - if reset_frame: - self.framer.resetFrame() - reset_frame = False - - def execute(self, request, *addr): - """ The callback to call with the resulting message - - :param request: The decoded request message - """ - broadcast = False - try: - if self.server.broadcast_enable and request.unit_id == 0: - broadcast = True - # if broadcasting then execute on all slave contexts, - # note response will be ignored - for unit_id in self.server.context.slaves(): - response = request.execute(self.server.context[unit_id]) - else: - context = self.server.context[request.unit_id] - response = request.execute(context) - except NoSuchSlaveException as ex: - _logger.error("requested slave does " - "not exist: %s" % request.unit_id) - if self.server.ignore_missing_slaves: - return # the client will simply timeout waiting for a response - response = request.doException(merror.GatewayNoResponse) - except Exception as ex: - _logger.error("Datastore unable to fulfill request: " - "%s; %s", ex, traceback.format_exc()) - response = request.doException(merror.SlaveFailure) - # no response when broadcasting - if not broadcast: - response.transaction_id = request.transaction_id - response.unit_id = request.unit_id - skip_encoding = False - if self.server.response_manipulator: - response, skip_encoding = self.server.response_manipulator(response) - self.send(response, *addr, skip_encoding=skip_encoding) - - def send(self, message, *addr, **kwargs): - def __send(msg, *addr): - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('send: [%s]- %s' % (message, b2a_hex(msg))) - if addr == (None,): - self._send_(msg) - else: - self._send_(msg, *addr) - skip_encoding = kwargs.get("skip_encoding", False) - if skip_encoding: - __send(message, *addr) - elif message.should_respond: - # self.server.control.Counter.BusMessage += 1 - pdu = self.framer.buildPacket(message) - __send(pdu, *addr) - else: - _logger.debug("Skipping sending response!!") - - # ----------------------------------------------------------------------- # - # Derived class implementations - # ----------------------------------------------------------------------- # - - def _send_(self, data): # pragma: no cover - """ Send a request (string) to the network - - :param message: The unencoded modbus response - """ - raise NotImplementedException("Method not implemented " - "by derived class") - - async def _recv_(self): # pragma: no cover - """ Receive data from the network - - :return: - """ - raise NotImplementedException("Method not implemented " - "by derived class") - - -class ModbusConnectedRequestHandler(ModbusBaseRequestHandler,asyncio.Protocol): - """ Implements the modbus server protocol - - This uses asyncio.Protocol to implement - the client handler for a connected protocol (TCP). - """ - - def connection_made(self, transport): - """ asyncio.BaseProtocol: Called when a connection is made. """ - super().connection_made(transport) - - self.client_address = transport.get_extra_info('peername') - self.server.active_connections[self.client_address] = self - _logger.debug("TCP client connection established " - "[%s:%s]" % self.client_address[:2]) - - def connection_lost(self, exc): - """ - asyncio.BaseProtocol: Called when the connection is lost or closed. - """ - super().connection_lost(exc) - client_addr = self.client_address[:2] - _logger.debug("TCP client disconnected [%s:%s]" % client_addr) - if self.client_address in self.server.active_connections: - self.server.active_connections.pop(self.client_address) - - def data_received(self, data): - """ - asyncio.Protocol: (TCP) Called when some data is received. - data is a non-empty bytes object containing the incoming data. - """ - self.receive_queue.put_nowait(data) - - async def _recv_(self): - return await self.receive_queue.get() - - def _send_(self, data): - """ tcp send """ - self.transport.write(data) - - -class ModbusDisconnectedRequestHandler(ModbusBaseRequestHandler, - asyncio.DatagramProtocol): - """ Implements the modbus server protocol - - This uses the socketserver.BaseRequestHandler to implement - the client handler for a disconnected protocol (UDP). The - only difference is that we have to specify who to send the - resulting packet data to. - """ - def __init__(self,owner): - super().__init__(owner) - _future = asyncio.get_event_loop().create_future() - self.server.on_connection_terminated = _future - - def connection_lost(self,exc): - super().connection_lost(exc) - self.server.on_connection_terminated.set_result(True) - - def datagram_received(self,data, addr): - """ - asyncio.DatagramProtocol: Called when a datagram is received. - data is a bytes object containing the incoming data. addr - is the address of the peer sending the data; the exact - format depends on the transport. - """ - self.receive_queue.put_nowait((data, addr)) - - def error_received(self,exc): # pragma: no cover - """ - asyncio.DatagramProtocol: Called when a previous send - or receive operation raises an OSError. exc is the - OSError instance. - - This method is called in rare conditions, - when the transport (e.g. UDP) detects that a datagram could - not be delivered to its recipient. In many conditions - though, undeliverable datagrams will be silently dropped. - """ - _logger.error("datagram connection error [%s]" % exc) - - async def _recv_(self): - return await self.receive_queue.get() - - def _send_(self, data, addr): - self.transport.sendto(data, addr=addr) - - -class ModbusServerFactory: - """ - Builder class for a modbus server - - This also holds the server datastore so that it is persisted between connections - """ - - def __init__(self, store, framer=None, identity=None, **kwargs): - import warnings - warnings.warn("deprecated API for asyncio. ServerFactory's are a " - "twisted construct and don't have an equivalent in " - "asyncio", - DeprecationWarning) - - -class ModbusSingleRequestHandler(ModbusBaseRequestHandler, asyncio.Protocol): - """ Implements the modbus server protocol - This uses asyncio.Protocol to implement - the client handler for a serial connection. - """ - def connection_made(self, transport): - super().connection_made(transport) - - _logger.debug("Serial connection established") - - def connection_lost(self, exc): - super().connection_lost(exc) - _logger.debug("Serial conection lost") - if hasattr(self.server, 'on_connection_lost'): - self.server.on_connection_lost() - - def data_received(self, data): - self.receive_queue.put_nowait(data) - - async def _recv_(self): - return await self.receive_queue.get() - - def _send_(self, data): - if self.transport is not None: - self.transport.write(data) - -# --------------------------------------------------------------------------- # -# Server Implementations -# --------------------------------------------------------------------------- # - -class ModbusTcpServer: - """ - A modbus threaded tcp socket server - - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - def __init__(self, - context, - framer=None, - identity=None, - address=None, - handler=None, - allow_reuse_address=False, - allow_reuse_port=False, - defer_start=False, - backlog=20, - loop=None, - **kwargs): - """ Overloaded initializer for the socket server - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param handler: A handler for each client session; default is - ModbusConnectedRequestHandler. The handler class - receives connection create/teardown events - :param allow_reuse_address: Whether the server will allow the - reuse of an address. - :param allow_reuse_port: Whether the server will allow the - reuse of a port. - :param backlog: is the maximum number of queued connections - passed to listen(). Defaults to 20, increase if many - connections are being made and broken to your Modbus slave - :param loop: optional asyncio event loop to run in. Will default to - asyncio.get_event_loop() supplied value if None. - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - :param response_manipulator: Callback method for manipulating the - response - """ - self.active_connections = {} - self.loop = loop or asyncio.get_event_loop() - self.allow_reuse_address = allow_reuse_address - self.decoder = ServerDecoder() - self.framer = framer or ModbusSocketFramer - self.context = context or ModbusServerContext() - self.control = ModbusControlBlock() - self.address = address or ("", Defaults.Port) - self.handler = handler or ModbusConnectedRequestHandler - self.handler.server = self - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.get('broadcast_enable', - Defaults.broadcast_enable) - self.response_manipulator = kwargs.get("response_manipulator", None) - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - # asyncio future that will be done once server has started - self.serving = self.loop.create_future() - # constructors cannot be declared async, so we have to - # defer the initialization of the server - self.server = None - if PYTHON_VERSION >= (3, 7): - # start_serving is new in version 3.7 - self.server_factory = self.loop.create_server( - lambda: self.handler(self), - *self.address, - reuse_address=allow_reuse_address, - reuse_port=allow_reuse_port, - backlog=backlog, - start_serving=not defer_start - ) - else: - self.server_factory = self.loop.create_server( - lambda: self.handler(self), - *self.address, - reuse_address=allow_reuse_address, - reuse_port=allow_reuse_port, - backlog=backlog - ) - - async def serve_forever(self): - if self.server is None: - self.server = await self.server_factory - self.serving.set_result(True) - await self.server.serve_forever() - else: - raise RuntimeError("Can't call serve_forever on " - "an already running server object") - - def server_close(self): - for k, v in self.active_connections.items(): - _logger.warning("aborting active session {}".format(k)) - v.handler_task.cancel() - self.active_connections = {} - self.server.close() - - -class ModbusTlsServer(ModbusTcpServer): - """ - A modbus threaded tls socket server - - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - def __init__(self, - context, - framer=None, - identity=None, - address=None, - sslctx=None, - certfile=None, - keyfile=None, - handler=None, - allow_reuse_address=False, - allow_reuse_port=False, - defer_start=False, - backlog=20, - loop=None, - **kwargs): - """ Overloaded initializer for the socket server - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param sslctx: The SSLContext to use for TLS (default None and auto - create) - :param certfile: The cert file path for TLS (used if sslctx is None) - :param keyfile: The key file path for TLS (used if sslctx is None) - :param handler: A handler for each client session; default is - ModbusConnectedRequestHandler. The handler class - receives connection create/teardown events - :param allow_reuse_address: Whether the server will allow the - reuse of an address. - :param allow_reuse_port: Whether the server will allow the - reuse of a port. - :param backlog: is the maximum number of queued connections - passed to listen(). Defaults to 20, increase if many - connections are being made and broken to your Modbus slave - :param loop: optional asyncio event loop to run in. Will default to - asyncio.get_event_loop() supplied value if None. - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - :param response_manipulator: Callback method for - manipulating the response - """ - self.active_connections = {} - self.loop = loop or asyncio.get_event_loop() - self.allow_reuse_address = allow_reuse_address - self.decoder = ServerDecoder() - self.framer = framer or ModbusTlsFramer - self.context = context or ModbusServerContext() - self.control = ModbusControlBlock() - self.address = address or ("", Defaults.Port) - self.handler = handler or ModbusConnectedRequestHandler - self.handler.server = self - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.get('broadcast_enable', - Defaults.broadcast_enable) - self.response_manipulator = kwargs.get("response_manipulator", None) - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - self.sslctx = sslctx - if self.sslctx is None: - self.sslctx = ssl.create_default_context() - self.sslctx.load_cert_chain(certfile=certfile, keyfile=keyfile) - # According to MODBUS/TCP Security Protocol Specification, it is - # TLSv2 at least - self.sslctx.options |= ssl.OP_NO_TLSv1_1 - self.sslctx.options |= ssl.OP_NO_TLSv1 - self.sslctx.options |= ssl.OP_NO_SSLv3 - self.sslctx.options |= ssl.OP_NO_SSLv2 - self.sslctx.verify_mode = ssl.CERT_OPTIONAL - self.sslctx.check_hostname = False - # asyncio future that will be done once server has started - self.serving = self.loop.create_future() - # constructors cannot be declared async, so we have to - # defer the initialization of the server - self.server = None - if PYTHON_VERSION >= (3, 7): - # start_serving is new in version 3.7 - self.server_factory = self.loop.create_server( - lambda: self.handler(self), - *self.address, - ssl=self.sslctx, - reuse_address=allow_reuse_address, - reuse_port=allow_reuse_port, - backlog=backlog, - start_serving=not defer_start - ) - else: - self.server_factory = self.loop.create_server( - lambda: self.handler(self), - *self.address, - ssl=self.sslctx, - reuse_address=allow_reuse_address, - reuse_port=allow_reuse_port, - backlog=backlog - ) - - -class ModbusUdpServer: - """ - A modbus threaded udp socket server - - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - def __init__(self, context, framer=None, identity=None, address=None, - handler=None, allow_reuse_address=False, - allow_reuse_port=False, - defer_start=False, - backlog=20, - loop=None, - **kwargs): - """ Overloaded initializer for the socket server - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param handler: A handler for each client session; default is - ModbusDisonnectedRequestHandler - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - :param response_manipulator: Callback method for - manipulating the response - """ - self.loop = loop or asyncio.get_event_loop() - self.decoder = ServerDecoder() - self.framer = framer or ModbusSocketFramer - self.context = context or ModbusServerContext() - self.control = ModbusControlBlock() - self.address = address or ("", Defaults.Port) - self.handler = handler or ModbusDisconnectedRequestHandler - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.get('broadcast_enable', - Defaults.broadcast_enable) - self.response_manipulator = kwargs.get("response_manipulator", None) - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - self.protocol = None - self.endpoint = None - self.on_connection_terminated = None - self.stop_serving = self.loop.create_future() - # asyncio future that will be done once server has started - self.serving = self.loop.create_future() - self.server_factory = self.loop.create_datagram_endpoint( - lambda: self.handler(self), - local_addr=self.address, - reuse_address=allow_reuse_address, - reuse_port=allow_reuse_port, - allow_broadcast=True - ) - - async def serve_forever(self): - if self.protocol is None: - self.protocol, self.endpoint = await self.server_factory - self.serving.set_result(True) - await self.stop_serving - else: - raise RuntimeError("Can't call serve_forever on an " - "already running server object") - - def server_close(self): - self.stop_serving.set_result(True) - if self.endpoint.handler_task is not None: - self.endpoint.handler_task.cancel() - - self.protocol.close() - - -class ModbusSerialServer(object): - """ - A modbus threaded serial socket server - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - handler = None - - def __init__(self, context, framer=None, identity=None, **kwargs): # pragma: no cover - """ Overloaded initializer for the socket server - If the identity structure is not passed in, the ModbusControlBlock - uses its own empty structure. - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param port: The serial port to attach to - :param stopbits: The number of stop bits to use - :param bytesize: The bytesize of the serial messages - :param parity: Which kind of parity to use - :param baudrate: The baud rate to use for the serial device - :param timeout: The timeout to use for the serial device - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - :param autoreonnect: True to enable automatic reconnection, - False otherwise - :param reconnect_delay: reconnect delay in seconds - :param response_manipulator: Callback method for - manipulating the response - """ - self.device = kwargs.get('port', 0) - self.stopbits = kwargs.get('stopbits', Defaults.Stopbits) - self.bytesize = kwargs.get('bytesize', Defaults.Bytesize) - self.parity = kwargs.get('parity', Defaults.Parity) - self.baudrate = kwargs.get('baudrate', Defaults.Baudrate) - self.timeout = kwargs.get('timeout', Defaults.Timeout) - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.get('broadcast_enable', - Defaults.broadcast_enable) - self.auto_reconnect = kwargs.get('auto_reconnect', False) - self.reconnect_delay = kwargs.get('reconnect_delay', 2) - self.reconnecting_task = None - - self.handler = kwargs.get("handler") or ModbusSingleRequestHandler - self.framer = framer or ModbusRtuFramer - self.decoder = ServerDecoder() - self.context = context or ModbusServerContext() - self.response_manipulator = kwargs.get("response_manipulator", None) - self.control = ModbusControlBlock() - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - self.protocol = None - self.transport = None - - async def start(self): - await self._connect() - - def _protocol_factory(self): - return self.handler(self) - - async def _delayed_connect(self): - await asyncio.sleep(self.reconnect_delay) - await self._connect() - - async def _connect(self): - if self.reconnecting_task is not None: - self.reconnecting_task = None - - try: - self.transport, self.protocol = await create_serial_connection( - asyncio.get_event_loop(), - self._protocol_factory, - self.device, - baudrate=self.baudrate, - bytesize=self.bytesize, - parity=self.parity, - stopbits=self.stopbits, - timeout=self.timeout - ) - except serial.serialutil.SerialException as e: - _logger.debug("Failed to open serial port: {}".format(self.device)) - if not self.auto_reconnect: - raise e - - self._check_reconnect() - - except Exception as e: - _logger.debug("Exception while create - {}".format(e)) - - def on_connection_lost(self): - if self.transport is not None: - self.transport.close() - self.transport = None - self.protocol = None - - self._check_reconnect() - - def _check_reconnect(self): - _logger.debug("checkking autoreconnect {} {}".format( - self.auto_reconnect, self.reconnecting_task)) - if self.auto_reconnect and (self.reconnecting_task is None): - _logger.debug("Scheduling serial connection reconnect") - loop = asyncio.get_event_loop() - self.reconnecting_task = loop.create_task(self._delayed_connect()) - - async def serve_forever(self): - while True: - await asyncio.sleep(360) - - -# --------------------------------------------------------------------------- # -# Creation Factories -# --------------------------------------------------------------------------- # -async def StartTcpServer(context=None, identity=None, address=None, - custom_functions=[], defer_start=True, **kwargs): - """ A factory to start and run a tcp modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param defer_start: if set, a coroutine which can be started and stopped - will be returned. Otherwise, the server will be immediately spun - up without the ability to shut it off from within the asyncio loop - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - :return: an initialized but inactive server object coroutine - """ - framer = kwargs.pop("framer", ModbusSocketFramer) - server = ModbusTcpServer(context, framer, identity, address, **kwargs) - - for f in custom_functions: - server.decoder.register(f) # pragma: no cover - - if not defer_start: - await server.serve_forever() - - return server - - -async def StartTlsServer(context=None, identity=None, address=None, - sslctx=None, - certfile=None, keyfile=None, - allow_reuse_address=False, - allow_reuse_port=False, - custom_functions=[], - defer_start=True, **kwargs): - """ A factory to start and run a tls modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param sslctx: The SSLContext to use for TLS (default None and auto create) - :param certfile: The cert file path for TLS (used if sslctx is None) - :param keyfile: The key file path for TLS (used if sslctx is None) - :param allow_reuse_address: Whether the server will allow the reuse of an - address. - :param allow_reuse_port: Whether the server will allow the reuse of a port. - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param defer_start: if set, a coroutine which can be started and stopped - will be returned. Otherwise, the server will be immediately spun - up without the ability to shut it off from within the asyncio loop - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - :return: an initialized but inactive server object coroutine - """ - framer = kwargs.pop("framer", ModbusTlsFramer) - server = ModbusTlsServer(context, framer, identity, address, sslctx, - certfile, keyfile, - allow_reuse_address=allow_reuse_address, - allow_reuse_port=allow_reuse_port, **kwargs) - - for f in custom_functions: - server.decoder.register(f) # pragma: no cover - - if not defer_start: - await server.serve_forever() - - return server - - -async def StartUdpServer(context=None, identity=None, address=None, - custom_functions=[], defer_start=True, **kwargs): - """ A factory to start and run a udp modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param framer: The framer to operate with (default ModbusSocketFramer) - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - """ - framer = kwargs.pop('framer', ModbusSocketFramer) - server = ModbusUdpServer(context, framer, identity, address, **kwargs) - - for f in custom_functions: - server.decoder.register(f) # pragma: no cover - - if not defer_start: - await server.serve_forever() # pragma: no cover - - return server - - -async def StartSerialServer(context=None, identity=None, - custom_functions=[], **kwargs): # pragma: no cover - """ A factory to start and run a serial modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param framer: The framer to operate with (default ModbusAsciiFramer) - :param port: The serial port to attach to - :param stopbits: The number of stop bits to use - :param bytesize: The bytesize of the serial messages - :param parity: Which kind of parity to use - :param baudrate: The baud rate to use for the serial device - :param timeout: The timeout to use for the serial device - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - """ - framer = kwargs.pop('framer', ModbusAsciiFramer) - server = ModbusSerialServer(context, framer, identity, **kwargs) - for f in custom_functions: - server.decoder.register(f) - await server.start() - await server.serve_forever() - - -def StopServer(): - """ - Helper method to stop Async Server - """ - import warnings - warnings.warn("deprecated API for asyncio. Call server_close() on " - "server object returned by StartXxxServer", - DeprecationWarning) - - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = [ - - "StartTcpServer", "StartTlsServer", "StartUdpServer", "StartSerialServer" - -] diff --git a/modbus/pymodbus/server/asynchronous.py b/modbus/pymodbus/server/asynchronous.py deleted file mode 100644 index 6ac991a..0000000 --- a/modbus/pymodbus/server/asynchronous.py +++ /dev/null @@ -1,363 +0,0 @@ -""" -Implementation of a Twisted Modbus Server ------------------------------------------- - -""" -from binascii import b2a_hex -from twisted.internet import protocol -from twisted.internet.protocol import ServerFactory -from twisted.internet import reactor - -from pymodbus.constants import Defaults -from pymodbus.utilities import hexlify_packets -from pymodbus.factory import ServerDecoder -from pymodbus.datastore import ModbusServerContext -from pymodbus.device import ModbusControlBlock -from pymodbus.device import ModbusAccessControl -from pymodbus.device import ModbusDeviceIdentification -from pymodbus.exceptions import NoSuchSlaveException -from pymodbus.transaction import (ModbusSocketFramer, - ModbusRtuFramer, - ModbusAsciiFramer, - ModbusBinaryFramer) -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.internal.ptwisted import InstallManagementConsole -from pymodbus.compat import IS_PYTHON3 - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Modbus TCP Server -# --------------------------------------------------------------------------- # -class ModbusTcpProtocol(protocol.Protocol): - """ Implements a modbus server in twisted """ - - def connectionMade(self): - """ Callback for when a client connects - - ..note:: since the protocol factory cannot be accessed from the - protocol __init__, the client connection made is essentially - our __init__ method. - """ - _logger.debug("Client Connected [%s]" % self.transport.getHost()) - self.framer = self.factory.framer(decoder=self.factory.decoder, - client=None) - - def connectionLost(self, reason): - """ Callback for when a client disconnects - - :param reason: The client's reason for disconnecting - """ - _logger.debug("Client Disconnected: %s" % reason) - - def dataReceived(self, data): - """ Callback when we receive any data - - :param data: The data sent by the client - """ - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('Data Received: ' + hexlify_packets(data)) - if not self.factory.control.ListenOnly: - units = self.factory.store.slaves() - single = self.factory.store.single - self.framer.processIncomingPacket(data, self._execute, - single=single, - unit=units) - - def _execute(self, request): - """ Executes the request and returns the result - - :param request: The decoded request message - """ - try: - context = self.factory.store[request.unit_id] - response = request.execute(context) - except NoSuchSlaveException as ex: - _logger.debug("requested slave does not exist: %s" % request.unit_id ) - if self.factory.ignore_missing_slaves: - return # the client will simply timeout waiting for a response - response = request.doException(merror.GatewayNoResponse) - except Exception as ex: - _logger.debug("Datastore unable to fulfill request: %s" % ex) - response = request.doException(merror.SlaveFailure) - - response.transaction_id = request.transaction_id - response.unit_id = request.unit_id - self._send(response) - - def _send(self, message): - """ Send a request (string) to the network - - :param message: The unencoded modbus response - """ - if message.should_respond: - self.factory.control.Counter.BusMessage += 1 - pdu = self.framer.buildPacket(message) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('send: %s' % b2a_hex(pdu)) - return self.transport.write(pdu) - - -class ModbusServerFactory(ServerFactory): - """ - Builder class for a modbus server - - This also holds the server datastore so that it is - persisted between connections - """ - - protocol = ModbusTcpProtocol - - def __init__(self, store, framer=None, identity=None, **kwargs): - """ Overloaded initializer for the modbus factory - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param store: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param ignore_missing_slaves: True to not send errors on a request to a missing slave - """ - self.decoder = ServerDecoder() - self.framer = framer or ModbusSocketFramer - self.store = store or ModbusServerContext() - self.control = ModbusControlBlock() - self.access = ModbusAccessControl() - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', Defaults.IgnoreMissingSlaves) - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - -# --------------------------------------------------------------------------- # -# Modbus UDP Server -# --------------------------------------------------------------------------- # -class ModbusUdpProtocol(protocol.DatagramProtocol): - """ Implements a modbus udp server in twisted """ - - def __init__(self, store, framer=None, identity=None, **kwargs): - """ Overloaded initializer for the modbus factory - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param store: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param ignore_missing_slaves: True to not send errors on a request to - a missing slave - """ - framer = framer or ModbusSocketFramer - self.decoder = ServerDecoder() - self.framer = framer(self.decoder) - self.store = store or ModbusServerContext() - self.control = ModbusControlBlock() - self.access = ModbusAccessControl() - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - def datagramReceived(self, data, addr): - """ Callback when we receive any data - - :param data: The data sent by the client - """ - _logger.debug("Client Connected [%s]" % addr) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug("Datagram Received: "+ hexlify_packets(data)) - if not self.control.ListenOnly: - continuation = lambda request: self._execute(request, addr) - self.framer.processIncomingPacket(data, continuation) - - def _execute(self, request, addr): - """ Executes the request and returns the result - - :param request: The decoded request message - """ - try: - context = self.store[request.unit_id] - response = request.execute(context) - except NoSuchSlaveException as ex: - _logger.debug("requested slave does not exist: " - "%s" % request.unit_id ) - if self.ignore_missing_slaves: - return # the client will simply timeout waiting for a response - response = request.doException(merror.GatewayNoResponse) - except Exception as ex: - _logger.debug("Datastore unable to fulfill request: %s" % ex) - response = request.doException(merror.SlaveFailure) - #self.framer.populateResult(response) - response.transaction_id = request.transaction_id - response.unit_id = request.unit_id - self._send(response, addr) - - def _send(self, message, addr): - """ Send a request (string) to the network - - :param message: The unencoded modbus response - :param addr: The (host, port) to send the message to - """ - self.control.Counter.BusMessage += 1 - pdu = self.framer.buildPacket(message) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('send: %s' % b2a_hex(pdu)) - return self.transport.write(pdu, addr) - - -# --------------------------------------------------------------------------- # -# Starting Factories -# --------------------------------------------------------------------------- # -def _is_main_thread(): - import threading - - if IS_PYTHON3: - if threading.current_thread() != threading.main_thread(): - _logger.debug("Running in spawned thread") - return False - else: - if not isinstance(threading.current_thread(), threading._MainThread): - _logger.debug("Running in spawned thread") - return False - _logger.debug("Running in Main thread") - return True - - -def StartTcpServer(context, identity=None, address=None, - console=False, defer_reactor_run=False, custom_functions=[], - **kwargs): - """ - Helper method to start the Modbus Async TCP server - - :param context: The server data context - :param identify: The server identity to use (default empty) - :param address: An optional (interface, port) to bind to. - :param console: A flag indicating if you want the debug console - :param ignore_missing_slaves: True to not send errors on a request \ - to a missing slave - :param defer_reactor_run: True/False defer running reactor.run() as part \ - of starting server, to be explictly started by the user - :param custom_functions: An optional list of custom function classes - supported by server instance. - """ - from twisted.internet import reactor - - address = address or ("", Defaults.Port) - framer = kwargs.pop("framer", ModbusSocketFramer) - factory = ModbusServerFactory(context, framer, identity, **kwargs) - for f in custom_functions: - factory.decoder.register(f) - if console: - InstallManagementConsole({'factory': factory}) - - _logger.info("Starting Modbus TCP Server on %s:%s" % address) - reactor.listenTCP(address[1], factory, interface=address[0]) - if not defer_reactor_run: - reactor.run(installSignalHandlers=_is_main_thread()) - - -def StartUdpServer(context, identity=None, address=None, - defer_reactor_run=False, custom_functions=[], **kwargs): - """ - Helper method to start the Modbus Async Udp server - - :param context: The server data context - :param identify: The server identity to use (default empty) - :param address: An optional (interface, port) to bind to. - :param ignore_missing_slaves: True to not send errors on a request \ - to a missing slave - :param defer_reactor_run: True/False defer running reactor.run() as part \ - of starting server, to be explictly started by the user - :param custom_functions: An optional list of custom function classes - supported by server instance. - """ - from twisted.internet import reactor - - address = address or ("", Defaults.Port) - framer = kwargs.pop("framer", ModbusSocketFramer) - server = ModbusUdpProtocol(context, framer, identity, **kwargs) - for f in custom_functions: - server.decoder.register(f) - - _logger.info("Starting Modbus UDP Server on %s:%s" % address) - reactor.listenUDP(address[1], server, interface=address[0]) - if not defer_reactor_run: - reactor.run(installSignalHandlers=_is_main_thread()) - - -def StartSerialServer(context, identity=None, framer=ModbusAsciiFramer, - defer_reactor_run=False, custom_functions=[], **kwargs): - """ - Helper method to start the Modbus Async Serial server - - :param context: The server data context - :param identify: The server identity to use (default empty) - :param framer: The framer to use (default ModbusAsciiFramer) - :param port: The serial port to attach to - :param baudrate: The baud rate to use for the serial device - :param console: A flag indicating if you want the debug console - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - :param defer_reactor_run: True/False defer running reactor.run() as part - of starting server, to be explictly started by the user - :param custom_functions: An optional list of custom function classes - supported by server instance. - - """ - from twisted.internet import reactor - from twisted.internet.serialport import SerialPort - - port = kwargs.get('port', '/dev/ttyS0') - baudrate = kwargs.get('baudrate', Defaults.Baudrate) - console = kwargs.get('console', False) - bytesize = kwargs.get("bytesize", Defaults.Bytesize) - stopbits = kwargs.get("stopbits", Defaults.Stopbits) - parity = kwargs.get("parity", Defaults.Parity) - timeout = kwargs.get("timeout", 0) - xonxoff = kwargs.get("xonxoff", 0) - rtscts = kwargs.get("rtscts", 0) - - _logger.info("Starting Modbus Serial Server on %s" % port) - factory = ModbusServerFactory(context, framer, identity, **kwargs) - for f in custom_functions: - factory.decoder.register(f) - if console: - InstallManagementConsole({'factory': factory}) - if console: - InstallManagementConsole({'factory': factory}) - - protocol = factory.buildProtocol(None) - SerialPort.getHost = lambda self: port # hack for logging - SerialPort(protocol, port, reactor, baudrate=baudrate, parity=parity, - stopbits=stopbits, timeout=timeout, xonxoff=xonxoff, - rtscts=rtscts, bytesize=bytesize) - if not defer_reactor_run: - reactor.run(installSignalHandlers=_is_main_thread()) - - -def StopServer(): - """ - Helper method to stop Async Server - """ - from twisted.internet import reactor - if _is_main_thread(): - reactor.stop() - _logger.debug("Stopping server from main thread") - else: - reactor.callFromThread(reactor.stop) - _logger.debug("Stopping Server from another thread") - - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # -__all__ = [ - "StartTcpServer", "StartUdpServer", "StartSerialServer", "StopServer" -] diff --git a/modbus/pymodbus/server/reactive/__init__.py b/modbus/pymodbus/server/reactive/__init__.py deleted file mode 100644 index bc4e394..0000000 --- a/modbus/pymodbus/server/reactive/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" diff --git a/modbus/pymodbus/server/reactive/default_config.json b/modbus/pymodbus/server/reactive/default_config.json index bf507f6..e69de29 100644 --- a/modbus/pymodbus/server/reactive/default_config.json +++ b/modbus/pymodbus/server/reactive/default_config.json @@ -1,32 +0,0 @@ -{ - "tcp": { - "handler": "ModbusConnectedRequestHandler", - "allow_reuse_address": true, - "allow_reuse_port": true, - "backlog": 20, - "ignore_missing_slaves": false - }, - "rtu": { - "handler": "ModbusSingleRequestHandler", - "stopbits": 1, - "bytesize": 8, - "parity": "N", - "baudrate": 9600, - "timeout": 3, - "auto_reconnect": false, - "reconnect_delay": 2 - }, - "tls": { - "handler": "ModbusConnectedRequestHandler", - "certfile": null, - "keyfile": null, - "allow_reuse_address": true, - "allow_reuse_port": true, - "backlog": 20, - "ignore_missing_slaves": false - }, - "udp": { - "handler": "ModbusDisonnectedRequestHandler", - "ignore_missing_slaves": false - } -} diff --git a/modbus/pymodbus/server/reactive/default_config.py b/modbus/pymodbus/server/reactive/default_config.py deleted file mode 100644 index ad274e8..0000000 --- a/modbus/pymodbus/server/reactive/default_config.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" - -DEFUALT_CONFIG = { - "tcp": { - "handler": "ModbusConnectedRequestHandler", - "allow_reuse_address": True, - "allow_reuse_port": True, - "backlog": 20, - "ignore_missing_slaves": False - }, - "serial": { - "handler": "ModbusSingleRequestHandler", - "stopbits": 1, - "bytesize": 8, - "parity": "N", - "baudrate": 9600, - "timeout": 3, - "auto_reconnect": False, - "reconnect_delay": 2 - }, - "tls": { - "handler": "ModbusConnectedRequestHandler", - "certfile": None, - "keyfile": None, - "allow_reuse_address": True, - "allow_reuse_port": True, - "backlog": 20, - "ignore_missing_slaves": False - }, - "udp": { - "handler": "ModbusDisonnectedRequestHandler", - "ignore_missing_slaves": False - } -} diff --git a/modbus/pymodbus/server/reactive/main.py b/modbus/pymodbus/server/reactive/main.py deleted file mode 100644 index cf14225..0000000 --- a/modbus/pymodbus/server/reactive/main.py +++ /dev/null @@ -1,372 +0,0 @@ -""" -Copyright (c) 2020 by RiptideIO -All rights reserved. -""" -import os -import asyncio -import time -import random -import logging -from pymodbus.version import version as pymodbus_version -from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION -from pymodbus.pdu import ExceptionResponse, ModbusExceptions -from pymodbus.datastore.store import (ModbusSparseDataBlock, - ModbusSequentialDataBlock) -from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext -from pymodbus.device import ModbusDeviceIdentification - -if not IS_PYTHON3 or PYTHON_VERSION < (3, 6): - print(f"You are running {PYTHON_VERSION}." - "Reactive server requires python3.6 or above") - exit() - - -try: - from aiohttp import web -except ImportError as e: - print("Reactive server requires aiohttp. " - "Please install with 'pip install aiohttp' and try again.") - exit(1) - -from pymodbus.server.async_io import (ModbusTcpServer, - ModbusTlsServer, - ModbusSerialServer, - ModbusUdpServer, - ModbusSingleRequestHandler, - ModbusConnectedRequestHandler, - ModbusDisconnectedRequestHandler) -from pymodbus.transaction import (ModbusRtuFramer, - ModbusSocketFramer, - ModbusTlsFramer, - ModbusAsciiFramer, - ModbusBinaryFramer) -logger = logging.getLogger(__name__) - -SERVER_MAPPER = { - "tcp": ModbusTcpServer, - "serial": ModbusSerialServer, - "udp": ModbusUdpServer, - "tls": ModbusTlsServer -} - -DEFAULT_FRAMER = { - "tcp": ModbusSocketFramer, - "rtu": ModbusRtuFramer, - "tls": ModbusTlsFramer, - "udp": ModbusSocketFramer, - "ascii": ModbusAsciiFramer, - "binary": ModbusBinaryFramer -} - -DEFAULT_MANIPULATOR = { - "response_type": "normal", # normal, error, delayed, empty - "delay_by": 0, - "error_code": ModbusExceptions.IllegalAddress, - "clear_after": 5 # request count - -} -DEFUALT_HANDLERS = { - "ModbusSingleRequestHandler": ModbusSingleRequestHandler, - "ModbusConnectedRequestHandler": ModbusConnectedRequestHandler, - "ModbusDisconnectedRequestHandler": ModbusDisconnectedRequestHandler -} -DEFAULT_MODBUS_MAP = {"start_offset": 0, - "count": 100, - "value": 0, "sparse": False} -DEFAULT_DATA_BLOCK = { - "co": DEFAULT_MODBUS_MAP, - "di": DEFAULT_MODBUS_MAP, - "ir": DEFAULT_MODBUS_MAP, - "hr": DEFAULT_MODBUS_MAP - -} - -HINT = """ -Reactive Modbus Server started. -{} - -=========================================================================== -Example Usage: -curl -X POST http://{}:{} -d '{{"response_type": "error", "error_code": 4}}' -=========================================================================== -""" - - -class ReactiveServer: - """ - Modbus Asynchronous Server which can manipulate the response dynamically. - Useful for testing - """ - def __init__(self, host, port, modbus_server, loop=None): - self._web_app = web.Application() - self._runner = web.AppRunner(self._web_app) - self._host = host - self._port = int(port) - self._modbus_server = modbus_server - self._loop = loop - self._add_routes() - self._counter = 0 - self._modbus_server.response_manipulator = self.manipulate_response - self._manipulator_config = dict(**DEFAULT_MANIPULATOR) - self._web_app.on_startup.append(self.start_modbus_server) - self._web_app.on_shutdown.append(self.stop_modbus_server) - - @property - def web_app(self): - return self._web_app - - @property - def manipulator_config(self): - return self._manipulator_config - - @manipulator_config.setter - def manipulator_config(self, value): - if isinstance(value, dict): - self._manipulator_config.update(**value) - - def _add_routes(self): - self._web_app.add_routes([ - web.post('/', self._response_manipulator)]) - - async def start_modbus_server(self, app): - """ - Start Modbus server as asyncio task after startup - :param app: Webapp - :return: - """ - try: - if hasattr(asyncio, "create_task"): - if isinstance(self._modbus_server, ModbusSerialServer): - app["modbus_serial_server"] = asyncio.create_task( - self._modbus_server.start()) - app["modbus_server"] = asyncio.create_task( - self._modbus_server.serve_forever()) - else: - if isinstance(self._modbus_server, ModbusSerialServer): - app["modbus_serial_server"] = asyncio.ensure_future( - self._modbus_server.start() - ) - app["modbus_server"] = asyncio.ensure_future( - self._modbus_server.serve_forever()) - - logger.info("Modbus server started") - except Exception as e: - logger.error("Error starting modbus server") - logger.error(e) - - async def stop_modbus_server(self, app): - """ - Stop modbus server - :param app: Webapp - :return: - """ - logger.info("Stopping modbus server") - if isinstance(self._modbus_server, ModbusSerialServer): - app["modbus_serial_server"].cancel() - app["modbus_server"].cancel() - await app["modbus_server"] - logger.info("Modbus server Stopped") - - async def _response_manipulator(self, request): - """ - POST request Handler for response manipulation end point - Payload is a dict with following fields - :response_type : One among (normal, delayed, error, empty, stray) - :error_code: Modbus error code for error response - :delay_by: Delay sending response by seconds - - :param request: - :return: - """ - data = await request.json() - self._manipulator_config.update(data) - return web.json_response(data=data) - - def update_manipulator_config(self, config): - """ - Updates manipulator config. Resets previous counters - :param config: Manipulator config (dict) - :return: - """ - self._counter = 0 - self._manipulator_config = config - - def manipulate_response(self, response): - """ - Manipulates the actual response according to the required error state. - :param response: Modbus response object - :return: Modbus response - """ - skip_encoding = False - if not self._manipulator_config: - return response - else: - clear_after = self._manipulator_config.get("clear_after") - if clear_after and self._counter > clear_after: - logger.info("Resetting manipulator" - " after {} responses".format(clear_after)) - self.update_manipulator_config(dict(DEFAULT_MANIPULATOR)) - return response - response_type = self._manipulator_config.get("response_type") - if response_type == "error": - error_code = self._manipulator_config.get("error_code") - logger.warning( - "Sending error response for all incoming requests") - err_response = ExceptionResponse(response.function_code, error_code) - err_response.transaction_id = response.transaction_id - err_response.unit_id = response.unit_id - response = err_response - self._counter += 1 - elif response_type == "delayed": - delay_by = self._manipulator_config.get("delay_by") - logger.warning( - "Delaying response by {}s for " - "all incoming requests".format(delay_by)) - time.sleep(delay_by) - self._counter += 1 - elif response_type == "empty": - logger.warning("Sending empty response") - self._counter += 1 - response.should_respond = False - elif response_type == "stray": - data_len = self._manipulator_config.get("data_len", 10) - if data_len <= 0: - logger.warning(f"Invalid data_len {data_len}. " - f"Using default lenght 10") - data_len = 10 - response = os.urandom(data_len) - self._counter += 1 - skip_encoding = True - return response, skip_encoding - - def run(self): - """ - Run Web app - :return: - """ - def _info(message): - msg = HINT.format(message, self._host, self._port) - print(msg) - # print(message) - web.run_app(self._web_app, host=self._host, port=self._port, - print=_info) - - async def run_async(self): - """ - Run Web app - :return: - """ - try: - await self._runner.setup() - site = web.TCPSite(self._runner, self._host, self._port) - await site.start() - except Exception as e: - logger.error(e) - - @classmethod - def create_identity(cls, vendor="Pymodbus", product_code="PM", - vendor_url='http://github.com/riptideio/pymodbus/', - product_name="Pymodbus Server", - model_name="Reactive Server", - version=pymodbus_version.short()): - """ - Create modbus identity - :param vendor: - :param product_code: - :param vendor_url: - :param product_name: - :param model_name: - :param version: - :return: ModbusIdentity object - """ - identity = ModbusDeviceIdentification() - identity.VendorName = vendor - identity.ProductCode = product_code - identity.VendorUrl = vendor_url - identity.ProductName = product_name - identity.ModelName = model_name - identity.MajorMinorRevision = version - - return identity - - @classmethod - def create_context(cls, data_block=None, unit=1, - single=False): - """ - Create Modbus context. - :param data_block: Datablock (dict) Refer DEFAULT_DATA_BLOCK - :param unit: Unit id for the slave - :param single: To run as a single slave - :return: ModbusServerContext object - """ - block = dict() - data_block = data_block or DEFAULT_DATA_BLOCK - for modbus_entity, block_desc in data_block.items(): - start_address = block_desc.get("start_address", 0) - default_count = block_desc.get("count", 0) - default_value = block_desc.get("value", 0) - default_values = [default_value]*default_count - sparse = block_desc.get("sparse", False) - db = ModbusSequentialDataBlock if not sparse else ModbusSparseDataBlock - if sparse: - address_map = block_desc.get("address_map") - if not address_map: - address_map = random.sample( - range(start_address+1, default_count), default_count-1) - address_map.insert(0, 0) - block[modbus_entity] = {add: val for add in sorted(address_map) for val in default_values} - else: - block[modbus_entity] =db(start_address, default_values) - - slave_context = ModbusSlaveContext(**block, zero_mode=True) - if not single: - slaves = {} - for i in unit: - slaves[i] = slave_context - else: - slaves = slave_context - server_context = ModbusServerContext(slaves, single=single) - return server_context - - @classmethod - def factory(cls, server, framer=None, context=None, unit=1, single=False, - host="localhost", modbus_port=5020, web_port=8080, - data_block=DEFAULT_DATA_BLOCK, identity=None, loop=None, **kwargs): - """ - Factory to create ReactiveModbusServer - :param server: Modbus server type (tcp, rtu, tls, udp) - :param framer: Modbus framer (ModbusSocketFramer, ModbusRTUFramer, ModbusTLSFramer) - :param context: Modbus server context to use - :param unit: Modbus unit id - :param single: Run in single mode - :param host: Host address to use for both web app and modbus server (default localhost) - :param modbus_port: Modbus port for TCP and UDP server(default: 5020) - :param web_port: Web App port (default: 8080) - :param data_block: Datablock (refer DEFAULT_DATA_BLOCK) - :param identity: Modbus identity object - :param loop: Asyncio loop to use - :param kwargs: Other server specific keyword arguments, refer corresponding servers documentation - :return: ReactiveServer object - """ - if server.lower() not in SERVER_MAPPER: - logger.error(f"Invalid server {server}", server) - exit(1) - server = SERVER_MAPPER.get(server) - if not framer: - framer = DEFAULT_FRAMER.get(server) - if not context: - context = cls.create_context(data_block=data_block, - unit=unit, single=single) - if not identity: - identity = cls.create_identity() - if server == ModbusSerialServer: - kwargs["port"] = modbus_port - server = server(context, framer=framer, identity=identity, - **kwargs) - else: - server = server(context, framer=framer, identity=identity, - address=(host, modbus_port), defer_start=False, - **kwargs) - return ReactiveServer(host, web_port, server, loop) - -# __END__ diff --git a/modbus/pymodbus/server/sync.py b/modbus/pymodbus/server/sync.py deleted file mode 100644 index 041069a..0000000 --- a/modbus/pymodbus/server/sync.py +++ /dev/null @@ -1,703 +0,0 @@ -""" -Implementation of a Threaded Modbus Server ------------------------------------------- - -""" -from binascii import b2a_hex -import serial -import socket -import ssl -import traceback - -from pymodbus.constants import Defaults -from pymodbus.utilities import hexlify_packets -from pymodbus.factory import ServerDecoder -from pymodbus.datastore import ModbusServerContext -from pymodbus.device import ModbusControlBlock -from pymodbus.device import ModbusDeviceIdentification -from pymodbus.transaction import * -from pymodbus.exceptions import NotImplementedException, NoSuchSlaveException -from pymodbus.pdu import ModbusExceptions as merror -from pymodbus.compat import socketserver, byte2int - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# Protocol Handlers -# --------------------------------------------------------------------------- # - -class ModbusBaseRequestHandler(socketserver.BaseRequestHandler): - """ Implements the modbus server protocol - - This uses the socketserver.BaseRequestHandler to implement - the client handler. - """ - running = False - framer = None - - def setup(self): - """ Callback for when a client connects - """ - _logger.debug("Client Connected [%s:%s]" % self.client_address) - self.running = True - self.framer = self.server.framer(self.server.decoder, client=None) - self.server.threads.append(self) - - def finish(self): - """ Callback for when a client disconnects - """ - _logger.debug("Client Disconnected [%s:%s]" % self.client_address) - self.server.threads.remove(self) - - def execute(self, request): - """ The callback to call with the resulting message - - :param request: The decoded request message - """ - broadcast = False - try: - if self.server.broadcast_enable and request.unit_id == 0: - broadcast = True - # if broadcasting then execute on all slave contexts, note response will be ignored - for unit_id in self.server.context.slaves(): - response = request.execute(self.server.context[unit_id]) - else: - context = self.server.context[request.unit_id] - response = request.execute(context) - except NoSuchSlaveException as ex: - _logger.debug("requested slave does " - "not exist: %s" % request.unit_id ) - if self.server.ignore_missing_slaves: - return # the client will simply timeout waiting for a response - response = request.doException(merror.GatewayNoResponse) - except Exception as ex: - _logger.debug("Datastore unable to fulfill request: " - "%s; %s", ex, traceback.format_exc()) - response = request.doException(merror.SlaveFailure) - # no response when broadcasting - if not broadcast: - response.transaction_id = request.transaction_id - response.unit_id = request.unit_id - self.send(response) - - # ----------------------------------------------------------------------- # - # Base class implementations - # ----------------------------------------------------------------------- # - def handle(self): - """ Callback when we receive any data - """ - raise NotImplementedException("Method not implemented" - " by derived class") - - def send(self, message): - """ Send a request (string) to the network - - :param message: The unencoded modbus response - """ - raise NotImplementedException("Method not implemented " - "by derived class") - - -class ModbusSingleRequestHandler(ModbusBaseRequestHandler): - """ Implements the modbus server protocol - - This uses the socketserver.BaseRequestHandler to implement - the client handler for a single client(serial clients) - """ - def handle(self): - """ Callback when we receive any data - """ - while self.running: - try: - data = self.request.recv(1024) - if data: - units = self.server.context.slaves() - if not isinstance(units, (list, tuple)): - units = [units] - # if broadcast is enabled make sure to process requests to address 0 - if self.server.broadcast_enable: - if 0 not in units: - units.append(0) - single = self.server.context.single - self.framer.processIncomingPacket(data, self.execute, - units, single=single) - except Exception as msg: - # Since we only have a single socket, we cannot exit - # Clear frame buffer - self.framer.resetFrame() - _logger.debug("Error: Socket error occurred %s" % msg) - - def send(self, message): - """ Send a request (string) to the network - - :param message: The unencoded modbus response - """ - if message.should_respond: - # self.server.control.Counter.BusMessage += 1 - pdu = self.framer.buildPacket(message) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('send: [%s]- %s' % (message, b2a_hex(pdu))) - return self.request.send(pdu) - - -class CustomSingleRequestHandler(ModbusSingleRequestHandler): - - def __init__(self, request, client_address, server): - self.request = request - self.client_address = client_address - self.server = server - self.running = True - self.setup() - - -class ModbusConnectedRequestHandler(ModbusBaseRequestHandler): - """ Implements the modbus server protocol - - This uses the socketserver.BaseRequestHandler to implement - the client handler for a connected protocol (TCP). - """ - - def handle(self): - """Callback when we receive any data, until self.running becomes False. - Blocks indefinitely awaiting data. If shutdown is required, then the - global socket.settimeout() may be used, to allow timely - checking of self.running. However, since this also affects socket - connects, if there are outgoing socket connections used in the same - program, then these will be prevented, if the specfied timeout is too - short. Hence, this is unreliable. - - To respond to Modbus...Server.server_close() (which clears each - handler's self.running), derive from this class to provide an - alternative handler that awakens from time to time when no input is - available and checks self.running. - Use Modbus...Server( handler=... ) keyword to supply the alternative - request handler class. - - """ - reset_frame = False - while self.running: - try: - units = self.server.context.slaves() - data = self.request.recv(1024) - if not data: - self.running = False - else: - if not isinstance(units, (list, tuple)): - units = [units] - # if broadcast is enabled make sure to - # process requests to address 0 - if self.server.broadcast_enable: - if 0 not in units: - units.append(0) - - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('Handling data: ' + hexlify_packets(data)) - single = self.server.context.single - self.framer.processIncomingPacket(data, self.execute, units, - single=single) - except socket.timeout as msg: - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug("Socket timeout occurred %s", msg) - reset_frame = True - except socket.error as msg: - _logger.error("Socket error occurred %s" % msg) - self.running = False - except: - _logger.error("Socket exception occurred " - "%s" % traceback.format_exc() ) - self.running = False - reset_frame = True - finally: - if reset_frame: - self.framer.resetFrame() - reset_frame = False - - def send(self, message): - """ Send a request (string) to the network - - :param message: The unencoded modbus response - """ - if message.should_respond: - # self.server.control.Counter.BusMessage += 1 - pdu = self.framer.buildPacket(message) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('send: [%s]- %s' % (message, b2a_hex(pdu))) - return self.request.send(pdu) - - -class ModbusDisconnectedRequestHandler(ModbusBaseRequestHandler): - """ Implements the modbus server protocol - - This uses the socketserver.BaseRequestHandler to implement - the client handler for a disconnected protocol (UDP). The - only difference is that we have to specify who to send the - resulting packet data to. - """ - socket = None - - def handle(self): - """ Callback when we receive any data - """ - reset_frame = False - while self.running: - try: - data, self.socket = self.request - if not data: - self.running = False - data = b'' - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('Handling data: ' + hexlify_packets(data)) - # if not self.server.control.ListenOnly: - units = self.server.context.slaves() - single = self.server.context.single - self.framer.processIncomingPacket(data, self.execute, - units, single=single) - except socket.timeout: pass - except socket.error as msg: - _logger.error("Socket error occurred %s" % msg) - self.running = False - reset_frame = True - except Exception as msg: - _logger.error(msg) - self.running = False - reset_frame = True - finally: - # Reset data after processing - self.request = (None, self.socket) - if reset_frame: - self.framer.resetFrame() - reset_frame = False - - def send(self, message): - """ Send a request (string) to the network - - :param message: The unencoded modbus response - """ - if message.should_respond: - #self.server.control.Counter.BusMessage += 1 - pdu = self.framer.buildPacket(message) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug('send: [%s]- %s' % (message, b2a_hex(pdu))) - return self.socket.sendto(pdu, self.client_address) - - -# --------------------------------------------------------------------------- # -# Server Implementations -# --------------------------------------------------------------------------- # -class ModbusTcpServer(socketserver.ThreadingTCPServer): - """ - A modbus threaded tcp socket server - - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - def __init__(self, context, framer=None, identity=None, - address=None, handler=None, allow_reuse_address=False, - **kwargs): - """ Overloaded initializer for the socket server - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param handler: A handler for each client session; default is - ModbusConnectedRequestHandler - :param allow_reuse_address: Whether the server will allow the - reuse of an address. - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - """ - self.threads = [] - self.allow_reuse_address = allow_reuse_address - self.decoder = ServerDecoder() - self.framer = framer or ModbusSocketFramer - self.context = context or ModbusServerContext() - self.control = ModbusControlBlock() - self.address = address or ("", Defaults.Port) - self.handler = handler or ModbusConnectedRequestHandler - self.ignore_missing_slaves = kwargs.pop('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.pop('broadcast_enable', - Defaults.broadcast_enable) - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - socketserver.ThreadingTCPServer.__init__(self, self.address, - self.handler, - **kwargs) - - def process_request(self, request, client): - """ Callback for connecting a new client thread - - :param request: The request to handle - :param client: The address of the client - """ - _logger.debug("Started thread to serve client at " + str(client)) - socketserver.ThreadingTCPServer.process_request(self, request, client) - - def shutdown(self): - """ Stops the serve_forever loop. - - Overridden to signal handlers to stop. - """ - for thread in self.threads: - thread.running = False - socketserver.ThreadingTCPServer.shutdown(self) - - def server_close(self): - """ Callback for stopping the running server - """ - _logger.debug("Modbus server stopped") - self.socket.close() - for thread in self.threads: - thread.running = False - - -class ModbusTlsServer(ModbusTcpServer): - """ - A modbus threaded TLS server - - We inherit and overload the ModbusTcpServer so that we - can control the client threads as well as have a single - server context instance. - """ - - def __init__(self, context, framer=None, identity=None, - address=None, handler=None, allow_reuse_address=False, - sslctx=None, certfile=None, keyfile=None, **kwargs): - """ Overloaded initializer for the ModbusTcpServer - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param handler: A handler for each client session; default is - ModbusConnectedRequestHandler - :param allow_reuse_address: Whether the server will allow the - reuse of an address. - :param sslctx: The SSLContext to use for TLS (default None and auto - create) - :param certfile: The cert file path for TLS (used if sslctx is None) - :param keyfile: The key file path for TLS (used if sslctx is None) - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - """ - self.sslctx = sslctx - if self.sslctx is None: - self.sslctx = ssl.create_default_context() - self.sslctx.load_cert_chain(certfile=certfile, keyfile=keyfile) - # According to MODBUS/TCP Security Protocol Specification, it is - # TLSv2 at least - self.sslctx.options |= ssl.OP_NO_TLSv1_1 - self.sslctx.options |= ssl.OP_NO_TLSv1 - self.sslctx.options |= ssl.OP_NO_SSLv3 - self.sslctx.options |= ssl.OP_NO_SSLv2 - self.sslctx.verify_mode = ssl.CERT_OPTIONAL - self.sslctx.check_hostname = False - - ModbusTcpServer.__init__(self, context, framer, identity, address, - handler, allow_reuse_address, **kwargs) - - def server_activate(self): - """ Callback for starting listening over TLS connection - """ - self.socket = self.sslctx.wrap_socket(self.socket, server_side=True) - socketserver.ThreadingTCPServer.server_activate(self) - - -class ModbusUdpServer(socketserver.ThreadingUDPServer): - """ - A modbus threaded udp socket server - - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - def __init__(self, context, framer=None, identity=None, address=None, - handler=None, **kwargs): - """ Overloaded initializer for the socket server - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param handler: A handler for each client session; default is - ModbusDisonnectedRequestHandler - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - """ - self.threads = [] - self.decoder = ServerDecoder() - self.framer = framer or ModbusSocketFramer - self.context = context or ModbusServerContext() - self.control = ModbusControlBlock() - self.address = address or ("", Defaults.Port) - self.handler = handler or ModbusDisconnectedRequestHandler - self.ignore_missing_slaves = kwargs.pop('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.pop('broadcast_enable', - Defaults.broadcast_enable) - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - socketserver.ThreadingUDPServer.__init__(self, - self.address, self.handler, **kwargs) - # self._BaseServer__shutdown_request = True - - def process_request(self, request, client): - """ Callback for connecting a new client thread - - :param request: The request to handle - :param client: The address of the client - """ - packet, socket = request # TODO I might have to rewrite - _logger.debug("Started thread to serve client at " + str(client)) - socketserver.ThreadingUDPServer.process_request(self, request, client) - - def server_close(self): - """ Callback for stopping the running server - """ - _logger.debug("Modbus server stopped") - self.socket.close() - for thread in self.threads: - thread.running = False - - -class ModbusSerialServer(object): - """ - A modbus threaded serial socket server - - We inherit and overload the socket server so that we - can control the client threads as well as have a single - server context instance. - """ - - handler = None - - def __init__(self, context, framer=None, identity=None, **kwargs): - """ Overloaded initializer for the socket server - - If the identify structure is not passed in, the ModbusControlBlock - uses its own empty structure. - - :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use - :param identity: An optional identify structure - :param port: The serial port to attach to - :param stopbits: The number of stop bits to use - :param bytesize: The bytesize of the serial messages - :param parity: Which kind of parity to use - :param baudrate: The baud rate to use for the serial device - :param timeout: The timeout to use for the serial device - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - :param broadcast_enable: True to treat unit_id 0 as broadcast address, - False to treat 0 as any other unit_id - """ - self.threads = [] - self.decoder = ServerDecoder() - self.framer = framer or ModbusAsciiFramer - self.context = context or ModbusServerContext() - self.control = ModbusControlBlock() - - if isinstance(identity, ModbusDeviceIdentification): - self.control.Identity.update(identity) - - self.device = kwargs.get('port', 0) - self.stopbits = kwargs.get('stopbits', Defaults.Stopbits) - self.bytesize = kwargs.get('bytesize', Defaults.Bytesize) - self.parity = kwargs.get('parity', Defaults.Parity) - self.baudrate = kwargs.get('baudrate', Defaults.Baudrate) - self.timeout = kwargs.get('timeout', Defaults.Timeout) - self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves', - Defaults.IgnoreMissingSlaves) - self.broadcast_enable = kwargs.get('broadcast_enable', - Defaults.broadcast_enable) - self.socket = None - if self._connect(): - self.is_running = True - self._build_handler() - - def _connect(self): - """ Connect to the serial server - - :returns: True if connection succeeded, False otherwise - """ - if self.socket: return True - try: - self.socket = serial.Serial(port=self.device, - timeout=self.timeout, - bytesize=self.bytesize, - stopbits=self.stopbits, - baudrate=self.baudrate, - parity=self.parity) - except serial.SerialException as msg: - _logger.error(msg) - return self.socket is not None - - def _build_handler(self): - """ A helper method to create and monkeypatch - a serial handler. - - :returns: A patched handler - """ - - request = self.socket - request.send = request.write - request.recv = request.read - self.handler = CustomSingleRequestHandler(request, - (self.device, self.device), - self) - - def serve_forever(self): - """ Callback for connecting a new client thread - """ - if self._connect(): - _logger.debug("Started thread to serve client") - if not self.handler: - self._build_handler() - while self.is_running: - if hasattr(self.handler, "response_manipulator"): - self.handler.response_manipulator() - else: - self.handler.handle() - else: - _logger.error("Error opening serial port , " - "Unable to start server!!") - - def server_close(self): - """ Callback for stopping the running server - """ - _logger.debug("Modbus server stopped") - self.is_running = False - self.handler.finish() - self.handler.running = False - self.handler = None - self.socket.close() - - -# --------------------------------------------------------------------------- # -# Creation Factories -# --------------------------------------------------------------------------- # -def StartTcpServer(context=None, identity=None, address=None, - custom_functions=[], **kwargs): - """ A factory to start and run a tcp modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - """ - framer = kwargs.pop("framer", ModbusSocketFramer) - server = ModbusTcpServer(context, framer, identity, address, **kwargs) - - for f in custom_functions: - server.decoder.register(f) - server.serve_forever() - - -def StartTlsServer(context=None, identity=None, address=None, sslctx=None, - certfile=None, keyfile=None, custom_functions=[], **kwargs): - """ A factory to start and run a tls modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param sslctx: The SSLContext to use for TLS (default None and auto create) - :param certfile: The cert file path for TLS (used if sslctx is None) - :param keyfile: The key file path for TLS (used if sslctx is None) - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - """ - framer = kwargs.pop("framer", ModbusTlsFramer) - server = ModbusTlsServer(context, framer, identity, address, sslctx=sslctx, - certfile=certfile, keyfile=keyfile, **kwargs) - - for f in custom_functions: - server.decoder.register(f) - server.serve_forever() - - -def StartUdpServer(context=None, identity=None, address=None, - custom_functions=[], **kwargs): - """ A factory to start and run a udp modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param address: An optional (interface, port) to bind to. - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param framer: The framer to operate with (default ModbusSocketFramer) - :param ignore_missing_slaves: True to not send errors on a request - to a missing slave - """ - framer = kwargs.pop('framer', ModbusSocketFramer) - server = ModbusUdpServer(context, framer, identity, address, **kwargs) - for f in custom_functions: - server.decoder.register(f) - server.serve_forever() - - -def StartSerialServer(context=None, identity=None, custom_functions=[], - **kwargs): - """ A factory to start and run a serial modbus server - - :param context: The ModbusServerContext datastore - :param identity: An optional identify structure - :param custom_functions: An optional list of custom function classes - supported by server instance. - :param framer: The framer to operate with (default ModbusAsciiFramer) - :param port: The serial port to attach to - :param stopbits: The number of stop bits to use - :param bytesize: The bytesize of the serial messages - :param parity: Which kind of parity to use - :param baudrate: The baud rate to use for the serial device - :param timeout: The timeout to use for the serial device - :param ignore_missing_slaves: True to not send errors on a request to a - missing slave - """ - framer = kwargs.pop('framer', ModbusAsciiFramer) - server = ModbusSerialServer(context, framer, identity, **kwargs) - for f in custom_functions: - server.decoder.register(f) - server.serve_forever() - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - - -__all__ = [ - "StartTcpServer", "StartTlsServer", "StartUdpServer", "StartSerialServer" -] - diff --git a/modbus/pymodbus/transaction.py b/modbus/pymodbus/transaction.py deleted file mode 100644 index e9f8c68..0000000 --- a/modbus/pymodbus/transaction.py +++ /dev/null @@ -1,539 +0,0 @@ -""" -Collection of transaction based abstractions - -""" - -import struct -import socket -import time -from threading import RLock -from functools import partial - -from pymodbus.exceptions import ModbusIOException, NotImplementedException -from pymodbus.exceptions import InvalidMessageReceivedException -from pymodbus.constants import Defaults -from pymodbus.framer.ascii_framer import ModbusAsciiFramer -from pymodbus.framer.rtu_framer import ModbusRtuFramer -from pymodbus.framer.socket_framer import ModbusSocketFramer -from pymodbus.framer.tls_framer import ModbusTlsFramer -from pymodbus.framer.binary_framer import ModbusBinaryFramer -from pymodbus.utilities import hexlify_packets, ModbusTransactionState -from pymodbus.compat import iterkeys, byte2int - - -# Python 2 compatibility. -try: - TimeoutError -except NameError: - TimeoutError = socket.timeout - - -# --------------------------------------------------------------------------- # -# Logging -# --------------------------------------------------------------------------- # -import logging -_logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- # -# The Global Transaction Manager -# --------------------------------------------------------------------------- # -class ModbusTransactionManager(object): - """ Implements a transaction for a manager - - The transaction protocol can be represented by the following pseudo code:: - - count = 0 - do - result = send(message) - if (timeout or result == bad) - count++ - else break - while (count < 3) - - This module helps to abstract this away from the framer and protocol. - """ - - def __init__(self, client, **kwargs): - """ Initializes an instance of the ModbusTransactionManager - - :param client: The client socket wrapper - :param retry_on_empty: Should the client retry on empty - :param retries: The number of retries to allow - """ - self.tid = Defaults.TransactionId - self.client = client - self.backoff = kwargs.get('backoff', Defaults.Backoff) or 0.3 - self.retry_on_empty = kwargs.get('retry_on_empty', - Defaults.RetryOnEmpty) - self.retry_on_invalid = kwargs.get('retry_on_invalid', - Defaults.RetryOnInvalid) - self.retries = kwargs.get('retries', Defaults.Retries) or 1 - self.reset_socket = kwargs.get('reset_socket', True) - self._transaction_lock = RLock() - self._no_response_devices = [] - if client: - self._set_adu_size() - - def _set_adu_size(self): - # base ADU size of modbus frame in bytes - if isinstance(self.client.framer, ModbusSocketFramer): - self.base_adu_size = 7 # tid(2), pid(2), length(2), uid(1) - elif isinstance(self.client.framer, ModbusRtuFramer): - self.base_adu_size = 3 # address(1), CRC(2) - elif isinstance(self.client.framer, ModbusAsciiFramer): - self.base_adu_size = 7 # start(1)+ Address(2), LRC(2) + end(2) - elif isinstance(self.client.framer, ModbusBinaryFramer): - self.base_adu_size = 5 # start(1) + Address(1), CRC(2) + end(1) - elif isinstance(self.client.framer, ModbusTlsFramer): - self.base_adu_size = 0 # no header and footer - else: - self.base_adu_size = -1 - - def _calculate_response_length(self, expected_pdu_size): - if self.base_adu_size == -1: - return None - else: - return self.base_adu_size + expected_pdu_size - - def _calculate_exception_length(self): - """ Returns the length of the Modbus Exception Response according to - the type of Framer. - """ - if isinstance(self.client.framer, (ModbusSocketFramer, - ModbusTlsFramer)): - return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1) - elif isinstance(self.client.framer, ModbusAsciiFramer): - return self.base_adu_size + 4 # Fcode(2), ExcecptionCode(2) - elif isinstance(self.client.framer, (ModbusRtuFramer, - ModbusBinaryFramer)): - return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1) - - return None - - def _validate_response(self, request, response, exp_resp_len): - """ - Validate Incoming response against request - :param request: Request sent - :param response: Response received - :param exp_resp_len: Expected response length - :return: New transactions state - """ - if not response: - return False - - mbap = self.client.framer.decode_data(response) - if mbap.get('unit') != request.unit_id or mbap.get('fcode') & 0x7F != request.function_code: - return False - - if 'length' in mbap and exp_resp_len: - return mbap.get('length') == exp_resp_len - return True - - def execute(self, request): - """ Starts the producer to send the next request to - consumer.write(Frame(request)) - """ - with self._transaction_lock: - try: - _logger.debug("Current transaction state - {}".format( - ModbusTransactionState.to_string(self.client.state)) - ) - retries = self.retries - request.transaction_id = self.getNextTID() - _logger.debug("Running transaction " - "{}".format(request.transaction_id)) - _buffer = hexlify_packets(self.client.framer._buffer) - if _buffer: - _logger.debug("Clearing current Frame " - ": - {}".format(_buffer)) - self.client.framer.resetFrame() - broadcast = (self.client.broadcast_enable - and request.unit_id == 0) - if broadcast: - self._transact(request, None, broadcast=True) - response = b'Broadcast write sent - no response expected' - else: - expected_response_length = None - if not isinstance(self.client.framer, ModbusSocketFramer): - if hasattr(request, "get_response_pdu_size"): - response_pdu_size = request.get_response_pdu_size() - if isinstance(self.client.framer, ModbusAsciiFramer): - response_pdu_size = response_pdu_size * 2 - if response_pdu_size: - expected_response_length = self._calculate_response_length(response_pdu_size) - if request.unit_id in self._no_response_devices: - full = True - else: - full = False - c_str = str(self.client) - if "modbusudpclient" in c_str.lower().strip(): - full = True - if not expected_response_length: - expected_response_length = Defaults.ReadSize - response, last_exception = self._transact( - request, - expected_response_length, - full=full, - broadcast=broadcast - ) - while retries > 0: - valid_response = self._validate_response( - request, response, expected_response_length - ) - if valid_response: - if request.unit_id in self._no_response_devices and response: - self._no_response_devices.remove(request.unit_id) - _logger.debug("Got response!!!") - break - else: - if not response: - if request.unit_id not in self._no_response_devices: - self._no_response_devices.append(request.unit_id) - if self.retry_on_empty: - response, last_exception = self._retry_transaction(retries, "empty", request, expected_response_length, full=full) - retries -= 1 - else: - # No response received and retries not enabled - break - else: - if self.retry_on_invalid: - response, last_exception = self._retry_transaction(retries, "invalid", request, expected_response_length, full=full) - retries -= 1 - else: - break - # full = False - addTransaction = partial(self.addTransaction, - tid=request.transaction_id) - self.client.framer.processIncomingPacket(response, - addTransaction, - request.unit_id) - response = self.getTransaction(request.transaction_id) - if not response: - if len(self.transactions): - response = self.getTransaction(tid=0) - else: - last_exception = last_exception or ( - "No Response received from the remote unit" - "/Unable to decode response") - response = ModbusIOException(last_exception, - request.function_code) - if self.reset_socket: - self.client.close() - if hasattr(self.client, "state"): - _logger.debug("Changing transaction state from " - "'PROCESSING REPLY' to " - "'TRANSACTION_COMPLETE'") - self.client.state = ( - ModbusTransactionState.TRANSACTION_COMPLETE) - - return response - except ModbusIOException as ex: - # Handle decode errors in processIncomingPacket method - _logger.exception(ex) - self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE - if self.reset_socket: - self.client.close() - return ex - - def _retry_transaction(self, retries, reason, - packet, response_length, full=False): - _logger.debug("Retry on {} response - {}".format(reason, retries)) - _logger.debug("Changing transaction state from " - "'WAITING_FOR_REPLY' to 'RETRYING'") - self.client.state = ModbusTransactionState.RETRYING - if self.backoff: - delay = 2 ** (self.retries - retries) * self.backoff - time.sleep(delay) - _logger.debug("Sleeping {}".format(delay)) - self.client.connect() - if hasattr(self.client, "_in_waiting"): - in_waiting = self.client._in_waiting() - if in_waiting: - if response_length == in_waiting: - result = self._recv(response_length, full) - return result, None - return self._transact(packet, response_length, full=full) - - def _transact(self, packet, response_length, - full=False, broadcast=False): - """ - Does a Write and Read transaction - :param packet: packet to be sent - :param response_length: Expected response length - :param full: the target device was notorious for its no response. Dont - waste time this time by partial querying - :return: response - """ - last_exception = None - try: - self.client.connect() - packet = self.client.framer.buildPacket(packet) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug("SEND: " + hexlify_packets(packet)) - size = self._send(packet) - if isinstance(size, bytes) and self.client.state == ModbusTransactionState.RETRYING: - _logger.debug("Changing transaction state from " - "'RETRYING' to 'PROCESSING REPLY'") - self.client.state = ModbusTransactionState.PROCESSING_REPLY - return size, None - if broadcast: - if size: - _logger.debug("Changing transaction state from 'SENDING' " - "to 'TRANSACTION_COMPLETE'") - self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE - return b'', None - if size: - _logger.debug("Changing transaction state from 'SENDING' " - "to 'WAITING FOR REPLY'") - self.client.state = ModbusTransactionState.WAITING_FOR_REPLY - if hasattr(self.client, "handle_local_echo") and self.client.handle_local_echo is True: - local_echo_packet = self._recv(size, full) - if local_echo_packet != packet: - return b'', "Wrong local echo" - result = self._recv(response_length, full) - # result2 = self._recv(response_length, full) - if _logger.isEnabledFor(logging.DEBUG): - _logger.debug("RECV: " + hexlify_packets(result)) - - except (socket.error, ModbusIOException, - InvalidMessageReceivedException) as msg: - if self.reset_socket: - self.client.close() - _logger.debug("Transaction failed. (%s) " % msg) - last_exception = msg - result = b'' - return result, last_exception - - def _send(self, packet, retrying=False): - return self.client.framer.sendPacket(packet) - - def _recv(self, expected_response_length, full): - total = None - if not full: - exception_length = self._calculate_exception_length() - if isinstance(self.client.framer, ModbusSocketFramer): - min_size = 8 - elif isinstance(self.client.framer, ModbusRtuFramer): - min_size = 2 - elif isinstance(self.client.framer, ModbusAsciiFramer): - min_size = 5 - elif isinstance(self.client.framer, ModbusBinaryFramer): - min_size = 3 - else: - min_size = expected_response_length - - read_min = self.client.framer.recvPacket(min_size) - if len(read_min) != min_size: - msg_start = "Incomplete message" if read_min else "No response" - raise InvalidMessageReceivedException( - "%s received, expected at least %d bytes " - "(%d received)" % (msg_start, min_size, len(read_min)) - ) - if read_min: - if isinstance(self.client.framer, ModbusSocketFramer): - func_code = byte2int(read_min[-1]) - elif isinstance(self.client.framer, ModbusRtuFramer): - func_code = byte2int(read_min[-1]) - elif isinstance(self.client.framer, ModbusAsciiFramer): - func_code = int(read_min[3:5], 16) - elif isinstance(self.client.framer, ModbusBinaryFramer): - func_code = byte2int(read_min[-1]) - else: - func_code = -1 - - if func_code < 0x80: # Not an error - if isinstance(self.client.framer, ModbusSocketFramer): - # Ommit UID, which is included in header size - h_size = self.client.framer._hsize - length = struct.unpack(">H", read_min[4:6])[0] - 1 - expected_response_length = h_size + length - if expected_response_length is not None: - expected_response_length -= min_size - total = expected_response_length + min_size - else: - expected_response_length = exception_length - min_size - total = expected_response_length + min_size - else: - total = expected_response_length - else: - read_min = b'' - total = expected_response_length - result = self.client.framer.recvPacket(expected_response_length) - result = read_min + result - actual = len(result) - if total is not None and actual != total: - msg_start = "Incomplete message" if actual else "No response" - _logger.debug("{} received, " - "Expected {} bytes Recieved " - "{} bytes !!!!".format(msg_start, total, actual)) - elif actual == 0: - # If actual == 0 and total is not None then the above - # should be triggered, so total must be None here - _logger.debug("No response received to unbounded read !!!!") - if self.client.state != ModbusTransactionState.PROCESSING_REPLY: - _logger.debug("Changing transaction state from " - "'WAITING FOR REPLY' to 'PROCESSING REPLY'") - self.client.state = ModbusTransactionState.PROCESSING_REPLY - return result - - def addTransaction(self, request, tid=None): - """ Adds a transaction to the handler - - This holds the request in case it needs to be resent. - After being sent, the request is removed. - - :param request: The request to hold on to - :param tid: The overloaded transaction id to use - """ - raise NotImplementedException("addTransaction") - - def getTransaction(self, tid): - """ Returns a transaction matching the referenced tid - - If the transaction does not exist, None is returned - - :param tid: The transaction to retrieve - """ - raise NotImplementedException("getTransaction") - - def delTransaction(self, tid): - """ Removes a transaction matching the referenced tid - - :param tid: The transaction to remove - """ - raise NotImplementedException("delTransaction") - - def getNextTID(self): - """ Retrieve the next unique transaction identifier - - This handles incrementing the identifier after - retrieval - - :returns: The next unique transaction identifier - """ - self.tid = (self.tid + 1) & 0xffff - return self.tid - - def reset(self): - """ Resets the transaction identifier """ - self.tid = Defaults.TransactionId - self.transactions = type(self.transactions)() - - -class DictTransactionManager(ModbusTransactionManager): - """ Impelements a transaction for a manager where the - results are keyed based on the supplied transaction id. - """ - - def __init__(self, client, **kwargs): - """ Initializes an instance of the ModbusTransactionManager - - :param client: The client socket wrapper - """ - self.transactions = {} - super(DictTransactionManager, self).__init__(client, **kwargs) - - def __iter__(self): - """ Iterater over the current managed transactions - - :returns: An iterator of the managed transactions - """ - return iterkeys(self.transactions) - - def addTransaction(self, request, tid=None): - """ Adds a transaction to the handler - - This holds the requets in case it needs to be resent. - After being sent, the request is removed. - - :param request: The request to hold on to - :param tid: The overloaded transaction id to use - """ - tid = tid if tid != None else request.transaction_id - _logger.debug("Adding transaction %d" % tid) - self.transactions[tid] = request - - def getTransaction(self, tid): - """ Returns a transaction matching the referenced tid - - If the transaction does not exist, None is returned - - :param tid: The transaction to retrieve - - """ - _logger.debug("Getting transaction %d" % tid) - - return self.transactions.pop(tid, None) - - def delTransaction(self, tid): - """ Removes a transaction matching the referenced tid - - :param tid: The transaction to remove - """ - _logger.debug("deleting transaction %d" % tid) - - self.transactions.pop(tid, None) - - -class FifoTransactionManager(ModbusTransactionManager): - """ Impelements a transaction for a manager where the - results are returned in a FIFO manner. - """ - - def __init__(self, client, **kwargs): - """ Initializes an instance of the ModbusTransactionManager - - :param client: The client socket wrapper - """ - super(FifoTransactionManager, self).__init__(client, **kwargs) - self.transactions = [] - - def __iter__(self): - """ Iterater over the current managed transactions - - :returns: An iterator of the managed transactions - """ - return iter(self.transactions) - - def addTransaction(self, request, tid=None): - """ Adds a transaction to the handler - - This holds the requets in case it needs to be resent. - After being sent, the request is removed. - - :param request: The request to hold on to - :param tid: The overloaded transaction id to use - """ - tid = tid if tid is not None else request.transaction_id - _logger.debug("Adding transaction %d" % tid) - - self.transactions.append(request) - - def getTransaction(self, tid): - """ Returns a transaction matching the referenced tid - - If the transaction does not exist, None is returned - - :param tid: The transaction to retrieve - """ - return self.transactions.pop(0) if self.transactions else None - - def delTransaction(self, tid): - """ Removes a transaction matching the referenced tid - - :param tid: The transaction to remove - """ - _logger.debug("Deleting transaction %d" % tid) - if self.transactions: self.transactions.pop(0) - -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # - -__all__ = [ - "FifoTransactionManager", - "DictTransactionManager", - "ModbusSocketFramer", "ModbusTlsFramer", "ModbusRtuFramer", - "ModbusAsciiFramer", "ModbusBinaryFramer", -] diff --git a/modbus/pymodbus/utilities.py b/modbus/pymodbus/utilities.py deleted file mode 100644 index 3f7e757..0000000 --- a/modbus/pymodbus/utilities.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Modbus Utilities ------------------ - -A collection of utilities for packing data, unpacking -data computing checksums, and decode checksums. -""" -from pymodbus.compat import int2byte, byte2int, IS_PYTHON3 -from six import string_types - - -class ModbusTransactionState(object): - """ - Modbus Client States - """ - IDLE = 0 - SENDING = 1 - WAITING_FOR_REPLY = 2 - WAITING_TURNAROUND_DELAY = 3 - PROCESSING_REPLY = 4 - PROCESSING_ERROR = 5 - TRANSACTION_COMPLETE = 6 - RETRYING = 7 - NO_RESPONSE_STATE = 8 - - @classmethod - def to_string(cls, state): - states = { - ModbusTransactionState.IDLE: "IDLE", - ModbusTransactionState.SENDING: "SENDING", - ModbusTransactionState.WAITING_FOR_REPLY: "WAITING_FOR_REPLY", - ModbusTransactionState.WAITING_TURNAROUND_DELAY: "WAITING_TURNAROUND_DELAY", - ModbusTransactionState.PROCESSING_REPLY: "PROCESSING_REPLY", - ModbusTransactionState.PROCESSING_ERROR: "PROCESSING_ERROR", - ModbusTransactionState.TRANSACTION_COMPLETE: "TRANSACTION_COMPLETE", - ModbusTransactionState.RETRYING: "RETRYING TRANSACTION", - } - return states.get(state, None) - - -# --------------------------------------------------------------------------- # -# Helpers -# --------------------------------------------------------------------------- # - -def default(value): - """ - Given a python object, return the default value - of that object. - - :param value: The value to get the default of - :returns: The default value - """ - return type(value)() - - -def dict_property(store, index): - """ Helper to create class properties from a dictionary. - Basically this allows you to remove a lot of possible - boilerplate code. - - :param store: The store store to pull from - :param index: The index into the store to close over - :returns: An initialized property set - """ - if hasattr(store, '__call__'): - getter = lambda self: store(self)[index] - setter = lambda self, value: store(self).__setitem__(index, value) - elif isinstance(store, str): - getter = lambda self: self.__getattribute__(store)[index] - setter = lambda self, value: self.__getattribute__(store).__setitem__( - index, value) - else: - getter = lambda self: store[index] - setter = lambda self, value: store.__setitem__(index, value) - - return property(getter, setter) - - -# --------------------------------------------------------------------------- # -# Bit packing functions -# --------------------------------------------------------------------------- # -def pack_bitstring(bits): - """ Creates a string out of an array of bits - - :param bits: A bit array - - example:: - - bits = [False, True, False, True] - result = pack_bitstring(bits) - """ - ret = b'' - i = packed = 0 - for bit in bits: - if bit: - packed += 128 - i += 1 - if i == 8: - ret += int2byte(packed) - i = packed = 0 - else: - packed >>= 1 - if 0 < i < 8: - packed >>= (7 - i) - ret += int2byte(packed) - return ret - - -def unpack_bitstring(string): - """ Creates bit array out of a string - - :param string: The modbus data packet to decode - - example:: - - bytes = 'bytes to decode' - result = unpack_bitstring(bytes) - """ - byte_count = len(string) - bits = [] - for byte in range(byte_count): - if IS_PYTHON3: - value = byte2int(int(string[byte])) - else: - value = byte2int(string[byte]) - for _ in range(8): - bits.append((value & 1) == 1) - value >>= 1 - return bits - - -def make_byte_string(s): - """ - Returns byte string from a given string, python3 specific fix - :param s: - :return: - """ - if IS_PYTHON3 and isinstance(s, string_types): - s = s.encode() - return s -# --------------------------------------------------------------------------- # -# Error Detection Functions -# --------------------------------------------------------------------------- # -def __generate_crc16_table(): - """ Generates a crc16 lookup table - - .. note:: This will only be generated once - """ - result = [] - for byte in range(256): - crc = 0x0000 - for _ in range(8): - if (byte ^ crc) & 0x0001: - crc = (crc >> 1) ^ 0xa001 - else: crc >>= 1 - byte >>= 1 - result.append(crc) - return result - -__crc16_table = __generate_crc16_table() - - -def computeCRC(data): - """ Computes a crc16 on the passed in string. For modbus, - this is only used on the binary serial protocols (in this - case RTU). - - The difference between modbus's crc16 and a normal crc16 - is that modbus starts the crc value out at 0xffff. - - :param data: The data to create a crc16 of - :returns: The calculated CRC - """ - crc = 0xffff - for a in data: - idx = __crc16_table[(crc ^ byte2int(a)) & 0xff] - crc = ((crc >> 8) & 0xff) ^ idx - swapped = ((crc << 8) & 0xff00) | ((crc >> 8) & 0x00ff) - return swapped - - -def checkCRC(data, check): - """ Checks if the data matches the passed in CRC - - :param data: The data to create a crc16 of - :param check: The CRC to validate - :returns: True if matched, False otherwise - """ - return computeCRC(data) == check - - -def computeLRC(data): - """ Used to compute the longitudinal redundancy check - against a string. This is only used on the serial ASCII - modbus protocol. A full description of this implementation - can be found in appendex B of the serial line modbus description. - - :param data: The data to apply a lrc to - :returns: The calculated LRC - - """ - lrc = sum(byte2int(a) for a in data) & 0xff - lrc = (lrc ^ 0xff) + 1 - return lrc & 0xff - - -def checkLRC(data, check): - """ Checks if the passed in data matches the LRC - - :param data: The data to calculate - :param check: The LRC to validate - :returns: True if matched, False otherwise - """ - return computeLRC(data) == check - - -def rtuFrameSize(data, byte_count_pos): - """ Calculates the size of the frame based on the byte count. - - :param data: The buffer containing the frame. - :param byte_count_pos: The index of the byte count in the buffer. - :returns: The size of the frame. - - The structure of frames with a byte count field is always the - same: - - - first, there are some header fields - - then the byte count field - - then as many data bytes as indicated by the byte count, - - finally the CRC (two bytes). - - To calculate the frame size, it is therefore sufficient to extract - the contents of the byte count field, add the position of this - field, and finally increment the sum by three (one byte for the - byte count field, two for the CRC). - """ - return byte2int(data[byte_count_pos]) + byte_count_pos + 3 - - -def hexlify_packets(packet): - """ - Returns hex representation of bytestring received - :param packet: - :return: - """ - if not packet: - return '' - if IS_PYTHON3: - return " ".join([hex(byte2int(x)) for x in packet]) - else: - return u" ".join([hex(byte2int(x)) for x in packet]) -# --------------------------------------------------------------------------- # -# Exported symbols -# --------------------------------------------------------------------------- # -__all__ = [ - 'pack_bitstring', 'unpack_bitstring', 'default', - 'computeCRC', 'checkCRC', 'computeLRC', 'checkLRC', 'rtuFrameSize' -] diff --git a/modbus/pymodbus/version.py b/modbus/pymodbus/version.py deleted file mode 100644 index 12e7eaa..0000000 --- a/modbus/pymodbus/version.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Handle the version information here; you should only have to -change the version tuple. - -Since we are using twisted's version class, we can also query -the svn version as well using the local .entries file. -""" - - -class Version(object): - - def __init__(self, package, major, minor, micro, pre=None): - """ - - :param package: Name of the package that this is a version of. - :param major: The major version number. - :param minor: The minor version number. - :param micro: The micro version number. - :param pre: The pre release tag - """ - self.package = package - self.major = major - self.minor = minor - self.micro = micro - self.pre = pre - - def short(self): - """ Return a string in canonical short version format - ...
-        """
-        if self.pre:
-            return '%d.%d.%d.%s' % (self.major, self.minor, self.micro, self.pre)
-        else:
-            return '%d.%d.%d' % (self.major, self.minor, self.micro)
-
-    def __str__(self):
-        """ Returns a string representation of the object
-
-        :returns: A string representation of this object
-        """
-        return '[%s, version %s]' % (self.package, self.short())
-
-
-version = Version('pymodbus', 2, 5, 3)
-
-version.__name__ = 'pymodbus'  # fix epydoc error
-
-# --------------------------------------------------------------------------- #
-# Exported symbols
-# --------------------------------------------------------------------------- #
-
-__all__ = ["version"]
diff --git a/modbus/serial/__init__.py b/modbus/serial/__init__.py
deleted file mode 100644
index caa4de1..0000000
--- a/modbus/serial/__init__.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#!/usr/bin/env python
-#
-# This is a wrapper module for different platform implementations
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2001-2020 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-import sys
-import importlib
-
-from serial.serialutil import *
-#~ SerialBase, SerialException, to_bytes, iterbytes
-
-__version__ = '3.5'
-
-VERSION = __version__
-
-# pylint: disable=wrong-import-position
-if sys.platform == 'cli':
-    from serial.serialcli import Serial
-else:
-    import os
-    # chose an implementation, depending on os
-    if os.name == 'nt':  # sys.platform == 'win32':
-        from serial.serialwin32 import Serial
-    elif os.name == 'posix':
-        from serial.serialposix import Serial, PosixPollSerial, VTIMESerial  # noqa
-    elif os.name == 'java':
-        from serial.serialjava import Serial
-    else:
-        raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
-
-
-protocol_handler_packages = [
-    'serial.urlhandler',
-]
-
-
-def serial_for_url(url, *args, **kwargs):
-    """\
-    Get an instance of the Serial class, depending on port/url. The port is not
-    opened when the keyword parameter 'do_not_open' is true, by default it
-    is. All other parameters are directly passed to the __init__ method when
-    the port is instantiated.
-
-    The list of package names that is searched for protocol handlers is kept in
-    ``protocol_handler_packages``.
-
-    e.g. we want to support a URL ``foobar://``. A module
-    ``my_handlers.protocol_foobar`` is provided by the user. Then
-    ``protocol_handler_packages.append("my_handlers")`` would extend the search
-    path so that ``serial_for_url("foobar://"))`` would work.
-    """
-    # check and remove extra parameter to not confuse the Serial class
-    do_open = not kwargs.pop('do_not_open', False)
-    # the default is to use the native implementation
-    klass = Serial
-    try:
-        url_lowercase = url.lower()
-    except AttributeError:
-        # it's not a string, use default
-        pass
-    else:
-        # if it is an URL, try to import the handler module from the list of possible packages
-        if '://' in url_lowercase:
-            protocol = url_lowercase.split('://', 1)[0]
-            module_name = '.protocol_{}'.format(protocol)
-            for package_name in protocol_handler_packages:
-                try:
-                    importlib.import_module(package_name)
-                    handler_module = importlib.import_module(module_name, package_name)
-                except ImportError:
-                    continue
-                else:
-                    if hasattr(handler_module, 'serial_class_for_url'):
-                        url, klass = handler_module.serial_class_for_url(url)
-                    else:
-                        klass = handler_module.Serial
-                    break
-            else:
-                raise ValueError('invalid URL, protocol {!r} not known'.format(protocol))
-    # instantiate and open when desired
-    instance = klass(None, *args, **kwargs)
-    instance.port = url
-    if do_open:
-        instance.open()
-    return instance
diff --git a/modbus/serial/__main__.py b/modbus/serial/__main__.py
deleted file mode 100644
index bd0a2e6..0000000
--- a/modbus/serial/__main__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .tools import miniterm
-
-miniterm.main()
diff --git a/modbus/serial/rfc2217.py b/modbus/serial/rfc2217.py
deleted file mode 100644
index 2ae188e..0000000
--- a/modbus/serial/rfc2217.py
+++ /dev/null
@@ -1,1351 +0,0 @@
-#! python
-#
-# This module implements a RFC2217 compatible client. RF2217 descibes a
-# protocol to access serial ports over TCP/IP and allows setting the baud rate,
-# modem control lines etc.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2001-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-# TODO:
-# - setting control line -> answer is not checked (had problems with one of the
-#   severs). consider implementing a compatibility mode flag to make check
-#   conditional
-# - write timeout not implemented at all
-
-# ###########################################################################
-# observations and issues with servers
-# ===========================================================================
-# sredird V2.2.1
-# - http://www.ibiblio.org/pub/Linux/system/serial/   sredird-2.2.2.tar.gz
-# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
-#   [105 1] instead of the actual value.
-# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
-#   numbers than 2**32?
-# - To get the signature [COM_PORT_OPTION 0] has to be sent.
-# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done
-# ===========================================================================
-# telnetcpcd (untested)
-# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
-# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
-# ===========================================================================
-# ser2net
-# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
-#   acknowledges that the client activates these options
-# - The configuration may be that the server prints a banner. As this client
-#   implementation does a flushInput on connect, this banner is hidden from
-#   the user application.
-# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
-#   second.
-# - To get the signature [COM_PORT_OPTION 0] has to be sent.
-# - run a server: run ser2net daemon, in /etc/ser2net.conf:
-#     2000:telnet:0:/dev/ttyS0:9600 remctl banner
-# ###########################################################################
-
-# How to identify ports? pySerial might want to support other protocols in the
-# future, so lets use an URL scheme.
-# for RFC2217 compliant servers we will use this:
-#    rfc2217://:[?option[&option...]]
-#
-# options:
-# - "logging" set log level print diagnostic messages (e.g. "logging=debug")
-# - "ign_set_control": do not look at the answers to SET_CONTROL
-# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read.
-#   Without this option it expects that the server sends notifications
-#   automatically on change (which most servers do and is according to the
-#   RFC).
-# the order of the options is not relevant
-
-from __future__ import absolute_import
-
-import logging
-import socket
-import struct
-import threading
-import time
-try:
-    import urlparse
-except ImportError:
-    import urllib.parse as urlparse
-try:
-    import Queue
-except ImportError:
-    import queue as Queue
-
-import serial
-from serial.serialutil import SerialBase, SerialException, to_bytes, \
-    iterbytes, PortNotOpenError, Timeout
-
-# port string is expected to be something like this:
-# rfc2217://host:port
-# host may be an IP or including domain, whatever.
-# port is 0...65535
-
-# map log level names to constants. used in from_url()
-LOGGER_LEVELS = {
-    'debug': logging.DEBUG,
-    'info': logging.INFO,
-    'warning': logging.WARNING,
-    'error': logging.ERROR,
-}
-
-
-# telnet protocol characters
-SE = b'\xf0'    # Subnegotiation End
-NOP = b'\xf1'   # No Operation
-DM = b'\xf2'    # Data Mark
-BRK = b'\xf3'   # Break
-IP = b'\xf4'    # Interrupt process
-AO = b'\xf5'    # Abort output
-AYT = b'\xf6'   # Are You There
-EC = b'\xf7'    # Erase Character
-EL = b'\xf8'    # Erase Line
-GA = b'\xf9'    # Go Ahead
-SB = b'\xfa'    # Subnegotiation Begin
-WILL = b'\xfb'
-WONT = b'\xfc'
-DO = b'\xfd'
-DONT = b'\xfe'
-IAC = b'\xff'   # Interpret As Command
-IAC_DOUBLED = b'\xff\xff'
-
-# selected telnet options
-BINARY = b'\x00'    # 8-bit data path
-ECHO = b'\x01'      # echo
-SGA = b'\x03'       # suppress go ahead
-
-# RFC2217
-COM_PORT_OPTION = b'\x2c'
-
-# Client to Access Server
-SET_BAUDRATE = b'\x01'
-SET_DATASIZE = b'\x02'
-SET_PARITY = b'\x03'
-SET_STOPSIZE = b'\x04'
-SET_CONTROL = b'\x05'
-NOTIFY_LINESTATE = b'\x06'
-NOTIFY_MODEMSTATE = b'\x07'
-FLOWCONTROL_SUSPEND = b'\x08'
-FLOWCONTROL_RESUME = b'\x09'
-SET_LINESTATE_MASK = b'\x0a'
-SET_MODEMSTATE_MASK = b'\x0b'
-PURGE_DATA = b'\x0c'
-
-SERVER_SET_BAUDRATE = b'\x65'
-SERVER_SET_DATASIZE = b'\x66'
-SERVER_SET_PARITY = b'\x67'
-SERVER_SET_STOPSIZE = b'\x68'
-SERVER_SET_CONTROL = b'\x69'
-SERVER_NOTIFY_LINESTATE = b'\x6a'
-SERVER_NOTIFY_MODEMSTATE = b'\x6b'
-SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
-SERVER_FLOWCONTROL_RESUME = b'\x6d'
-SERVER_SET_LINESTATE_MASK = b'\x6e'
-SERVER_SET_MODEMSTATE_MASK = b'\x6f'
-SERVER_PURGE_DATA = b'\x70'
-
-RFC2217_ANSWER_MAP = {
-    SET_BAUDRATE: SERVER_SET_BAUDRATE,
-    SET_DATASIZE: SERVER_SET_DATASIZE,
-    SET_PARITY: SERVER_SET_PARITY,
-    SET_STOPSIZE: SERVER_SET_STOPSIZE,
-    SET_CONTROL: SERVER_SET_CONTROL,
-    NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
-    NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
-    FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
-    FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
-    SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
-    SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
-    PURGE_DATA: SERVER_PURGE_DATA,
-}
-
-SET_CONTROL_REQ_FLOW_SETTING = b'\x00'        # Request Com Port Flow Control Setting (outbound/both)
-SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01'     # Use No Flow Control (outbound/both)
-SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02'     # Use XON/XOFF Flow Control (outbound/both)
-SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03'     # Use HARDWARE Flow Control (outbound/both)
-SET_CONTROL_REQ_BREAK_STATE = b'\x04'         # Request BREAK State
-SET_CONTROL_BREAK_ON = b'\x05'                # Set BREAK State ON
-SET_CONTROL_BREAK_OFF = b'\x06'               # Set BREAK State OFF
-SET_CONTROL_REQ_DTR = b'\x07'                 # Request DTR Signal State
-SET_CONTROL_DTR_ON = b'\x08'                  # Set DTR Signal State ON
-SET_CONTROL_DTR_OFF = b'\x09'                 # Set DTR Signal State OFF
-SET_CONTROL_REQ_RTS = b'\x0a'                 # Request RTS Signal State
-SET_CONTROL_RTS_ON = b'\x0b'                  # Set RTS Signal State ON
-SET_CONTROL_RTS_OFF = b'\x0c'                 # Set RTS Signal State OFF
-SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d'     # Request Com Port Flow Control Setting (inbound)
-SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e'  # Use No Flow Control (inbound)
-SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f'   # Use XON/XOFF Flow Control (inbound)
-SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10'   # Use HARDWARE Flow Control (inbound)
-SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11'    # Use DCD Flow Control (outbound/both)
-SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12'    # Use DTR Flow Control (inbound)
-SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13'    # Use DSR Flow Control (outbound/both)
-
-LINESTATE_MASK_TIMEOUT = 128        # Time-out Error
-LINESTATE_MASK_SHIFTREG_EMPTY = 64  # Transfer Shift Register Empty
-LINESTATE_MASK_TRANSREG_EMPTY = 32  # Transfer Holding Register Empty
-LINESTATE_MASK_BREAK_DETECT = 16    # Break-detect Error
-LINESTATE_MASK_FRAMING_ERROR = 8    # Framing Error
-LINESTATE_MASK_PARTIY_ERROR = 4     # Parity Error
-LINESTATE_MASK_OVERRUN_ERROR = 2    # Overrun Error
-LINESTATE_MASK_DATA_READY = 1       # Data Ready
-
-MODEMSTATE_MASK_CD = 128            # Receive Line Signal Detect (also known as Carrier Detect)
-MODEMSTATE_MASK_RI = 64             # Ring Indicator
-MODEMSTATE_MASK_DSR = 32            # Data-Set-Ready Signal State
-MODEMSTATE_MASK_CTS = 16            # Clear-To-Send Signal State
-MODEMSTATE_MASK_CD_CHANGE = 8       # Delta Receive Line Signal Detect
-MODEMSTATE_MASK_RI_CHANGE = 4       # Trailing-edge Ring Detector
-MODEMSTATE_MASK_DSR_CHANGE = 2      # Delta Data-Set-Ready
-MODEMSTATE_MASK_CTS_CHANGE = 1      # Delta Clear-To-Send
-
-PURGE_RECEIVE_BUFFER = b'\x01'      # Purge access server receive data buffer
-PURGE_TRANSMIT_BUFFER = b'\x02'     # Purge access server transmit data buffer
-PURGE_BOTH_BUFFERS = b'\x03'        # Purge both the access server receive data
-                                    # buffer and the access server transmit data buffer
-
-
-RFC2217_PARITY_MAP = {
-    serial.PARITY_NONE: 1,
-    serial.PARITY_ODD: 2,
-    serial.PARITY_EVEN: 3,
-    serial.PARITY_MARK: 4,
-    serial.PARITY_SPACE: 5,
-}
-RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
-
-RFC2217_STOPBIT_MAP = {
-    serial.STOPBITS_ONE: 1,
-    serial.STOPBITS_ONE_POINT_FIVE: 3,
-    serial.STOPBITS_TWO: 2,
-}
-RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
-
-# Telnet filter states
-M_NORMAL = 0
-M_IAC_SEEN = 1
-M_NEGOTIATE = 2
-
-# TelnetOption and TelnetSubnegotiation states
-REQUESTED = 'REQUESTED'
-ACTIVE = 'ACTIVE'
-INACTIVE = 'INACTIVE'
-REALLY_INACTIVE = 'REALLY_INACTIVE'
-
-
-class TelnetOption(object):
-    """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
-
-    def __init__(self, connection, name, option, send_yes, send_no, ack_yes,
-                 ack_no, initial_state, activation_callback=None):
-        """\
-        Initialize option.
-        :param connection: connection used to transmit answers
-        :param name: a readable name for debug outputs
-        :param send_yes: what to send when option is to be enabled.
-        :param send_no: what to send when option is to be disabled.
-        :param ack_yes: what to expect when remote agrees on option.
-        :param ack_no: what to expect when remote disagrees on option.
-        :param initial_state: options initialized with REQUESTED are tried to
-            be enabled on startup. use INACTIVE for all others.
-        """
-        self.connection = connection
-        self.name = name
-        self.option = option
-        self.send_yes = send_yes
-        self.send_no = send_no
-        self.ack_yes = ack_yes
-        self.ack_no = ack_no
-        self.state = initial_state
-        self.active = False
-        self.activation_callback = activation_callback
-
-    def __repr__(self):
-        """String for debug outputs"""
-        return "{o.name}:{o.active}({o.state})".format(o=self)
-
-    def process_incoming(self, command):
-        """\
-        A DO/DONT/WILL/WONT was received for this option, update state and
-        answer when needed.
-        """
-        if command == self.ack_yes:
-            if self.state is REQUESTED:
-                self.state = ACTIVE
-                self.active = True
-                if self.activation_callback is not None:
-                    self.activation_callback()
-            elif self.state is ACTIVE:
-                pass
-            elif self.state is INACTIVE:
-                self.state = ACTIVE
-                self.connection.telnet_send_option(self.send_yes, self.option)
-                self.active = True
-                if self.activation_callback is not None:
-                    self.activation_callback()
-            elif self.state is REALLY_INACTIVE:
-                self.connection.telnet_send_option(self.send_no, self.option)
-            else:
-                raise ValueError('option in illegal state {!r}'.format(self))
-        elif command == self.ack_no:
-            if self.state is REQUESTED:
-                self.state = INACTIVE
-                self.active = False
-            elif self.state is ACTIVE:
-                self.state = INACTIVE
-                self.connection.telnet_send_option(self.send_no, self.option)
-                self.active = False
-            elif self.state is INACTIVE:
-                pass
-            elif self.state is REALLY_INACTIVE:
-                pass
-            else:
-                raise ValueError('option in illegal state {!r}'.format(self))
-
-
-class TelnetSubnegotiation(object):
-    """\
-    A object to handle subnegotiation of options. In this case actually
-    sub-sub options for RFC 2217. It is used to track com port options.
-    """
-
-    def __init__(self, connection, name, option, ack_option=None):
-        if ack_option is None:
-            ack_option = option
-        self.connection = connection
-        self.name = name
-        self.option = option
-        self.value = None
-        self.ack_option = ack_option
-        self.state = INACTIVE
-
-    def __repr__(self):
-        """String for debug outputs."""
-        return "{sn.name}:{sn.state}".format(sn=self)
-
-    def set(self, value):
-        """\
-        Request a change of the value. a request is sent to the server. if
-        the client needs to know if the change is performed he has to check the
-        state of this object.
-        """
-        self.value = value
-        self.state = REQUESTED
-        self.connection.rfc2217_send_subnegotiation(self.option, self.value)
-        if self.connection.logger:
-            self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value))
-
-    def is_ready(self):
-        """\
-        Check if answer from server has been received. when server rejects
-        the change, raise a ValueError.
-        """
-        if self.state == REALLY_INACTIVE:
-            raise ValueError("remote rejected value for option {!r}".format(self.name))
-        return self.state == ACTIVE
-    # add property to have a similar interface as TelnetOption
-    active = property(is_ready)
-
-    def wait(self, timeout=3):
-        """\
-        Wait until the subnegotiation has been acknowledged or timeout. It
-        can also throw a value error when the answer from the server does not
-        match the value sent.
-        """
-        timeout_timer = Timeout(timeout)
-        while not timeout_timer.expired():
-            time.sleep(0.05)    # prevent 100% CPU load
-            if self.is_ready():
-                break
-        else:
-            raise SerialException("timeout while waiting for option {!r}".format(self.name))
-
-    def check_answer(self, suboption):
-        """\
-        Check an incoming subnegotiation block. The parameter already has
-        cut off the header like sub option number and com port option value.
-        """
-        if self.value == suboption[:len(self.value)]:
-            self.state = ACTIVE
-        else:
-            # error propagation done in is_ready
-            self.state = REALLY_INACTIVE
-        if self.connection.logger:
-            self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state))
-
-
-class Serial(SerialBase):
-    """Serial port implementation for RFC 2217 remote serial ports."""
-
-    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
-                 9600, 19200, 38400, 57600, 115200)
-
-    def __init__(self, *args, **kwargs):
-        self._thread = None
-        self._socket = None
-        self._linestate = 0
-        self._modemstate = None
-        self._modemstate_timeout = Timeout(-1)
-        self._remote_suspend_flow = False
-        self._write_lock = None
-        self.logger = None
-        self._ignore_set_control_answer = False
-        self._poll_modem_state = False
-        self._network_timeout = 3
-        self._telnet_options = None
-        self._rfc2217_port_settings = None
-        self._rfc2217_options = None
-        self._read_buffer = None
-        super(Serial, self).__init__(*args, **kwargs)  # must be last call in case of auto-open
-
-    def open(self):
-        """\
-        Open port with current settings. This may throw a SerialException
-        if the port cannot be opened.
-        """
-        self.logger = None
-        self._ignore_set_control_answer = False
-        self._poll_modem_state = False
-        self._network_timeout = 3
-        if self._port is None:
-            raise SerialException("Port must be configured before it can be used.")
-        if self.is_open:
-            raise SerialException("Port is already open.")
-        try:
-            self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5)  # XXX good value?
-            self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
-        except Exception as msg:
-            self._socket = None
-            raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
-
-        # use a thread save queue as buffer. it also simplifies implementing
-        # the read timeout
-        self._read_buffer = Queue.Queue()
-        # to ensure that user writes does not interfere with internal
-        # telnet/rfc2217 options establish a lock
-        self._write_lock = threading.Lock()
-        # name the following separately so that, below, a check can be easily done
-        mandadory_options = [
-            TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
-            TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
-        ]
-        # all supported telnet options
-        self._telnet_options = [
-            TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
-            TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
-            TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
-            TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
-            TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
-        ] + mandadory_options
-        # RFC 2217 specific states
-        # COM port settings
-        self._rfc2217_port_settings = {
-            'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
-            'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
-            'parity':   TelnetSubnegotiation(self, 'parity',   SET_PARITY,   SERVER_SET_PARITY),
-            'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
-        }
-        # There are more subnegotiation objects, combine all in one dictionary
-        # for easy access
-        self._rfc2217_options = {
-            'purge':    TelnetSubnegotiation(self, 'purge',    PURGE_DATA,   SERVER_PURGE_DATA),
-            'control':  TelnetSubnegotiation(self, 'control',  SET_CONTROL,  SERVER_SET_CONTROL),
-        }
-        self._rfc2217_options.update(self._rfc2217_port_settings)
-        # cache for line and modem states that the server sends to us
-        self._linestate = 0
-        self._modemstate = None
-        self._modemstate_timeout = Timeout(-1)
-        # RFC 2217 flow control between server and client
-        self._remote_suspend_flow = False
-
-        self.is_open = True
-        self._thread = threading.Thread(target=self._telnet_read_loop)
-        self._thread.setDaemon(True)
-        self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port))
-        self._thread.start()
-
-        try:    # must clean-up if open fails
-            # negotiate Telnet/RFC 2217 -> send initial requests
-            for option in self._telnet_options:
-                if option.state is REQUESTED:
-                    self.telnet_send_option(option.send_yes, option.option)
-            # now wait until important options are negotiated
-            timeout = Timeout(self._network_timeout)
-            while not timeout.expired():
-                time.sleep(0.05)    # prevent 100% CPU load
-                if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
-                    break
-            else:
-                raise SerialException(
-                    "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options))
-            if self.logger:
-                self.logger.info("Negotiated options: {}".format(self._telnet_options))
-
-            # fine, go on, set RFC 2217 specific things
-            self._reconfigure_port()
-            # all things set up get, now a clean start
-            if not self._dsrdtr:
-                self._update_dtr_state()
-            if not self._rtscts:
-                self._update_rts_state()
-            self.reset_input_buffer()
-            self.reset_output_buffer()
-        except:
-            self.close()
-            raise
-
-    def _reconfigure_port(self):
-        """Set communication parameters on opened port."""
-        if self._socket is None:
-            raise SerialException("Can only operate on open ports")
-
-        # if self._timeout != 0 and self._interCharTimeout is not None:
-            # XXX
-
-        if self._write_timeout is not None:
-            raise NotImplementedError('write_timeout is currently not supported')
-            # XXX
-
-        # Setup the connection
-        # to get good performance, all parameter changes are sent first...
-        if not 0 < self._baudrate < 2 ** 32:
-            raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
-        self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
-        self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
-        self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
-        self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
-
-        # and now wait until parameters are active
-        items = self._rfc2217_port_settings.values()
-        if self.logger:
-            self.logger.debug("Negotiating settings: {}".format(items))
-        timeout = Timeout(self._network_timeout)
-        while not timeout.expired():
-            time.sleep(0.05)    # prevent 100% CPU load
-            if sum(o.active for o in items) == len(items):
-                break
-        else:
-            raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items))
-        if self.logger:
-            self.logger.info("Negotiated settings: {}".format(items))
-
-        if self._rtscts and self._xonxoff:
-            raise ValueError('xonxoff and rtscts together are not supported')
-        elif self._rtscts:
-            self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL)
-        elif self._xonxoff:
-            self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL)
-        else:
-            self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL)
-
-    def close(self):
-        """Close port"""
-        self.is_open = False
-        if self._socket:
-            try:
-                self._socket.shutdown(socket.SHUT_RDWR)
-                self._socket.close()
-            except:
-                # ignore errors.
-                pass
-        if self._thread:
-            self._thread.join(7)  # XXX more than socket timeout
-            self._thread = None
-            # in case of quick reconnects, give the server some time
-            time.sleep(0.3)
-        self._socket = None
-
-    def from_url(self, url):
-        """\
-        extract host and port from an URL string, other settings are extracted
-        an stored in instance
-        """
-        parts = urlparse.urlsplit(url)
-        if parts.scheme != "rfc2217":
-            raise SerialException(
-                'expected a string in the form '
-                '"rfc2217://:[?option[&option...]]": '
-                'not starting with rfc2217:// ({!r})'.format(parts.scheme))
-        try:
-            # process options now, directly altering self
-            for option, values in urlparse.parse_qs(parts.query, True).items():
-                if option == 'logging':
-                    logging.basicConfig()   # XXX is that good to call it here?
-                    self.logger = logging.getLogger('pySerial.rfc2217')
-                    self.logger.setLevel(LOGGER_LEVELS[values[0]])
-                    self.logger.debug('enabled logging')
-                elif option == 'ign_set_control':
-                    self._ignore_set_control_answer = True
-                elif option == 'poll_modem':
-                    self._poll_modem_state = True
-                elif option == 'timeout':
-                    self._network_timeout = float(values[0])
-                else:
-                    raise ValueError('unknown option: {!r}'.format(option))
-            if not 0 <= parts.port < 65536:
-                raise ValueError("port not in range 0...65535")
-        except ValueError as e:
-            raise SerialException(
-                'expected a string in the form '
-                '"rfc2217://:[?option[&option...]]": {}'.format(e))
-        return (parts.hostname, parts.port)
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    @property
-    def in_waiting(self):
-        """Return the number of bytes currently in the input buffer."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return self._read_buffer.qsize()
-
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        data = bytearray()
-        try:
-            timeout = Timeout(self._timeout)
-            while len(data) < size:
-                if self._thread is None or not self._thread.is_alive():
-                    raise SerialException('connection failed (reader thread died)')
-                buf = self._read_buffer.get(True, timeout.time_left())
-                if buf is None:
-                    return bytes(data)
-                data += buf
-                if timeout.expired():
-                    break
-        except Queue.Empty:  # -> timeout
-            pass
-        return bytes(data)
-
-    def write(self, data):
-        """\
-        Output the given byte string over the serial port. Can block if the
-        connection is blocked. May raise SerialException if the connection is
-        closed.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        with self._write_lock:
-            try:
-                self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
-            except socket.error as e:
-                raise SerialException("connection failed (socket error): {}".format(e))
-        return len(data)
-
-    def reset_input_buffer(self):
-        """Clear input buffer, discarding all that is in the buffer."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER)
-        # empty read buffer
-        while self._read_buffer.qsize():
-            self._read_buffer.get(False)
-
-    def reset_output_buffer(self):
-        """\
-        Clear output buffer, aborting the current output and
-        discarding all that is in the buffer.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
-
-    def _update_break_state(self):
-        """\
-        Set break: Controls TXD. When active, to transmitting is
-        possible.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        if self.logger:
-            self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
-        if self._break_state:
-            self.rfc2217_set_control(SET_CONTROL_BREAK_ON)
-        else:
-            self.rfc2217_set_control(SET_CONTROL_BREAK_OFF)
-
-    def _update_rts_state(self):
-        """Set terminal status line: Request To Send."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        if self.logger:
-            self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
-        if self._rts_state:
-            self.rfc2217_set_control(SET_CONTROL_RTS_ON)
-        else:
-            self.rfc2217_set_control(SET_CONTROL_RTS_OFF)
-
-    def _update_dtr_state(self):
-        """Set terminal status line: Data Terminal Ready."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        if self.logger:
-            self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
-        if self._dtr_state:
-            self.rfc2217_set_control(SET_CONTROL_DTR_ON)
-        else:
-            self.rfc2217_set_control(SET_CONTROL_DTR_OFF)
-
-    @property
-    def cts(self):
-        """Read terminal status line: Clear To Send."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS)
-
-    @property
-    def dsr(self):
-        """Read terminal status line: Data Set Ready."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR)
-
-    @property
-    def ri(self):
-        """Read terminal status line: Ring Indicator."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return bool(self.get_modem_state() & MODEMSTATE_MASK_RI)
-
-    @property
-    def cd(self):
-        """Read terminal status line: Carrier Detect."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return bool(self.get_modem_state() & MODEMSTATE_MASK_CD)
-
-    # - - - platform specific - - -
-    # None so far
-
-    # - - - RFC2217 specific - - -
-
-    def _telnet_read_loop(self):
-        """Read loop for the socket."""
-        mode = M_NORMAL
-        suboption = None
-        try:
-            while self.is_open:
-                try:
-                    data = self._socket.recv(1024)
-                except socket.timeout:
-                    # just need to get out of recv form time to time to check if
-                    # still alive
-                    continue
-                except socket.error as e:
-                    # connection fails -> terminate loop
-                    if self.logger:
-                        self.logger.debug("socket error in reader thread: {}".format(e))
-                    self._read_buffer.put(None)
-                    break
-                if not data:
-                    self._read_buffer.put(None)
-                    break  # lost connection
-                for byte in iterbytes(data):
-                    if mode == M_NORMAL:
-                        # interpret as command or as data
-                        if byte == IAC:
-                            mode = M_IAC_SEEN
-                        else:
-                            # store data in read buffer or sub option buffer
-                            # depending on state
-                            if suboption is not None:
-                                suboption += byte
-                            else:
-                                self._read_buffer.put(byte)
-                    elif mode == M_IAC_SEEN:
-                        if byte == IAC:
-                            # interpret as command doubled -> insert character
-                            # itself
-                            if suboption is not None:
-                                suboption += IAC
-                            else:
-                                self._read_buffer.put(IAC)
-                            mode = M_NORMAL
-                        elif byte == SB:
-                            # sub option start
-                            suboption = bytearray()
-                            mode = M_NORMAL
-                        elif byte == SE:
-                            # sub option end -> process it now
-                            self._telnet_process_subnegotiation(bytes(suboption))
-                            suboption = None
-                            mode = M_NORMAL
-                        elif byte in (DO, DONT, WILL, WONT):
-                            # negotiation
-                            telnet_command = byte
-                            mode = M_NEGOTIATE
-                        else:
-                            # other telnet commands
-                            self._telnet_process_command(byte)
-                            mode = M_NORMAL
-                    elif mode == M_NEGOTIATE:  # DO, DONT, WILL, WONT was received, option now following
-                        self._telnet_negotiate_option(telnet_command, byte)
-                        mode = M_NORMAL
-        finally:
-            if self.logger:
-                self.logger.debug("read thread terminated")
-
-    # - incoming telnet commands and options
-
-    def _telnet_process_command(self, command):
-        """Process commands other than DO, DONT, WILL, WONT."""
-        # Currently none. RFC2217 only uses negotiation and subnegotiation.
-        if self.logger:
-            self.logger.warning("ignoring Telnet command: {!r}".format(command))
-
-    def _telnet_negotiate_option(self, command, option):
-        """Process incoming DO, DONT, WILL, WONT."""
-        # check our registered telnet options and forward command to them
-        # they know themselves if they have to answer or not
-        known = False
-        for item in self._telnet_options:
-            # can have more than one match! as some options are duplicated for
-            # 'us' and 'them'
-            if item.option == option:
-                item.process_incoming(command)
-                known = True
-        if not known:
-            # handle unknown options
-            # only answer to positive requests and deny them
-            if command == WILL or command == DO:
-                self.telnet_send_option((DONT if command == WILL else WONT), option)
-                if self.logger:
-                    self.logger.warning("rejected Telnet option: {!r}".format(option))
-
-    def _telnet_process_subnegotiation(self, suboption):
-        """Process subnegotiation, the data between IAC SB and IAC SE."""
-        if suboption[0:1] == COM_PORT_OPTION:
-            if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
-                self._linestate = ord(suboption[2:3])  # ensure it is a number
-                if self.logger:
-                    self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate))
-            elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
-                self._modemstate = ord(suboption[2:3])  # ensure it is a number
-                if self.logger:
-                    self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate))
-                # update time when we think that a poll would make sense
-                self._modemstate_timeout.restart(0.3)
-            elif suboption[1:2] == FLOWCONTROL_SUSPEND:
-                self._remote_suspend_flow = True
-            elif suboption[1:2] == FLOWCONTROL_RESUME:
-                self._remote_suspend_flow = False
-            else:
-                for item in self._rfc2217_options.values():
-                    if item.ack_option == suboption[1:2]:
-                        #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
-                        item.check_answer(bytes(suboption[2:]))
-                        break
-                else:
-                    if self.logger:
-                        self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption))
-        else:
-            if self.logger:
-                self.logger.warning("ignoring subnegotiation: {!r}".format(suboption))
-
-    # - outgoing telnet commands and options
-
-    def _internal_raw_write(self, data):
-        """internal socket write with no data escaping. used to send telnet stuff."""
-        with self._write_lock:
-            self._socket.sendall(data)
-
-    def telnet_send_option(self, action, option):
-        """Send DO, DONT, WILL, WONT."""
-        self._internal_raw_write(IAC + action + option)
-
-    def rfc2217_send_subnegotiation(self, option, value=b''):
-        """Subnegotiation of RFC2217 parameters."""
-        value = value.replace(IAC, IAC_DOUBLED)
-        self._internal_raw_write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE)
-
-    def rfc2217_send_purge(self, value):
-        """\
-        Send purge request to the remote.
-        (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS)
-        """
-        item = self._rfc2217_options['purge']
-        item.set(value)  # transmit desired purge type
-        item.wait(self._network_timeout)  # wait for acknowledge from the server
-
-    def rfc2217_set_control(self, value):
-        """transmit change of control line to remote"""
-        item = self._rfc2217_options['control']
-        item.set(value)  # transmit desired control type
-        if self._ignore_set_control_answer:
-            # answers are ignored when option is set. compatibility mode for
-            # servers that answer, but not the expected one... (or no answer
-            # at all) i.e. sredird
-            time.sleep(0.1)  # this helps getting the unit tests passed
-        else:
-            item.wait(self._network_timeout)  # wait for acknowledge from the server
-
-    def rfc2217_flow_server_ready(self):
-        """\
-        check if server is ready to receive data. block for some time when
-        not.
-        """
-        #~ if self._remote_suspend_flow:
-        #~     wait---
-
-    def get_modem_state(self):
-        """\
-        get last modem state (cached value. If value is "old", request a new
-        one. This cache helps that we don't issue to many requests when e.g. all
-        status lines, one after the other is queried by the user (CTS, DSR
-        etc.)
-        """
-        # active modem state polling enabled? is the value fresh enough?
-        if self._poll_modem_state and self._modemstate_timeout.expired():
-            if self.logger:
-                self.logger.debug('polling modem state')
-            # when it is older, request an update
-            self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE)
-            timeout = Timeout(self._network_timeout)
-            while not timeout.expired():
-                time.sleep(0.05)    # prevent 100% CPU load
-                # when expiration time is updated, it means that there is a new
-                # value
-                if not self._modemstate_timeout.expired():
-                    break
-            else:
-                if self.logger:
-                    self.logger.warning('poll for modem state failed')
-            # even when there is a timeout, do not generate an error just
-            # return the last known value. this way we can support buggy
-            # servers that do not respond to polls, but send automatic
-            # updates.
-        if self._modemstate is not None:
-            if self.logger:
-                self.logger.debug('using cached modem state')
-            return self._modemstate
-        else:
-            # never received a notification from the server
-            raise SerialException("remote sends no NOTIFY_MODEMSTATE")
-
-
-#############################################################################
-# The following is code that helps implementing an RFC 2217 server.
-
-class PortManager(object):
-    """\
-    This class manages the state of Telnet and RFC 2217. It needs a serial
-    instance and a connection to work with. Connection is expected to implement
-    a (thread safe) write function, that writes the string to the network.
-    """
-
-    def __init__(self, serial_port, connection, logger=None):
-        self.serial = serial_port
-        self.connection = connection
-        self.logger = logger
-        self._client_is_rfc2217 = False
-
-        # filter state machine
-        self.mode = M_NORMAL
-        self.suboption = None
-        self.telnet_command = None
-
-        # states for modem/line control events
-        self.modemstate_mask = 255
-        self.last_modemstate = None
-        self.linstate_mask = 0
-
-        # all supported telnet options
-        self._telnet_options = [
-            TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
-            TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
-            TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
-            TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
-            TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
-            TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
-            TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
-        ]
-
-        # negotiate Telnet/RFC2217 -> send initial requests
-        if self.logger:
-            self.logger.debug("requesting initial Telnet/RFC 2217 options")
-        for option in self._telnet_options:
-            if option.state is REQUESTED:
-                self.telnet_send_option(option.send_yes, option.option)
-        # issue 1st modem state notification
-
-    def _client_ok(self):
-        """\
-        callback of telnet option. It gets called when option is activated.
-        This one here is used to detect when the client agrees on RFC 2217. A
-        flag is set so that other functions like check_modem_lines know if the
-        client is OK.
-        """
-        # The callback is used for we and they so if one party agrees, we're
-        # already happy. it seems not all servers do the negotiation correctly
-        # and i guess there are incorrect clients too.. so be happy if client
-        # answers one or the other positively.
-        self._client_is_rfc2217 = True
-        if self.logger:
-            self.logger.info("client accepts RFC 2217")
-        # this is to ensure that the client gets a notification, even if there
-        # was no change
-        self.check_modem_lines(force_notification=True)
-
-    # - outgoing telnet commands and options
-
-    def telnet_send_option(self, action, option):
-        """Send DO, DONT, WILL, WONT."""
-        self.connection.write(IAC + action + option)
-
-    def rfc2217_send_subnegotiation(self, option, value=b''):
-        """Subnegotiation of RFC 2217 parameters."""
-        value = value.replace(IAC, IAC_DOUBLED)
-        self.connection.write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE)
-
-    # - check modem lines, needs to be called periodically from user to
-    # establish polling
-
-    def check_modem_lines(self, force_notification=False):
-        """\
-        read control lines from serial port and compare the last value sent to remote.
-        send updates on changes.
-        """
-        modemstate = (
-            (self.serial.cts and MODEMSTATE_MASK_CTS) |
-            (self.serial.dsr and MODEMSTATE_MASK_DSR) |
-            (self.serial.ri and MODEMSTATE_MASK_RI) |
-            (self.serial.cd and MODEMSTATE_MASK_CD))
-        # check what has changed
-        deltas = modemstate ^ (self.last_modemstate or 0)  # when last is None -> 0
-        if deltas & MODEMSTATE_MASK_CTS:
-            modemstate |= MODEMSTATE_MASK_CTS_CHANGE
-        if deltas & MODEMSTATE_MASK_DSR:
-            modemstate |= MODEMSTATE_MASK_DSR_CHANGE
-        if deltas & MODEMSTATE_MASK_RI:
-            modemstate |= MODEMSTATE_MASK_RI_CHANGE
-        if deltas & MODEMSTATE_MASK_CD:
-            modemstate |= MODEMSTATE_MASK_CD_CHANGE
-        # if new state is different and the mask allows this change, send
-        # notification. suppress notifications when client is not rfc2217
-        if modemstate != self.last_modemstate or force_notification:
-            if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
-                self.rfc2217_send_subnegotiation(
-                    SERVER_NOTIFY_MODEMSTATE,
-                    to_bytes([modemstate & self.modemstate_mask]))
-                if self.logger:
-                    self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate))
-            # save last state, but forget about deltas.
-            # otherwise it would also notify about changing deltas which is
-            # probably not very useful
-            self.last_modemstate = modemstate & 0xf0
-
-    # - outgoing data escaping
-
-    def escape(self, data):
-        """\
-        This generator function is for the user. All outgoing data has to be
-        properly escaped, so that no IAC character in the data stream messes up
-        the Telnet state machine in the server.
-
-        socket.sendall(escape(data))
-        """
-        for byte in iterbytes(data):
-            if byte == IAC:
-                yield IAC
-                yield IAC
-            else:
-                yield byte
-
-    # - incoming data filter
-
-    def filter(self, data):
-        """\
-        Handle a bunch of incoming bytes. This is a generator. It will yield
-        all characters not of interest for Telnet/RFC 2217.
-
-        The idea is that the reader thread pushes data from the socket through
-        this filter:
-
-        for byte in filter(socket.recv(1024)):
-            # do things like CR/LF conversion/whatever
-            # and write data to the serial port
-            serial.write(byte)
-
-        (socket error handling code left as exercise for the reader)
-        """
-        for byte in iterbytes(data):
-            if self.mode == M_NORMAL:
-                # interpret as command or as data
-                if byte == IAC:
-                    self.mode = M_IAC_SEEN
-                else:
-                    # store data in sub option buffer or pass it to our
-                    # consumer depending on state
-                    if self.suboption is not None:
-                        self.suboption += byte
-                    else:
-                        yield byte
-            elif self.mode == M_IAC_SEEN:
-                if byte == IAC:
-                    # interpret as command doubled -> insert character
-                    # itself
-                    if self.suboption is not None:
-                        self.suboption += byte
-                    else:
-                        yield byte
-                    self.mode = M_NORMAL
-                elif byte == SB:
-                    # sub option start
-                    self.suboption = bytearray()
-                    self.mode = M_NORMAL
-                elif byte == SE:
-                    # sub option end -> process it now
-                    self._telnet_process_subnegotiation(bytes(self.suboption))
-                    self.suboption = None
-                    self.mode = M_NORMAL
-                elif byte in (DO, DONT, WILL, WONT):
-                    # negotiation
-                    self.telnet_command = byte
-                    self.mode = M_NEGOTIATE
-                else:
-                    # other telnet commands
-                    self._telnet_process_command(byte)
-                    self.mode = M_NORMAL
-            elif self.mode == M_NEGOTIATE:  # DO, DONT, WILL, WONT was received, option now following
-                self._telnet_negotiate_option(self.telnet_command, byte)
-                self.mode = M_NORMAL
-
-    # - incoming telnet commands and options
-
-    def _telnet_process_command(self, command):
-        """Process commands other than DO, DONT, WILL, WONT."""
-        # Currently none. RFC2217 only uses negotiation and subnegotiation.
-        if self.logger:
-            self.logger.warning("ignoring Telnet command: {!r}".format(command))
-
-    def _telnet_negotiate_option(self, command, option):
-        """Process incoming DO, DONT, WILL, WONT."""
-        # check our registered telnet options and forward command to them
-        # they know themselves if they have to answer or not
-        known = False
-        for item in self._telnet_options:
-            # can have more than one match! as some options are duplicated for
-            # 'us' and 'them'
-            if item.option == option:
-                item.process_incoming(command)
-                known = True
-        if not known:
-            # handle unknown options
-            # only answer to positive requests and deny them
-            if command == WILL or command == DO:
-                self.telnet_send_option((DONT if command == WILL else WONT), option)
-                if self.logger:
-                    self.logger.warning("rejected Telnet option: {!r}".format(option))
-
-    def _telnet_process_subnegotiation(self, suboption):
-        """Process subnegotiation, the data between IAC SB and IAC SE."""
-        if suboption[0:1] == COM_PORT_OPTION:
-            if self.logger:
-                self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption))
-            if suboption[1:2] == SET_BAUDRATE:
-                backup = self.serial.baudrate
-                try:
-                    (baudrate,) = struct.unpack(b"!I", suboption[2:6])
-                    if baudrate != 0:
-                        self.serial.baudrate = baudrate
-                except ValueError as e:
-                    if self.logger:
-                        self.logger.error("failed to set baud rate: {}".format(e))
-                    self.serial.baudrate = backup
-                else:
-                    if self.logger:
-                        self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate))
-                self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
-            elif suboption[1:2] == SET_DATASIZE:
-                backup = self.serial.bytesize
-                try:
-                    (datasize,) = struct.unpack(b"!B", suboption[2:3])
-                    if datasize != 0:
-                        self.serial.bytesize = datasize
-                except ValueError as e:
-                    if self.logger:
-                        self.logger.error("failed to set data size: {}".format(e))
-                    self.serial.bytesize = backup
-                else:
-                    if self.logger:
-                        self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize))
-                self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
-            elif suboption[1:2] == SET_PARITY:
-                backup = self.serial.parity
-                try:
-                    parity = struct.unpack(b"!B", suboption[2:3])[0]
-                    if parity != 0:
-                        self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
-                except ValueError as e:
-                    if self.logger:
-                        self.logger.error("failed to set parity: {}".format(e))
-                    self.serial.parity = backup
-                else:
-                    if self.logger:
-                        self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity))
-                self.rfc2217_send_subnegotiation(
-                    SERVER_SET_PARITY,
-                    struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
-            elif suboption[1:2] == SET_STOPSIZE:
-                backup = self.serial.stopbits
-                try:
-                    stopbits = struct.unpack(b"!B", suboption[2:3])[0]
-                    if stopbits != 0:
-                        self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
-                except ValueError as e:
-                    if self.logger:
-                        self.logger.error("failed to set stop bits: {}".format(e))
-                    self.serial.stopbits = backup
-                else:
-                    if self.logger:
-                        self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits))
-                self.rfc2217_send_subnegotiation(
-                    SERVER_SET_STOPSIZE,
-                    struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
-            elif suboption[1:2] == SET_CONTROL:
-                if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
-                    if self.serial.xonxoff:
-                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
-                    elif self.serial.rtscts:
-                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
-                    else:
-                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
-                elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
-                    self.serial.xonxoff = False
-                    self.serial.rtscts = False
-                    if self.logger:
-                        self.logger.info("changed flow control to None")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
-                elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
-                    self.serial.xonxoff = True
-                    if self.logger:
-                        self.logger.info("changed flow control to XON/XOFF")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
-                elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
-                    self.serial.rtscts = True
-                    if self.logger:
-                        self.logger.info("changed flow control to RTS/CTS")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
-                elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
-                    if self.logger:
-                        self.logger.warning("requested break state - not implemented")
-                    pass  # XXX needs cached value
-                elif suboption[2:3] == SET_CONTROL_BREAK_ON:
-                    self.serial.break_condition = True
-                    if self.logger:
-                        self.logger.info("changed BREAK to active")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
-                elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
-                    self.serial.break_condition = False
-                    if self.logger:
-                        self.logger.info("changed BREAK to inactive")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
-                elif suboption[2:3] == SET_CONTROL_REQ_DTR:
-                    if self.logger:
-                        self.logger.warning("requested DTR state - not implemented")
-                    pass  # XXX needs cached value
-                elif suboption[2:3] == SET_CONTROL_DTR_ON:
-                    self.serial.dtr = True
-                    if self.logger:
-                        self.logger.info("changed DTR to active")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
-                elif suboption[2:3] == SET_CONTROL_DTR_OFF:
-                    self.serial.dtr = False
-                    if self.logger:
-                        self.logger.info("changed DTR to inactive")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
-                elif suboption[2:3] == SET_CONTROL_REQ_RTS:
-                    if self.logger:
-                        self.logger.warning("requested RTS state - not implemented")
-                    pass  # XXX needs cached value
-                    #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
-                elif suboption[2:3] == SET_CONTROL_RTS_ON:
-                    self.serial.rts = True
-                    if self.logger:
-                        self.logger.info("changed RTS to active")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
-                elif suboption[2:3] == SET_CONTROL_RTS_OFF:
-                    self.serial.rts = False
-                    if self.logger:
-                        self.logger.info("changed RTS to inactive")
-                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
-                #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
-                #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
-                #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
-                #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
-                #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
-                #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
-                #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
-            elif suboption[1:2] == NOTIFY_LINESTATE:
-                # client polls for current state
-                self.rfc2217_send_subnegotiation(
-                    SERVER_NOTIFY_LINESTATE,
-                    to_bytes([0]))   # sorry, nothing like that implemented
-            elif suboption[1:2] == NOTIFY_MODEMSTATE:
-                if self.logger:
-                    self.logger.info("request for modem state")
-                # client polls for current state
-                self.check_modem_lines(force_notification=True)
-            elif suboption[1:2] == FLOWCONTROL_SUSPEND:
-                if self.logger:
-                    self.logger.info("suspend")
-                self._remote_suspend_flow = True
-            elif suboption[1:2] == FLOWCONTROL_RESUME:
-                if self.logger:
-                    self.logger.info("resume")
-                self._remote_suspend_flow = False
-            elif suboption[1:2] == SET_LINESTATE_MASK:
-                self.linstate_mask = ord(suboption[2:3])  # ensure it is a number
-                if self.logger:
-                    self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask))
-            elif suboption[1:2] == SET_MODEMSTATE_MASK:
-                self.modemstate_mask = ord(suboption[2:3])  # ensure it is a number
-                if self.logger:
-                    self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask))
-            elif suboption[1:2] == PURGE_DATA:
-                if suboption[2:3] == PURGE_RECEIVE_BUFFER:
-                    self.serial.reset_input_buffer()
-                    if self.logger:
-                        self.logger.info("purge in")
-                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
-                elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
-                    self.serial.reset_output_buffer()
-                    if self.logger:
-                        self.logger.info("purge out")
-                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
-                elif suboption[2:3] == PURGE_BOTH_BUFFERS:
-                    self.serial.reset_input_buffer()
-                    self.serial.reset_output_buffer()
-                    if self.logger:
-                        self.logger.info("purge both")
-                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
-                else:
-                    if self.logger:
-                        self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:])))
-            else:
-                if self.logger:
-                    self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:])))
-        else:
-            if self.logger:
-                self.logger.warning("unknown subnegotiation: {!r}".format(suboption))
-
-
-# simple client test
-if __name__ == '__main__':
-    import sys
-    s = Serial('rfc2217://localhost:7000', 115200)
-    sys.stdout.write('{}\n'.format(s))
-
-    sys.stdout.write("write...\n")
-    s.write(b"hello\n")
-    s.flush()
-    sys.stdout.write("read: {}\n".format(s.read(5)))
-    s.close()
diff --git a/modbus/serial/rs485.py b/modbus/serial/rs485.py
deleted file mode 100644
index d7aff6f..0000000
--- a/modbus/serial/rs485.py
+++ /dev/null
@@ -1,94 +0,0 @@
-#!/usr/bin/env python
-
-# RS485 support
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-"""\
-The settings for RS485 are stored in a dedicated object that can be applied to
-serial ports (where supported).
-NOTE: Some implementations may only support a subset of the settings.
-"""
-
-from __future__ import absolute_import
-
-import time
-import serial
-
-
-class RS485Settings(object):
-    def __init__(
-            self,
-            rts_level_for_tx=True,
-            rts_level_for_rx=False,
-            loopback=False,
-            delay_before_tx=None,
-            delay_before_rx=None):
-        self.rts_level_for_tx = rts_level_for_tx
-        self.rts_level_for_rx = rts_level_for_rx
-        self.loopback = loopback
-        self.delay_before_tx = delay_before_tx
-        self.delay_before_rx = delay_before_rx
-
-
-class RS485(serial.Serial):
-    """\
-    A subclass that replaces the write method with one that toggles RTS
-    according to the RS485 settings.
-
-    NOTE: This may work unreliably on some serial ports (control signals not
-          synchronized or delayed compared to data). Using delays may be
-          unreliable (varying times, larger than expected) as the OS may not
-          support very fine grained delays (no smaller than in the order of
-          tens of milliseconds).
-
-    NOTE: Some implementations support this natively. Better performance
-          can be expected when the native version is used.
-
-    NOTE: The loopback property is ignored by this implementation. The actual
-          behavior depends on the used hardware.
-
-    Usage:
-
-        ser = RS485(...)
-        ser.rs485_mode = RS485Settings(...)
-        ser.write(b'hello')
-    """
-
-    def __init__(self, *args, **kwargs):
-        super(RS485, self).__init__(*args, **kwargs)
-        self._alternate_rs485_settings = None
-
-    def write(self, b):
-        """Write to port, controlling RTS before and after transmitting."""
-        if self._alternate_rs485_settings is not None:
-            # apply level for TX and optional delay
-            self.setRTS(self._alternate_rs485_settings.rts_level_for_tx)
-            if self._alternate_rs485_settings.delay_before_tx is not None:
-                time.sleep(self._alternate_rs485_settings.delay_before_tx)
-            # write and wait for data to be written
-            super(RS485, self).write(b)
-            super(RS485, self).flush()
-            # optional delay and apply level for RX
-            if self._alternate_rs485_settings.delay_before_rx is not None:
-                time.sleep(self._alternate_rs485_settings.delay_before_rx)
-            self.setRTS(self._alternate_rs485_settings.rts_level_for_rx)
-        else:
-            super(RS485, self).write(b)
-
-    # redirect where the property stores the settings so that underlying Serial
-    # instance does not see them
-    @property
-    def rs485_mode(self):
-        """\
-        Enable RS485 mode and apply new settings, set to None to disable.
-        See serial.rs485.RS485Settings for more info about the value.
-        """
-        return self._alternate_rs485_settings
-
-    @rs485_mode.setter
-    def rs485_mode(self, rs485_settings):
-        self._alternate_rs485_settings = rs485_settings
diff --git a/modbus/serial/serialcli.py b/modbus/serial/serialcli.py
deleted file mode 100644
index 4614736..0000000
--- a/modbus/serial/serialcli.py
+++ /dev/null
@@ -1,253 +0,0 @@
-#! python
-#
-# Backend for .NET/Mono (IronPython), .NET >= 2
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2008-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-import System
-import System.IO.Ports
-from serial.serialutil import *
-
-# must invoke function with byte array, make a helper to convert strings
-# to byte arrays
-sab = System.Array[System.Byte]
-
-
-def as_byte_array(string):
-    return sab([ord(x) for x in string])  # XXX will require adaption when run with a 3.x compatible IronPython
-
-
-class Serial(SerialBase):
-    """Serial port implementation for .NET/Mono."""
-
-    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
-                 9600, 19200, 38400, 57600, 115200)
-
-    def open(self):
-        """\
-        Open port with current settings. This may throw a SerialException
-        if the port cannot be opened.
-        """
-        if self._port is None:
-            raise SerialException("Port must be configured before it can be used.")
-        if self.is_open:
-            raise SerialException("Port is already open.")
-        try:
-            self._port_handle = System.IO.Ports.SerialPort(self.portstr)
-        except Exception as msg:
-            self._port_handle = None
-            raise SerialException("could not open port %s: %s" % (self.portstr, msg))
-
-        # if RTS and/or DTR are not set before open, they default to True
-        if self._rts_state is None:
-            self._rts_state = True
-        if self._dtr_state is None:
-            self._dtr_state = True
-
-        self._reconfigure_port()
-        self._port_handle.Open()
-        self.is_open = True
-        if not self._dsrdtr:
-            self._update_dtr_state()
-        if not self._rtscts:
-            self._update_rts_state()
-        self.reset_input_buffer()
-
-    def _reconfigure_port(self):
-        """Set communication parameters on opened port."""
-        if not self._port_handle:
-            raise SerialException("Can only operate on a valid port handle")
-
-        #~ self._port_handle.ReceivedBytesThreshold = 1
-
-        if self._timeout is None:
-            self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
-        else:
-            self._port_handle.ReadTimeout = int(self._timeout * 1000)
-
-        # if self._timeout != 0 and self._interCharTimeout is not None:
-            # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:]
-
-        if self._write_timeout is None:
-            self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
-        else:
-            self._port_handle.WriteTimeout = int(self._write_timeout * 1000)
-
-        # Setup the connection info.
-        try:
-            self._port_handle.BaudRate = self._baudrate
-        except IOError as e:
-            # catch errors from illegal baudrate settings
-            raise ValueError(str(e))
-
-        if self._bytesize == FIVEBITS:
-            self._port_handle.DataBits = 5
-        elif self._bytesize == SIXBITS:
-            self._port_handle.DataBits = 6
-        elif self._bytesize == SEVENBITS:
-            self._port_handle.DataBits = 7
-        elif self._bytesize == EIGHTBITS:
-            self._port_handle.DataBits = 8
-        else:
-            raise ValueError("Unsupported number of data bits: %r" % self._bytesize)
-
-        if self._parity == PARITY_NONE:
-            self._port_handle.Parity = getattr(System.IO.Ports.Parity, 'None')  # reserved keyword in Py3k
-        elif self._parity == PARITY_EVEN:
-            self._port_handle.Parity = System.IO.Ports.Parity.Even
-        elif self._parity == PARITY_ODD:
-            self._port_handle.Parity = System.IO.Ports.Parity.Odd
-        elif self._parity == PARITY_MARK:
-            self._port_handle.Parity = System.IO.Ports.Parity.Mark
-        elif self._parity == PARITY_SPACE:
-            self._port_handle.Parity = System.IO.Ports.Parity.Space
-        else:
-            raise ValueError("Unsupported parity mode: %r" % self._parity)
-
-        if self._stopbits == STOPBITS_ONE:
-            self._port_handle.StopBits = System.IO.Ports.StopBits.One
-        elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
-            self._port_handle.StopBits = System.IO.Ports.StopBits.OnePointFive
-        elif self._stopbits == STOPBITS_TWO:
-            self._port_handle.StopBits = System.IO.Ports.StopBits.Two
-        else:
-            raise ValueError("Unsupported number of stop bits: %r" % self._stopbits)
-
-        if self._rtscts and self._xonxoff:
-            self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSendXOnXOff
-        elif self._rtscts:
-            self._port_handle.Handshake = System.IO.Ports.Handshake.RequestToSend
-        elif self._xonxoff:
-            self._port_handle.Handshake = System.IO.Ports.Handshake.XOnXOff
-        else:
-            self._port_handle.Handshake = getattr(System.IO.Ports.Handshake, 'None')   # reserved keyword in Py3k
-
-    #~ def __del__(self):
-        #~ self.close()
-
-    def close(self):
-        """Close port"""
-        if self.is_open:
-            if self._port_handle:
-                try:
-                    self._port_handle.Close()
-                except System.IO.Ports.InvalidOperationException:
-                    # ignore errors. can happen for unplugged USB serial devices
-                    pass
-                self._port_handle = None
-            self.is_open = False
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    @property
-    def in_waiting(self):
-        """Return the number of characters currently in the input buffer."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return self._port_handle.BytesToRead
-
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        # must use single byte reads as this is the only way to read
-        # without applying encodings
-        data = bytearray()
-        while size:
-            try:
-                data.append(self._port_handle.ReadByte())
-            except System.TimeoutException:
-                break
-            else:
-                size -= 1
-        return bytes(data)
-
-    def write(self, data):
-        """Output the given string over the serial port."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        #~ if not isinstance(data, (bytes, bytearray)):
-            #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
-        try:
-            # must call overloaded method with byte array argument
-            # as this is the only one not applying encodings
-            self._port_handle.Write(as_byte_array(data), 0, len(data))
-        except System.TimeoutException:
-            raise SerialTimeoutException('Write timeout')
-        return len(data)
-
-    def reset_input_buffer(self):
-        """Clear input buffer, discarding all that is in the buffer."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._port_handle.DiscardInBuffer()
-
-    def reset_output_buffer(self):
-        """\
-        Clear output buffer, aborting the current output and
-        discarding all that is in the buffer.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._port_handle.DiscardOutBuffer()
-
-    def _update_break_state(self):
-        """
-        Set break: Controls TXD. When active, to transmitting is possible.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._port_handle.BreakState = bool(self._break_state)
-
-    def _update_rts_state(self):
-        """Set terminal status line: Request To Send"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._port_handle.RtsEnable = bool(self._rts_state)
-
-    def _update_dtr_state(self):
-        """Set terminal status line: Data Terminal Ready"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._port_handle.DtrEnable = bool(self._dtr_state)
-
-    @property
-    def cts(self):
-        """Read terminal status line: Clear To Send"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return self._port_handle.CtsHolding
-
-    @property
-    def dsr(self):
-        """Read terminal status line: Data Set Ready"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return self._port_handle.DsrHolding
-
-    @property
-    def ri(self):
-        """Read terminal status line: Ring Indicator"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        #~ return self._port_handle.XXX
-        return False  # XXX an error would be better
-
-    @property
-    def cd(self):
-        """Read terminal status line: Carrier Detect"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        return self._port_handle.CDHolding
-
-    # - - platform specific - - - -
-    # none
diff --git a/modbus/serial/serialjava.py b/modbus/serial/serialjava.py
deleted file mode 100644
index 0789a78..0000000
--- a/modbus/serial/serialjava.py
+++ /dev/null
@@ -1,251 +0,0 @@
-#!jython
-#
-# Backend Jython with JavaComm
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2002-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-from serial.serialutil import *
-
-
-def my_import(name):
-    mod = __import__(name)
-    components = name.split('.')
-    for comp in components[1:]:
-        mod = getattr(mod, comp)
-    return mod
-
-
-def detect_java_comm(names):
-    """try given list of modules and return that imports"""
-    for name in names:
-        try:
-            mod = my_import(name)
-            mod.SerialPort
-            return mod
-        except (ImportError, AttributeError):
-            pass
-    raise ImportError("No Java Communications API implementation found")
-
-
-# Java Communications API implementations
-# http://mho.republika.pl/java/comm/
-
-comm = detect_java_comm([
-    'javax.comm',  # Sun/IBM
-    'gnu.io',      # RXTX
-])
-
-
-def device(portnumber):
-    """Turn a port number into a device name"""
-    enum = comm.CommPortIdentifier.getPortIdentifiers()
-    ports = []
-    while enum.hasMoreElements():
-        el = enum.nextElement()
-        if el.getPortType() == comm.CommPortIdentifier.PORT_SERIAL:
-            ports.append(el)
-    return ports[portnumber].getName()
-
-
-class Serial(SerialBase):
-    """\
-    Serial port class, implemented with Java Communications API and
-    thus usable with jython and the appropriate java extension.
-    """
-
-    def open(self):
-        """\
-        Open port with current settings. This may throw a SerialException
-        if the port cannot be opened.
-        """
-        if self._port is None:
-            raise SerialException("Port must be configured before it can be used.")
-        if self.is_open:
-            raise SerialException("Port is already open.")
-        if type(self._port) == type(''):      # strings are taken directly
-            portId = comm.CommPortIdentifier.getPortIdentifier(self._port)
-        else:
-            portId = comm.CommPortIdentifier.getPortIdentifier(device(self._port))     # numbers are transformed to a comport id obj
-        try:
-            self.sPort = portId.open("python serial module", 10)
-        except Exception as msg:
-            self.sPort = None
-            raise SerialException("Could not open port: %s" % msg)
-        self._reconfigurePort()
-        self._instream = self.sPort.getInputStream()
-        self._outstream = self.sPort.getOutputStream()
-        self.is_open = True
-
-    def _reconfigurePort(self):
-        """Set communication parameters on opened port."""
-        if not self.sPort:
-            raise SerialException("Can only operate on a valid port handle")
-
-        self.sPort.enableReceiveTimeout(30)
-        if self._bytesize == FIVEBITS:
-            jdatabits = comm.SerialPort.DATABITS_5
-        elif self._bytesize == SIXBITS:
-            jdatabits = comm.SerialPort.DATABITS_6
-        elif self._bytesize == SEVENBITS:
-            jdatabits = comm.SerialPort.DATABITS_7
-        elif self._bytesize == EIGHTBITS:
-            jdatabits = comm.SerialPort.DATABITS_8
-        else:
-            raise ValueError("unsupported bytesize: %r" % self._bytesize)
-
-        if self._stopbits == STOPBITS_ONE:
-            jstopbits = comm.SerialPort.STOPBITS_1
-        elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
-            jstopbits = comm.SerialPort.STOPBITS_1_5
-        elif self._stopbits == STOPBITS_TWO:
-            jstopbits = comm.SerialPort.STOPBITS_2
-        else:
-            raise ValueError("unsupported number of stopbits: %r" % self._stopbits)
-
-        if self._parity == PARITY_NONE:
-            jparity = comm.SerialPort.PARITY_NONE
-        elif self._parity == PARITY_EVEN:
-            jparity = comm.SerialPort.PARITY_EVEN
-        elif self._parity == PARITY_ODD:
-            jparity = comm.SerialPort.PARITY_ODD
-        elif self._parity == PARITY_MARK:
-            jparity = comm.SerialPort.PARITY_MARK
-        elif self._parity == PARITY_SPACE:
-            jparity = comm.SerialPort.PARITY_SPACE
-        else:
-            raise ValueError("unsupported parity type: %r" % self._parity)
-
-        jflowin = jflowout = 0
-        if self._rtscts:
-            jflowin |= comm.SerialPort.FLOWCONTROL_RTSCTS_IN
-            jflowout |= comm.SerialPort.FLOWCONTROL_RTSCTS_OUT
-        if self._xonxoff:
-            jflowin |= comm.SerialPort.FLOWCONTROL_XONXOFF_IN
-            jflowout |= comm.SerialPort.FLOWCONTROL_XONXOFF_OUT
-
-        self.sPort.setSerialPortParams(self._baudrate, jdatabits, jstopbits, jparity)
-        self.sPort.setFlowControlMode(jflowin | jflowout)
-
-        if self._timeout >= 0:
-            self.sPort.enableReceiveTimeout(int(self._timeout*1000))
-        else:
-            self.sPort.disableReceiveTimeout()
-
-    def close(self):
-        """Close port"""
-        if self.is_open:
-            if self.sPort:
-                self._instream.close()
-                self._outstream.close()
-                self.sPort.close()
-                self.sPort = None
-            self.is_open = False
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    @property
-    def in_waiting(self):
-        """Return the number of characters currently in the input buffer."""
-        if not self.sPort:
-            raise PortNotOpenError()
-        return self._instream.available()
-
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.sPort:
-            raise PortNotOpenError()
-        read = bytearray()
-        if size > 0:
-            while len(read) < size:
-                x = self._instream.read()
-                if x == -1:
-                    if self.timeout >= 0:
-                        break
-                else:
-                    read.append(x)
-        return bytes(read)
-
-    def write(self, data):
-        """Output the given string over the serial port."""
-        if not self.sPort:
-            raise PortNotOpenError()
-        if not isinstance(data, (bytes, bytearray)):
-            raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
-        self._outstream.write(data)
-        return len(data)
-
-    def reset_input_buffer(self):
-        """Clear input buffer, discarding all that is in the buffer."""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self._instream.skip(self._instream.available())
-
-    def reset_output_buffer(self):
-        """\
-        Clear output buffer, aborting the current output and
-        discarding all that is in the buffer.
-        """
-        if not self.sPort:
-            raise PortNotOpenError()
-        self._outstream.flush()
-
-    def send_break(self, duration=0.25):
-        """Send break condition. Timed, returns to idle state after given duration."""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.sendBreak(duration*1000.0)
-
-    def _update_break_state(self):
-        """Set break: Controls TXD. When active, to transmitting is possible."""
-        if self.fd is None:
-            raise PortNotOpenError()
-        raise SerialException("The _update_break_state function is not implemented in java.")
-
-    def _update_rts_state(self):
-        """Set terminal status line: Request To Send"""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.setRTS(self._rts_state)
-
-    def _update_dtr_state(self):
-        """Set terminal status line: Data Terminal Ready"""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.setDTR(self._dtr_state)
-
-    @property
-    def cts(self):
-        """Read terminal status line: Clear To Send"""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.isCTS()
-
-    @property
-    def dsr(self):
-        """Read terminal status line: Data Set Ready"""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.isDSR()
-
-    @property
-    def ri(self):
-        """Read terminal status line: Ring Indicator"""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.isRI()
-
-    @property
-    def cd(self):
-        """Read terminal status line: Carrier Detect"""
-        if not self.sPort:
-            raise PortNotOpenError()
-        self.sPort.isCD()
diff --git a/modbus/serial/serialposix.py b/modbus/serial/serialposix.py
deleted file mode 100644
index 7aceb76..0000000
--- a/modbus/serial/serialposix.py
+++ /dev/null
@@ -1,900 +0,0 @@
-#!/usr/bin/env python
-#
-# backend for serial IO for POSIX compatible systems, like Linux, OSX
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2001-2020 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-#
-# parts based on code from Grant B. Edwards  :
-#  ftp://ftp.visi.com/users/grante/python/PosixSerial.py
-#
-# references: http://www.easysw.com/~mike/serial/serial.html
-
-# Collection of port names (was previously used by number_to_device which was
-# removed.
-# - Linux                   /dev/ttyS%d (confirmed)
-# - cygwin/win32            /dev/com%d (confirmed)
-# - openbsd (OpenBSD)       /dev/cua%02d
-# - bsd*, freebsd*          /dev/cuad%d
-# - darwin (OS X)           /dev/cuad%d
-# - netbsd                  /dev/dty%02d (NetBSD 1.6 testing by Erk)
-# - irix (IRIX)             /dev/ttyf%d (partially tested) names depending on flow control
-# - hp (HP-UX)              /dev/tty%dp0 (not tested)
-# - sunos (Solaris/SunOS)   /dev/tty%c (letters, 'a'..'z') (confirmed)
-# - aix (AIX)               /dev/tty%d
-
-
-from __future__ import absolute_import
-
-# pylint: disable=abstract-method
-import errno
-import fcntl
-import os
-import select
-import struct
-import sys
-import termios
-
-import serial
-from serial.serialutil import SerialBase, SerialException, to_bytes, \
-    PortNotOpenError, SerialTimeoutException, Timeout
-
-
-class PlatformSpecificBase(object):
-    BAUDRATE_CONSTANTS = {}
-
-    def _set_special_baudrate(self, baudrate):
-        raise NotImplementedError('non-standard baudrates are not supported on this platform')
-
-    def _set_rs485_mode(self, rs485_settings):
-        raise NotImplementedError('RS485 not supported on this platform')
-
-    def set_low_latency_mode(self, low_latency_settings):
-        raise NotImplementedError('Low latency not supported on this platform')
-
-    def _update_break_state(self):
-        """\
-        Set break: Controls TXD. When active, no transmitting is possible.
-        """
-        if self._break_state:
-            fcntl.ioctl(self.fd, TIOCSBRK)
-        else:
-            fcntl.ioctl(self.fd, TIOCCBRK)
-    
-
-# some systems support an extra flag to enable the two in POSIX unsupported
-# paritiy settings for MARK and SPACE
-CMSPAR = 0  # default, for unsupported platforms, override below
-
-# try to detect the OS so that a device can be selected...
-# this code block should supply a device() and set_special_baudrate() function
-# for the platform
-plat = sys.platform.lower()
-
-if plat[:5] == 'linux':    # Linux (confirmed)  # noqa
-    import array
-
-    # extra termios flags
-    CMSPAR = 0o10000000000  # Use "stick" (mark/space) parity
-
-    # baudrate ioctls
-    TCGETS2 = 0x802C542A
-    TCSETS2 = 0x402C542B
-    BOTHER = 0o010000
-
-    # RS485 ioctls
-    TIOCGRS485 = 0x542E
-    TIOCSRS485 = 0x542F
-    SER_RS485_ENABLED = 0b00000001
-    SER_RS485_RTS_ON_SEND = 0b00000010
-    SER_RS485_RTS_AFTER_SEND = 0b00000100
-    SER_RS485_RX_DURING_TX = 0b00010000
-
-    class PlatformSpecific(PlatformSpecificBase):
-        BAUDRATE_CONSTANTS = {
-            0:       0o000000,  # hang up
-            50:      0o000001,
-            75:      0o000002,
-            110:     0o000003,
-            134:     0o000004,
-            150:     0o000005,
-            200:     0o000006,
-            300:     0o000007,
-            600:     0o000010,
-            1200:    0o000011,
-            1800:    0o000012,
-            2400:    0o000013,
-            4800:    0o000014,
-            9600:    0o000015,
-            19200:   0o000016,
-            38400:   0o000017,
-            57600:   0o010001,
-            115200:  0o010002,
-            230400:  0o010003,
-            460800:  0o010004,
-            500000:  0o010005,
-            576000:  0o010006,
-            921600:  0o010007,
-            1000000: 0o010010,
-            1152000: 0o010011,
-            1500000: 0o010012,
-            2000000: 0o010013,
-            2500000: 0o010014,
-            3000000: 0o010015,
-            3500000: 0o010016,
-            4000000: 0o010017
-        }
-
-        def set_low_latency_mode(self, low_latency_settings):
-            buf = array.array('i', [0] * 32)
-
-            try:
-                # get serial_struct
-                fcntl.ioctl(self.fd, termios.TIOCGSERIAL, buf)
-
-                # set or unset ASYNC_LOW_LATENCY flag
-                if low_latency_settings:
-                    buf[4] |= 0x2000
-                else:
-                    buf[4] &= ~0x2000
-
-                # set serial_struct
-                fcntl.ioctl(self.fd, termios.TIOCSSERIAL, buf)
-            except IOError as e:
-                raise ValueError('Failed to update ASYNC_LOW_LATENCY flag to {}: {}'.format(low_latency_settings, e))
-
-        def _set_special_baudrate(self, baudrate):
-            # right size is 44 on x86_64, allow for some growth
-            buf = array.array('i', [0] * 64)
-            try:
-                # get serial_struct
-                fcntl.ioctl(self.fd, TCGETS2, buf)
-                # set custom speed
-                buf[2] &= ~termios.CBAUD
-                buf[2] |= BOTHER
-                buf[9] = buf[10] = baudrate
-
-                # set serial_struct
-                fcntl.ioctl(self.fd, TCSETS2, buf)
-            except IOError as e:
-                raise ValueError('Failed to set custom baud rate ({}): {}'.format(baudrate, e))
-
-        def _set_rs485_mode(self, rs485_settings):
-            buf = array.array('i', [0] * 8)  # flags, delaytx, delayrx, padding
-            try:
-                fcntl.ioctl(self.fd, TIOCGRS485, buf)
-                buf[0] |= SER_RS485_ENABLED
-                if rs485_settings is not None:
-                    if rs485_settings.loopback:
-                        buf[0] |= SER_RS485_RX_DURING_TX
-                    else:
-                        buf[0] &= ~SER_RS485_RX_DURING_TX
-                    if rs485_settings.rts_level_for_tx:
-                        buf[0] |= SER_RS485_RTS_ON_SEND
-                    else:
-                        buf[0] &= ~SER_RS485_RTS_ON_SEND
-                    if rs485_settings.rts_level_for_rx:
-                        buf[0] |= SER_RS485_RTS_AFTER_SEND
-                    else:
-                        buf[0] &= ~SER_RS485_RTS_AFTER_SEND
-                    if rs485_settings.delay_before_tx is not None:
-                        buf[1] = int(rs485_settings.delay_before_tx * 1000)
-                    if rs485_settings.delay_before_rx is not None:
-                        buf[2] = int(rs485_settings.delay_before_rx * 1000)
-                else:
-                    buf[0] = 0  # clear SER_RS485_ENABLED
-                fcntl.ioctl(self.fd, TIOCSRS485, buf)
-            except IOError as e:
-                raise ValueError('Failed to set RS485 mode: {}'.format(e))
-
-
-elif plat == 'cygwin':       # cygwin/win32 (confirmed)
-
-    class PlatformSpecific(PlatformSpecificBase):
-        BAUDRATE_CONSTANTS = {
-            128000: 0x01003,
-            256000: 0x01005,
-            500000: 0x01007,
-            576000: 0x01008,
-            921600: 0x01009,
-            1000000: 0x0100a,
-            1152000: 0x0100b,
-            1500000: 0x0100c,
-            2000000: 0x0100d,
-            2500000: 0x0100e,
-            3000000: 0x0100f
-        }
-
-
-elif plat[:6] == 'darwin':   # OS X
-    import array
-    IOSSIOSPEED = 0x80045402  # _IOW('T', 2, speed_t)
-
-    class PlatformSpecific(PlatformSpecificBase):
-        osx_version = os.uname()[2].split('.')
-        TIOCSBRK = 0x2000747B # _IO('t', 123)
-        TIOCCBRK = 0x2000747A # _IO('t', 122)
-
-        # Tiger or above can support arbitrary serial speeds
-        if int(osx_version[0]) >= 8:
-            def _set_special_baudrate(self, baudrate):
-                # use IOKit-specific call to set up high speeds
-                buf = array.array('i', [baudrate])
-                fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1)
-
-        def _update_break_state(self):
-            """\
-            Set break: Controls TXD. When active, no transmitting is possible.
-            """
-            if self._break_state:
-                fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK)
-            else:
-                fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK)
-
-elif plat[:3] == 'bsd' or \
-     plat[:7] == 'freebsd' or \
-     plat[:6] == 'netbsd' or \
-     plat[:7] == 'openbsd':
-
-    class ReturnBaudrate(object):
-        def __getitem__(self, key):
-            return key
-
-    class PlatformSpecific(PlatformSpecificBase):
-        # Only tested on FreeBSD:
-        # The baud rate may be passed in as
-        # a literal value.
-        BAUDRATE_CONSTANTS = ReturnBaudrate()
-
-        TIOCSBRK = 0x2000747B # _IO('t', 123)
-        TIOCCBRK = 0x2000747A # _IO('t', 122)
-
-        
-        def _update_break_state(self):
-            """\
-            Set break: Controls TXD. When active, no transmitting is possible.
-            """
-            if self._break_state:
-                fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK)
-            else:
-                fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK)
-
-else:
-    class PlatformSpecific(PlatformSpecificBase):
-        pass
-
-
-# load some constants for later use.
-# try to use values from termios, use defaults from linux otherwise
-TIOCMGET = getattr(termios, 'TIOCMGET', 0x5415)
-TIOCMBIS = getattr(termios, 'TIOCMBIS', 0x5416)
-TIOCMBIC = getattr(termios, 'TIOCMBIC', 0x5417)
-TIOCMSET = getattr(termios, 'TIOCMSET', 0x5418)
-
-# TIOCM_LE = getattr(termios, 'TIOCM_LE', 0x001)
-TIOCM_DTR = getattr(termios, 'TIOCM_DTR', 0x002)
-TIOCM_RTS = getattr(termios, 'TIOCM_RTS', 0x004)
-# TIOCM_ST = getattr(termios, 'TIOCM_ST', 0x008)
-# TIOCM_SR = getattr(termios, 'TIOCM_SR', 0x010)
-
-TIOCM_CTS = getattr(termios, 'TIOCM_CTS', 0x020)
-TIOCM_CAR = getattr(termios, 'TIOCM_CAR', 0x040)
-TIOCM_RNG = getattr(termios, 'TIOCM_RNG', 0x080)
-TIOCM_DSR = getattr(termios, 'TIOCM_DSR', 0x100)
-TIOCM_CD = getattr(termios, 'TIOCM_CD', TIOCM_CAR)
-TIOCM_RI = getattr(termios, 'TIOCM_RI', TIOCM_RNG)
-# TIOCM_OUT1 = getattr(termios, 'TIOCM_OUT1', 0x2000)
-# TIOCM_OUT2 = getattr(termios, 'TIOCM_OUT2', 0x4000)
-if hasattr(termios, 'TIOCINQ'):
-    TIOCINQ = termios.TIOCINQ
-else:
-    TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
-TIOCOUTQ = getattr(termios, 'TIOCOUTQ', 0x5411)
-
-TIOCM_zero_str = struct.pack('I', 0)
-TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
-TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
-
-TIOCSBRK = getattr(termios, 'TIOCSBRK', 0x5427)
-TIOCCBRK = getattr(termios, 'TIOCCBRK', 0x5428)
-
-
-class Serial(SerialBase, PlatformSpecific):
-    """\
-    Serial port class POSIX implementation. Serial port configuration is
-    done with termios and fcntl. Runs on Linux and many other Un*x like
-    systems.
-    """
-
-    def open(self):
-        """\
-        Open port with current settings. This may throw a SerialException
-        if the port cannot be opened."""
-        if self._port is None:
-            raise SerialException("Port must be configured before it can be used.")
-        if self.is_open:
-            raise SerialException("Port is already open.")
-        self.fd = None
-        # open
-        try:
-            self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
-        except OSError as msg:
-            self.fd = None
-            raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
-        #~ fcntl.fcntl(self.fd, fcntl.F_SETFL, 0)  # set blocking
-
-        self.pipe_abort_read_r, self.pipe_abort_read_w = None, None
-        self.pipe_abort_write_r, self.pipe_abort_write_w = None, None
-
-        try:
-            self._reconfigure_port(force_update=True)
-
-            try:
-                if not self._dsrdtr:
-                    self._update_dtr_state()
-                if not self._rtscts:
-                    self._update_rts_state()
-            except IOError as e:
-                # ignore Invalid argument and Inappropriate ioctl
-                if e.errno not in (errno.EINVAL, errno.ENOTTY):
-                    raise
-
-            self._reset_input_buffer()
-
-            self.pipe_abort_read_r, self.pipe_abort_read_w = os.pipe()
-            self.pipe_abort_write_r, self.pipe_abort_write_w = os.pipe()
-            fcntl.fcntl(self.pipe_abort_read_r, fcntl.F_SETFL, os.O_NONBLOCK)
-            fcntl.fcntl(self.pipe_abort_write_r, fcntl.F_SETFL, os.O_NONBLOCK)
-        except BaseException:
-            try:
-                os.close(self.fd)
-            except Exception:
-                # ignore any exception when closing the port
-                # also to keep original exception that happened when setting up
-                pass
-            self.fd = None
-
-            if self.pipe_abort_read_w is not None:
-                os.close(self.pipe_abort_read_w)
-                self.pipe_abort_read_w = None
-            if self.pipe_abort_read_r is not None:
-                os.close(self.pipe_abort_read_r)
-                self.pipe_abort_read_r = None
-            if self.pipe_abort_write_w is not None:
-                os.close(self.pipe_abort_write_w)
-                self.pipe_abort_write_w = None
-            if self.pipe_abort_write_r is not None:
-                os.close(self.pipe_abort_write_r)
-                self.pipe_abort_write_r = None
-
-            raise
-
-        self.is_open = True
-
-    def _reconfigure_port(self, force_update=False):
-        """Set communication parameters on opened port."""
-        if self.fd is None:
-            raise SerialException("Can only operate on a valid file descriptor")
-
-        # if exclusive lock is requested, create it before we modify anything else
-        if self._exclusive is not None:
-            if self._exclusive:
-                try:
-                    fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
-                except IOError as msg:
-                    raise SerialException(msg.errno, "Could not exclusively lock port {}: {}".format(self._port, msg))
-            else:
-                fcntl.flock(self.fd, fcntl.LOCK_UN)
-
-        custom_baud = None
-
-        vmin = vtime = 0                # timeout is done via select
-        if self._inter_byte_timeout is not None:
-            vmin = 1
-            vtime = int(self._inter_byte_timeout * 10)
-        try:
-            orig_attr = termios.tcgetattr(self.fd)
-            iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
-        except termios.error as msg:      # if a port is nonexistent but has a /dev file, it'll fail here
-            raise SerialException("Could not configure port: {}".format(msg))
-        # set up raw mode / no echo / binary
-        cflag |= (termios.CLOCAL | termios.CREAD)
-        lflag &= ~(termios.ICANON | termios.ECHO | termios.ECHOE |
-                   termios.ECHOK | termios.ECHONL |
-                   termios.ISIG | termios.IEXTEN)  # |termios.ECHOPRT
-        for flag in ('ECHOCTL', 'ECHOKE'):  # netbsd workaround for Erk
-            if hasattr(termios, flag):
-                lflag &= ~getattr(termios, flag)
-
-        oflag &= ~(termios.OPOST | termios.ONLCR | termios.OCRNL)
-        iflag &= ~(termios.INLCR | termios.IGNCR | termios.ICRNL | termios.IGNBRK)
-        if hasattr(termios, 'IUCLC'):
-            iflag &= ~termios.IUCLC
-        if hasattr(termios, 'PARMRK'):
-            iflag &= ~termios.PARMRK
-
-        # setup baud rate
-        try:
-            ispeed = ospeed = getattr(termios, 'B{}'.format(self._baudrate))
-        except AttributeError:
-            try:
-                ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate]
-            except KeyError:
-                #~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
-
-                # See if BOTHER is defined for this platform; if it is, use
-                # this for a speed not defined in the baudrate constants list.
-                try:
-                    ispeed = ospeed = BOTHER
-                except NameError:
-                    # may need custom baud rate, it isn't in our list.
-                    ispeed = ospeed = getattr(termios, 'B38400')
-
-                try:
-                    custom_baud = int(self._baudrate)  # store for later
-                except ValueError:
-                    raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate))
-                else:
-                    if custom_baud < 0:
-                        raise ValueError('Invalid baud rate: {!r}'.format(self._baudrate))
-
-        # setup char len
-        cflag &= ~termios.CSIZE
-        if self._bytesize == 8:
-            cflag |= termios.CS8
-        elif self._bytesize == 7:
-            cflag |= termios.CS7
-        elif self._bytesize == 6:
-            cflag |= termios.CS6
-        elif self._bytesize == 5:
-            cflag |= termios.CS5
-        else:
-            raise ValueError('Invalid char len: {!r}'.format(self._bytesize))
-        # setup stop bits
-        if self._stopbits == serial.STOPBITS_ONE:
-            cflag &= ~(termios.CSTOPB)
-        elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
-            cflag |= (termios.CSTOPB)  # XXX same as TWO.. there is no POSIX support for 1.5
-        elif self._stopbits == serial.STOPBITS_TWO:
-            cflag |= (termios.CSTOPB)
-        else:
-            raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits))
-        # setup parity
-        iflag &= ~(termios.INPCK | termios.ISTRIP)
-        if self._parity == serial.PARITY_NONE:
-            cflag &= ~(termios.PARENB | termios.PARODD | CMSPAR)
-        elif self._parity == serial.PARITY_EVEN:
-            cflag &= ~(termios.PARODD | CMSPAR)
-            cflag |= (termios.PARENB)
-        elif self._parity == serial.PARITY_ODD:
-            cflag &= ~CMSPAR
-            cflag |= (termios.PARENB | termios.PARODD)
-        elif self._parity == serial.PARITY_MARK and CMSPAR:
-            cflag |= (termios.PARENB | CMSPAR | termios.PARODD)
-        elif self._parity == serial.PARITY_SPACE and CMSPAR:
-            cflag |= (termios.PARENB | CMSPAR)
-            cflag &= ~(termios.PARODD)
-        else:
-            raise ValueError('Invalid parity: {!r}'.format(self._parity))
-        # setup flow control
-        # xonxoff
-        if hasattr(termios, 'IXANY'):
-            if self._xonxoff:
-                iflag |= (termios.IXON | termios.IXOFF)  # |termios.IXANY)
-            else:
-                iflag &= ~(termios.IXON | termios.IXOFF | termios.IXANY)
-        else:
-            if self._xonxoff:
-                iflag |= (termios.IXON | termios.IXOFF)
-            else:
-                iflag &= ~(termios.IXON | termios.IXOFF)
-        # rtscts
-        if hasattr(termios, 'CRTSCTS'):
-            if self._rtscts:
-                cflag |= (termios.CRTSCTS)
-            else:
-                cflag &= ~(termios.CRTSCTS)
-        elif hasattr(termios, 'CNEW_RTSCTS'):   # try it with alternate constant name
-            if self._rtscts:
-                cflag |= (termios.CNEW_RTSCTS)
-            else:
-                cflag &= ~(termios.CNEW_RTSCTS)
-        # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
-
-        # buffer
-        # vmin "minimal number of characters to be read. 0 for non blocking"
-        if vmin < 0 or vmin > 255:
-            raise ValueError('Invalid vmin: {!r}'.format(vmin))
-        cc[termios.VMIN] = vmin
-        # vtime
-        if vtime < 0 or vtime > 255:
-            raise ValueError('Invalid vtime: {!r}'.format(vtime))
-        cc[termios.VTIME] = vtime
-        # activate settings
-        if force_update or [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr:
-            termios.tcsetattr(
-                self.fd,
-                termios.TCSANOW,
-                [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
-
-        # apply custom baud rate, if any
-        if custom_baud is not None:
-            self._set_special_baudrate(custom_baud)
-
-        if self._rs485_mode is not None:
-            self._set_rs485_mode(self._rs485_mode)
-
-    def close(self):
-        """Close port"""
-        if self.is_open:
-            if self.fd is not None:
-                os.close(self.fd)
-                self.fd = None
-                os.close(self.pipe_abort_read_w)
-                os.close(self.pipe_abort_read_r)
-                os.close(self.pipe_abort_write_w)
-                os.close(self.pipe_abort_write_r)
-                self.pipe_abort_read_r, self.pipe_abort_read_w = None, None
-                self.pipe_abort_write_r, self.pipe_abort_write_w = None, None
-            self.is_open = False
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    @property
-    def in_waiting(self):
-        """Return the number of bytes currently in the input buffer."""
-        #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str)
-        s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
-        return struct.unpack('I', s)[0]
-
-    # select based implementation, proved to work on many systems
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        read = bytearray()
-        timeout = Timeout(self._timeout)
-        while len(read) < size:
-            try:
-                ready, _, _ = select.select([self.fd, self.pipe_abort_read_r], [], [], timeout.time_left())
-                if self.pipe_abort_read_r in ready:
-                    os.read(self.pipe_abort_read_r, 1000)
-                    break
-                # If select was used with a timeout, and the timeout occurs, it
-                # returns with empty lists -> thus abort read operation.
-                # For timeout == 0 (non-blocking operation) also abort when
-                # there is nothing to read.
-                if not ready:
-                    break   # timeout
-                buf = os.read(self.fd, size - len(read))
-            except OSError as e:
-                # this is for Python 3.x where select.error is a subclass of
-                # OSError ignore BlockingIOErrors and EINTR. other errors are shown
-                # https://www.python.org/dev/peps/pep-0475.
-                if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
-                    raise SerialException('read failed: {}'.format(e))
-            except select.error as e:
-                # this is for Python 2.x
-                # ignore BlockingIOErrors and EINTR. all errors are shown
-                # see also http://www.python.org/dev/peps/pep-3151/#select
-                if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
-                    raise SerialException('read failed: {}'.format(e))
-            else:
-                # read should always return some data as select reported it was
-                # ready to read when we get to this point.
-                if not buf:
-                    # Disconnected devices, at least on Linux, show the
-                    # behavior that they are always ready to read immediately
-                    # but reading returns nothing.
-                    raise SerialException(
-                        'device reports readiness to read but returned no data '
-                        '(device disconnected or multiple access on port?)')
-                read.extend(buf)
-
-            if timeout.expired():
-                break
-        return bytes(read)
-
-    def cancel_read(self):
-        if self.is_open:
-            os.write(self.pipe_abort_read_w, b"x")
-
-    def cancel_write(self):
-        if self.is_open:
-            os.write(self.pipe_abort_write_w, b"x")
-
-    def write(self, data):
-        """Output the given byte string over the serial port."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        d = to_bytes(data)
-        tx_len = length = len(d)
-        timeout = Timeout(self._write_timeout)
-        while tx_len > 0:
-            try:
-                n = os.write(self.fd, d)
-                if timeout.is_non_blocking:
-                    # Zero timeout indicates non-blocking - simply return the
-                    # number of bytes of data actually written
-                    return n
-                elif not timeout.is_infinite:
-                    # when timeout is set, use select to wait for being ready
-                    # with the time left as timeout
-                    if timeout.expired():
-                        raise SerialTimeoutException('Write timeout')
-                    abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left())
-                    if abort:
-                        os.read(self.pipe_abort_write_r, 1000)
-                        break
-                    if not ready:
-                        raise SerialTimeoutException('Write timeout')
-                else:
-                    assert timeout.time_left() is None
-                    # wait for write operation
-                    abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], None)
-                    if abort:
-                        os.read(self.pipe_abort_write_r, 1)
-                        break
-                    if not ready:
-                        raise SerialException('write failed (select)')
-                d = d[n:]
-                tx_len -= n
-            except SerialException:
-                raise
-            except OSError as e:
-                # this is for Python 3.x where select.error is a subclass of
-                # OSError ignore BlockingIOErrors and EINTR. other errors are shown
-                # https://www.python.org/dev/peps/pep-0475.
-                if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
-                    raise SerialException('write failed: {}'.format(e))
-            except select.error as e:
-                # this is for Python 2.x
-                # ignore BlockingIOErrors and EINTR. all errors are shown
-                # see also http://www.python.org/dev/peps/pep-3151/#select
-                if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
-                    raise SerialException('write failed: {}'.format(e))
-            if not timeout.is_non_blocking and timeout.expired():
-                raise SerialTimeoutException('Write timeout')
-        return length - len(d)
-
-    def flush(self):
-        """\
-        Flush of file like objects. In this case, wait until all data
-        is written.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        termios.tcdrain(self.fd)
-
-    def _reset_input_buffer(self):
-        """Clear input buffer, discarding all that is in the buffer."""
-        termios.tcflush(self.fd, termios.TCIFLUSH)
-
-    def reset_input_buffer(self):
-        """Clear input buffer, discarding all that is in the buffer."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._reset_input_buffer()
-
-    def reset_output_buffer(self):
-        """\
-        Clear output buffer, aborting the current output and discarding all
-        that is in the buffer.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        termios.tcflush(self.fd, termios.TCOFLUSH)
-
-    def send_break(self, duration=0.25):
-        """\
-        Send break condition. Timed, returns to idle state after given
-        duration.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        termios.tcsendbreak(self.fd, int(duration / 0.25))
-
-    def _update_rts_state(self):
-        """Set terminal status line: Request To Send"""
-        if self._rts_state:
-            fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str)
-        else:
-            fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str)
-
-    def _update_dtr_state(self):
-        """Set terminal status line: Data Terminal Ready"""
-        if self._dtr_state:
-            fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str)
-        else:
-            fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str)
-
-    @property
-    def cts(self):
-        """Read terminal status line: Clear To Send"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
-        return struct.unpack('I', s)[0] & TIOCM_CTS != 0
-
-    @property
-    def dsr(self):
-        """Read terminal status line: Data Set Ready"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
-        return struct.unpack('I', s)[0] & TIOCM_DSR != 0
-
-    @property
-    def ri(self):
-        """Read terminal status line: Ring Indicator"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
-        return struct.unpack('I', s)[0] & TIOCM_RI != 0
-
-    @property
-    def cd(self):
-        """Read terminal status line: Carrier Detect"""
-        if not self.is_open:
-            raise PortNotOpenError()
-        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
-        return struct.unpack('I', s)[0] & TIOCM_CD != 0
-
-    # - - platform specific - - - -
-
-    @property
-    def out_waiting(self):
-        """Return the number of bytes currently in the output buffer."""
-        #~ s = fcntl.ioctl(self.fd, termios.FIONREAD, TIOCM_zero_str)
-        s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str)
-        return struct.unpack('I', s)[0]
-
-    def fileno(self):
-        """\
-        For easier use of the serial port instance with select.
-        WARNING: this function is not portable to different platforms!
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        return self.fd
-
-    def set_input_flow_control(self, enable=True):
-        """\
-        Manually control flow - when software flow control is enabled.
-        This will send XON (true) or XOFF (false) to the other device.
-        WARNING: this function is not portable to different platforms!
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        if enable:
-            termios.tcflow(self.fd, termios.TCION)
-        else:
-            termios.tcflow(self.fd, termios.TCIOFF)
-
-    def set_output_flow_control(self, enable=True):
-        """\
-        Manually control flow of outgoing data - when hardware or software flow
-        control is enabled.
-        WARNING: this function is not portable to different platforms!
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        if enable:
-            termios.tcflow(self.fd, termios.TCOON)
-        else:
-            termios.tcflow(self.fd, termios.TCOOFF)
-
-    def nonblocking(self):
-        """DEPRECATED - has no use"""
-        import warnings
-        warnings.warn("nonblocking() has no effect, already nonblocking", DeprecationWarning)
-
-
-class PosixPollSerial(Serial):
-    """\
-    Poll based read implementation. Not all systems support poll properly.
-    However this one has better handling of errors, such as a device
-    disconnecting while it's in use (e.g. USB-serial unplugged).
-    """
-
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        read = bytearray()
-        timeout = Timeout(self._timeout)
-        poll = select.poll()
-        poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
-        poll.register(self.pipe_abort_read_r, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
-        if size > 0:
-            while len(read) < size:
-                # print "\tread(): size",size, "have", len(read)    #debug
-                # wait until device becomes ready to read (or something fails)
-                for fd, event in poll.poll(None if timeout.is_infinite else (timeout.time_left() * 1000)):
-                    if fd == self.pipe_abort_read_r:
-                        break
-                    if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
-                        raise SerialException('device reports error (poll)')
-                    #  we don't care if it is select.POLLIN or timeout, that's
-                    #  handled below
-                if fd == self.pipe_abort_read_r:
-                    os.read(self.pipe_abort_read_r, 1000)
-                    break
-                buf = os.read(self.fd, size - len(read))
-                read.extend(buf)
-                if timeout.expired() \
-                        or (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0) and not buf:
-                    break   # early abort on timeout
-        return bytes(read)
-
-
-class VTIMESerial(Serial):
-    """\
-    Implement timeout using vtime of tty device instead of using select.
-    This means that no inter character timeout can be specified and that
-    the error handling is degraded.
-
-    Overall timeout is disabled when inter-character timeout is used.
-
-    Note that this implementation does NOT support cancel_read(), it will
-    just ignore that.
-    """
-
-    def _reconfigure_port(self, force_update=True):
-        """Set communication parameters on opened port."""
-        super(VTIMESerial, self)._reconfigure_port()
-        fcntl.fcntl(self.fd, fcntl.F_SETFL, 0)  # clear O_NONBLOCK
-
-        if self._inter_byte_timeout is not None:
-            vmin = 1
-            vtime = int(self._inter_byte_timeout * 10)
-        elif self._timeout is None:
-            vmin = 1
-            vtime = 0
-        else:
-            vmin = 0
-            vtime = int(self._timeout * 10)
-        try:
-            orig_attr = termios.tcgetattr(self.fd)
-            iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
-        except termios.error as msg:      # if a port is nonexistent but has a /dev file, it'll fail here
-            raise serial.SerialException("Could not configure port: {}".format(msg))
-
-        if vtime < 0 or vtime > 255:
-            raise ValueError('Invalid vtime: {!r}'.format(vtime))
-        cc[termios.VTIME] = vtime
-        cc[termios.VMIN] = vmin
-
-        termios.tcsetattr(
-                self.fd,
-                termios.TCSANOW,
-                [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
-
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        read = bytearray()
-        while len(read) < size:
-            buf = os.read(self.fd, size - len(read))
-            if not buf:
-                break
-            read.extend(buf)
-        return bytes(read)
-
-    # hack to make hasattr return false
-    cancel_read = property()
diff --git a/modbus/serial/serialutil.py b/modbus/serial/serialutil.py
deleted file mode 100644
index 789219e..0000000
--- a/modbus/serial/serialutil.py
+++ /dev/null
@@ -1,697 +0,0 @@
-#! python
-#
-# Base class and support functions used by various backends.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2001-2020 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-import io
-import time
-
-# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
-# isn't returning the contents (very unfortunate). Therefore we need special
-# cases and test for it. Ensure that there is a ``memoryview`` object for older
-# Python versions. This is easier than making every test dependent on its
-# existence.
-try:
-    memoryview
-except (NameError, AttributeError):
-    # implementation does not matter as we do not really use it.
-    # it just must not inherit from something else we might care for.
-    class memoryview(object):   # pylint: disable=redefined-builtin,invalid-name
-        pass
-
-try:
-    unicode
-except (NameError, AttributeError):
-    unicode = str       # for Python 3, pylint: disable=redefined-builtin,invalid-name
-
-try:
-    basestring
-except (NameError, AttributeError):
-    basestring = (str,)    # for Python 3, pylint: disable=redefined-builtin,invalid-name
-
-
-# "for byte in data" fails for python3 as it returns ints instead of bytes
-def iterbytes(b):
-    """Iterate over bytes, returning bytes instead of ints (python3)"""
-    if isinstance(b, memoryview):
-        b = b.tobytes()
-    i = 0
-    while True:
-        a = b[i:i + 1]
-        i += 1
-        if a:
-            yield a
-        else:
-            break
-
-
-# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
-# so a simple ``bytes(sequence)`` doesn't work for all versions
-def to_bytes(seq):
-    """convert a sequence to a bytes type"""
-    if isinstance(seq, bytes):
-        return seq
-    elif isinstance(seq, bytearray):
-        return bytes(seq)
-    elif isinstance(seq, memoryview):
-        return seq.tobytes()
-    elif isinstance(seq, unicode):
-        raise TypeError('unicode strings are not supported, please encode to bytes: {!r}'.format(seq))
-    else:
-        # handle list of integers and bytes (one or more items) for Python 2 and 3
-        return bytes(bytearray(seq))
-
-
-# create control bytes
-XON = to_bytes([17])
-XOFF = to_bytes([19])
-
-CR = to_bytes([13])
-LF = to_bytes([10])
-
-
-PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
-STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
-FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
-
-PARITY_NAMES = {
-    PARITY_NONE: 'None',
-    PARITY_EVEN: 'Even',
-    PARITY_ODD: 'Odd',
-    PARITY_MARK: 'Mark',
-    PARITY_SPACE: 'Space',
-}
-
-
-class SerialException(IOError):
-    """Base class for serial port related exceptions."""
-
-
-class SerialTimeoutException(SerialException):
-    """Write timeouts give an exception"""
-
-
-class PortNotOpenError(SerialException):
-    """Port is not open"""
-    def __init__(self):
-        super(PortNotOpenError, self).__init__('Attempting to use a port that is not open')
-
-
-class Timeout(object):
-    """\
-    Abstraction for timeout operations. Using time.monotonic() if available
-    or time.time() in all other cases.
-
-    The class can also be initialized with 0 or None, in order to support
-    non-blocking and fully blocking I/O operations. The attributes
-    is_non_blocking and is_infinite are set accordingly.
-    """
-    if hasattr(time, 'monotonic'):
-        # Timeout implementation with time.monotonic(). This function is only
-        # supported by Python 3.3 and above. It returns a time in seconds
-        # (float) just as time.time(), but is not affected by system clock
-        # adjustments.
-        TIME = time.monotonic
-    else:
-        # Timeout implementation with time.time(). This is compatible with all
-        # Python versions but has issues if the clock is adjusted while the
-        # timeout is running.
-        TIME = time.time
-
-    def __init__(self, duration):
-        """Initialize a timeout with given duration"""
-        self.is_infinite = (duration is None)
-        self.is_non_blocking = (duration == 0)
-        self.duration = duration
-        if duration is not None:
-            self.target_time = self.TIME() + duration
-        else:
-            self.target_time = None
-
-    def expired(self):
-        """Return a boolean, telling if the timeout has expired"""
-        return self.target_time is not None and self.time_left() <= 0
-
-    def time_left(self):
-        """Return how many seconds are left until the timeout expires"""
-        if self.is_non_blocking:
-            return 0
-        elif self.is_infinite:
-            return None
-        else:
-            delta = self.target_time - self.TIME()
-            if delta > self.duration:
-                # clock jumped, recalculate
-                self.target_time = self.TIME() + self.duration
-                return self.duration
-            else:
-                return max(0, delta)
-
-    def restart(self, duration):
-        """\
-        Restart a timeout, only supported if a timeout was already set up
-        before.
-        """
-        self.duration = duration
-        self.target_time = self.TIME() + duration
-
-
-class SerialBase(io.RawIOBase):
-    """\
-    Serial port base class. Provides __init__ function and properties to
-    get/set port settings.
-    """
-
-    # default values, may be overridden in subclasses that do not support all values
-    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
-                 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
-                 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
-                 3000000, 3500000, 4000000)
-    BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
-    PARITIES = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
-    STOPBITS = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
-
-    def __init__(self,
-                 port=None,
-                 baudrate=9600,
-                 bytesize=EIGHTBITS,
-                 parity=PARITY_NONE,
-                 stopbits=STOPBITS_ONE,
-                 timeout=None,
-                 xonxoff=False,
-                 rtscts=False,
-                 write_timeout=None,
-                 dsrdtr=False,
-                 inter_byte_timeout=None,
-                 exclusive=None,
-                 **kwargs):
-        """\
-        Initialize comm port object. If a "port" is given, then the port will be
-        opened immediately. Otherwise a Serial port object in closed state
-        is returned.
-        """
-
-        self.is_open = False
-        self.portstr = None
-        self.name = None
-        # correct values are assigned below through properties
-        self._port = None
-        self._baudrate = None
-        self._bytesize = None
-        self._parity = None
-        self._stopbits = None
-        self._timeout = None
-        self._write_timeout = None
-        self._xonxoff = None
-        self._rtscts = None
-        self._dsrdtr = None
-        self._inter_byte_timeout = None
-        self._rs485_mode = None  # disabled by default
-        self._rts_state = True
-        self._dtr_state = True
-        self._break_state = False
-        self._exclusive = None
-
-        # assign values using get/set methods using the properties feature
-        self.port = port
-        self.baudrate = baudrate
-        self.bytesize = bytesize
-        self.parity = parity
-        self.stopbits = stopbits
-        self.timeout = timeout
-        self.write_timeout = write_timeout
-        self.xonxoff = xonxoff
-        self.rtscts = rtscts
-        self.dsrdtr = dsrdtr
-        self.inter_byte_timeout = inter_byte_timeout
-        self.exclusive = exclusive
-
-        # watch for backward compatible kwargs
-        if 'writeTimeout' in kwargs:
-            self.write_timeout = kwargs.pop('writeTimeout')
-        if 'interCharTimeout' in kwargs:
-            self.inter_byte_timeout = kwargs.pop('interCharTimeout')
-        if kwargs:
-            raise ValueError('unexpected keyword arguments: {!r}'.format(kwargs))
-
-        if port is not None:
-            self.open()
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    # to be implemented by subclasses:
-    # def open(self):
-    # def close(self):
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    @property
-    def port(self):
-        """\
-        Get the current port setting. The value that was passed on init or using
-        setPort() is passed back.
-        """
-        return self._port
-
-    @port.setter
-    def port(self, port):
-        """\
-        Change the port.
-        """
-        if port is not None and not isinstance(port, basestring):
-            raise ValueError('"port" must be None or a string, not {}'.format(type(port)))
-        was_open = self.is_open
-        if was_open:
-            self.close()
-        self.portstr = port
-        self._port = port
-        self.name = self.portstr
-        if was_open:
-            self.open()
-
-    @property
-    def baudrate(self):
-        """Get the current baud rate setting."""
-        return self._baudrate
-
-    @baudrate.setter
-    def baudrate(self, baudrate):
-        """\
-        Change baud rate. It raises a ValueError if the port is open and the
-        baud rate is not possible. If the port is closed, then the value is
-        accepted and the exception is raised when the port is opened.
-        """
-        try:
-            b = int(baudrate)
-        except TypeError:
-            raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
-        else:
-            if b < 0:
-                raise ValueError("Not a valid baudrate: {!r}".format(baudrate))
-            self._baudrate = b
-            if self.is_open:
-                self._reconfigure_port()
-
-    @property
-    def bytesize(self):
-        """Get the current byte size setting."""
-        return self._bytesize
-
-    @bytesize.setter
-    def bytesize(self, bytesize):
-        """Change byte size."""
-        if bytesize not in self.BYTESIZES:
-            raise ValueError("Not a valid byte size: {!r}".format(bytesize))
-        self._bytesize = bytesize
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def exclusive(self):
-        """Get the current exclusive access setting."""
-        return self._exclusive
-
-    @exclusive.setter
-    def exclusive(self, exclusive):
-        """Change the exclusive access setting."""
-        self._exclusive = exclusive
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def parity(self):
-        """Get the current parity setting."""
-        return self._parity
-
-    @parity.setter
-    def parity(self, parity):
-        """Change parity setting."""
-        if parity not in self.PARITIES:
-            raise ValueError("Not a valid parity: {!r}".format(parity))
-        self._parity = parity
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def stopbits(self):
-        """Get the current stop bits setting."""
-        return self._stopbits
-
-    @stopbits.setter
-    def stopbits(self, stopbits):
-        """Change stop bits size."""
-        if stopbits not in self.STOPBITS:
-            raise ValueError("Not a valid stop bit size: {!r}".format(stopbits))
-        self._stopbits = stopbits
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def timeout(self):
-        """Get the current timeout setting."""
-        return self._timeout
-
-    @timeout.setter
-    def timeout(self, timeout):
-        """Change timeout setting."""
-        if timeout is not None:
-            try:
-                timeout + 1     # test if it's a number, will throw a TypeError if not...
-            except TypeError:
-                raise ValueError("Not a valid timeout: {!r}".format(timeout))
-            if timeout < 0:
-                raise ValueError("Not a valid timeout: {!r}".format(timeout))
-        self._timeout = timeout
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def write_timeout(self):
-        """Get the current timeout setting."""
-        return self._write_timeout
-
-    @write_timeout.setter
-    def write_timeout(self, timeout):
-        """Change timeout setting."""
-        if timeout is not None:
-            if timeout < 0:
-                raise ValueError("Not a valid timeout: {!r}".format(timeout))
-            try:
-                timeout + 1     # test if it's a number, will throw a TypeError if not...
-            except TypeError:
-                raise ValueError("Not a valid timeout: {!r}".format(timeout))
-
-        self._write_timeout = timeout
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def inter_byte_timeout(self):
-        """Get the current inter-character timeout setting."""
-        return self._inter_byte_timeout
-
-    @inter_byte_timeout.setter
-    def inter_byte_timeout(self, ic_timeout):
-        """Change inter-byte timeout setting."""
-        if ic_timeout is not None:
-            if ic_timeout < 0:
-                raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
-            try:
-                ic_timeout + 1     # test if it's a number, will throw a TypeError if not...
-            except TypeError:
-                raise ValueError("Not a valid timeout: {!r}".format(ic_timeout))
-
-        self._inter_byte_timeout = ic_timeout
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def xonxoff(self):
-        """Get the current XON/XOFF setting."""
-        return self._xonxoff
-
-    @xonxoff.setter
-    def xonxoff(self, xonxoff):
-        """Change XON/XOFF setting."""
-        self._xonxoff = xonxoff
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def rtscts(self):
-        """Get the current RTS/CTS flow control setting."""
-        return self._rtscts
-
-    @rtscts.setter
-    def rtscts(self, rtscts):
-        """Change RTS/CTS flow control setting."""
-        self._rtscts = rtscts
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def dsrdtr(self):
-        """Get the current DSR/DTR flow control setting."""
-        return self._dsrdtr
-
-    @dsrdtr.setter
-    def dsrdtr(self, dsrdtr=None):
-        """Change DsrDtr flow control setting."""
-        if dsrdtr is None:
-            # if not set, keep backwards compatibility and follow rtscts setting
-            self._dsrdtr = self._rtscts
-        else:
-            # if defined independently, follow its value
-            self._dsrdtr = dsrdtr
-        if self.is_open:
-            self._reconfigure_port()
-
-    @property
-    def rts(self):
-        return self._rts_state
-
-    @rts.setter
-    def rts(self, value):
-        self._rts_state = value
-        if self.is_open:
-            self._update_rts_state()
-
-    @property
-    def dtr(self):
-        return self._dtr_state
-
-    @dtr.setter
-    def dtr(self, value):
-        self._dtr_state = value
-        if self.is_open:
-            self._update_dtr_state()
-
-    @property
-    def break_condition(self):
-        return self._break_state
-
-    @break_condition.setter
-    def break_condition(self, value):
-        self._break_state = value
-        if self.is_open:
-            self._update_break_state()
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-    # functions useful for RS-485 adapters
-
-    @property
-    def rs485_mode(self):
-        """\
-        Enable RS485 mode and apply new settings, set to None to disable.
-        See serial.rs485.RS485Settings for more info about the value.
-        """
-        return self._rs485_mode
-
-    @rs485_mode.setter
-    def rs485_mode(self, rs485_settings):
-        self._rs485_mode = rs485_settings
-        if self.is_open:
-            self._reconfigure_port()
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    _SAVED_SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
-                       'dsrdtr', 'rtscts', 'timeout', 'write_timeout',
-                       'inter_byte_timeout')
-
-    def get_settings(self):
-        """\
-        Get current port settings as a dictionary. For use with
-        apply_settings().
-        """
-        return dict([(key, getattr(self, '_' + key)) for key in self._SAVED_SETTINGS])
-
-    def apply_settings(self, d):
-        """\
-        Apply stored settings from a dictionary returned from
-        get_settings(). It's allowed to delete keys from the dictionary. These
-        values will simply left unchanged.
-        """
-        for key in self._SAVED_SETTINGS:
-            if key in d and d[key] != getattr(self, '_' + key):   # check against internal "_" value
-                setattr(self, key, d[key])          # set non "_" value to use properties write function
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    def __repr__(self):
-        """String representation of the current port settings and its state."""
-        return '{name}(port={p.portstr!r}, ' \
-               'baudrate={p.baudrate!r}, bytesize={p.bytesize!r}, parity={p.parity!r}, ' \
-               'stopbits={p.stopbits!r}, timeout={p.timeout!r}, xonxoff={p.xonxoff!r}, ' \
-               'rtscts={p.rtscts!r}, dsrdtr={p.dsrdtr!r})'.format(
-                   name=self.__class__.__name__, id=id(self), p=self)
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-    # compatibility with io library
-    # pylint: disable=invalid-name,missing-docstring
-
-    def readable(self):
-        return True
-
-    def writable(self):
-        return True
-
-    def seekable(self):
-        return False
-
-    def readinto(self, b):
-        data = self.read(len(b))
-        n = len(data)
-        try:
-            b[:n] = data
-        except TypeError as err:
-            import array
-            if not isinstance(b, array.array):
-                raise err
-            b[:n] = array.array('b', data)
-        return n
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-    # context manager
-
-    def __enter__(self):
-        if self._port is not None and not self.is_open:
-            self.open()
-        return self
-
-    def __exit__(self, *args, **kwargs):
-        self.close()
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    def send_break(self, duration=0.25):
-        """\
-        Send break condition. Timed, returns to idle state after given
-        duration.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        self.break_condition = True
-        time.sleep(duration)
-        self.break_condition = False
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-    # backwards compatibility / deprecated functions
-
-    def flushInput(self):
-        self.reset_input_buffer()
-
-    def flushOutput(self):
-        self.reset_output_buffer()
-
-    def inWaiting(self):
-        return self.in_waiting
-
-    def sendBreak(self, duration=0.25):
-        self.send_break(duration)
-
-    def setRTS(self, value=1):
-        self.rts = value
-
-    def setDTR(self, value=1):
-        self.dtr = value
-
-    def getCTS(self):
-        return self.cts
-
-    def getDSR(self):
-        return self.dsr
-
-    def getRI(self):
-        return self.ri
-
-    def getCD(self):
-        return self.cd
-
-    def setPort(self, port):
-        self.port = port
-
-    @property
-    def writeTimeout(self):
-        return self.write_timeout
-
-    @writeTimeout.setter
-    def writeTimeout(self, timeout):
-        self.write_timeout = timeout
-
-    @property
-    def interCharTimeout(self):
-        return self.inter_byte_timeout
-
-    @interCharTimeout.setter
-    def interCharTimeout(self, interCharTimeout):
-        self.inter_byte_timeout = interCharTimeout
-
-    def getSettingsDict(self):
-        return self.get_settings()
-
-    def applySettingsDict(self, d):
-        self.apply_settings(d)
-
-    def isOpen(self):
-        return self.is_open
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-    # additional functionality
-
-    def read_all(self):
-        """\
-        Read all bytes currently available in the buffer of the OS.
-        """
-        return self.read(self.in_waiting)
-
-    def read_until(self, expected=LF, size=None):
-        """\
-        Read until an expected sequence is found ('\n' by default), the size
-        is exceeded or until timeout occurs.
-        """
-        lenterm = len(expected)
-        line = bytearray()
-        timeout = Timeout(self._timeout)
-        while True:
-            c = self.read(1)
-            if c:
-                line += c
-                if line[-lenterm:] == expected:
-                    break
-                if size is not None and len(line) >= size:
-                    break
-            else:
-                break
-            if timeout.expired():
-                break
-        return bytes(line)
-
-    def iread_until(self, *args, **kwargs):
-        """\
-        Read lines, implemented as generator. It will raise StopIteration on
-        timeout (empty read).
-        """
-        while True:
-            line = self.read_until(*args, **kwargs)
-            if not line:
-                break
-            yield line
-
-
-#  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-if __name__ == '__main__':
-    import sys
-    s = SerialBase()
-    sys.stdout.write('port name:  {}\n'.format(s.name))
-    sys.stdout.write('baud rates: {}\n'.format(s.BAUDRATES))
-    sys.stdout.write('byte sizes: {}\n'.format(s.BYTESIZES))
-    sys.stdout.write('parities:   {}\n'.format(s.PARITIES))
-    sys.stdout.write('stop bits:  {}\n'.format(s.STOPBITS))
-    sys.stdout.write('{}\n'.format(s))
diff --git a/modbus/serial/serialwin32.py b/modbus/serial/serialwin32.py
deleted file mode 100644
index e7da929..0000000
--- a/modbus/serial/serialwin32.py
+++ /dev/null
@@ -1,477 +0,0 @@
-#! python
-#
-# backend for Windows ("win32" incl. 32/64 bit support)
-#
-# (C) 2001-2020 Chris Liechti 
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# SPDX-License-Identifier:    BSD-3-Clause
-#
-# Initial patch to use ctypes by Giovanni Bajo 
-
-from __future__ import absolute_import
-
-# pylint: disable=invalid-name,too-few-public-methods
-import ctypes
-import time
-from serial import win32
-
-import serial
-from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException
-
-
-class Serial(SerialBase):
-    """Serial port implementation for Win32 based on ctypes."""
-
-    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
-                 9600, 19200, 38400, 57600, 115200)
-
-    def __init__(self, *args, **kwargs):
-        self._port_handle = None
-        self._overlapped_read = None
-        self._overlapped_write = None
-        super(Serial, self).__init__(*args, **kwargs)
-
-    def open(self):
-        """\
-        Open port with current settings. This may throw a SerialException
-        if the port cannot be opened.
-        """
-        if self._port is None:
-            raise SerialException("Port must be configured before it can be used.")
-        if self.is_open:
-            raise SerialException("Port is already open.")
-        # the "\\.\COMx" format is required for devices other than COM1-COM8
-        # not all versions of windows seem to support this properly
-        # so that the first few ports are used with the DOS device name
-        port = self.name
-        try:
-            if port.upper().startswith('COM') and int(port[3:]) > 8:
-                port = '\\\\.\\' + port
-        except ValueError:
-            # for like COMnotanumber
-            pass
-        self._port_handle = win32.CreateFile(
-            port,
-            win32.GENERIC_READ | win32.GENERIC_WRITE,
-            0,  # exclusive access
-            None,  # no security
-            win32.OPEN_EXISTING,
-            win32.FILE_ATTRIBUTE_NORMAL | win32.FILE_FLAG_OVERLAPPED,
-            0)
-        if self._port_handle == win32.INVALID_HANDLE_VALUE:
-            self._port_handle = None    # 'cause __del__ is called anyway
-            raise SerialException("could not open port {!r}: {!r}".format(self.portstr, ctypes.WinError()))
-
-        try:
-            self._overlapped_read = win32.OVERLAPPED()
-            self._overlapped_read.hEvent = win32.CreateEvent(None, 1, 0, None)
-            self._overlapped_write = win32.OVERLAPPED()
-            #~ self._overlapped_write.hEvent = win32.CreateEvent(None, 1, 0, None)
-            self._overlapped_write.hEvent = win32.CreateEvent(None, 0, 0, None)
-
-            # Setup a 4k buffer
-            win32.SetupComm(self._port_handle, 4096, 4096)
-
-            # Save original timeout values:
-            self._orgTimeouts = win32.COMMTIMEOUTS()
-            win32.GetCommTimeouts(self._port_handle, ctypes.byref(self._orgTimeouts))
-
-            self._reconfigure_port()
-
-            # Clear buffers:
-            # Remove anything that was there
-            win32.PurgeComm(
-                self._port_handle,
-                win32.PURGE_TXCLEAR | win32.PURGE_TXABORT |
-                win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
-        except:
-            try:
-                self._close()
-            except:
-                # ignore any exception when closing the port
-                # also to keep original exception that happened when setting up
-                pass
-            self._port_handle = None
-            raise
-        else:
-            self.is_open = True
-
-    def _reconfigure_port(self):
-        """Set communication parameters on opened port."""
-        if not self._port_handle:
-            raise SerialException("Can only operate on a valid port handle")
-
-        # Set Windows timeout values
-        # timeouts is a tuple with the following items:
-        # (ReadIntervalTimeout,ReadTotalTimeoutMultiplier,
-        #  ReadTotalTimeoutConstant,WriteTotalTimeoutMultiplier,
-        #  WriteTotalTimeoutConstant)
-        timeouts = win32.COMMTIMEOUTS()
-        if self._timeout is None:
-            pass  # default of all zeros is OK
-        elif self._timeout == 0:
-            timeouts.ReadIntervalTimeout = win32.MAXDWORD
-        else:
-            timeouts.ReadTotalTimeoutConstant = max(int(self._timeout * 1000), 1)
-        if self._timeout != 0 and self._inter_byte_timeout is not None:
-            timeouts.ReadIntervalTimeout = max(int(self._inter_byte_timeout * 1000), 1)
-
-        if self._write_timeout is None:
-            pass
-        elif self._write_timeout == 0:
-            timeouts.WriteTotalTimeoutConstant = win32.MAXDWORD
-        else:
-            timeouts.WriteTotalTimeoutConstant = max(int(self._write_timeout * 1000), 1)
-        win32.SetCommTimeouts(self._port_handle, ctypes.byref(timeouts))
-
-        win32.SetCommMask(self._port_handle, win32.EV_ERR)
-
-        # Setup the connection info.
-        # Get state and modify it:
-        comDCB = win32.DCB()
-        win32.GetCommState(self._port_handle, ctypes.byref(comDCB))
-        comDCB.BaudRate = self._baudrate
-
-        if self._bytesize == serial.FIVEBITS:
-            comDCB.ByteSize = 5
-        elif self._bytesize == serial.SIXBITS:
-            comDCB.ByteSize = 6
-        elif self._bytesize == serial.SEVENBITS:
-            comDCB.ByteSize = 7
-        elif self._bytesize == serial.EIGHTBITS:
-            comDCB.ByteSize = 8
-        else:
-            raise ValueError("Unsupported number of data bits: {!r}".format(self._bytesize))
-
-        if self._parity == serial.PARITY_NONE:
-            comDCB.Parity = win32.NOPARITY
-            comDCB.fParity = 0  # Disable Parity Check
-        elif self._parity == serial.PARITY_EVEN:
-            comDCB.Parity = win32.EVENPARITY
-            comDCB.fParity = 1  # Enable Parity Check
-        elif self._parity == serial.PARITY_ODD:
-            comDCB.Parity = win32.ODDPARITY
-            comDCB.fParity = 1  # Enable Parity Check
-        elif self._parity == serial.PARITY_MARK:
-            comDCB.Parity = win32.MARKPARITY
-            comDCB.fParity = 1  # Enable Parity Check
-        elif self._parity == serial.PARITY_SPACE:
-            comDCB.Parity = win32.SPACEPARITY
-            comDCB.fParity = 1  # Enable Parity Check
-        else:
-            raise ValueError("Unsupported parity mode: {!r}".format(self._parity))
-
-        if self._stopbits == serial.STOPBITS_ONE:
-            comDCB.StopBits = win32.ONESTOPBIT
-        elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
-            comDCB.StopBits = win32.ONE5STOPBITS
-        elif self._stopbits == serial.STOPBITS_TWO:
-            comDCB.StopBits = win32.TWOSTOPBITS
-        else:
-            raise ValueError("Unsupported number of stop bits: {!r}".format(self._stopbits))
-
-        comDCB.fBinary = 1  # Enable Binary Transmission
-        # Char. w/ Parity-Err are replaced with 0xff (if fErrorChar is set to TRUE)
-        if self._rs485_mode is None:
-            if self._rtscts:
-                comDCB.fRtsControl = win32.RTS_CONTROL_HANDSHAKE
-            else:
-                comDCB.fRtsControl = win32.RTS_CONTROL_ENABLE if self._rts_state else win32.RTS_CONTROL_DISABLE
-            comDCB.fOutxCtsFlow = self._rtscts
-        else:
-            # checks for unsupported settings
-            # XXX verify if platform really does not have a setting for those
-            if not self._rs485_mode.rts_level_for_tx:
-                raise ValueError(
-                    'Unsupported value for RS485Settings.rts_level_for_tx: {!r} (only True is allowed)'.format(
-                        self._rs485_mode.rts_level_for_tx,))
-            if self._rs485_mode.rts_level_for_rx:
-                raise ValueError(
-                    'Unsupported value for RS485Settings.rts_level_for_rx: {!r} (only False is allowed)'.format(
-                        self._rs485_mode.rts_level_for_rx,))
-            if self._rs485_mode.delay_before_tx is not None:
-                raise ValueError(
-                    'Unsupported value for RS485Settings.delay_before_tx: {!r} (only None is allowed)'.format(
-                        self._rs485_mode.delay_before_tx,))
-            if self._rs485_mode.delay_before_rx is not None:
-                raise ValueError(
-                    'Unsupported value for RS485Settings.delay_before_rx: {!r} (only None is allowed)'.format(
-                        self._rs485_mode.delay_before_rx,))
-            if self._rs485_mode.loopback:
-                raise ValueError(
-                    'Unsupported value for RS485Settings.loopback: {!r} (only False is allowed)'.format(
-                        self._rs485_mode.loopback,))
-            comDCB.fRtsControl = win32.RTS_CONTROL_TOGGLE
-            comDCB.fOutxCtsFlow = 0
-
-        if self._dsrdtr:
-            comDCB.fDtrControl = win32.DTR_CONTROL_HANDSHAKE
-        else:
-            comDCB.fDtrControl = win32.DTR_CONTROL_ENABLE if self._dtr_state else win32.DTR_CONTROL_DISABLE
-        comDCB.fOutxDsrFlow = self._dsrdtr
-        comDCB.fOutX = self._xonxoff
-        comDCB.fInX = self._xonxoff
-        comDCB.fNull = 0
-        comDCB.fErrorChar = 0
-        comDCB.fAbortOnError = 0
-        comDCB.XonChar = serial.XON
-        comDCB.XoffChar = serial.XOFF
-
-        if not win32.SetCommState(self._port_handle, ctypes.byref(comDCB)):
-            raise SerialException(
-                'Cannot configure port, something went wrong. '
-                'Original message: {!r}'.format(ctypes.WinError()))
-
-    #~ def __del__(self):
-        #~ self.close()
-
-    def _close(self):
-        """internal close port helper"""
-        if self._port_handle is not None:
-            # Restore original timeout values:
-            win32.SetCommTimeouts(self._port_handle, self._orgTimeouts)
-            if self._overlapped_read is not None:
-                self.cancel_read()
-                win32.CloseHandle(self._overlapped_read.hEvent)
-                self._overlapped_read = None
-            if self._overlapped_write is not None:
-                self.cancel_write()
-                win32.CloseHandle(self._overlapped_write.hEvent)
-                self._overlapped_write = None
-            win32.CloseHandle(self._port_handle)
-            self._port_handle = None
-
-    def close(self):
-        """Close port"""
-        if self.is_open:
-            self._close()
-            self.is_open = False
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-
-    @property
-    def in_waiting(self):
-        """Return the number of bytes currently in the input buffer."""
-        flags = win32.DWORD()
-        comstat = win32.COMSTAT()
-        if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
-            raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
-        return comstat.cbInQue
-
-    def read(self, size=1):
-        """\
-        Read size bytes from the serial port. If a timeout is set it may
-        return less characters as requested. With no timeout it will block
-        until the requested number of bytes is read.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        if size > 0:
-            win32.ResetEvent(self._overlapped_read.hEvent)
-            flags = win32.DWORD()
-            comstat = win32.COMSTAT()
-            if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
-                raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
-            n = min(comstat.cbInQue, size) if self.timeout == 0 else size
-            if n > 0:
-                buf = ctypes.create_string_buffer(n)
-                rc = win32.DWORD()
-                read_ok = win32.ReadFile(
-                    self._port_handle,
-                    buf,
-                    n,
-                    ctypes.byref(rc),
-                    ctypes.byref(self._overlapped_read))
-                if not read_ok and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
-                    raise SerialException("ReadFile failed ({!r})".format(ctypes.WinError()))
-                result_ok = win32.GetOverlappedResult(
-                    self._port_handle,
-                    ctypes.byref(self._overlapped_read),
-                    ctypes.byref(rc),
-                    True)
-                if not result_ok:
-                    if win32.GetLastError() != win32.ERROR_OPERATION_ABORTED:
-                        raise SerialException("GetOverlappedResult failed ({!r})".format(ctypes.WinError()))
-                read = buf.raw[:rc.value]
-            else:
-                read = bytes()
-        else:
-            read = bytes()
-        return bytes(read)
-
-    def write(self, data):
-        """Output the given byte string over the serial port."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        #~ if not isinstance(data, (bytes, bytearray)):
-            #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
-        # convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview
-        data = to_bytes(data)
-        if data:
-            #~ win32event.ResetEvent(self._overlapped_write.hEvent)
-            n = win32.DWORD()
-            success = win32.WriteFile(self._port_handle, data, len(data), ctypes.byref(n), self._overlapped_write)
-            if self._write_timeout != 0:  # if blocking (None) or w/ write timeout (>0)
-                if not success and win32.GetLastError() not in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
-                    raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
-
-                # Wait for the write to complete.
-                #~ win32.WaitForSingleObject(self._overlapped_write.hEvent, win32.INFINITE)
-                win32.GetOverlappedResult(self._port_handle, self._overlapped_write, ctypes.byref(n), True)
-                if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:
-                    return n.value  # canceled IO is no error
-                if n.value != len(data):
-                    raise SerialTimeoutException('Write timeout')
-                return n.value
-            else:
-                errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()
-                if errorcode in (win32.ERROR_INVALID_USER_BUFFER, win32.ERROR_NOT_ENOUGH_MEMORY,
-                                 win32.ERROR_OPERATION_ABORTED):
-                    return 0
-                elif errorcode in (win32.ERROR_SUCCESS, win32.ERROR_IO_PENDING):
-                    # no info on true length provided by OS function in async mode
-                    return len(data)
-                else:
-                    raise SerialException("WriteFile failed ({!r})".format(ctypes.WinError()))
-        else:
-            return 0
-
-    def flush(self):
-        """\
-        Flush of file like objects. In this case, wait until all data
-        is written.
-        """
-        while self.out_waiting:
-            time.sleep(0.05)
-        # XXX could also use WaitCommEvent with mask EV_TXEMPTY, but it would
-        # require overlapped IO and it's also only possible to set a single mask
-        # on the port---
-
-    def reset_input_buffer(self):
-        """Clear input buffer, discarding all that is in the buffer."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
-
-    def reset_output_buffer(self):
-        """\
-        Clear output buffer, aborting the current output and discarding all
-        that is in the buffer.
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT)
-
-    def _update_break_state(self):
-        """Set break: Controls TXD. When active, to transmitting is possible."""
-        if not self.is_open:
-            raise PortNotOpenError()
-        if self._break_state:
-            win32.SetCommBreak(self._port_handle)
-        else:
-            win32.ClearCommBreak(self._port_handle)
-
-    def _update_rts_state(self):
-        """Set terminal status line: Request To Send"""
-        if self._rts_state:
-            win32.EscapeCommFunction(self._port_handle, win32.SETRTS)
-        else:
-            win32.EscapeCommFunction(self._port_handle, win32.CLRRTS)
-
-    def _update_dtr_state(self):
-        """Set terminal status line: Data Terminal Ready"""
-        if self._dtr_state:
-            win32.EscapeCommFunction(self._port_handle, win32.SETDTR)
-        else:
-            win32.EscapeCommFunction(self._port_handle, win32.CLRDTR)
-
-    def _GetCommModemStatus(self):
-        if not self.is_open:
-            raise PortNotOpenError()
-        stat = win32.DWORD()
-        win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat))
-        return stat.value
-
-    @property
-    def cts(self):
-        """Read terminal status line: Clear To Send"""
-        return win32.MS_CTS_ON & self._GetCommModemStatus() != 0
-
-    @property
-    def dsr(self):
-        """Read terminal status line: Data Set Ready"""
-        return win32.MS_DSR_ON & self._GetCommModemStatus() != 0
-
-    @property
-    def ri(self):
-        """Read terminal status line: Ring Indicator"""
-        return win32.MS_RING_ON & self._GetCommModemStatus() != 0
-
-    @property
-    def cd(self):
-        """Read terminal status line: Carrier Detect"""
-        return win32.MS_RLSD_ON & self._GetCommModemStatus() != 0
-
-    # - - platform specific - - - -
-
-    def set_buffer_size(self, rx_size=4096, tx_size=None):
-        """\
-        Recommend a buffer size to the driver (device driver can ignore this
-        value). Must be called after the port is opened.
-        """
-        if tx_size is None:
-            tx_size = rx_size
-        win32.SetupComm(self._port_handle, rx_size, tx_size)
-
-    def set_output_flow_control(self, enable=True):
-        """\
-        Manually control flow - when software flow control is enabled.
-        This will do the same as if XON (true) or XOFF (false) are received
-        from the other device and control the transmission accordingly.
-        WARNING: this function is not portable to different platforms!
-        """
-        if not self.is_open:
-            raise PortNotOpenError()
-        if enable:
-            win32.EscapeCommFunction(self._port_handle, win32.SETXON)
-        else:
-            win32.EscapeCommFunction(self._port_handle, win32.SETXOFF)
-
-    @property
-    def out_waiting(self):
-        """Return how many bytes the in the outgoing buffer"""
-        flags = win32.DWORD()
-        comstat = win32.COMSTAT()
-        if not win32.ClearCommError(self._port_handle, ctypes.byref(flags), ctypes.byref(comstat)):
-            raise SerialException("ClearCommError failed ({!r})".format(ctypes.WinError()))
-        return comstat.cbOutQue
-
-    def _cancel_overlapped_io(self, overlapped):
-        """Cancel a blocking read operation, may be called from other thread"""
-        # check if read operation is pending
-        rc = win32.DWORD()
-        err = win32.GetOverlappedResult(
-            self._port_handle,
-            ctypes.byref(overlapped),
-            ctypes.byref(rc),
-            False)
-        if not err and win32.GetLastError() in (win32.ERROR_IO_PENDING, win32.ERROR_IO_INCOMPLETE):
-            # cancel, ignoring any errors (e.g. it may just have finished on its own)
-            win32.CancelIoEx(self._port_handle, overlapped)
-
-    def cancel_read(self):
-        """Cancel a blocking read operation, may be called from other thread"""
-        self._cancel_overlapped_io(self._overlapped_read)
-
-    def cancel_write(self):
-        """Cancel a blocking write operation, may be called from other thread"""
-        self._cancel_overlapped_io(self._overlapped_write)
-
-    @SerialBase.exclusive.setter
-    def exclusive(self, exclusive):
-        """Change the exclusive access setting."""
-        if exclusive is not None and not exclusive:
-            raise ValueError('win32 only supports exclusive access (not: {})'.format(exclusive))
-        else:
-            serial.SerialBase.exclusive.__set__(self, exclusive)
diff --git a/modbus/serial/threaded/__init__.py b/modbus/serial/threaded/__init__.py
deleted file mode 100644
index b8940b6..0000000
--- a/modbus/serial/threaded/__init__.py
+++ /dev/null
@@ -1,297 +0,0 @@
-#!/usr/bin/env python3
-#
-# Working with threading and pySerial
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2015-2016 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-"""\
-Support threading with serial ports.
-"""
-from __future__ import absolute_import
-
-import serial
-import threading
-
-
-class Protocol(object):
-    """\
-    Protocol as used by the ReaderThread. This base class provides empty
-    implementations of all methods.
-    """
-
-    def connection_made(self, transport):
-        """Called when reader thread is started"""
-
-    def data_received(self, data):
-        """Called with snippets received from the serial port"""
-
-    def connection_lost(self, exc):
-        """\
-        Called when the serial port is closed or the reader loop terminated
-        otherwise.
-        """
-        if isinstance(exc, Exception):
-            raise exc
-
-
-class Packetizer(Protocol):
-    """
-    Read binary packets from serial port. Packets are expected to be terminated
-    with a TERMINATOR byte (null byte by default).
-
-    The class also keeps track of the transport.
-    """
-
-    TERMINATOR = b'\0'
-
-    def __init__(self):
-        self.buffer = bytearray()
-        self.transport = None
-
-    def connection_made(self, transport):
-        """Store transport"""
-        self.transport = transport
-
-    def connection_lost(self, exc):
-        """Forget transport"""
-        self.transport = None
-        super(Packetizer, self).connection_lost(exc)
-
-    def data_received(self, data):
-        """Buffer received data, find TERMINATOR, call handle_packet"""
-        self.buffer.extend(data)
-        while self.TERMINATOR in self.buffer:
-            packet, self.buffer = self.buffer.split(self.TERMINATOR, 1)
-            self.handle_packet(packet)
-
-    def handle_packet(self, packet):
-        """Process packets - to be overridden by subclassing"""
-        raise NotImplementedError('please implement functionality in handle_packet')
-
-
-class FramedPacket(Protocol):
-    """
-    Read binary packets. Packets are expected to have a start and stop marker.
-
-    The class also keeps track of the transport.
-    """
-
-    START = b'('
-    STOP = b')'
-
-    def __init__(self):
-        self.packet = bytearray()
-        self.in_packet = False
-        self.transport = None
-
-    def connection_made(self, transport):
-        """Store transport"""
-        self.transport = transport
-
-    def connection_lost(self, exc):
-        """Forget transport"""
-        self.transport = None
-        self.in_packet = False
-        del self.packet[:]
-        super(FramedPacket, self).connection_lost(exc)
-
-    def data_received(self, data):
-        """Find data enclosed in START/STOP, call handle_packet"""
-        for byte in serial.iterbytes(data):
-            if byte == self.START:
-                self.in_packet = True
-            elif byte == self.STOP:
-                self.in_packet = False
-                self.handle_packet(bytes(self.packet)) # make read-only copy
-                del self.packet[:]
-            elif self.in_packet:
-                self.packet.extend(byte)
-            else:
-                self.handle_out_of_packet_data(byte)
-
-    def handle_packet(self, packet):
-        """Process packets - to be overridden by subclassing"""
-        raise NotImplementedError('please implement functionality in handle_packet')
-
-    def handle_out_of_packet_data(self, data):
-        """Process data that is received outside of packets"""
-        pass
-
-
-class LineReader(Packetizer):
-    """
-    Read and write (Unicode) lines from/to serial port.
-    The encoding is applied.
-    """
-
-    TERMINATOR = b'\r\n'
-    ENCODING = 'utf-8'
-    UNICODE_HANDLING = 'replace'
-
-    def handle_packet(self, packet):
-        self.handle_line(packet.decode(self.ENCODING, self.UNICODE_HANDLING))
-
-    def handle_line(self, line):
-        """Process one line - to be overridden by subclassing"""
-        raise NotImplementedError('please implement functionality in handle_line')
-
-    def write_line(self, text):
-        """
-        Write text to the transport. ``text`` is a Unicode string and the encoding
-        is applied before sending ans also the newline is append.
-        """
-        # + is not the best choice but bytes does not support % or .format in py3 and we want a single write call
-        self.transport.write(text.encode(self.ENCODING, self.UNICODE_HANDLING) + self.TERMINATOR)
-
-
-class ReaderThread(threading.Thread):
-    """\
-    Implement a serial port read loop and dispatch to a Protocol instance (like
-    the asyncio.Protocol) but do it with threads.
-
-    Calls to close() will close the serial port but it is also possible to just
-    stop() this thread and continue the serial port instance otherwise.
-    """
-
-    def __init__(self, serial_instance, protocol_factory):
-        """\
-        Initialize thread.
-
-        Note that the serial_instance' timeout is set to one second!
-        Other settings are not changed.
-        """
-        super(ReaderThread, self).__init__()
-        self.daemon = True
-        self.serial = serial_instance
-        self.protocol_factory = protocol_factory
-        self.alive = True
-        self._lock = threading.Lock()
-        self._connection_made = threading.Event()
-        self.protocol = None
-
-    def stop(self):
-        """Stop the reader thread"""
-        self.alive = False
-        if hasattr(self.serial, 'cancel_read'):
-            self.serial.cancel_read()
-        self.join(2)
-
-    def run(self):
-        """Reader loop"""
-        if not hasattr(self.serial, 'cancel_read'):
-            self.serial.timeout = 1
-        self.protocol = self.protocol_factory()
-        try:
-            self.protocol.connection_made(self)
-        except Exception as e:
-            self.alive = False
-            self.protocol.connection_lost(e)
-            self._connection_made.set()
-            return
-        error = None
-        self._connection_made.set()
-        while self.alive and self.serial.is_open:
-            try:
-                # read all that is there or wait for one byte (blocking)
-                data = self.serial.read(self.serial.in_waiting or 1)
-            except serial.SerialException as e:
-                # probably some I/O problem such as disconnected USB serial
-                # adapters -> exit
-                error = e
-                break
-            else:
-                if data:
-                    # make a separated try-except for called user code
-                    try:
-                        self.protocol.data_received(data)
-                    except Exception as e:
-                        error = e
-                        break
-        self.alive = False
-        self.protocol.connection_lost(error)
-        self.protocol = None
-
-    def write(self, data):
-        """Thread safe writing (uses lock)"""
-        with self._lock:
-            return self.serial.write(data)
-
-    def close(self):
-        """Close the serial port and exit reader thread (uses lock)"""
-        # use the lock to let other threads finish writing
-        with self._lock:
-            # first stop reading, so that closing can be done on idle port
-            self.stop()
-            self.serial.close()
-
-    def connect(self):
-        """
-        Wait until connection is set up and return the transport and protocol
-        instances.
-        """
-        if self.alive:
-            self._connection_made.wait()
-            if not self.alive:
-                raise RuntimeError('connection_lost already called')
-            return (self, self.protocol)
-        else:
-            raise RuntimeError('already stopped')
-
-    # - -  context manager, returns protocol
-
-    def __enter__(self):
-        """\
-        Enter context handler. May raise RuntimeError in case the connection
-        could not be created.
-        """
-        self.start()
-        self._connection_made.wait()
-        if not self.alive:
-            raise RuntimeError('connection_lost already called')
-        return self.protocol
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        """Leave context: close port"""
-        self.close()
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# test
-if __name__ == '__main__':
-    # pylint: disable=wrong-import-position
-    import sys
-    import time
-    import traceback
-
-    #~ PORT = 'spy:///dev/ttyUSB0'
-    PORT = 'loop://'
-
-    class PrintLines(LineReader):
-        def connection_made(self, transport):
-            super(PrintLines, self).connection_made(transport)
-            sys.stdout.write('port opened\n')
-            self.write_line('hello world')
-
-        def handle_line(self, data):
-            sys.stdout.write('line received: {!r}\n'.format(data))
-
-        def connection_lost(self, exc):
-            if exc:
-                traceback.print_exc(exc)
-            sys.stdout.write('port closed\n')
-
-    ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
-    with ReaderThread(ser, PrintLines) as protocol:
-        protocol.write_line('hello')
-        time.sleep(2)
-
-    # alternative usage
-    ser = serial.serial_for_url(PORT, baudrate=115200, timeout=1)
-    t = ReaderThread(ser, PrintLines)
-    t.start()
-    transport, protocol = t.connect()
-    protocol.write_line('hello')
-    time.sleep(2)
-    t.close()
diff --git a/modbus/serial/tools/__init__.py b/modbus/serial/tools/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/modbus/serial/tools/hexlify_codec.py b/modbus/serial/tools/hexlify_codec.py
deleted file mode 100644
index bd8f6b0..0000000
--- a/modbus/serial/tools/hexlify_codec.py
+++ /dev/null
@@ -1,126 +0,0 @@
-#! python
-#
-# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2015-2016 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-"""\
-Python 'hex' Codec - 2-digit hex with spaces content transfer encoding.
-
-Encode and decode may be a bit missleading at first sight...
-
-The textual representation is a hex dump: e.g. "40 41"
-The "encoded" data of this is the binary form, e.g. b"@A"
-
-Therefore decoding is binary to text and thus converting binary data to hex dump.
-
-"""
-
-from __future__ import absolute_import
-
-import codecs
-import serial
-
-
-try:
-    unicode
-except (NameError, AttributeError):
-    unicode = str       # for Python 3, pylint: disable=redefined-builtin,invalid-name
-
-
-HEXDIGITS = '0123456789ABCDEF'
-
-
-# Codec APIs
-
-def hex_encode(data, errors='strict'):
-    """'40 41 42' -> b'@ab'"""
-    return (serial.to_bytes([int(h, 16) for h in data.split()]), len(data))
-
-
-def hex_decode(data, errors='strict'):
-    """b'@ab' -> '40 41 42'"""
-    return (unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data))), len(data))
-
-
-class Codec(codecs.Codec):
-    def encode(self, data, errors='strict'):
-        """'40 41 42' -> b'@ab'"""
-        return serial.to_bytes([int(h, 16) for h in data.split()])
-
-    def decode(self, data, errors='strict'):
-        """b'@ab' -> '40 41 42'"""
-        return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data)))
-
-
-class IncrementalEncoder(codecs.IncrementalEncoder):
-    """Incremental hex encoder"""
-
-    def __init__(self, errors='strict'):
-        self.errors = errors
-        self.state = 0
-
-    def reset(self):
-        self.state = 0
-
-    def getstate(self):
-        return self.state
-
-    def setstate(self, state):
-        self.state = state
-
-    def encode(self, data, final=False):
-        """\
-        Incremental encode, keep track of digits and emit a byte when a pair
-        of hex digits is found. The space is optional unless the error
-        handling is defined to be 'strict'.
-        """
-        state = self.state
-        encoded = []
-        for c in data.upper():
-            if c in HEXDIGITS:
-                z = HEXDIGITS.index(c)
-                if state:
-                    encoded.append(z + (state & 0xf0))
-                    state = 0
-                else:
-                    state = 0x100 + (z << 4)
-            elif c == ' ':      # allow spaces to separate values
-                if state and self.errors == 'strict':
-                    raise UnicodeError('odd number of hex digits')
-                state = 0
-            else:
-                if self.errors == 'strict':
-                    raise UnicodeError('non-hex digit found: {!r}'.format(c))
-        self.state = state
-        return serial.to_bytes(encoded)
-
-
-class IncrementalDecoder(codecs.IncrementalDecoder):
-    """Incremental decoder"""
-    def decode(self, data, final=False):
-        return unicode(''.join('{:02X} '.format(ord(b)) for b in serial.iterbytes(data)))
-
-
-class StreamWriter(Codec, codecs.StreamWriter):
-    """Combination of hexlify codec and StreamWriter"""
-
-
-class StreamReader(Codec, codecs.StreamReader):
-    """Combination of hexlify codec and StreamReader"""
-
-
-def getregentry():
-    """encodings module API"""
-    return codecs.CodecInfo(
-        name='hexlify',
-        encode=hex_encode,
-        decode=hex_decode,
-        incrementalencoder=IncrementalEncoder,
-        incrementaldecoder=IncrementalDecoder,
-        streamwriter=StreamWriter,
-        streamreader=StreamReader,
-        #~ _is_text_encoding=True,
-    )
diff --git a/modbus/serial/tools/list_ports.py b/modbus/serial/tools/list_ports.py
deleted file mode 100644
index 0d7e3d4..0000000
--- a/modbus/serial/tools/list_ports.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python
-#
-# Serial port enumeration. Console tool and backend selection.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2011-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-"""\
-This module will provide a function called comports that returns an
-iterable (generator or list) that will enumerate available com ports. Note that
-on some systems non-existent ports may be listed.
-
-Additionally a grep function is supplied that can be used to search for ports
-based on their descriptions or hardware ID.
-"""
-
-from __future__ import absolute_import
-
-import sys
-import os
-import re
-
-# chose an implementation, depending on os
-#~ if sys.platform == 'cli':
-#~ else:
-if os.name == 'nt':  # sys.platform == 'win32':
-    from serial.tools.list_ports_windows import comports
-elif os.name == 'posix':
-    from serial.tools.list_ports_posix import comports
-#~ elif os.name == 'java':
-else:
-    raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-def grep(regexp, include_links=False):
-    """\
-    Search for ports using a regular expression. Port name, description and
-    hardware ID are searched. The function returns an iterable that returns the
-    same tuples as comport() would do.
-    """
-    r = re.compile(regexp, re.I)
-    for info in comports(include_links):
-        port, desc, hwid = info
-        if r.search(port) or r.search(desc) or r.search(hwid):
-            yield info
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-def main():
-    import argparse
-
-    parser = argparse.ArgumentParser(description='Serial port enumeration')
-
-    parser.add_argument(
-        'regexp',
-        nargs='?',
-        help='only show ports that match this regex')
-
-    parser.add_argument(
-        '-v', '--verbose',
-        action='store_true',
-        help='show more messages')
-
-    parser.add_argument(
-        '-q', '--quiet',
-        action='store_true',
-        help='suppress all messages')
-
-    parser.add_argument(
-        '-n',
-        type=int,
-        help='only output the N-th entry')
-
-    parser.add_argument(
-        '-s', '--include-links',
-        action='store_true',
-        help='include entries that are symlinks to real devices')
-
-    args = parser.parse_args()
-
-    hits = 0
-    # get iteraror w/ or w/o filter
-    if args.regexp:
-        if not args.quiet:
-            sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp))
-        iterator = sorted(grep(args.regexp, include_links=args.include_links))
-    else:
-        iterator = sorted(comports(include_links=args.include_links))
-    # list them
-    for n, (port, desc, hwid) in enumerate(iterator, 1):
-        if args.n is None or args.n == n:
-            sys.stdout.write("{:20}\n".format(port))
-            if args.verbose:
-                sys.stdout.write("    desc: {}\n".format(desc))
-                sys.stdout.write("    hwid: {}\n".format(hwid))
-        hits += 1
-    if not args.quiet:
-        if hits:
-            sys.stderr.write("{} ports found\n".format(hits))
-        else:
-            sys.stderr.write("no ports found\n")
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# test
-if __name__ == '__main__':
-    main()
diff --git a/modbus/serial/tools/list_ports_common.py b/modbus/serial/tools/list_ports_common.py
deleted file mode 100644
index 617f3dc..0000000
--- a/modbus/serial/tools/list_ports_common.py
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/usr/bin/env python
-#
-# This is a helper module for the various platform dependent list_port
-# implementations.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-import re
-import glob
-import os
-import os.path
-
-
-def numsplit(text):
-    """\
-    Convert string into a list of texts and numbers in order to support a
-    natural sorting.
-    """
-    result = []
-    for group in re.split(r'(\d+)', text):
-        if group:
-            try:
-                group = int(group)
-            except ValueError:
-                pass
-            result.append(group)
-    return result
-
-
-class ListPortInfo(object):
-    """Info collection base class for serial ports"""
-
-    def __init__(self, device, skip_link_detection=False):
-        self.device = device
-        self.name = os.path.basename(device)
-        self.description = 'n/a'
-        self.hwid = 'n/a'
-        # USB specific data
-        self.vid = None
-        self.pid = None
-        self.serial_number = None
-        self.location = None
-        self.manufacturer = None
-        self.product = None
-        self.interface = None
-        # special handling for links
-        if not skip_link_detection and device is not None and os.path.islink(device):
-            self.hwid = 'LINK={}'.format(os.path.realpath(device))
-
-    def usb_description(self):
-        """return a short string to name the port based on USB info"""
-        if self.interface is not None:
-            return '{} - {}'.format(self.product, self.interface)
-        elif self.product is not None:
-            return self.product
-        else:
-            return self.name
-
-    def usb_info(self):
-        """return a string with USB related information about device"""
-        return 'USB VID:PID={:04X}:{:04X}{}{}'.format(
-            self.vid or 0,
-            self.pid or 0,
-            ' SER={}'.format(self.serial_number) if self.serial_number is not None else '',
-            ' LOCATION={}'.format(self.location) if self.location is not None else '')
-
-    def apply_usb_info(self):
-        """update description and hwid from USB data"""
-        self.description = self.usb_description()
-        self.hwid = self.usb_info()
-
-    def __eq__(self, other):
-        return isinstance(other, ListPortInfo) and self.device == other.device
-
-    def __hash__(self):
-        return hash(self.device)
-
-    def __lt__(self, other):
-        if not isinstance(other, ListPortInfo):
-            raise TypeError('unorderable types: {}() and {}()'.format(
-                type(self).__name__,
-                type(other).__name__))
-        return numsplit(self.device) < numsplit(other.device)
-
-    def __str__(self):
-        return '{} - {}'.format(self.device, self.description)
-
-    def __getitem__(self, index):
-        """Item access: backwards compatible -> (port, desc, hwid)"""
-        if index == 0:
-            return self.device
-        elif index == 1:
-            return self.description
-        elif index == 2:
-            return self.hwid
-        else:
-            raise IndexError('{} > 2'.format(index))
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-def list_links(devices):
-    """\
-    search all /dev devices and look for symlinks to known ports already
-    listed in devices.
-    """
-    links = []
-    for device in glob.glob('/dev/*'):
-        if os.path.islink(device) and os.path.realpath(device) in devices:
-            links.append(device)
-    return links
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# test
-if __name__ == '__main__':
-    print(ListPortInfo('dummy'))
diff --git a/modbus/serial/tools/list_ports_linux.py b/modbus/serial/tools/list_ports_linux.py
deleted file mode 100644
index c8c1cfc..0000000
--- a/modbus/serial/tools/list_ports_linux.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env python
-#
-# This is a module that gathers a list of serial ports including details on
-# GNU/Linux systems.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2011-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-import glob
-import os
-from serial.tools import list_ports_common
-
-
-class SysFS(list_ports_common.ListPortInfo):
-    """Wrapper for easy sysfs access and device info"""
-
-    def __init__(self, device):
-        super(SysFS, self).__init__(device)
-        # special handling for links
-        if device is not None and os.path.islink(device):
-            device = os.path.realpath(device)
-            is_link = True
-        else:
-            is_link = False
-        self.usb_device_path = None
-        if os.path.exists('/sys/class/tty/{}/device'.format(self.name)):
-            self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name))
-            self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem')))
-        else:
-            self.device_path = None
-            self.subsystem = None
-        # check device type
-        if self.subsystem == 'usb-serial':
-            self.usb_interface_path = os.path.dirname(self.device_path)
-        elif self.subsystem == 'usb':
-            self.usb_interface_path = self.device_path
-        else:
-            self.usb_interface_path = None
-        # fill-in info for USB devices
-        if self.usb_interface_path is not None:
-            self.usb_device_path = os.path.dirname(self.usb_interface_path)
-
-            try:
-                num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces'))
-            except ValueError:
-                num_if = 1
-
-            self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16)
-            self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16)
-            self.serial_number = self.read_line(self.usb_device_path, 'serial')
-            if num_if > 1:  # multi interface devices like FT4232
-                self.location = os.path.basename(self.usb_interface_path)
-            else:
-                self.location = os.path.basename(self.usb_device_path)
-
-            self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer')
-            self.product = self.read_line(self.usb_device_path, 'product')
-            self.interface = self.read_line(self.usb_interface_path, 'interface')
-
-        if self.subsystem in ('usb', 'usb-serial'):
-            self.apply_usb_info()
-        #~ elif self.subsystem in ('pnp', 'amba'):  # PCI based devices, raspi
-        elif self.subsystem == 'pnp':  # PCI based devices
-            self.description = self.name
-            self.hwid = self.read_line(self.device_path, 'id')
-        elif self.subsystem == 'amba':  # raspi
-            self.description = self.name
-            self.hwid = os.path.basename(self.device_path)
-
-        if is_link:
-            self.hwid += ' LINK={}'.format(device)
-
-    def read_line(self, *args):
-        """\
-        Helper function to read a single line from a file.
-        One or more parameters are allowed, they are joined with os.path.join.
-        Returns None on errors..
-        """
-        try:
-            with open(os.path.join(*args)) as f:
-                line = f.readline().strip()
-            return line
-        except IOError:
-            return None
-
-
-def comports(include_links=False):
-    devices = glob.glob('/dev/ttyS*')           # built-in serial ports
-    devices.extend(glob.glob('/dev/ttyUSB*'))   # usb-serial with own driver
-    devices.extend(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial port exar (DELL Edge 3001)
-    devices.extend(glob.glob('/dev/ttyACM*'))   # usb-serial with CDC-ACM profile
-    devices.extend(glob.glob('/dev/ttyAMA*'))   # ARM internal port (raspi)
-    devices.extend(glob.glob('/dev/rfcomm*'))   # BT serial devices
-    devices.extend(glob.glob('/dev/ttyAP*'))    # Advantech multi-port serial controllers
-    if include_links:
-        devices.extend(list_ports_common.list_links(devices))
-    return [info
-            for info in [SysFS(d) for d in devices]
-            if info.subsystem != "platform"]    # hide non-present internal serial ports
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# test
-if __name__ == '__main__':
-    for info in sorted(comports()):
-        print("{0}: {0.subsystem}".format(info))
diff --git a/modbus/serial/tools/list_ports_osx.py b/modbus/serial/tools/list_ports_osx.py
deleted file mode 100644
index 51b4e8c..0000000
--- a/modbus/serial/tools/list_ports_osx.py
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/usr/bin/env python
-#
-# This is a module that gathers a list of serial ports including details on OSX
-#
-# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
-# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
-# and modifications by cliechti, hoihu, hardkrash
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2013-2020
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-
-# List all of the callout devices in OS/X by querying IOKit.
-
-# See the following for a reference of how to do this:
-# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
-
-# More help from darwin_hid.py
-
-# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
-
-from __future__ import absolute_import
-
-import ctypes
-
-from serial.tools import list_ports_common
-
-iokit = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/IOKit.framework/IOKit')
-cf = ctypes.cdll.LoadLibrary('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
-
-# kIOMasterPortDefault is no longer exported in BigSur but no biggie, using NULL works just the same
-kIOMasterPortDefault = 0 # WAS: ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
-kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
-
-kCFStringEncodingMacRoman = 0
-kCFStringEncodingUTF8 = 0x08000100
-
-# defined in `IOKit/usb/USBSpec.h`
-kUSBVendorString = 'USB Vendor Name'
-kUSBSerialNumberString = 'USB Serial Number'
-
-# `io_name_t` defined as `typedef char io_name_t[128];`
-# in `device/device_types.h`
-io_name_size = 128
-
-# defined in `mach/kern_return.h`
-KERN_SUCCESS = 0
-# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h`
-kern_return_t = ctypes.c_int
-
-iokit.IOServiceMatching.restype = ctypes.c_void_p
-
-iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-iokit.IOServiceGetMatchingServices.restype = kern_return_t
-
-iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-iokit.IOServiceGetMatchingServices.restype = kern_return_t
-
-iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
-iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
-
-iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-iokit.IORegistryEntryGetPath.restype = kern_return_t
-
-iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-iokit.IORegistryEntryGetName.restype = kern_return_t
-
-iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-iokit.IOObjectGetClass.restype = kern_return_t
-
-iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
-
-
-cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
-cf.CFStringCreateWithCString.restype = ctypes.c_void_p
-
-cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
-cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
-
-cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32]
-cf.CFStringGetCString.restype = ctypes.c_bool
-
-cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
-cf.CFNumberGetValue.restype = ctypes.c_void_p
-
-# void CFRelease ( CFTypeRef cf );
-cf.CFRelease.argtypes = [ctypes.c_void_p]
-cf.CFRelease.restype = None
-
-# CFNumber type defines
-kCFNumberSInt8Type = 1
-kCFNumberSInt16Type = 2
-kCFNumberSInt32Type = 3
-kCFNumberSInt64Type = 4
-
-
-def get_string_property(device_type, property):
-    """
-    Search the given device for the specified string property
-
-    @param device_type Type of Device
-    @param property String to search for
-    @return Python string containing the value, or None if not found.
-    """
-    key = cf.CFStringCreateWithCString(
-            kCFAllocatorDefault,
-            property.encode("utf-8"),
-            kCFStringEncodingUTF8)
-
-    CFContainer = iokit.IORegistryEntryCreateCFProperty(
-            device_type,
-            key,
-            kCFAllocatorDefault,
-            0)
-    output = None
-
-    if CFContainer:
-        output = cf.CFStringGetCStringPtr(CFContainer, 0)
-        if output is not None:
-            output = output.decode('utf-8')
-        else:
-            buffer = ctypes.create_string_buffer(io_name_size);
-            success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8)
-            if success:
-                output = buffer.value.decode('utf-8')
-        cf.CFRelease(CFContainer)
-    return output
-
-
-def get_int_property(device_type, property, cf_number_type):
-    """
-    Search the given device for the specified string property
-
-    @param device_type Device to search
-    @param property String to search for
-    @param cf_number_type CFType number
-
-    @return Python string containing the value, or None if not found.
-    """
-    key = cf.CFStringCreateWithCString(
-            kCFAllocatorDefault,
-            property.encode("utf-8"),
-            kCFStringEncodingUTF8)
-
-    CFContainer = iokit.IORegistryEntryCreateCFProperty(
-            device_type,
-            key,
-            kCFAllocatorDefault,
-            0)
-
-    if CFContainer:
-        if (cf_number_type == kCFNumberSInt32Type):
-            number = ctypes.c_uint32()
-        elif (cf_number_type == kCFNumberSInt16Type):
-            number = ctypes.c_uint16()
-        cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number))
-        cf.CFRelease(CFContainer)
-        return number.value
-    return None
-
-def IORegistryEntryGetName(device):
-    devicename = ctypes.create_string_buffer(io_name_size);
-    res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename))
-    if res != KERN_SUCCESS:
-        return None
-    # this works in python2 but may not be valid. Also I don't know if
-    # this encoding is guaranteed. It may be dependent on system locale.
-    return devicename.value.decode('utf-8')
-
-def IOObjectGetClass(device):
-    classname = ctypes.create_string_buffer(io_name_size)
-    iokit.IOObjectGetClass(device, ctypes.byref(classname))
-    return classname.value
-
-def GetParentDeviceByType(device, parent_type):
-    """ Find the first parent of a device that implements the parent_type
-        @param IOService Service to inspect
-        @return Pointer to the parent type, or None if it was not found.
-    """
-    # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
-    parent_type = parent_type.encode('utf-8')
-    while IOObjectGetClass(device) != parent_type:
-        parent = ctypes.c_void_p()
-        response = iokit.IORegistryEntryGetParentEntry(
-                device,
-                "IOService".encode("utf-8"),
-                ctypes.byref(parent))
-        # If we weren't able to find a parent for the device, we're done.
-        if response != KERN_SUCCESS:
-            return None
-        device = parent
-    return device
-
-
-def GetIOServicesByType(service_type):
-    """
-    returns iterator over specified service_type
-    """
-    serial_port_iterator = ctypes.c_void_p()
-
-    iokit.IOServiceGetMatchingServices(
-            kIOMasterPortDefault,
-            iokit.IOServiceMatching(service_type.encode('utf-8')),
-            ctypes.byref(serial_port_iterator))
-
-    services = []
-    while iokit.IOIteratorIsValid(serial_port_iterator):
-        service = iokit.IOIteratorNext(serial_port_iterator)
-        if not service:
-            break
-        services.append(service)
-    iokit.IOObjectRelease(serial_port_iterator)
-    return services
-
-
-def location_to_string(locationID):
-    """
-    helper to calculate port and bus number from locationID
-    """
-    loc = ['{}-'.format(locationID >> 24)]
-    while locationID & 0xf00000:
-        if len(loc) > 1:
-            loc.append('.')
-        loc.append('{}'.format((locationID >> 20) & 0xf))
-        locationID <<= 4
-    return ''.join(loc)
-
-
-class SuitableSerialInterface(object):
-    pass
-
-
-def scan_interfaces():
-    """
-    helper function to scan USB interfaces
-    returns a list of SuitableSerialInterface objects with name and id attributes
-    """
-    interfaces = []
-    for service in GetIOServicesByType('IOSerialBSDClient'):
-        device = get_string_property(service, "IOCalloutDevice")
-        if device:
-            usb_device = GetParentDeviceByType(service, "IOUSBInterface")
-            if usb_device:
-                name = get_string_property(usb_device, "USB Interface Name") or None
-                locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or ''
-                i = SuitableSerialInterface()
-                i.id = locationID
-                i.name = name
-                interfaces.append(i)
-    return interfaces
-
-
-def search_for_locationID_in_interfaces(serial_interfaces, locationID):
-    for interface in serial_interfaces:
-        if (interface.id == locationID):
-            return interface.name
-    return None
-
-
-def comports(include_links=False):
-    # XXX include_links is currently ignored. are links in /dev even supported here?
-    # Scan for all iokit serial ports
-    services = GetIOServicesByType('IOSerialBSDClient')
-    ports = []
-    serial_interfaces = scan_interfaces()
-    for service in services:
-        # First, add the callout device file.
-        device = get_string_property(service, "IOCalloutDevice")
-        if device:
-            info = list_ports_common.ListPortInfo(device)
-            # If the serial port is implemented by IOUSBDevice
-            # NOTE IOUSBDevice was deprecated as of 10.11 and finally on Apple Silicon
-            # devices has been completely removed.  Thanks to @oskay for this patch.
-            usb_device = GetParentDeviceByType(service, "IOUSBHostDevice")
-            if not usb_device:
-                usb_device = GetParentDeviceByType(service, "IOUSBDevice")
-            if usb_device:
-                # fetch some useful informations from properties
-                info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type)
-                info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type)
-                info.serial_number = get_string_property(usb_device, kUSBSerialNumberString)
-                # We know this is a usb device, so the
-                # IORegistryEntryName should always be aliased to the
-                # usb product name string descriptor.
-                info.product = IORegistryEntryGetName(usb_device) or 'n/a'
-                info.manufacturer = get_string_property(usb_device, kUSBVendorString)
-                locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type)
-                info.location = location_to_string(locationID)
-                info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID)
-                info.apply_usb_info()
-            ports.append(info)
-    return ports
-
-# test
-if __name__ == '__main__':
-    for port, desc, hwid in sorted(comports()):
-        print("{}: {} [{}]".format(port, desc, hwid))
diff --git a/modbus/serial/tools/list_ports_posix.py b/modbus/serial/tools/list_ports_posix.py
deleted file mode 100644
index 79bc8ed..0000000
--- a/modbus/serial/tools/list_ports_posix.py
+++ /dev/null
@@ -1,119 +0,0 @@
-#!/usr/bin/env python
-#
-# This is a module that gathers a list of serial ports on POSIXy systems.
-# For some specific implementations, see also list_ports_linux, list_ports_osx
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2011-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-"""\
-The ``comports`` function is expected to return an iterable that yields tuples
-of 3 strings: port name, human readable description and a hardware ID.
-
-As currently no method is known to get the second two strings easily, they are
-currently just identical to the port name.
-"""
-
-from __future__ import absolute_import
-
-import glob
-import sys
-import os
-from serial.tools import list_ports_common
-
-# try to detect the OS so that a device can be selected...
-plat = sys.platform.lower()
-
-if plat[:5] == 'linux':    # Linux (confirmed)  # noqa
-    from serial.tools.list_ports_linux import comports
-
-elif plat[:6] == 'darwin':   # OS X (confirmed)
-    from serial.tools.list_ports_osx import comports
-
-elif plat == 'cygwin':       # cygwin/win32
-    # cygwin accepts /dev/com* in many contexts
-    # (such as 'open' call, explicit 'ls'), but 'glob.glob'
-    # and bare 'ls' do not; so use /dev/ttyS* instead
-    def comports(include_links=False):
-        devices = glob.glob('/dev/ttyS*')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:7] == 'openbsd':    # OpenBSD
-    def comports(include_links=False):
-        devices = glob.glob('/dev/cua*')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:3] == 'bsd' or plat[:7] == 'freebsd':
-    def comports(include_links=False):
-        devices = glob.glob('/dev/cua*[!.init][!.lock]')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:6] == 'netbsd':   # NetBSD
-    def comports(include_links=False):
-        """scan for available ports. return a list of device names."""
-        devices = glob.glob('/dev/dty*')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:4] == 'irix':     # IRIX
-    def comports(include_links=False):
-        """scan for available ports. return a list of device names."""
-        devices = glob.glob('/dev/ttyf*')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:2] == 'hp':       # HP-UX (not tested)
-    def comports(include_links=False):
-        """scan for available ports. return a list of device names."""
-        devices = glob.glob('/dev/tty*p0')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:5] == 'sunos':    # Solaris/SunOS
-    def comports(include_links=False):
-        """scan for available ports. return a list of device names."""
-        devices = glob.glob('/dev/tty*c')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-elif plat[:3] == 'aix':      # AIX
-    def comports(include_links=False):
-        """scan for available ports. return a list of device names."""
-        devices = glob.glob('/dev/tty*')
-        if include_links:
-            devices.extend(list_ports_common.list_links(devices))
-        return [list_ports_common.ListPortInfo(d) for d in devices]
-
-else:
-    # platform detection has failed...
-    import serial
-    sys.stderr.write("""\
-don't know how to enumerate ttys on this system.
-! I you know how the serial ports are named send this information to
-! the author of this module:
-
-sys.platform = {!r}
-os.name = {!r}
-pySerial version = {}
-
-also add the naming scheme of the serial ports and with a bit luck you can get
-this module running...
-""".format(sys.platform, os.name, serial.VERSION))
-    raise ImportError("Sorry: no implementation for your platform ('{}') available".format(os.name))
-
-# test
-if __name__ == '__main__':
-    for port, desc, hwid in sorted(comports()):
-        print("{}: {} [{}]".format(port, desc, hwid))
diff --git a/modbus/serial/tools/list_ports_windows.py b/modbus/serial/tools/list_ports_windows.py
deleted file mode 100644
index 0b4a5b1..0000000
--- a/modbus/serial/tools/list_ports_windows.py
+++ /dev/null
@@ -1,427 +0,0 @@
-#! python
-#
-# Enumerate serial ports on Windows including a human readable description
-# and hardware information.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2001-2016 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-# pylint: disable=invalid-name,too-few-public-methods
-import re
-import ctypes
-from ctypes.wintypes import BOOL
-from ctypes.wintypes import HWND
-from ctypes.wintypes import DWORD
-from ctypes.wintypes import WORD
-from ctypes.wintypes import LONG
-from ctypes.wintypes import ULONG
-from ctypes.wintypes import HKEY
-from ctypes.wintypes import BYTE
-import serial
-from serial.win32 import ULONG_PTR
-from serial.tools import list_ports_common
-
-
-def ValidHandle(value, func, arguments):
-    if value == 0:
-        raise ctypes.WinError()
-    return value
-
-
-NULL = 0
-HDEVINFO = ctypes.c_void_p
-LPCTSTR = ctypes.c_wchar_p
-PCTSTR = ctypes.c_wchar_p
-PTSTR = ctypes.c_wchar_p
-LPDWORD = PDWORD = ctypes.POINTER(DWORD)
-#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE)
-LPBYTE = PBYTE = ctypes.c_void_p        # XXX avoids error about types
-
-ACCESS_MASK = DWORD
-REGSAM = ACCESS_MASK
-
-
-class GUID(ctypes.Structure):
-    _fields_ = [
-        ('Data1', DWORD),
-        ('Data2', WORD),
-        ('Data3', WORD),
-        ('Data4', BYTE * 8),
-    ]
-
-    def __str__(self):
-        return "{{{:08x}-{:04x}-{:04x}-{}-{}}}".format(
-            self.Data1,
-            self.Data2,
-            self.Data3,
-            ''.join(["{:02x}".format(d) for d in self.Data4[:2]]),
-            ''.join(["{:02x}".format(d) for d in self.Data4[2:]]),
-        )
-
-
-class SP_DEVINFO_DATA(ctypes.Structure):
-    _fields_ = [
-        ('cbSize', DWORD),
-        ('ClassGuid', GUID),
-        ('DevInst', DWORD),
-        ('Reserved', ULONG_PTR),
-    ]
-
-    def __str__(self):
-        return "ClassGuid:{} DevInst:{}".format(self.ClassGuid, self.DevInst)
-
-
-PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)
-
-PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p
-
-setupapi = ctypes.windll.LoadLibrary("setupapi")
-SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
-SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO]
-SetupDiDestroyDeviceInfoList.restype = BOOL
-
-SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW
-SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD]
-SetupDiClassGuidsFromName.restype = BOOL
-
-SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
-SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA]
-SetupDiEnumDeviceInfo.restype = BOOL
-
-SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW
-SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD]
-SetupDiGetClassDevs.restype = HDEVINFO
-SetupDiGetClassDevs.errcheck = ValidHandle
-
-SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW
-SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD]
-SetupDiGetDeviceRegistryProperty.restype = BOOL
-
-SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW
-SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD]
-SetupDiGetDeviceInstanceId.restype = BOOL
-
-SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey
-SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM]
-SetupDiOpenDevRegKey.restype = HKEY
-
-advapi32 = ctypes.windll.LoadLibrary("Advapi32")
-RegCloseKey = advapi32.RegCloseKey
-RegCloseKey.argtypes = [HKEY]
-RegCloseKey.restype = LONG
-
-RegQueryValueEx = advapi32.RegQueryValueExW
-RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD]
-RegQueryValueEx.restype = LONG
-
-cfgmgr32 = ctypes.windll.LoadLibrary("Cfgmgr32")
-CM_Get_Parent = cfgmgr32.CM_Get_Parent
-CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG]
-CM_Get_Parent.restype = LONG
-
-CM_Get_Device_IDW = cfgmgr32.CM_Get_Device_IDW
-CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG]
-CM_Get_Device_IDW.restype = LONG
-
-CM_MapCrToWin32Err = cfgmgr32.CM_MapCrToWin32Err
-CM_MapCrToWin32Err.argtypes = [DWORD, DWORD]
-CM_MapCrToWin32Err.restype = DWORD
-
-
-DIGCF_PRESENT = 2
-DIGCF_DEVICEINTERFACE = 16
-INVALID_HANDLE_VALUE = 0
-ERROR_INSUFFICIENT_BUFFER = 122
-ERROR_NOT_FOUND = 1168
-SPDRP_HARDWAREID = 1
-SPDRP_FRIENDLYNAME = 12
-SPDRP_LOCATION_PATHS = 35
-SPDRP_MFG = 11
-DICS_FLAG_GLOBAL = 1
-DIREG_DEV = 0x00000001
-KEY_READ = 0x20019
-
-
-MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5
-
-
-def get_parent_serial_number(child_devinst, child_vid, child_pid, depth=0, last_serial_number=None):
-    """ Get the serial number of the parent of a device.
-
-    Args:
-        child_devinst: The device instance handle to get the parent serial number of.
-        child_vid: The vendor ID of the child device.
-        child_pid: The product ID of the child device.
-        depth: The current iteration depth of the USB device tree.
-    """
-
-    # If the traversal depth is beyond the max, abandon attempting to find the serial number.
-    if depth > MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH:
-        return '' if not last_serial_number else last_serial_number
-
-    # Get the parent device instance.
-    devinst = DWORD()
-    ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0)
-
-    if ret:
-        win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0))
-
-        # If there is no parent available, the child was the root device. We cannot traverse
-        # further.
-        if win_error == ERROR_NOT_FOUND:
-            return '' if not last_serial_number else last_serial_number
-
-        raise ctypes.WinError(win_error)
-
-    # Get the ID of the parent device and parse it for vendor ID, product ID, and serial number.
-    parentHardwareID = ctypes.create_unicode_buffer(250)
-
-    ret = CM_Get_Device_IDW(
-        devinst,
-        parentHardwareID,
-        ctypes.sizeof(parentHardwareID) - 1,
-        0)
-
-    if ret:
-        raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0)))
-
-    parentHardwareID_str = parentHardwareID.value
-    m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?',
-                  parentHardwareID_str,
-                  re.I)
-
-    # return early if we have no matches (likely malformed serial, traversed too far)
-    if not m:
-        return '' if not last_serial_number else last_serial_number
-
-    vid = None
-    pid = None
-    serial_number = None
-    if m.group(1):
-        vid = int(m.group(1), 16)
-    if m.group(3):
-        pid = int(m.group(3), 16)
-    if m.group(7):
-        serial_number = m.group(7)
-
-    # store what we found as a fallback for malformed serial values up the chain
-    found_serial_number = serial_number
-
-    # Check that the USB serial number only contains alpha-numeric characters. It may be a windows
-    # device ID (ephemeral ID).
-    if serial_number and not re.match(r'^\w+$', serial_number):
-        serial_number = None
-
-    if not vid or not pid:
-        # If pid and vid are not available at this device level, continue to the parent.
-        return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number)
-
-    if pid != child_pid or vid != child_vid:
-        # If the VID or PID has changed, we are no longer looking at the same physical device. The
-        # serial number is unknown.
-        return '' if not last_serial_number else last_serial_number
-
-    # In this case, the vid and pid of the parent device are identical to the child. However, if
-    # there still isn't a serial number available, continue to the next parent.
-    if not serial_number:
-        return get_parent_serial_number(devinst, child_vid, child_pid, depth + 1, found_serial_number)
-
-    # Finally, the VID and PID are identical to the child and a serial number is present, so return
-    # it.
-    return serial_number
-
-
-def iterate_comports():
-    """Return a generator that yields descriptions for serial ports"""
-    PortsGUIDs = (GUID * 8)()  # so far only seen one used, so hope 8 are enough...
-    ports_guids_size = DWORD()
-    if not SetupDiClassGuidsFromName(
-            "Ports",
-            PortsGUIDs,
-            ctypes.sizeof(PortsGUIDs),
-            ctypes.byref(ports_guids_size)):
-        raise ctypes.WinError()
-
-    ModemsGUIDs = (GUID * 8)()  # so far only seen one used, so hope 8 are enough...
-    modems_guids_size = DWORD()
-    if not SetupDiClassGuidsFromName(
-            "Modem",
-            ModemsGUIDs,
-            ctypes.sizeof(ModemsGUIDs),
-            ctypes.byref(modems_guids_size)):
-        raise ctypes.WinError()
-
-    GUIDs = PortsGUIDs[:ports_guids_size.value] + ModemsGUIDs[:modems_guids_size.value]
-
-    # repeat for all possible GUIDs
-    for index in range(len(GUIDs)):
-        bInterfaceNumber = None
-        g_hdi = SetupDiGetClassDevs(
-            ctypes.byref(GUIDs[index]),
-            None,
-            NULL,
-            DIGCF_PRESENT)  # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports
-
-        devinfo = SP_DEVINFO_DATA()
-        devinfo.cbSize = ctypes.sizeof(devinfo)
-        index = 0
-        while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)):
-            index += 1
-
-            # get the real com port name
-            hkey = SetupDiOpenDevRegKey(
-                g_hdi,
-                ctypes.byref(devinfo),
-                DICS_FLAG_GLOBAL,
-                0,
-                DIREG_DEV,  # DIREG_DRV for SW info
-                KEY_READ)
-            port_name_buffer = ctypes.create_unicode_buffer(250)
-            port_name_length = ULONG(ctypes.sizeof(port_name_buffer))
-            RegQueryValueEx(
-                hkey,
-                "PortName",
-                None,
-                None,
-                ctypes.byref(port_name_buffer),
-                ctypes.byref(port_name_length))
-            RegCloseKey(hkey)
-
-            # unfortunately does this method also include parallel ports.
-            # we could check for names starting with COM or just exclude LPT
-            # and hope that other "unknown" names are serial ports...
-            if port_name_buffer.value.startswith('LPT'):
-                continue
-
-            # hardware ID
-            szHardwareID = ctypes.create_unicode_buffer(250)
-            # try to get ID that includes serial number
-            if not SetupDiGetDeviceInstanceId(
-                    g_hdi,
-                    ctypes.byref(devinfo),
-                    #~ ctypes.byref(szHardwareID),
-                    szHardwareID,
-                    ctypes.sizeof(szHardwareID) - 1,
-                    None):
-                # fall back to more generic hardware ID if that would fail
-                if not SetupDiGetDeviceRegistryProperty(
-                        g_hdi,
-                        ctypes.byref(devinfo),
-                        SPDRP_HARDWAREID,
-                        None,
-                        ctypes.byref(szHardwareID),
-                        ctypes.sizeof(szHardwareID) - 1,
-                        None):
-                    # Ignore ERROR_INSUFFICIENT_BUFFER
-                    if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
-                        raise ctypes.WinError()
-            # stringify
-            szHardwareID_str = szHardwareID.value
-
-            info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True)
-
-            # in case of USB, make a more readable string, similar to that form
-            # that we also generate on other platforms
-            if szHardwareID_str.startswith('USB'):
-                m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(.*))?', szHardwareID_str, re.I)
-                if m:
-                    info.vid = int(m.group(1), 16)
-                    if m.group(3):
-                        info.pid = int(m.group(3), 16)
-                    if m.group(5):
-                        bInterfaceNumber = int(m.group(5))
-
-                    # Check that the USB serial number only contains alpha-numeric characters. It
-                    # may be a windows device ID (ephemeral ID) for composite devices.
-                    if m.group(7) and re.match(r'^\w+$', m.group(7)):
-                        info.serial_number = m.group(7)
-                    else:
-                        info.serial_number = get_parent_serial_number(devinfo.DevInst, info.vid, info.pid)
-
-                # calculate a location string
-                loc_path_str = ctypes.create_unicode_buffer(250)
-                if SetupDiGetDeviceRegistryProperty(
-                        g_hdi,
-                        ctypes.byref(devinfo),
-                        SPDRP_LOCATION_PATHS,
-                        None,
-                        ctypes.byref(loc_path_str),
-                        ctypes.sizeof(loc_path_str) - 1,
-                        None):
-                    m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', loc_path_str.value)
-                    location = []
-                    for g in m:
-                        if g.group(1):
-                            location.append('{:d}'.format(int(g.group(1)) + 1))
-                        else:
-                            if len(location) > 1:
-                                location.append('.')
-                            else:
-                                location.append('-')
-                            location.append(g.group(2))
-                    if bInterfaceNumber is not None:
-                        location.append(':{}.{}'.format(
-                            'x',  # XXX how to determine correct bConfigurationValue?
-                            bInterfaceNumber))
-                    if location:
-                        info.location = ''.join(location)
-                info.hwid = info.usb_info()
-            elif szHardwareID_str.startswith('FTDIBUS'):
-                m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I)
-                if m:
-                    info.vid = int(m.group(1), 16)
-                    info.pid = int(m.group(2), 16)
-                    if m.group(4):
-                        info.serial_number = m.group(4)
-                # USB location is hidden by FDTI driver :(
-                info.hwid = info.usb_info()
-            else:
-                info.hwid = szHardwareID_str
-
-            # friendly name
-            szFriendlyName = ctypes.create_unicode_buffer(250)
-            if SetupDiGetDeviceRegistryProperty(
-                    g_hdi,
-                    ctypes.byref(devinfo),
-                    SPDRP_FRIENDLYNAME,
-                    #~ SPDRP_DEVICEDESC,
-                    None,
-                    ctypes.byref(szFriendlyName),
-                    ctypes.sizeof(szFriendlyName) - 1,
-                    None):
-                info.description = szFriendlyName.value
-            #~ else:
-                # Ignore ERROR_INSUFFICIENT_BUFFER
-                #~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
-                    #~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value))
-                # ignore errors and still include the port in the list, friendly name will be same as port name
-
-            # manufacturer
-            szManufacturer = ctypes.create_unicode_buffer(250)
-            if SetupDiGetDeviceRegistryProperty(
-                    g_hdi,
-                    ctypes.byref(devinfo),
-                    SPDRP_MFG,
-                    #~ SPDRP_DEVICEDESC,
-                    None,
-                    ctypes.byref(szManufacturer),
-                    ctypes.sizeof(szManufacturer) - 1,
-                    None):
-                info.manufacturer = szManufacturer.value
-            yield info
-        SetupDiDestroyDeviceInfoList(g_hdi)
-
-
-def comports(include_links=False):
-    """Return a list of info objects about serial ports"""
-    return list(iterate_comports())
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# test
-if __name__ == '__main__':
-    for port, desc, hwid in sorted(comports()):
-        print("{}: {} [{}]".format(port, desc, hwid))
diff --git a/modbus/serial/tools/miniterm.py b/modbus/serial/tools/miniterm.py
deleted file mode 100644
index 2cceff6..0000000
--- a/modbus/serial/tools/miniterm.py
+++ /dev/null
@@ -1,1042 +0,0 @@
-#!/usr/bin/env python
-#
-# Very simple serial terminal
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C)2002-2020 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-from __future__ import absolute_import
-
-import codecs
-import os
-import sys
-import threading
-
-import serial
-from serial.tools.list_ports import comports
-from serial.tools import hexlify_codec
-
-# pylint: disable=wrong-import-order,wrong-import-position
-
-codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
-
-try:
-    raw_input
-except NameError:
-    # pylint: disable=redefined-builtin,invalid-name
-    raw_input = input   # in python3 it's "raw"
-    unichr = chr
-
-
-def key_description(character):
-    """generate a readable description for a key"""
-    ascii_code = ord(character)
-    if ascii_code < 32:
-        return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
-    else:
-        return repr(character)
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-class ConsoleBase(object):
-    """OS abstraction for console (input/output codec, no echo)"""
-
-    def __init__(self):
-        if sys.version_info >= (3, 0):
-            self.byte_output = sys.stdout.buffer
-        else:
-            self.byte_output = sys.stdout
-        self.output = sys.stdout
-
-    def setup(self):
-        """Set console to read single characters, no echo"""
-
-    def cleanup(self):
-        """Restore default console settings"""
-
-    def getkey(self):
-        """Read a single key from the console"""
-        return None
-
-    def write_bytes(self, byte_string):
-        """Write bytes (already encoded)"""
-        self.byte_output.write(byte_string)
-        self.byte_output.flush()
-
-    def write(self, text):
-        """Write string"""
-        self.output.write(text)
-        self.output.flush()
-
-    def cancel(self):
-        """Cancel getkey operation"""
-
-    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
-    # context manager:
-    # switch terminal temporary to normal mode (e.g. to get user input)
-
-    def __enter__(self):
-        self.cleanup()
-        return self
-
-    def __exit__(self, *args, **kwargs):
-        self.setup()
-
-
-if os.name == 'nt':  # noqa
-    import msvcrt
-    import ctypes
-    import platform
-
-    class Out(object):
-        """file-like wrapper that uses os.write"""
-
-        def __init__(self, fd):
-            self.fd = fd
-
-        def flush(self):
-            pass
-
-        def write(self, s):
-            os.write(self.fd, s)
-
-    class Console(ConsoleBase):
-        fncodes = {
-            ';': '\1bOP',  # F1
-            '<': '\1bOQ',  # F2
-            '=': '\1bOR',  # F3
-            '>': '\1bOS',  # F4
-            '?': '\1b[15~',  # F5
-            '@': '\1b[17~',  # F6
-            'A': '\1b[18~',  # F7
-            'B': '\1b[19~',  # F8
-            'C': '\1b[20~',  # F9
-            'D': '\1b[21~',  # F10
-        }
-        navcodes = {
-            'H': '\x1b[A',  # UP
-            'P': '\x1b[B',  # DOWN
-            'K': '\x1b[D',  # LEFT
-            'M': '\x1b[C',  # RIGHT
-            'G': '\x1b[H',  # HOME
-            'O': '\x1b[F',  # END
-            'R': '\x1b[2~',  # INSERT
-            'S': '\x1b[3~',  # DELETE
-            'I': '\x1b[5~',  # PGUP
-            'Q': '\x1b[6~',  # PGDN        
-        }
-        
-        def __init__(self):
-            super(Console, self).__init__()
-            self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
-            self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
-            ctypes.windll.kernel32.SetConsoleOutputCP(65001)
-            ctypes.windll.kernel32.SetConsoleCP(65001)
-            # ANSI handling available through SetConsoleMode since Windows 10 v1511 
-            # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1
-            if platform.release() == '10' and int(platform.version().split('.')[2]) > 10586:
-                ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
-                import ctypes.wintypes as wintypes
-                if not hasattr(wintypes, 'LPDWORD'): # PY2
-                    wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD)
-                SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode
-                GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode
-                GetStdHandle = ctypes.windll.kernel32.GetStdHandle
-                mode = wintypes.DWORD()
-                GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode))
-                if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0:
-                    SetConsoleMode(GetStdHandle(-11), mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
-                    self._saved_cm = mode
-            self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
-            # the change of the code page is not propagated to Python, manually fix it
-            sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
-            sys.stdout = self.output
-            self.output.encoding = 'UTF-8'  # needed for input
-
-        def __del__(self):
-            ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
-            ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
-            try:
-                ctypes.windll.kernel32.SetConsoleMode(ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm)
-            except AttributeError: # in case no _saved_cm
-                pass
-
-        def getkey(self):
-            while True:
-                z = msvcrt.getwch()
-                if z == unichr(13):
-                    return unichr(10)
-                elif z is unichr(0) or z is unichr(0xe0):
-                    try:
-                        code = msvcrt.getwch()
-                        if z is unichr(0):
-                            return self.fncodes[code]
-                        else:
-                            return self.navcodes[code]
-                    except KeyError:
-                        pass
-                else:
-                    return z
-
-        def cancel(self):
-            # CancelIo, CancelSynchronousIo do not seem to work when using
-            # getwch, so instead, send a key to the window with the console
-            hwnd = ctypes.windll.kernel32.GetConsoleWindow()
-            ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
-
-elif os.name == 'posix':
-    import atexit
-    import termios
-    import fcntl
-
-    class Console(ConsoleBase):
-        def __init__(self):
-            super(Console, self).__init__()
-            self.fd = sys.stdin.fileno()
-            self.old = termios.tcgetattr(self.fd)
-            atexit.register(self.cleanup)
-            if sys.version_info < (3, 0):
-                self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
-            else:
-                self.enc_stdin = sys.stdin
-
-        def setup(self):
-            new = termios.tcgetattr(self.fd)
-            new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
-            new[6][termios.VMIN] = 1
-            new[6][termios.VTIME] = 0
-            termios.tcsetattr(self.fd, termios.TCSANOW, new)
-
-        def getkey(self):
-            c = self.enc_stdin.read(1)
-            if c == unichr(0x7f):
-                c = unichr(8)    # map the BS key (which yields DEL) to backspace
-            return c
-
-        def cancel(self):
-            fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
-
-        def cleanup(self):
-            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
-
-else:
-    raise NotImplementedError(
-        'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-class Transform(object):
-    """do-nothing: forward all data unchanged"""
-    def rx(self, text):
-        """text received from serial port"""
-        return text
-
-    def tx(self, text):
-        """text to be sent to serial port"""
-        return text
-
-    def echo(self, text):
-        """text to be sent but displayed on console"""
-        return text
-
-
-class CRLF(Transform):
-    """ENTER sends CR+LF"""
-
-    def tx(self, text):
-        return text.replace('\n', '\r\n')
-
-
-class CR(Transform):
-    """ENTER sends CR"""
-
-    def rx(self, text):
-        return text.replace('\r', '\n')
-
-    def tx(self, text):
-        return text.replace('\n', '\r')
-
-
-class LF(Transform):
-    """ENTER sends LF"""
-
-
-class NoTerminal(Transform):
-    """remove typical terminal control codes from input"""
-
-    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
-    REPLACEMENT_MAP.update(
-        {
-            0x7F: 0x2421,  # DEL
-            0x9B: 0x2425,  # CSI
-        })
-
-    def rx(self, text):
-        return text.translate(self.REPLACEMENT_MAP)
-
-    echo = rx
-
-
-class NoControls(NoTerminal):
-    """Remove all control codes, incl. CR+LF"""
-
-    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
-    REPLACEMENT_MAP.update(
-        {
-            0x20: 0x2423,  # visual space
-            0x7F: 0x2421,  # DEL
-            0x9B: 0x2425,  # CSI
-        })
-
-
-class Printable(Transform):
-    """Show decimal code for all non-ASCII characters and replace most control codes"""
-
-    def rx(self, text):
-        r = []
-        for c in text:
-            if ' ' <= c < '\x7f' or c in '\r\n\b\t':
-                r.append(c)
-            elif c < ' ':
-                r.append(unichr(0x2400 + ord(c)))
-            else:
-                r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
-                r.append(' ')
-        return ''.join(r)
-
-    echo = rx
-
-
-class Colorize(Transform):
-    """Apply different colors for received and echo"""
-
-    def __init__(self):
-        # XXX make it configurable, use colorama?
-        self.input_color = '\x1b[37m'
-        self.echo_color = '\x1b[31m'
-
-    def rx(self, text):
-        return self.input_color + text
-
-    def echo(self, text):
-        return self.echo_color + text
-
-
-class DebugIO(Transform):
-    """Print what is sent and received"""
-
-    def rx(self, text):
-        sys.stderr.write(' [RX:{!r}] '.format(text))
-        sys.stderr.flush()
-        return text
-
-    def tx(self, text):
-        sys.stderr.write(' [TX:{!r}] '.format(text))
-        sys.stderr.flush()
-        return text
-
-
-# other ideas:
-# - add date/time for each newline
-# - insert newline after: a) timeout b) packet end character
-
-EOL_TRANSFORMATIONS = {
-    'crlf': CRLF,
-    'cr': CR,
-    'lf': LF,
-}
-
-TRANSFORMATIONS = {
-    'direct': Transform,    # no transformation
-    'default': NoTerminal,
-    'nocontrol': NoControls,
-    'printable': Printable,
-    'colorize': Colorize,
-    'debug': DebugIO,
-}
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-def ask_for_port():
-    """\
-    Show a list of ports and ask the user for a choice. To make selection
-    easier on systems with long device names, also allow the input of an
-    index.
-    """
-    sys.stderr.write('\n--- Available ports:\n')
-    ports = []
-    for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
-        sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
-        ports.append(port)
-    while True:
-        port = raw_input('--- Enter port index or full name: ')
-        try:
-            index = int(port) - 1
-            if not 0 <= index < len(ports):
-                sys.stderr.write('--- Invalid index!\n')
-                continue
-        except ValueError:
-            pass
-        else:
-            port = ports[index]
-        return port
-
-
-class Miniterm(object):
-    """\
-    Terminal application. Copy data from serial port to console and vice versa.
-    Handle special keys from the console to show menu etc.
-    """
-
-    def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
-        self.console = Console()
-        self.serial = serial_instance
-        self.echo = echo
-        self.raw = False
-        self.input_encoding = 'UTF-8'
-        self.output_encoding = 'UTF-8'
-        self.eol = eol
-        self.filters = filters
-        self.update_transformations()
-        self.exit_character = unichr(0x1d)  # GS/CTRL+]
-        self.menu_character = unichr(0x14)  # Menu: CTRL+T
-        self.alive = None
-        self._reader_alive = None
-        self.receiver_thread = None
-        self.rx_decoder = None
-        self.tx_decoder = None
-
-    def _start_reader(self):
-        """Start reader thread"""
-        self._reader_alive = True
-        # start serial->console thread
-        self.receiver_thread = threading.Thread(target=self.reader, name='rx')
-        self.receiver_thread.daemon = True
-        self.receiver_thread.start()
-
-    def _stop_reader(self):
-        """Stop reader thread only, wait for clean exit of thread"""
-        self._reader_alive = False
-        if hasattr(self.serial, 'cancel_read'):
-            self.serial.cancel_read()
-        self.receiver_thread.join()
-
-    def start(self):
-        """start worker threads"""
-        self.alive = True
-        self._start_reader()
-        # enter console->serial loop
-        self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
-        self.transmitter_thread.daemon = True
-        self.transmitter_thread.start()
-        self.console.setup()
-
-    def stop(self):
-        """set flag to stop worker threads"""
-        self.alive = False
-
-    def join(self, transmit_only=False):
-        """wait for worker threads to terminate"""
-        self.transmitter_thread.join()
-        if not transmit_only:
-            if hasattr(self.serial, 'cancel_read'):
-                self.serial.cancel_read()
-            self.receiver_thread.join()
-
-    def close(self):
-        self.serial.close()
-
-    def update_transformations(self):
-        """take list of transformation classes and instantiate them for rx and tx"""
-        transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
-                                                             for f in self.filters]
-        self.tx_transformations = [t() for t in transformations]
-        self.rx_transformations = list(reversed(self.tx_transformations))
-
-    def set_rx_encoding(self, encoding, errors='replace'):
-        """set encoding for received data"""
-        self.input_encoding = encoding
-        self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
-
-    def set_tx_encoding(self, encoding, errors='replace'):
-        """set encoding for transmitted data"""
-        self.output_encoding = encoding
-        self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
-
-    def dump_port_settings(self):
-        """Write current settings to sys.stderr"""
-        sys.stderr.write("\n--- Settings: {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
-            p=self.serial))
-        sys.stderr.write('--- RTS: {:8}  DTR: {:8}  BREAK: {:8}\n'.format(
-            ('active' if self.serial.rts else 'inactive'),
-            ('active' if self.serial.dtr else 'inactive'),
-            ('active' if self.serial.break_condition else 'inactive')))
-        try:
-            sys.stderr.write('--- CTS: {:8}  DSR: {:8}  RI: {:8}  CD: {:8}\n'.format(
-                ('active' if self.serial.cts else 'inactive'),
-                ('active' if self.serial.dsr else 'inactive'),
-                ('active' if self.serial.ri else 'inactive'),
-                ('active' if self.serial.cd else 'inactive')))
-        except serial.SerialException:
-            # on RFC 2217 ports, it can happen if no modem state notification was
-            # yet received. ignore this error.
-            pass
-        sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
-        sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
-        sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
-        sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
-        sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
-        sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
-
-    def reader(self):
-        """loop and copy serial->console"""
-        try:
-            while self.alive and self._reader_alive:
-                # read all that is there or wait for one byte
-                data = self.serial.read(self.serial.in_waiting or 1)
-                if data:
-                    if self.raw:
-                        self.console.write_bytes(data)
-                    else:
-                        text = self.rx_decoder.decode(data)
-                        for transformation in self.rx_transformations:
-                            text = transformation.rx(text)
-                        self.console.write(text)
-        except serial.SerialException:
-            self.alive = False
-            self.console.cancel()
-            raise       # XXX handle instead of re-raise?
-
-    def writer(self):
-        """\
-        Loop and copy console->serial until self.exit_character character is
-        found. When self.menu_character is found, interpret the next key
-        locally.
-        """
-        menu_active = False
-        try:
-            while self.alive:
-                try:
-                    c = self.console.getkey()
-                except KeyboardInterrupt:
-                    c = '\x03'
-                if not self.alive:
-                    break
-                if menu_active:
-                    self.handle_menu_key(c)
-                    menu_active = False
-                elif c == self.menu_character:
-                    menu_active = True      # next char will be for menu
-                elif c == self.exit_character:
-                    self.stop()             # exit app
-                    break
-                else:
-                    #~ if self.raw:
-                    text = c
-                    for transformation in self.tx_transformations:
-                        text = transformation.tx(text)
-                    self.serial.write(self.tx_encoder.encode(text))
-                    if self.echo:
-                        echo_text = c
-                        for transformation in self.tx_transformations:
-                            echo_text = transformation.echo(echo_text)
-                        self.console.write(echo_text)
-        except:
-            self.alive = False
-            raise
-
-    def handle_menu_key(self, c):
-        """Implement a simple menu / settings"""
-        if c == self.menu_character or c == self.exit_character:
-            # Menu/exit character again -> send itself
-            self.serial.write(self.tx_encoder.encode(c))
-            if self.echo:
-                self.console.write(c)
-        elif c == '\x15':                       # CTRL+U -> upload file
-            self.upload_file()
-        elif c in '\x08hH?':                    # CTRL+H, h, H, ? -> Show help
-            sys.stderr.write(self.get_help_text())
-        elif c == '\x12':                       # CTRL+R -> Toggle RTS
-            self.serial.rts = not self.serial.rts
-            sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
-        elif c == '\x04':                       # CTRL+D -> Toggle DTR
-            self.serial.dtr = not self.serial.dtr
-            sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
-        elif c == '\x02':                       # CTRL+B -> toggle BREAK condition
-            self.serial.break_condition = not self.serial.break_condition
-            sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
-        elif c == '\x05':                       # CTRL+E -> toggle local echo
-            self.echo = not self.echo
-            sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
-        elif c == '\x06':                       # CTRL+F -> edit filters
-            self.change_filter()
-        elif c == '\x0c':                       # CTRL+L -> EOL mode
-            modes = list(EOL_TRANSFORMATIONS)   # keys
-            eol = modes.index(self.eol) + 1
-            if eol >= len(modes):
-                eol = 0
-            self.eol = modes[eol]
-            sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
-            self.update_transformations()
-        elif c == '\x01':                       # CTRL+A -> set encoding
-            self.change_encoding()
-        elif c == '\x09':                       # CTRL+I -> info
-            self.dump_port_settings()
-        #~ elif c == '\x01':                       # CTRL+A -> cycle escape mode
-        #~ elif c == '\x0c':                       # CTRL+L -> cycle linefeed mode
-        elif c in 'pP':                         # P -> change port
-            self.change_port()
-        elif c in 'zZ':                         # S -> suspend / open port temporarily
-            self.suspend_port()
-        elif c in 'bB':                         # B -> change baudrate
-            self.change_baudrate()
-        elif c == '8':                          # 8 -> change to 8 bits
-            self.serial.bytesize = serial.EIGHTBITS
-            self.dump_port_settings()
-        elif c == '7':                          # 7 -> change to 8 bits
-            self.serial.bytesize = serial.SEVENBITS
-            self.dump_port_settings()
-        elif c in 'eE':                         # E -> change to even parity
-            self.serial.parity = serial.PARITY_EVEN
-            self.dump_port_settings()
-        elif c in 'oO':                         # O -> change to odd parity
-            self.serial.parity = serial.PARITY_ODD
-            self.dump_port_settings()
-        elif c in 'mM':                         # M -> change to mark parity
-            self.serial.parity = serial.PARITY_MARK
-            self.dump_port_settings()
-        elif c in 'sS':                         # S -> change to space parity
-            self.serial.parity = serial.PARITY_SPACE
-            self.dump_port_settings()
-        elif c in 'nN':                         # N -> change to no parity
-            self.serial.parity = serial.PARITY_NONE
-            self.dump_port_settings()
-        elif c == '1':                          # 1 -> change to 1 stop bits
-            self.serial.stopbits = serial.STOPBITS_ONE
-            self.dump_port_settings()
-        elif c == '2':                          # 2 -> change to 2 stop bits
-            self.serial.stopbits = serial.STOPBITS_TWO
-            self.dump_port_settings()
-        elif c == '3':                          # 3 -> change to 1.5 stop bits
-            self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
-            self.dump_port_settings()
-        elif c in 'xX':                         # X -> change software flow control
-            self.serial.xonxoff = (c == 'X')
-            self.dump_port_settings()
-        elif c in 'rR':                         # R -> change hardware flow control
-            self.serial.rtscts = (c == 'R')
-            self.dump_port_settings()
-        elif c in 'qQ':
-            self.stop()                         # Q -> exit app
-        else:
-            sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
-
-    def upload_file(self):
-        """Ask user for filenname and send its contents"""
-        sys.stderr.write('\n--- File to upload: ')
-        sys.stderr.flush()
-        with self.console:
-            filename = sys.stdin.readline().rstrip('\r\n')
-            if filename:
-                try:
-                    with open(filename, 'rb') as f:
-                        sys.stderr.write('--- Sending file {} ---\n'.format(filename))
-                        while True:
-                            block = f.read(1024)
-                            if not block:
-                                break
-                            self.serial.write(block)
-                            # Wait for output buffer to drain.
-                            self.serial.flush()
-                            sys.stderr.write('.')   # Progress indicator.
-                    sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
-                except IOError as e:
-                    sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
-
-    def change_filter(self):
-        """change the i/o transformations"""
-        sys.stderr.write('\n--- Available Filters:\n')
-        sys.stderr.write('\n'.join(
-            '---   {:<10} = {.__doc__}'.format(k, v)
-            for k, v in sorted(TRANSFORMATIONS.items())))
-        sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
-        with self.console:
-            new_filters = sys.stdin.readline().lower().split()
-        if new_filters:
-            for f in new_filters:
-                if f not in TRANSFORMATIONS:
-                    sys.stderr.write('--- unknown filter: {!r}\n'.format(f))
-                    break
-            else:
-                self.filters = new_filters
-                self.update_transformations()
-        sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
-
-    def change_encoding(self):
-        """change encoding on the serial port"""
-        sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
-        with self.console:
-            new_encoding = sys.stdin.readline().strip()
-        if new_encoding:
-            try:
-                codecs.lookup(new_encoding)
-            except LookupError:
-                sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
-            else:
-                self.set_rx_encoding(new_encoding)
-                self.set_tx_encoding(new_encoding)
-        sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
-        sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
-
-    def change_baudrate(self):
-        """change the baudrate"""
-        sys.stderr.write('\n--- Baudrate: ')
-        sys.stderr.flush()
-        with self.console:
-            backup = self.serial.baudrate
-            try:
-                self.serial.baudrate = int(sys.stdin.readline().strip())
-            except ValueError as e:
-                sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
-                self.serial.baudrate = backup
-            else:
-                self.dump_port_settings()
-
-    def change_port(self):
-        """Have a conversation with the user to change the serial port"""
-        with self.console:
-            try:
-                port = ask_for_port()
-            except KeyboardInterrupt:
-                port = None
-        if port and port != self.serial.port:
-            # reader thread needs to be shut down
-            self._stop_reader()
-            # save settings
-            settings = self.serial.getSettingsDict()
-            try:
-                new_serial = serial.serial_for_url(port, do_not_open=True)
-                # restore settings and open
-                new_serial.applySettingsDict(settings)
-                new_serial.rts = self.serial.rts
-                new_serial.dtr = self.serial.dtr
-                new_serial.open()
-                new_serial.break_condition = self.serial.break_condition
-            except Exception as e:
-                sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
-                new_serial.close()
-            else:
-                self.serial.close()
-                self.serial = new_serial
-                sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
-            # and restart the reader thread
-            self._start_reader()
-
-    def suspend_port(self):
-        """\
-        open port temporarily, allow reconnect, exit and port change to get
-        out of the loop
-        """
-        # reader thread needs to be shut down
-        self._stop_reader()
-        self.serial.close()
-        sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
-        do_change_port = False
-        while not self.serial.is_open:
-            sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
-                exit=key_description(self.exit_character)))
-            k = self.console.getkey()
-            if k == self.exit_character:
-                self.stop()             # exit app
-                break
-            elif k in 'pP':
-                do_change_port = True
-                break
-            try:
-                self.serial.open()
-            except Exception as e:
-                sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
-        if do_change_port:
-            self.change_port()
-        else:
-            # and restart the reader thread
-            self._start_reader()
-            sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
-
-    def get_help_text(self):
-        """return the help text"""
-        # help text, starts with blank line!
-        return """
---- pySerial ({version}) - miniterm - help
----
---- {exit:8} Exit program (alias {menu} Q)
---- {menu:8} Menu escape key, followed by:
---- Menu keys:
----    {menu:7} Send the menu character itself to remote
----    {exit:7} Send the exit character itself to remote
----    {info:7} Show info
----    {upload:7} Upload file (prompt will be shown)
----    {repr:7} encoding
----    {filter:7} edit filters
---- Toggles:
----    {rts:7} RTS   {dtr:7} DTR   {brk:7} BREAK
----    {echo:7} echo  {eol:7} EOL
----
---- Port settings ({menu} followed by the following):
----    p          change port
----    7 8        set data bits
----    N E O S M  change parity (None, Even, Odd, Space, Mark)
----    1 2 3      set stop bits (1, 2, 1.5)
----    b          change baud rate
----    x X        disable/enable software flow control
----    r R        disable/enable hardware flow control
-""".format(version=getattr(serial, 'VERSION', 'unknown version'),
-           exit=key_description(self.exit_character),
-           menu=key_description(self.menu_character),
-           rts=key_description('\x12'),
-           dtr=key_description('\x04'),
-           brk=key_description('\x02'),
-           echo=key_description('\x05'),
-           info=key_description('\x09'),
-           upload=key_description('\x15'),
-           repr=key_description('\x01'),
-           filter=key_description('\x06'),
-           eol=key_description('\x0c'))
-
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# default args can be used to override when calling main() from an other script
-# e.g to create a miniterm-my-device.py
-def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
-    """Command line tool, entry point"""
-
-    import argparse
-
-    parser = argparse.ArgumentParser(
-        description='Miniterm - A simple terminal program for the serial port.')
-
-    parser.add_argument(
-        'port',
-        nargs='?',
-        help='serial port name ("-" to show port list)',
-        default=default_port)
-
-    parser.add_argument(
-        'baudrate',
-        nargs='?',
-        type=int,
-        help='set baud rate, default: %(default)s',
-        default=default_baudrate)
-
-    group = parser.add_argument_group('port settings')
-
-    group.add_argument(
-        '--parity',
-        choices=['N', 'E', 'O', 'S', 'M'],
-        type=lambda c: c.upper(),
-        help='set parity, one of {N E O S M}, default: N',
-        default='N')
-
-    group.add_argument(
-        '--rtscts',
-        action='store_true',
-        help='enable RTS/CTS flow control (default off)',
-        default=False)
-
-    group.add_argument(
-        '--xonxoff',
-        action='store_true',
-        help='enable software flow control (default off)',
-        default=False)
-
-    group.add_argument(
-        '--rts',
-        type=int,
-        help='set initial RTS line state (possible values: 0, 1)',
-        default=default_rts)
-
-    group.add_argument(
-        '--dtr',
-        type=int,
-        help='set initial DTR line state (possible values: 0, 1)',
-        default=default_dtr)
-
-    group.add_argument(
-        '--non-exclusive',
-        dest='exclusive',
-        action='store_false',
-        help='disable locking for native ports',
-        default=True)
-
-    group.add_argument(
-        '--ask',
-        action='store_true',
-        help='ask again for port when open fails',
-        default=False)
-
-    group = parser.add_argument_group('data handling')
-
-    group.add_argument(
-        '-e', '--echo',
-        action='store_true',
-        help='enable local echo (default off)',
-        default=False)
-
-    group.add_argument(
-        '--encoding',
-        dest='serial_port_encoding',
-        metavar='CODEC',
-        help='set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s',
-        default='UTF-8')
-
-    group.add_argument(
-        '-f', '--filter',
-        action='append',
-        metavar='NAME',
-        help='add text transformation',
-        default=[])
-
-    group.add_argument(
-        '--eol',
-        choices=['CR', 'LF', 'CRLF'],
-        type=lambda c: c.upper(),
-        help='end of line mode',
-        default='CRLF')
-
-    group.add_argument(
-        '--raw',
-        action='store_true',
-        help='Do no apply any encodings/transformations',
-        default=False)
-
-    group = parser.add_argument_group('hotkeys')
-
-    group.add_argument(
-        '--exit-char',
-        type=int,
-        metavar='NUM',
-        help='Unicode of special character that is used to exit the application, default: %(default)s',
-        default=0x1d)  # GS/CTRL+]
-
-    group.add_argument(
-        '--menu-char',
-        type=int,
-        metavar='NUM',
-        help='Unicode code of special character that is used to control miniterm (menu), default: %(default)s',
-        default=0x14)  # Menu: CTRL+T
-
-    group = parser.add_argument_group('diagnostics')
-
-    group.add_argument(
-        '-q', '--quiet',
-        action='store_true',
-        help='suppress non-error messages',
-        default=False)
-
-    group.add_argument(
-        '--develop',
-        action='store_true',
-        help='show Python traceback on error',
-        default=False)
-
-    args = parser.parse_args()
-
-    if args.menu_char == args.exit_char:
-        parser.error('--exit-char can not be the same as --menu-char')
-
-    if args.filter:
-        if 'help' in args.filter:
-            sys.stderr.write('Available filters:\n')
-            sys.stderr.write('\n'.join(
-                '{:<10} = {.__doc__}'.format(k, v)
-                for k, v in sorted(TRANSFORMATIONS.items())))
-            sys.stderr.write('\n')
-            sys.exit(1)
-        filters = args.filter
-    else:
-        filters = ['default']
-
-    while True:
-        # no port given on command line -> ask user now
-        if args.port is None or args.port == '-':
-            try:
-                args.port = ask_for_port()
-            except KeyboardInterrupt:
-                sys.stderr.write('\n')
-                parser.error('user aborted and port is not given')
-            else:
-                if not args.port:
-                    parser.error('port is not given')
-        try:
-            serial_instance = serial.serial_for_url(
-                args.port,
-                args.baudrate,
-                parity=args.parity,
-                rtscts=args.rtscts,
-                xonxoff=args.xonxoff,
-                do_not_open=True)
-
-            if not hasattr(serial_instance, 'cancel_read'):
-                # enable timeout for alive flag polling if cancel_read is not available
-                serial_instance.timeout = 1
-
-            if args.dtr is not None:
-                if not args.quiet:
-                    sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
-                serial_instance.dtr = args.dtr
-            if args.rts is not None:
-                if not args.quiet:
-                    sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
-                serial_instance.rts = args.rts
-
-            if isinstance(serial_instance, serial.Serial):
-                serial_instance.exclusive = args.exclusive
-
-            serial_instance.open()
-        except serial.SerialException as e:
-            sys.stderr.write('could not open port {!r}: {}\n'.format(args.port, e))
-            if args.develop:
-                raise
-            if not args.ask:
-                sys.exit(1)
-            else:
-                args.port = '-'
-        else:
-            break
-
-    miniterm = Miniterm(
-        serial_instance,
-        echo=args.echo,
-        eol=args.eol.lower(),
-        filters=filters)
-    miniterm.exit_character = unichr(args.exit_char)
-    miniterm.menu_character = unichr(args.menu_char)
-    miniterm.raw = args.raw
-    miniterm.set_rx_encoding(args.serial_port_encoding)
-    miniterm.set_tx_encoding(args.serial_port_encoding)
-
-    if not args.quiet:
-        sys.stderr.write('--- Miniterm on {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
-            p=miniterm.serial))
-        sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
-            key_description(miniterm.exit_character),
-            key_description(miniterm.menu_character),
-            key_description(miniterm.menu_character),
-            key_description('\x08')))
-
-    miniterm.start()
-    try:
-        miniterm.join(True)
-    except KeyboardInterrupt:
-        pass
-    if not args.quiet:
-        sys.stderr.write('\n--- exit ---\n')
-    miniterm.join()
-    miniterm.close()
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-if __name__ == '__main__':
-    main()
diff --git a/modbus/serial/urlhandler/__init__.py b/modbus/serial/urlhandler/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/modbus/serial/urlhandler/protocol_alt.py b/modbus/serial/urlhandler/protocol_alt.py
deleted file mode 100644
index 2e666ca..0000000
--- a/modbus/serial/urlhandler/protocol_alt.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#! python
-#
-# This module implements a special URL handler that allows selecting an
-# alternate implementation provided by some backends.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-#
-# URL format:    alt://port[?option[=value][&option[=value]]]
-# options:
-# - class=X used class named X instead of Serial
-#
-# example:
-#   use poll based implementation on Posix (Linux):
-#   python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial
-
-from __future__ import absolute_import
-
-try:
-    import urlparse
-except ImportError:
-    import urllib.parse as urlparse
-
-import serial
-
-
-def serial_class_for_url(url):
-    """extract host and port from an URL string"""
-    parts = urlparse.urlsplit(url)
-    if parts.scheme != 'alt':
-        raise serial.SerialException(
-            'expected a string in the form "alt://port[?option[=value][&option[=value]]]": '
-            'not starting with alt:// ({!r})'.format(parts.scheme))
-    class_name = 'Serial'
-    try:
-        for option, values in urlparse.parse_qs(parts.query, True).items():
-            if option == 'class':
-                class_name = values[0]
-            else:
-                raise ValueError('unknown option: {!r}'.format(option))
-    except ValueError as e:
-        raise serial.SerialException(
-            'expected a string in the form '
-            '"alt://port[?option[=value][&option[=value]]]": {!r}'.format(e))
-    if not hasattr(serial, class_name):
-        raise ValueError('unknown class: {!r}'.format(class_name))
-    cls = getattr(serial, class_name)
-    if not issubclass(cls, serial.Serial):
-        raise ValueError('class {!r} is not an instance of Serial'.format(class_name))
-    return (''.join([parts.netloc, parts.path]), cls)
-
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-if __name__ == '__main__':
-    s = serial.serial_for_url('alt:///dev/ttyS0?class=PosixPollSerial')
-    print(s)
diff --git a/modbus/serial/urlhandler/protocol_cp2110.py b/modbus/serial/urlhandler/protocol_cp2110.py
deleted file mode 100644
index 44ad4eb..0000000
--- a/modbus/serial/urlhandler/protocol_cp2110.py
+++ /dev/null
@@ -1,258 +0,0 @@
-#! python
-#
-# Backend for Silicon Labs CP2110/4 HID-to-UART devices.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2001-2015 Chris Liechti 
-# (C) 2019 Google LLC
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-
-# This backend implements support for HID-to-UART devices manufactured
-# by Silicon Labs and marketed as CP2110 and CP2114. The
-# implementation is (mostly) OS-independent and in userland. It relies
-# on cython-hidapi (https://github.com/trezor/cython-hidapi).
-
-# The HID-to-UART protocol implemented by CP2110/4 is described in the
-# AN434 document from Silicon Labs:
-# https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf
-
-# TODO items:
-
-# - rtscts support is configured for hardware flow control, but the
-#   signaling is missing (AN434 suggests this is done through GPIO).
-# - Cancelling reads and writes is not supported.
-# - Baudrate validation is not implemented, as it depends on model and configuration.
-
-import struct
-import threading
-
-try:
-    import urlparse
-except ImportError:
-    import urllib.parse as urlparse
-
-try:
-    import Queue
-except ImportError:
-    import queue as Queue
-
-import hid  # hidapi
-
-import serial
-from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout
-
-
-# Report IDs and related constant
-_REPORT_GETSET_UART_ENABLE = 0x41
-_DISABLE_UART = 0x00
-_ENABLE_UART = 0x01
-
-_REPORT_SET_PURGE_FIFOS = 0x43
-_PURGE_TX_FIFO = 0x01
-_PURGE_RX_FIFO = 0x02
-
-_REPORT_GETSET_UART_CONFIG = 0x50
-
-_REPORT_SET_TRANSMIT_LINE_BREAK = 0x51
-_REPORT_SET_STOP_LINE_BREAK = 0x52
-
-
-class Serial(SerialBase):
-    # This is not quite correct. AN343 specifies that the minimum
-    # baudrate is different between CP2110 and CP2114, and it's halved
-    # when using non-8-bit symbols.
-    BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200,
-                 38400, 57600, 115200, 230400, 460800, 500000, 576000,
-                 921600, 1000000)
-
-    def __init__(self, *args, **kwargs):
-        self._hid_handle = None
-        self._read_buffer = None
-        self._thread = None
-        super(Serial, self).__init__(*args, **kwargs)
-
-    def open(self):
-        if self._port is None:
-            raise SerialException("Port must be configured before it can be used.")
-        if self.is_open:
-            raise SerialException("Port is already open.")
-
-        self._read_buffer = Queue.Queue()
-
-        self._hid_handle = hid.device()
-        try:
-            portpath = self.from_url(self.portstr)
-            self._hid_handle.open_path(portpath)
-        except OSError as msg:
-            raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
-
-        try:
-            self._reconfigure_port()
-        except:
-            try:
-                self._hid_handle.close()
-            except:
-                pass
-            self._hid_handle = None
-            raise
-        else:
-            self.is_open = True
-            self._thread = threading.Thread(target=self._hid_read_loop)
-            self._thread.setDaemon(True)
-            self._thread.setName('pySerial CP2110 reader thread for {}'.format(self._port))
-            self._thread.start()
-
-    def from_url(self, url):
-        parts = urlparse.urlsplit(url)
-        if parts.scheme != "cp2110":
-            raise SerialException(
-                'expected a string in the forms '
-                '"cp2110:///dev/hidraw9" or "cp2110://0001:0023:00": '
-                'not starting with cp2110:// {{!r}}'.format(parts.scheme))
-        if parts.netloc:  # cp2100://BUS:DEVICE:ENDPOINT, for libusb
-            return parts.netloc.encode('utf-8')
-        return parts.path.encode('utf-8')
-
-    def close(self):
-        self.is_open = False
-        if self._thread:
-            self._thread.join(1)  # read timeout is 0.1
-            self._thread = None
-        self._hid_handle.close()
-        self._hid_handle = None
-
-    def _reconfigure_port(self):
-        parity_value = None
-        if self._parity == serial.PARITY_NONE:
-            parity_value = 0x00
-        elif self._parity == serial.PARITY_ODD:
-            parity_value = 0x01
-        elif self._parity == serial.PARITY_EVEN:
-            parity_value = 0x02
-        elif self._parity == serial.PARITY_MARK:
-            parity_value = 0x03
-        elif self._parity == serial.PARITY_SPACE:
-            parity_value = 0x04
-        else:
-            raise ValueError('Invalid parity: {!r}'.format(self._parity))
-
-        if self.rtscts:
-            flow_control_value = 0x01
-        else:
-            flow_control_value = 0x00
-
-        data_bits_value = None
-        if self._bytesize == 5:
-            data_bits_value = 0x00
-        elif self._bytesize == 6:
-            data_bits_value = 0x01
-        elif self._bytesize == 7:
-            data_bits_value = 0x02
-        elif self._bytesize == 8:
-            data_bits_value = 0x03
-        else:
-            raise ValueError('Invalid char len: {!r}'.format(self._bytesize))
-
-        stop_bits_value = None
-        if self._stopbits == serial.STOPBITS_ONE:
-            stop_bits_value = 0x00
-        elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
-            stop_bits_value = 0x01
-        elif self._stopbits == serial.STOPBITS_TWO:
-            stop_bits_value = 0x01
-        else:
-            raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits))
-
-        configuration_report = struct.pack(
-            '>BLBBBB',
-            _REPORT_GETSET_UART_CONFIG,
-            self._baudrate,
-            parity_value,
-            flow_control_value,
-            data_bits_value,
-            stop_bits_value)
-
-        self._hid_handle.send_feature_report(configuration_report)
-
-        self._hid_handle.send_feature_report(
-            bytes((_REPORT_GETSET_UART_ENABLE, _ENABLE_UART)))
-        self._update_break_state()
-
-    @property
-    def in_waiting(self):
-        return self._read_buffer.qsize()
-
-    def reset_input_buffer(self):
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._hid_handle.send_feature_report(
-            bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_RX_FIFO)))
-        # empty read buffer
-        while self._read_buffer.qsize():
-            self._read_buffer.get(False)
-
-    def reset_output_buffer(self):
-        if not self.is_open:
-            raise PortNotOpenError()
-        self._hid_handle.send_feature_report(
-            bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_TX_FIFO)))
-
-    def _update_break_state(self):
-        if not self._hid_handle:
-            raise PortNotOpenError()
-
-        if self._break_state:
-            self._hid_handle.send_feature_report(
-                bytes((_REPORT_SET_TRANSMIT_LINE_BREAK, 0)))
-        else:
-            # Note that while AN434 states "There are no data bytes in
-            # the payload other than the Report ID", either hidapi or
-            # Linux does not seem to send the report otherwise.
-            self._hid_handle.send_feature_report(
-                bytes((_REPORT_SET_STOP_LINE_BREAK, 0)))
-
-    def read(self, size=1):
-        if not self.is_open:
-            raise PortNotOpenError()
-
-        data = bytearray()
-        try:
-            timeout = Timeout(self._timeout)
-            while len(data) < size:
-                if self._thread is None:
-                    raise SerialException('connection failed (reader thread died)')
-                buf = self._read_buffer.get(True, timeout.time_left())
-                if buf is None:
-                    return bytes(data)
-                data += buf
-                if timeout.expired():
-                    break
-        except Queue.Empty:  # -> timeout
-            pass
-        return bytes(data)
-
-    def write(self, data):
-        if not self.is_open:
-            raise PortNotOpenError()
-        data = to_bytes(data)
-        tx_len = len(data)
-        while tx_len > 0:
-            to_be_sent = min(tx_len, 0x3F)
-            report = to_bytes([to_be_sent]) + data[:to_be_sent]
-            self._hid_handle.write(report)
-
-            data = data[to_be_sent:]
-            tx_len = len(data)
-
-    def _hid_read_loop(self):
-        try:
-            while self.is_open:
-                data = self._hid_handle.read(64, timeout_ms=100)
-                if not data:
-                    continue
-                data_len = data.pop(0)
-                assert data_len == len(data)
-                self._read_buffer.put(bytearray(data))
-        finally:
-            self._thread = None
diff --git a/modbus/serial/urlhandler/protocol_hwgrep.py b/modbus/serial/urlhandler/protocol_hwgrep.py
deleted file mode 100644
index 1a288c9..0000000
--- a/modbus/serial/urlhandler/protocol_hwgrep.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#! python
-#
-# This module implements a special URL handler that uses the port listing to
-# find ports by searching the string descriptions.
-#
-# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C) 2011-2015 Chris Liechti 
-#
-# SPDX-License-Identifier:    BSD-3-Clause
-#
-# URL format:    hwgrep://&