This repository has been archived by the owner on Jun 3, 2022. It is now read-only.
forked from openenergymonitor/emonhub
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEmonModbusTcpInterfacer.py
213 lines (188 loc) · 9.3 KB
/
EmonModbusTcpInterfacer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import time
import Cargo
try:
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.client.sync import ModbusTcpClient as ModbusClient
from pymodbus.transaction import ModbusRtuFramer as ModbusFramer
pymodbus_found = True
except ImportError:
pymodbus_found = False
import emonhub_coder as ehc
from emonhub_interfacer import EmonHubInterfacer
"""class EmonModbusTcpInterfacer
Monitors Modbus devices using modbus tcp
At this stage, only read_holding_registers() is implemented in the read() method
if needed, please change the function to read_input_registers()
"""
class EmonModbusTcpInterfacer(EmonHubInterfacer):
def __init__(self, name, modbus_IP='192.168.1.10', modbus_port=0):
"""Initialize Interfacer
com_port (string): path to COM port
"""
# Initialization
super().__init__(name)
self._modcon = False
if not pymodbus_found:
self._log.error("PYMODBUS NOT PRESENT BUT NEEDED !!")
# open connection
if pymodbus_found:
self._log.info("pymodbus installed")
self._con = self._open_modTCP(modbus_IP, modbus_port)
if self._modcon:
self._log.info("Modbustcp client Connected")
else:
self._log.info("Connection to ModbusTCP client failed. Will try again")
def set(self, **kwargs):
for key in kwargs:
setting = kwargs[key]
self._settings[key] = setting
self._log.debug("Setting %s %s: %s", self.name, key, setting)
def close(self):
# Close TCP connection
if self._con is not None:
self._log.debug("Closing tcp port")
self._con.close()
def _open_modTCP(self, modbus_IP, modbus_port, framer=ModbusFramer):
""" Open connection to modbus device """
try:
c = ModbusClient(modbus_IP, modbus_port, framer=ModbusFramer)
if c.connect():
self._modcon = True
else:
self._log.debug("Connection failed")
self._modcon = False
except Exception as e:
self._log.error("modbusTCP connection failed %s", e)
#raise EmonHubInterfacerInitError('Could not open connection to host %s' %modbus_IP)
else:
return c
def read(self):
""" Read registers from client"""
if pymodbus_found:
time.sleep(float(self._settings["interval"]))
f = []
c = Cargo.new_cargo(rawdata="")
# valid datacodes list and number of registers associated
# in modbus protocol, one register is 16 bits or 2 bytes
valid_datacodes = ({'h': 1, 'H': 1, 'i': 2, 'l': 2, 'I': 2, 'L': 2, 'f': 2, 'q': 4, 'Q': 4, 'd': 4})
if not self._modcon:
self._con.close()
self._log.info("Not connected, retrying connect %s", self.init_settings)
self._con = self._open_modTCP(self.init_settings["modbus_IP"], self.init_settings["modbus_port"])
if self._modcon:
# fetch nodeid
if 'nodeId' in self._settings:
node = str(self._settings["nodeId"])
else:
self._log.error("please provide a nodeId")
return
# stores registers
if 'register' in self._settings:
registers = self._settings["register"]
else:
self._log.error("please provide a register number or a list of registers")
return
# fetch unitids if present
UnitIds = self._settings.get("nUnit", None)
# stores names
# fetch datacode or datacodes
if node in ehc.nodelist and 'rx' in ehc.nodelist[node]:
rNames = ehc.nodelist[node]['rx']['names']
if 'datacode' in ehc.nodelist[node]['rx']:
datacode = ehc.nodelist[node]['rx']['datacode']
datacodes = None
elif 'datacodes' in ehc.nodelist[node]['rx']:
datacodes = ehc.nodelist[node]['rx']['datacodes']
else:
self._log.error("please provide a datacode or a list of datacodes")
return
# check if number of registers and number of names are the same
if len(rNames) != len(registers):
self._log.error("You have to define an equal number of registers and of names")
return
# check if number of names and number of datacodes are the same
if datacodes is not None:
if len(datacodes) != len(rNames):
self._log.error("You are using datacodes. You have to define an equal number of datacodes and of names")
return
# calculate expected size in bytes and search for invalid datacode(s)
expectedSize = 0
if datacodes is not None:
for code in datacodes:
if code not in valid_datacodes:
self._log.debug("-" * 46)
self._log.debug("invalid datacode")
self._log.debug("-" * 46)
return
else:
expectedSize += valid_datacodes[code] * 2
else:
if datacode not in valid_datacodes:
self._log.debug("-" * 46)
self._log.debug("invalid datacode")
self._log.debug("-" * 46)
return
else:
expectedSize = len(rNames) * valid_datacodes[datacode] * 2
self._log.debug("expected bytes number after encoding: %s", expectedSize)
# at this stage, we don't have any invalid datacode(s)
# so we can loop and read registers
for idx, rName in enumerate(rNames):
register = int(registers[idx], 0)
if UnitIds is not None:
unitId = int(UnitIds[idx])
else:
unitId = 1
if datacodes is not None:
datacode = datacodes[idx]
self._log.debug("datacode %s", datacode)
qty = valid_datacodes[datacode]
self._log.debug("reading register #: %s, qty #: %s, unit #: %s", register, qty, unitId)
try:
self.rVal = self._con.read_input_registers(address=register, count=qty, unit=unitId)
# assert self.rVal.function_code < 0x80
except Exception as e:
self._log.error("Connection failed on read of register: %s : %s", register, e)
self._modcon = False
#missing datas will lead to an incorrect encoding
#we have to drop the payload
return
else:
#self._log.debug("register value: %s type= %s", self.rVal.registers, type(self.rVal.registers))
#f = f + self.rVal.registers
decoder = BinaryPayloadDecoder.fromRegisters(self.rVal.registers, byteorder=Endian.Big, wordorder=Endian.Big)
if datacode == 'h':
rValD = decoder.decode_16bit_int()
elif datacode == 'H':
rValD = decoder.decode_16bit_uint()
elif datacode == 'i':
rValD = decoder.decode_32bit_int()
elif datacode == 'l':
rValD = decoder.decode_32bit_int()
elif datacode == 'I':
rValD = decoder.decode_32bit_uint()
elif datacode == 'L':
rValD = decoder.decode_32bit_uint()
elif datacode == 'f':
rValD = decoder.decode_32bit_float() * 10
elif datacode == 'q':
rValD = decoder.decode_64bit_int()
elif datacode == 'Q':
rValD = decoder.decode_64bit_uint()
elif datacode == 'd':
rValD = decoder.decode_64bit_float() * 10
t = ehc.encode(datacode, rValD)
f = f + list(t)
self._log.debug("Encoded value: %s", t)
self._log.debug("value: %s", rValD)
#test if payload length is OK
if len(f) == expectedSize:
self._log.debug("payload size OK (%d)", len(f))
self._log.debug("reporting data: %s", f)
c.nodeid = node
c.realdata = f
self._log.debug("Return from read data: %s", c.realdata)
return c
else:
self._log.error("incorrect payload size: %d expecting %d", len(f), expectedSize)