Hide keyboard shortcuts

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""" 

7 

8import logging 

9from typing import List, Union 

10 

11from IPy import IP 

12from pymodbus.client.sync import ModbusTcpClient 

13from pymodbus.constants import Defaults as ModbusDefaults 

14from pymodbus.exceptions import ConnectionException 

15 

16from .base import CommunicationProtocol 

17from ..configuration import configdataclass 

18 

19 

20class ModbusTcpConnectionFailedException(ConnectionException): 

21 """ 

22 Exception raised when the connection failed. 

23 """ 

24 pass 

25 

26 

27@configdataclass 

28class ModbusTcpCommunicationConfig: 

29 """ 

30 Configuration dataclass for :class:`ModbusTcpCommunication`. 

31 """ 

32 

33 #: Host is the IP address of the connected device. 

34 host: str 

35 

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 

39 

40 #: TCP port 

41 port: int = ModbusDefaults.Port 

42 

43 def clean_values(self): 

44 # host, raises ValueError on its own if not suitable 

45 IP(self.host) 

46 

47 

48class ModbusTcpCommunication(CommunicationProtocol): 

49 """ 

50 Implements the Communication Protocol for modbus TCP. 

51 """ 

52 

53 def __init__(self, configuration): 

54 """Constructor for modbus""" 

55 super().__init__(configuration) 

56 

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 ) 

68 

69 @staticmethod 

70 def config_cls(): 

71 return ModbusTcpCommunicationConfig 

72 

73 def open(self) -> None: 

74 """ 

75 Open the Modbus TCP connection. 

76 

77 :raises ModbusTcpConnectionFailedException: if the connection fails. 

78 """ 

79 

80 # open the port 

81 logging.debug('Open Modbus TCP Port.') 

82 

83 with self.access_lock: 

84 if not self.client.connect(): 

85 raise ModbusTcpConnectionFailedException 

86 

87 def close(self): 

88 """ 

89 Close the Modbus TCP connection. 

90 """ 

91 

92 # close the port 

93 logging.debug('Close Modbus TCP Port.') 

94 

95 with self.access_lock: 

96 self.client.close() 

97 

98 def write_registers(self, address: int, values: Union[List[int], int]): 

99 """ 

100 Write values from the specified address forward. 

101 

102 :param address: address of the first register 

103 :param values: list with all values 

104 """ 

105 

106 logging.debug( 

107 'Write registers {address} with values {values}'.format( 

108 address=address, 

109 values=values, 

110 ) 

111 ) 

112 

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 

119 

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. 

124 

125 :param address: address of the first register 

126 :param count: count of registers to read 

127 :return: list of `int` values 

128 """ 

129 

130 logging.debug( 

131 'Read holding registers {address} with count {count}.'.format( 

132 address=address, 

133 count=count, 

134 ) 

135 ) 

136 

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 

145 

146 logging.debug( 

147 'Returned holding registers {address}: {registers}'.format( 

148 address=address, 

149 registers=registers, 

150 ) 

151 ) 

152 

153 return registers 

154 

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. 

159 

160 :param address: address of the first register 

161 :param count: count of registers to read 

162 :return: list of `int` values 

163 """ 

164 

165 logging.debug( 

166 'Read input registers {address} with count {count}.'.format( 

167 address=address, 

168 count=count, 

169 ) 

170 ) 

171 

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 

180 

181 logging.debug( 

182 'Returned input registers {address}: {registers}'.format( 

183 address=address, 

184 registers=registers, 

185 ) 

186 ) 

187 

188 return registers