Coverage for C:\Users\hjanssen\HOME\pyCharmProjects\ethz_hvl\hvl_ccb\hvl_ccb\comm\modbus_tcp.py : 43%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# Copyright (c) 2019-2020 ETH Zurich, SIS ID and HVL D-ITET
2#
3"""
4Communication protocol for modbus TCP ports. Makes use of the
5`pymodbus <https://pymodbus.readthedocs.io/en/latest/>`_ library.
6"""
8import logging
9from typing import List, Union
11from IPy import IP
12from pymodbus.client.sync import ModbusTcpClient
13from pymodbus.constants import Defaults as ModbusDefaults
14from pymodbus.exceptions import ConnectionException
16from .base import CommunicationProtocol
17from ..configuration import configdataclass
20class ModbusTcpConnectionFailedException(ConnectionException):
21 """
22 Exception raised when the connection failed.
23 """
24 pass
27@configdataclass
28class ModbusTcpCommunicationConfig:
29 """
30 Configuration dataclass for :class:`ModbusTcpCommunication`.
31 """
33 #: Host is the IP address of the connected device.
34 host: str
36 #: Unit number to be used when connecting with Modbus/TCP. Typically this is used
37 #: when connecting to a relay having Modbus/RTU-connected devices.
38 unit: int
40 #: TCP port
41 port: int = ModbusDefaults.Port
43 def clean_values(self):
44 # host, raises ValueError on its own if not suitable
45 IP(self.host)
48class ModbusTcpCommunication(CommunicationProtocol):
49 """
50 Implements the Communication Protocol for modbus TCP.
51 """
53 def __init__(self, configuration):
54 """Constructor for modbus"""
55 super().__init__(configuration)
57 # create the modbus port specified in the configuration
58 logging.debug(
59 'Create ModbusTcpClient with host: "{}", Port: "{}", Unit: "{}"'.format(
60 self.config.host,
61 self.config.port,
62 self.config.unit,
63 )
64 )
65 self.client = ModbusTcpClient(
66 self.config.host, port=self.config.port, unit=self.config.unit
67 )
69 @staticmethod
70 def config_cls():
71 return ModbusTcpCommunicationConfig
73 def open(self) -> None:
74 """
75 Open the Modbus TCP connection.
77 :raises ModbusTcpConnectionFailedException: if the connection fails.
78 """
80 # open the port
81 logging.debug('Open Modbus TCP Port.')
83 with self.access_lock:
84 if not self.client.connect():
85 raise ModbusTcpConnectionFailedException
87 def close(self):
88 """
89 Close the Modbus TCP connection.
90 """
92 # close the port
93 logging.debug('Close Modbus TCP Port.')
95 with self.access_lock:
96 self.client.close()
98 def write_registers(self, address: int, values: Union[List[int], int]):
99 """
100 Write values from the specified address forward.
102 :param address: address of the first register
103 :param values: list with all values
104 """
106 logging.debug(
107 'Write registers {address} with values {values}'.format(
108 address=address,
109 values=values,
110 )
111 )
113 with self.access_lock:
114 try:
115 self.client.write_registers(address=address, values=values,
116 unit=self.config.unit)
117 except ConnectionException as e:
118 raise ModbusTcpConnectionFailedException from e
120 def read_holding_registers(self, address: int, count: int) -> List[int]:
121 """
122 Read specified number of register starting with given address and return
123 the values from each register.
125 :param address: address of the first register
126 :param count: count of registers to read
127 :return: list of `int` values
128 """
130 logging.debug(
131 'Read holding registers {address} with count {count}.'.format(
132 address=address,
133 count=count,
134 )
135 )
137 with self.access_lock:
138 try:
139 registers = self.client.read_holding_registers(
140 address=address,
141 count=count
142 ).registers
143 except ConnectionException as e:
144 raise ModbusTcpConnectionFailedException from e
146 logging.debug(
147 'Returned holding registers {address}: {registers}'.format(
148 address=address,
149 registers=registers,
150 )
151 )
153 return registers
155 def read_input_registers(self, address: int, count: int) -> List[int]:
156 """
157 Read specified number of register starting with given address and return
158 the values from each register in a list.
160 :param address: address of the first register
161 :param count: count of registers to read
162 :return: list of `int` values
163 """
165 logging.debug(
166 'Read input registers {address} with count {count}.'.format(
167 address=address,
168 count=count,
169 )
170 )
172 with self.access_lock:
173 try:
174 registers = self.client.read_input_registers(
175 address=address,
176 count=count
177 ).registers
178 except ConnectionException as e:
179 raise ModbusTcpConnectionFailedException from e
181 logging.debug(
182 'Returned input registers {address}: {registers}'.format(
183 address=address,
184 registers=registers,
185 )
186 )
188 return registers