Ejemplo n.º 1
0
class ModbusHub:
    """Thread safe wrapper class for pymodbus."""
    def __init__(self, client_config):
        """Initialize the Modbus hub."""

        # generic configuration
        self._client = None
        self._lock = threading.Lock()
        self._config_name = client_config[CONF_NAME]
        self._config_type = client_config[CONF_TYPE]
        self._config_port = client_config[CONF_PORT]
        self._config_timeout = client_config[CONF_TIMEOUT]
        self._config_delay = 0

        if self._config_type == "serial":
            # serial configuration
            self._config_method = client_config[CONF_METHOD]
            self._config_baudrate = client_config[CONF_BAUDRATE]
            self._config_stopbits = client_config[CONF_STOPBITS]
            self._config_bytesize = client_config[CONF_BYTESIZE]
            self._config_parity = client_config[CONF_PARITY]
        else:
            # network configuration
            self._config_host = client_config[CONF_HOST]
            self._config_delay = client_config[CONF_DELAY]
            if self._config_delay > 0:
                _LOGGER.warning(
                    "Parameter delay is accepted but not used in this version")

    @property
    def name(self):
        """Return the name of this hub."""
        return self._config_name

    def setup(self):
        """Set up pymodbus client."""
        if self._config_type == "serial":
            self._client = ModbusSerialClient(
                method=self._config_method,
                port=self._config_port,
                baudrate=self._config_baudrate,
                stopbits=self._config_stopbits,
                bytesize=self._config_bytesize,
                parity=self._config_parity,
                timeout=self._config_timeout,
            )
        elif self._config_type == "rtuovertcp":
            self._client = ModbusTcpClient(
                host=self._config_host,
                port=self._config_port,
                framer=ModbusRtuFramer,
                timeout=self._config_timeout,
            )
        elif self._config_type == "tcp":
            self._client = ModbusTcpClient(
                host=self._config_host,
                port=self._config_port,
                timeout=self._config_timeout,
            )
        elif self._config_type == "udp":
            self._client = ModbusUdpClient(
                host=self._config_host,
                port=self._config_port,
                timeout=self._config_timeout,
            )
        else:
            assert False

        # Connect device
        self.connect()

    def close(self):
        """Disconnect client."""
        with self._lock:
            self._client.close()

    def connect(self):
        """Connect client."""
        with self._lock:
            self._client.connect()

    def read_coils(self, unit, address, count):
        """Read coils."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            return self._client.read_coils(address, count, **kwargs)

    def read_discrete_inputs(self, unit, address, count):
        """Read discrete inputs."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            return self._client.read_discrete_inputs(address, count, **kwargs)

    def read_input_registers(self, unit, address, count):
        """Read input registers."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            return self._client.read_input_registers(address, count, **kwargs)

    def read_holding_registers(self, unit, address, count):
        """Read holding registers."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            return self._client.read_holding_registers(address, count,
                                                       **kwargs)

    def write_coil(self, unit, address, value):
        """Write coil."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            self._client.write_coil(address, value, **kwargs)

    def write_register(self, unit, address, value):
        """Write register."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            self._client.write_register(address, value, **kwargs)

    def write_registers(self, unit, address, values):
        """Write registers."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            self._client.write_registers(address, values, **kwargs)
Ejemplo n.º 2
0
class ModbusHub:
    """Thread safe wrapper class for pymodbus."""
    def __init__(self, client_config):
        """Initialize the modbus hub."""
        # generic configuration
        self._client = None
        self._kwargs = {'unit': client_config[CONF_MASTER_UNIT_ID]}
        self._lock = threading.Lock()
        self._config_type = client_config[CONF_TYPE]
        self._config_port = client_config[CONF_PORT]
        self._config_timeout = client_config[CONF_TIMEOUT]
        self._config_delay = 0

        if self._config_type == "serial":
            # serial configuration
            self._config_method = client_config[CONF_METHOD]
            self._config_baudrate = client_config[CONF_BAUDRATE]
            self._config_stopbits = client_config[CONF_STOPBITS]
            self._config_bytesize = client_config[CONF_BYTESIZE]
            self._config_parity = client_config[CONF_PARITY]
        else:
            # network configuration
            self._config_host = client_config[CONF_HOST]

    def setup(self):
        """Set up pymodbus client."""
        if self._config_type == "serial":
            from pymodbus.client.sync import ModbusSerialClient
            self._client = ModbusSerialClient(
                method=self._config_method,
                port=self._config_port,
                baudrate=self._config_baudrate,
                stopbits=self._config_stopbits,
                bytesize=self._config_bytesize,
                parity=self._config_parity,
                timeout=self._config_timeout,
                retry_on_empty=True,
            )
        elif self._config_type == "rtuovertcp":
            from pymodbus.client.sync import ModbusTcpClient
            from pymodbus.transaction import ModbusRtuFramer
            self._client = ModbusTcpClient(
                host=self._config_host,
                port=self._config_port,
                framer=ModbusRtuFramer,
                timeout=self._config_timeout,
            )
        elif self._config_type == "tcp":
            from pymodbus.client.sync import ModbusTcpClient
            self._client = ModbusTcpClient(
                host=self._config_host,
                port=self._config_port,
                timeout=self._config_timeout,
            )
        elif self._config_type == "udp":
            from pymodbus.client.sync import ModbusUdpClient
            self._client = ModbusUdpClient(
                host=self._config_host,
                port=self._config_port,
                timeout=self._config_timeout,
            )
        else:
            raise ValueError(("Unsupported config_type, must be serial, " +
                              "tcp, udp, rtuovertcp"))

        # Connect device
        self.connect()

    def close(self):
        """Disconnect client."""
        with self._lock:
            self._client.close()

    def connect(self):
        """Connect client."""
        with self._lock:
            self._client.connect()

    def read_coils(self, address, count=1):
        """Read coils."""
        with self._lock:
            return self._client.read_coils(address, count, **self._kwargs)

    def read_input_registers(self, address, count=1):
        """Read input registers."""
        with self._lock:
            return self._client.read_input_registers(address, count,
                                                     **self._kwargs)

    def read_holding_registers(self, address, count=1):
        """Read holding registers."""
        with self._lock:
            return self._client.read_holding_registers(address, count,
                                                       **self._kwargs)

    def write_coil(self, address, value):
        """Write coil."""
        with self._lock:
            self._client.write_coil(address, value, **self._kwargs)

    def write_register(self, address, value):
        """Write register."""
        with self._lock:
            self._client.write_register(address, value, **self._kwargs)

    def write_registers(self, address, values):
        """Write registers."""
        with self._lock:
            self._client.write_registers(address, values, **self._kwargs)
Ejemplo n.º 3
0
class CtModbus(Ctui):
    """Commands that users may use at the application prompt."""
    name = 'ctmodbus'
    version = '0.5'
    description = 'a security professional\'s swiss army knife for interacting with Modbus devices'
    prompt = 'ctmodbus> '
    session = None
    unit_id = 1
    statusbar = 'Session:{}'.format(session)

    def do_debug(self, args, output_text):
        message_dialog(title='Debug', text=str(eval(args)))

    def validate_serial_device(self, device):
        devices = [x.device for x in serial.tools.list_ports.comports()]
        devices_pretty = '\n'.join([x for x in devices])
        assert (device in devices), '{} is not in: \n{}'.format(
            device, devices_pretty)
        return device

    def do_connect_ascii(self, args, output_text):
        """Connect to a Modbus ASCII serial device"""
        assert (
            self.session == None
        ), 'Session already open.  Close first.'  # ToDo assert session type
        device = self.validate_serial_device(args)
        self.session = ModbusSerialClient(method='ascii',
                                          port=device,
                                          timeout=1)
        message_dialog(title='Success',
                       text='Session opened with {}'.format(device))

    def do_connect_rtu(self, args, output_text):
        """Connect to a Modbus RTU serial device"""
        assert (
            self.session == None
        ), 'Session already open.  Close first.'  # ToDo assert session type
        device = self.validate_serial_device(args)
        self.session = ModbusSerialClient(method='rtu', port=device, timeout=1)
        message_dialog(title='Success',
                       text='Session opened with {}'.format(device))

    def parse_ip_port(self, ip_port):
        parts = ip_port.split(':')
        assert (0 < len(parts) <
                3), 'Must be in format ip or host or ip:port or host:port'
        host = parts[0]
        port = 502
        if len(parts) == 2:
            port = int(parts[1])
        return host, port

    def do_connect_tcp(self, args, output_text):
        """Connect to a Modbus TCP device"""
        assert (
            self.session == None
        ), 'Session already open.  Close first.'  # ToDo assert session type
        host, port = self.parse_ip_port(args)
        self.session = ModbusTcpClient(host, port)
        message_dialog(title='Success',
                       text='Session opened with {}:{}'.format(host, port))

    def do_connect_udp(self, args, output_text):
        """Connect to a Modbus UDP device"""
        assert (
            self.session == None
        ), 'Session already open.  Close first.'  # ToDo assert session type
        host, port = self.parse_ip_port(args)
        self.session = ModbusUdpClient(host, port)
        message_dialog(title='Success',
                       text='Session opened with {}:{}'.format(host, port))

    def do_close(self, args, output_text):
        """Close a session."""
        assert (
            self.session
        ), 'There is not an open session.  Connect to one first'  # ToDo assert session type
        self.session.close()
        message_dialog(title='Success',
                       text='Session closed with {}'.format(self.session))
        self.session = None
        return None

    def do_read_id(self, args, output_text):
        """Read device identification data."""
        assert (
            self.session
        ), 'There is not an open session.  Connect to one first'  # ToDo assert session type
        request = ReadDeviceInformationRequest(unit=1)
        response = self.session.execute(request)
        message_dialog(title="Response", text=str(reponse))
        return None

    def log_and_output_bits(self, desc, start, stop, results):
        """Log in project database and output to screen"""
        date, time = str(datetime.today()).split()
        output_text = '{} {} - {}'.format(date, time, desc)
        i = 0
        for address in range(start, stop):
            # Finish line after 32 bits of output
            if i % 32 == 0: output_text += '\n{:>5}:  '.format(address)
            i += 1
            # Print next bit
            output_text += str(results[address])
            # Print spaces ever 4 bits
            if i % 4 == 0: output_text += ' '
        # TODO: Add to self.storage
        output_text += '\n'
        return output_text

    def log_and_output_words(self, desc, start, stop, results):
        """Log in project database and output to screen"""
        date, time = str(datetime.today()).split()
        output_text = '{} {} - {}'.format(date, time, desc)
        i = 0
        for address in range(start, stop):
            # Finish line after 8 words of output
            if i % 8 == 0: output_text += '\n{:>5}:  '.format(address)
            i += 1
            # Print next word
            output_text += '{:04x}'.format(results[address]) + ' '
        # TODO: Add to self.storage
        output_text += '\n'
        return output_text

    def response_message_dialog(self, message, results):
        la, lr, fa = None, None, None  #last_addres, last_result, first_address
        table = [['Addr', 'Int', 'HEX', 'ASCII']]
        for address, result in results.items():
            if address - 1 == la and result == lr:
                if fa == None:
                    fa = la
            elif la != None:
                if fa == None:
                    table.append([la, lr, '{:04x}'.format(lr), str(chr(lr))])
                    fa = None
                else:
                    s = '{0}-{1}'.format(fa, la)
                    table.append([s, lr, '{:04x}'.format(lr), str(chr(lr))])
                    fa = None
            la, lr = address, result
        # Print final output from for loop
        if fa == None:
            table.append([la, lr, '{:04x}'.format(lr), str(chr(lr))])
        else:
            s = '{0}-{1}'.format(fa, la)
            table.append([s, lr, '{:04x}'.format(lr), str(chr(lr))])
        message += tabulate(table, headers='firstrow', tablefmt='simple')
        message_dialog(title='Success', text=message)

    def read_common(self, args):
        assert (self.session
                ), 'There is not an open session.  Connect to one first'
        # TODO assert session type
        assert (len(args) > 0), 'No address specified'
        parts = args.split(maxsplit=1)
        csr = parts[0]

        max_count = 100
        if len(parts) == 2:
            assert (parts[1].isdigit()), 'max_count must be a digit'
            max_count = int(parts[1])

        loops = Loops(csr, minimum=0, maximum=65535)
        for loop in loops.max_count(max_count):
            yield loop.values()

    def do_read_discrete_inputs(self, args, output_text):
        """Read digital inputs in format: 30,50,70-99,105."""
        desc = 'Modbus Function 2, Read Discrete Inputs'
        results = {}
        for start, stop, count in self.read_common(args):
            response = self.session.read_discrete_inputs(start,
                                                         count,
                                                         unit=self.unit_id)
            for address, result in zip(range(start, stop), response.bits):
                results[address] = int(result)
            output_text += self.log_and_output_bits(desc, start, stop, results)
        csr = args.split()[0]
        message = '{}: {}\n\n'.format(desc, csr)
        self.response_message_dialog(message, results)
        return output_text

    def do_read_coils(self, args, output_text):
        """Read digital outputs in format: 30,50,70-99,105."""
        desc = 'Modbus Function 1, Read Coils'
        results = {}
        for start, stop, count in self.read_common(args):
            response = self.session.read_coils(start, count, unit=self.unit_id)
            for address, result in zip(range(start, stop), response.bits):
                results[address] = int(result)
            output_text += self.log_and_output_bits(desc, start, stop, results)
        csr = args.split()[0]
        message = '{}: {}\n\n'.format(desc, csr)
        self.response_message_dialog(message, results)
        return output_text

    def do_read_input_registers(self, args, output_text):
        """Read analog inputs or internal registers in format: 30,50,70-99,105."""
        desc = 'Modbus Function 4, Read Input Registers'
        results = {}
        for start, stop, count in self.read_common(args):
            response = self.session.read_input_registers(start,
                                                         count,
                                                         unit=self.unit_id)
            for address, result in zip(range(start, stop), response.registers):
                results[address] = result
            output_text += self.log_and_output_words(desc, start, stop,
                                                     results)
        csr = args.split()[0]
        message = '{}: {}\n\n'.format(desc, csr)
        self.response_message_dialog(message, results)
        return output_text

    def do_read_holding_registers(self, args, output_text):
        """Read digital inputs in format: 30,50,70-99,105."""
        desc = 'Modbus Function 3, Read Holding Registers'
        results = {}
        for start, stop, count in self.read_common(args):
            response = self.session.read_holding_registers(start,
                                                           count,
                                                           unit=self.unit_id)
            for address, result in zip(range(start, stop), response.registers):
                results[address] = result
            output_text += self.log_and_output_words(desc, start, stop,
                                                     results)
        csr = args.split()[0]
        message = '{}: {}\n\n'.format(desc, csr)
        self.response_message_dialog(message, results)
        return output_text

    def do_write_registers(self, args, output_text):
        """Write to registers in format: <address> <int>..."""
        start, values = args.split(maxsplit=1)
        assert (start.isdigit()), 'start must be an integer'
        int_start = int(start)
        try:
            list_int_values = [int(x) for x in values.split()]
        except:
            raise AssertionError('List of values to write must be decimals')
        if len(list_int_values) == 1:
            self.session.write_register(int_start,
                                        list_int_values[0],
                                        unit=self.unit_id)
            desc = 'Modbus Function 5, Write Single Register'
        else:
            self.session.write_registers(int_start,
                                         list_int_values,
                                         unit=self.unit_id)
            desc = 'Modbus Function 5, Write Multiple Registers'
        message_dialog(title='Success',
                       text=f'Wrote {list_int_values} starting at {int_start}')
        results = {}
        stop = int_start + len(list_int_values)
        for i in range(len(list_int_values)):
            results[int_start + i] = list_int_values[i]
        output_text += self.log_and_output_words(desc, int_start, stop,
                                                 results)
        return output_text

    def do_write_coils(self, args, output_text):
        """Write to registers in format: <address> <int>..."""
        desc = 'Modbus Function 3, Read Holding Registers'
        start, values = args.split(maxsplit=1)
        assert (start.isdigit()), 'start must be an integer'
        int_start = int(start)
        try:
            list_bool_values = [bool(int(x)) for x in values.replace(' ', '')]
        except:
            raise AssertionError('List of values to write must be decimals')
        if len(list_bool_values) == 1:
            self.session.write_coil(int_start,
                                    list_bool_values[0],
                                    unit=self.unit_id)
            desc = 'Modbus Function 5, Write Single Coil'
        else:
            self.session.write_coils(int_start,
                                     list_bool_values,
                                     unit=self.unit_id)
            desc = 'Modbus Function 15, Write Multiple Coils'
        message_dialog(
            title='Success',
            text=f'Wrote {list_bool_values} starting at {int_start}')
        results = {}
        stop = int_start + len(list_bool_values)
        for i in range(len(list_bool_values)):
            results[int_start + i] = int(list_bool_values[i])
        output_text += self.log_and_output_bits(desc, int_start, stop, results)
        return output_text
Ejemplo n.º 4
0
class ModbusHub:
    """Thread safe wrapper class for pymodbus."""

    def __init__(self, client_config):
        """Initialize the Modbus hub."""

        # generic configuration
        self._client = None
        self._in_error = False
        self._lock = threading.Lock()
        self._config_name = client_config[CONF_NAME]
        self._config_type = client_config[CONF_TYPE]
        self._config_port = client_config[CONF_PORT]
        self._config_timeout = client_config[CONF_TIMEOUT]
        self._config_delay = 0

        Defaults.Timeout = 10
        if self._config_type == "serial":
            # serial configuration
            self._config_method = client_config[CONF_METHOD]
            self._config_baudrate = client_config[CONF_BAUDRATE]
            self._config_stopbits = client_config[CONF_STOPBITS]
            self._config_bytesize = client_config[CONF_BYTESIZE]
            self._config_parity = client_config[CONF_PARITY]
        else:
            # network configuration
            self._config_host = client_config[CONF_HOST]
            self._config_delay = client_config[CONF_DELAY]

        if self._config_delay > 0:
            _LOGGER.warning("Parameter delay is accepted but not used in this version")

    @property
    def name(self):
        """Return the name of this hub."""
        return self._config_name

    def _log_error(self, exception_error: ModbusException, error_state=True):
        log_text = "Pymodbus: " + str(exception_error)
        if self._in_error:
            _LOGGER.debug(log_text)
        else:
            _LOGGER.error(log_text)
            self._in_error = error_state

    def setup(self):
        """Set up pymodbus client."""
        try:
            if self._config_type == "serial":
                self._client = ModbusSerialClient(
                    method=self._config_method,
                    port=self._config_port,
                    baudrate=self._config_baudrate,
                    stopbits=self._config_stopbits,
                    bytesize=self._config_bytesize,
                    parity=self._config_parity,
                    timeout=self._config_timeout,
                    retry_on_empty=True,
                )
            elif self._config_type == "rtuovertcp":
                self._client = ModbusTcpClient(
                    host=self._config_host,
                    port=self._config_port,
                    framer=ModbusRtuFramer,
                    timeout=self._config_timeout,
                )
            elif self._config_type == "tcp":
                self._client = ModbusTcpClient(
                    host=self._config_host,
                    port=self._config_port,
                    timeout=self._config_timeout,
                )
            elif self._config_type == "udp":
                self._client = ModbusUdpClient(
                    host=self._config_host,
                    port=self._config_port,
                    timeout=self._config_timeout,
                )
        except ModbusException as exception_error:
            self._log_error(exception_error, error_state=False)
            return

        # Connect device
        self.connect()

    def close(self):
        """Disconnect client."""
        with self._lock:
            try:
                if self._client:
                    self._client.close()
                    self._client = None
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return

    def connect(self):
        """Connect client."""
        with self._lock:
            try:
                self._client.connect()
            except ModbusException as exception_error:
                self._log_error(exception_error, error_state=False)
                return

    def read_coils(self, unit, address, count):
        """Read coils."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.read_coils(address, count, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return None
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return None
            self._in_error = False
            return result

    def read_discrete_inputs(self, unit, address, count):
        """Read discrete inputs."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.read_discrete_inputs(address, count, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return None
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return None
            self._in_error = False
            return result

    def read_input_registers(self, unit, address, count):
        """Read input registers."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.read_input_registers(address, count, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return None
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return None
            self._in_error = False
            return result

    def read_holding_registers(self, unit, address, count):
        """Read holding registers."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.read_holding_registers(address, count, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return None
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return None
            self._in_error = False
            return result

    def write_coil(self, unit, address, value) -> bool:
        """Write coil."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.write_coil(address, value, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return False
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return False
            self._in_error = False
            return True

    def write_coils(self, unit, address, values) -> bool:
        """Write coil."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.write_coils(address, values, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return False
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return False
            self._in_error = False
            return True

    def write_register(self, unit, address, value) -> bool:
        """Write register."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.write_register(address, value, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return False
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return False
            self._in_error = False
            return True

    def write_registers(self, unit, address, values) -> bool:
        """Write registers."""
        with self._lock:
            kwargs = {"unit": unit} if unit else {}
            try:
                result = self._client.write_registers(address, values, **kwargs)
            except ModbusException as exception_error:
                self._log_error(exception_error)
                return False
            if isinstance(result, (ExceptionResponse, IllegalFunctionRequest)):
                self._log_error(result)
                return False
            self._in_error = False
            return True