energymeter.py 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  1. #!/usr/bin/env python3
  2. """
  3. EnergyMeter: A Python module for interfacing with several energy meters.
  4. """
  5. __author__ = 'Stan Janssen'
  6. __email__ = 'stanjanssen@finetuned.nl'
  7. __url__ = 'https://finetuned.nl/'
  8. __license__ = 'Apache License, Version 2.0'
  9. __version__ = '1.0.5'
  10. __status__ = 'Beta'
  11. import random
  12. import socket
  13. import struct
  14. import asyncio
  15. import time
  16. import minimalmodbus
  17. import sbus
  18. from collections import Iterable
  19. class ABBMeter:
  20. """Meter class that uses a minimalmodbus Instrument to query an ABB meter."""
  21. def __init__(self, port, baudrate=38400, slaveaddress=1, type=None):
  22. """ Initialize the ABBMeter object.
  23. Arguments:
  24. * port: a serial port (string)
  25. * baudrate: the baudrate to use (integer)
  26. * slaveaddress: the address of the modbus device (integer)
  27. * Specification of the type. Used to limit the registers to a specific set.
  28. Returns:
  29. * An ABBMeter object
  30. """
  31. minimalmodbus.BAUDRATE = baudrate
  32. minimalmodbus.TIMEOUT = 0.5
  33. self.instrument = minimalmodbus.Instrument(port, slaveaddress)
  34. if type in ABBMeter.REGSETS:
  35. self.registers = [register for register in ABBMeter.REGS if register['name'] in ABBMeter.REGSETS[type]]
  36. else:
  37. self.registers = ABBMeter.REGS
  38. def read(self, regnames=None):
  39. """ Read one, many or all registers from the device
  40. Args:
  41. * regnames (str or list). If None, read all. If string, read
  42. single register. If list, read all registers from list.
  43. Returns:
  44. * If single register, it returns a single value. If all or list,
  45. return a dict with the keys and values.
  46. Raises:
  47. * KeyError, TypeError, IOError
  48. """
  49. if regnames is None:
  50. return self._batch_read(self.registers)
  51. if type(regnames) is list:
  52. registers = [register for register in self.registers if register['name'] in regnames]
  53. if len(registers) < len(regnames):
  54. regs_not_available = [regname for regname in regnames if regname not in \
  55. [register['name'] for register in self.registers]]
  56. print("Warning: the following registers are not available on this device: " +
  57. ", ".join(regs_not_available))
  58. print("The available registers are: %s" +
  59. ", ".join(register['name'] for register in self.registers))
  60. if len(registers) == 0:
  61. return {}
  62. registers.sort(key=lambda reg: reg['start'])
  63. return self._batch_read(registers)
  64. elif type(regnames) is str:
  65. registers = [register for register in self.registers if register['name'] == regnames]
  66. if len(registers) == 0:
  67. return "Register not found on device."
  68. return self._read_single(registers[0])
  69. else:
  70. raise TypeError
  71. def _read_single(self, register):
  72. """
  73. Read a single register and return the value. Not to be called directly.
  74. Arguments:
  75. * register: a 'register' dict that contains info on the register.
  76. Returns:
  77. * The interpreted value from the meter.
  78. """
  79. if register['length'] is 1:
  80. return self.instrument.read_register(registeraddress=register['start'],
  81. number_of_decimals=register['decimals'],
  82. signed=register['signed'])
  83. if register['length'] is 2:
  84. value = self.instrument.read_long(registeraddress=register['start'],
  85. signed=register['signed'])
  86. return value / 10 ** register['decimals']
  87. if register['length'] is 4:
  88. value = self.instrument.read_registers(registeraddress=register['start'],
  89. number_of_registers=register['length'])
  90. return self._convert_value(values=value,
  91. signed=register['signed'],
  92. number_of_decimals=register['decimals'])
  93. def _read_multiple(self, registers):
  94. """
  95. Read multiple registers from the slave device and return their values as a dict.
  96. Arguments:
  97. * A list of registers (complete structs)
  98. Returns:
  99. * A dict containing all keys and values
  100. """
  101. first_reg = min([register['start'] for register in registers])
  102. num_regs = max([register['start'] + register['length'] for register in registers]) - first_reg
  103. values = self.instrument.read_registers(registeraddress=first_reg,
  104. number_of_registers=num_regs)
  105. return self._interpret_result(values, registers)
  106. def _batch_read(self, registers):
  107. """
  108. Read multiple registers in batches, limiting each batch to at most 125 registers.
  109. Arguments:
  110. * A list of registers (complete structs)
  111. Returns:
  112. * A dict containing all keys and values
  113. """
  114. # Count up to at most 128 registers:
  115. start_reg = registers[0]['start']
  116. batch = []
  117. results = {}
  118. for register in registers:
  119. if register['start'] + register['length'] - start_reg <= 125:
  120. batch.append(register)
  121. else:
  122. results.update(self._read_multiple(batch))
  123. batch = []
  124. batch.append(register)
  125. start_reg = register['start']
  126. results.update(self._read_multiple(batch))
  127. return results
  128. def _interpret_result(self, data, registers):
  129. """
  130. Pull the returned string apart and package the data back to its
  131. intended form.
  132. Arguments:
  133. * data: list of register values returned from the device
  134. * registers: the original requested set of registers
  135. Returns:
  136. * A dict containing the register names and resulting values
  137. """
  138. first_reg = min([register['start'] for register in registers])
  139. results = {}
  140. for register in registers:
  141. regname = register['name']
  142. start = register['start'] - first_reg
  143. end = start + register['length']
  144. values = data[start:end]
  145. results[regname] = self._convert_value(values=values,
  146. signed=register['signed'],
  147. number_of_decimals=register['decimals'])
  148. return results
  149. def _convert_value(self, values, signed=False, number_of_decimals=0):
  150. """
  151. Convert a list of returned integers to the intended value.
  152. Arguments:
  153. * bytestring: a list of integers that together represent the value
  154. * signed: whether the value is a signed value
  155. * decimals: number of decimals the return value should contain
  156. """
  157. number_of_registers = len(values)
  158. formatcode_i = '>'
  159. formatcode_o = '>'
  160. if number_of_registers == 1:
  161. formatcode_i += "H"
  162. if signed:
  163. formatcode_o += "h"
  164. else:
  165. formatcode_o += "H"
  166. if number_of_registers == 2:
  167. formatcode_i += 'HH'
  168. if signed:
  169. formatcode_o += "l"
  170. else:
  171. formatcode_o += "L"
  172. if number_of_registers == 4:
  173. formatcode_i += "HHHH"
  174. if signed:
  175. formatcode_o += "q"
  176. else:
  177. formatcode_o += "Q"
  178. bytestring = struct.pack(formatcode_i, *values)
  179. value = struct.unpack(formatcode_o, bytestring)[0]
  180. if value in ABBMeter.NULLS:
  181. return None
  182. else:
  183. return float(value) / 10 ** number_of_decimals
  184. # Register map of the ABB A and B series energy meters.
  185. REGS = [{'name': 'active_import', 'start': 20480, 'length': 4, 'signed': True, 'decimals': 2},
  186. {'name': 'active_export', 'start': 20484, 'length': 4, 'signed': True, 'decimals': 2},
  187. {'name': 'active_net', 'start': 20488, 'length': 4, 'signed': True, 'decimals': 2},
  188. {'name': 'reactive_import', 'start': 20492, 'length': 4, 'signed': True, 'decimals': 2},
  189. {'name': 'reactive_export', 'start': 20496, 'length': 4, 'signed': True, 'decimals': 2},
  190. {'name': 'reactive_net', 'start': 20500, 'length': 4, 'signed': True, 'decimals': 2},
  191. {'name': 'apparent_import', 'start': 20504, 'length': 4, 'signed': True, 'decimals': 2},
  192. {'name': 'apparent_export', 'start': 20508, 'length': 4, 'signed': True, 'decimals': 2},
  193. {'name': 'apparent_net', 'start': 20512, 'length': 4, 'signed': True, 'decimals': 2},
  194. {'name': 'active_import_co2', 'start': 20516, 'length': 4, 'signed': True, 'decimals': 2},
  195. {'name': 'active_import_currency', 'start': 20532, 'length': 4, 'signed': True, 'decimals': 2},
  196. {'name': 'active_import_tariff_1', 'start': 20848, 'length': 4, 'signed': False, 'decimals': 2},
  197. {'name': 'active_import_tariff_2', 'start': 20852, 'length': 4, 'signed': False, 'decimals': 2},
  198. {'name': 'active_import_tariff_3', 'start': 20856, 'length': 4, 'signed': False, 'decimals': 2},
  199. {'name': 'active_import_tariff_4', 'start': 20860, 'length': 4, 'signed': False, 'decimals': 2},
  200. {'name': 'active_export_tariff_1', 'start': 20880, 'length': 4, 'signed': False, 'decimals': 2},
  201. {'name': 'active_export_tariff_2', 'start': 20884, 'length': 4, 'signed': False, 'decimals': 2},
  202. {'name': 'active_export_tariff_3', 'start': 20888, 'length': 4, 'signed': False, 'decimals': 2},
  203. {'name': 'active_export_tariff_4', 'start': 20892, 'length': 4, 'signed': False, 'decimals': 2},
  204. {'name': 'reactive_import_tariff_1', 'start': 20912, 'length': 4, 'signed': False, 'decimals': 2},
  205. {'name': 'reactive_import_tariff_2', 'start': 20916, 'length': 4, 'signed': False, 'decimals': 2},
  206. {'name': 'reactive_import_tariff_3', 'start': 20920, 'length': 4, 'signed': False, 'decimals': 2},
  207. {'name': 'reactive_import_tariff_4', 'start': 20924, 'length': 4, 'signed': False, 'decimals': 2},
  208. {'name': 'reactive_export_tariff_1', 'start': 20944, 'length': 4, 'signed': False, 'decimals': 2},
  209. {'name': 'reactive_export_tariff_2', 'start': 20948, 'length': 4, 'signed': False, 'decimals': 2},
  210. {'name': 'reactive_export_tariff_3', 'start': 20952, 'length': 4, 'signed': False, 'decimals': 2},
  211. {'name': 'reactive_export_tariff_4', 'start': 20956, 'length': 4, 'signed': False, 'decimals': 2},
  212. {'name': 'active_import_l1', 'start': 21600, 'length': 4, 'signed': False, 'decimals': 2},
  213. {'name': 'active_import_l2', 'start': 21604, 'length': 4, 'signed': False, 'decimals': 2},
  214. {'name': 'active_import_l3', 'start': 21608, 'length': 4, 'signed': False, 'decimals': 2},
  215. {'name': 'active_export_l1', 'start': 21612, 'length': 4, 'signed': False, 'decimals': 2},
  216. {'name': 'active_export_l2', 'start': 21616, 'length': 4, 'signed': False, 'decimals': 2},
  217. {'name': 'active_export_l3', 'start': 21620, 'length': 4, 'signed': False, 'decimals': 2},
  218. {'name': 'active_net_l1', 'start': 21624, 'length': 4, 'signed': True, 'decimals': 2},
  219. {'name': 'active_net_l2', 'start': 21628, 'length': 4, 'signed': True, 'decimals': 2},
  220. {'name': 'active_net_l3', 'start': 21632, 'length': 4, 'signed': True, 'decimals': 2},
  221. {'name': 'reactive_import_l1', 'start': 21636, 'length': 4, 'signed': False, 'decimals': 2},
  222. {'name': 'reactive_import_l2', 'start': 21640, 'length': 4, 'signed': False, 'decimals': 2},
  223. {'name': 'reactive_import_l3', 'start': 21644, 'length': 4, 'signed': False, 'decimals': 2},
  224. {'name': 'reactive_export_l1', 'start': 21648, 'length': 4, 'signed': False, 'decimals': 2},
  225. {'name': 'reactive_export_l2', 'start': 21652, 'length': 4, 'signed': False, 'decimals': 2},
  226. {'name': 'reactive_export_l3', 'start': 21656, 'length': 4, 'signed': False, 'decimals': 2},
  227. {'name': 'reactive_net_l1', 'start': 21660, 'length': 4, 'signed': True, 'decimals': 2},
  228. {'name': 'reactive_net_l2', 'start': 21664, 'length': 4, 'signed': True, 'decimals': 2},
  229. {'name': 'reactive_net_l3', 'start': 21668, 'length': 4, 'signed': True, 'decimals': 2},
  230. {'name': 'apparent_import_l1', 'start': 21672, 'length': 4, 'signed': False, 'decimals': 2},
  231. {'name': 'apparent_import_l2', 'start': 21676, 'length': 4, 'signed': False, 'decimals': 2},
  232. {'name': 'apparent_import_l3', 'start': 21680, 'length': 4, 'signed': False, 'decimals': 2},
  233. {'name': 'apparent_export_l1', 'start': 21684, 'length': 4, 'signed': False, 'decimals': 2},
  234. {'name': 'apparent_export_l2', 'start': 21688, 'length': 4, 'signed': False, 'decimals': 2},
  235. {'name': 'apparent_export_l3', 'start': 21692, 'length': 4, 'signed': False, 'decimals': 2},
  236. {'name': 'apparent_net_l1', 'start': 21696, 'length': 4, 'signed': True, 'decimals': 2},
  237. {'name': 'apparent_net_l2', 'start': 21700, 'length': 4, 'signed': True, 'decimals': 2},
  238. {'name': 'apparent_net_l3', 'start': 21704, 'length': 4, 'signed': True, 'decimals': 2},
  239. {'name': 'resettable_active_import', 'start': 21804, 'length': 4, 'signed': False, 'decimals': 2},
  240. {'name': 'resettable_active_export', 'start': 21808, 'length': 4, 'signed': False, 'decimals': 2},
  241. {'name': 'resettable_reactive_import', 'start': 21812, 'length': 4, 'signed': False, 'decimals': 2},
  242. {'name': 'resettable_reactive_export', 'start': 21816, 'length': 4, 'signed': False, 'decimals': 2},
  243. {'name': 'voltage_l1_n', 'start': 23296, 'length': 2, 'signed': False, 'decimals': 1},
  244. {'name': 'voltage_l2_n', 'start': 23298, 'length': 2, 'signed': False, 'decimals': 1},
  245. {'name': 'voltage_l3_n', 'start': 23300, 'length': 2, 'signed': False, 'decimals': 1},
  246. {'name': 'voltage_l1_l2', 'start': 23302, 'length': 2, 'signed': False, 'decimals': 1},
  247. {'name': 'voltage_l3_l2', 'start': 23304, 'length': 2, 'signed': False, 'decimals': 1},
  248. {'name': 'voltage_l1_l3', 'start': 23306, 'length': 2, 'signed': False, 'decimals': 1},
  249. {'name': 'current_l1', 'start': 23308, 'length': 2, 'signed': False, 'decimals': 2},
  250. {'name': 'current_l2', 'start': 23310, 'length': 2, 'signed': False, 'decimals': 2},
  251. {'name': 'current_l3', 'start': 23312, 'length': 2, 'signed': False, 'decimals': 2},
  252. {'name': 'current_n', 'start': 23314, 'length': 2, 'signed': False, 'decimals': 2},
  253. {'name': 'active_power_total', 'start': 23316, 'length': 2, 'signed': True, 'decimals': 2},
  254. {'name': 'active_power_l1', 'start': 23318, 'length': 2, 'signed': True, 'decimals': 2},
  255. {'name': 'active_power_l2', 'start': 23320, 'length': 2, 'signed': True, 'decimals': 2},
  256. {'name': 'active_power_l3', 'start': 23322, 'length': 2, 'signed': True, 'decimals': 2},
  257. {'name': 'reactive_power_total', 'start': 23324, 'length': 2, 'signed': True, 'decimals': 2},
  258. {'name': 'reactive_power_l1', 'start': 23326, 'length': 2, 'signed': True, 'decimals': 2},
  259. {'name': 'reactive_power_l2', 'start': 23328, 'length': 2, 'signed': True, 'decimals': 2},
  260. {'name': 'reactive_power_l3', 'start': 23330, 'length': 2, 'signed': True, 'decimals': 2},
  261. {'name': 'apparent_power_total', 'start': 23332, 'length': 2, 'signed': True, 'decimals': 2},
  262. {'name': 'apparent_power_l1', 'start': 23334, 'length': 2, 'signed': True, 'decimals': 2},
  263. {'name': 'apparent_power_l2', 'start': 23336, 'length': 2, 'signed': True, 'decimals': 2},
  264. {'name': 'apparent_power_l3', 'start': 23338, 'length': 2, 'signed': True, 'decimals': 2},
  265. {'name': 'frequency', 'start': 23340, 'length': 1, 'signed': False, 'decimals': 2},
  266. {'name': 'phase_angle_power_total', 'start': 23341, 'length': 1, 'signed': True, 'decimals': 1},
  267. {'name': 'phase_angle_power_l1', 'start': 23342, 'length': 1, 'signed': True, 'decimals': 1},
  268. {'name': 'phase_angle_power_l2', 'start': 23343, 'length': 1, 'signed': True, 'decimals': 1},
  269. {'name': 'phase_angle_power_l3', 'start': 23344, 'length': 1, 'signed': True, 'decimals': 1},
  270. {'name': 'phase_angle_voltage_l1', 'start': 23345, 'length': 1, 'signed': True, 'decimals': 1},
  271. {'name': 'phase_angle_voltage_l2', 'start': 23346, 'length': 1, 'signed': True, 'decimals': 1},
  272. {'name': 'phase_angle_voltage_l3', 'start': 23347, 'length': 1, 'signed': True, 'decimals': 1},
  273. {'name': 'phase_angle_current_l1', 'start': 23351, 'length': 1, 'signed': True, 'decimals': 1},
  274. {'name': 'phase_angle_current_l2', 'start': 23352, 'length': 1, 'signed': True, 'decimals': 1},
  275. {'name': 'phase_angle_current_l3', 'start': 23353, 'length': 1, 'signed': True, 'decimals': 1},
  276. {'name': 'power_factor_total', 'start': 23354, 'length': 1, 'signed': True, 'decimals': 3},
  277. {'name': 'power_factor_l1', 'start': 23355, 'length': 1, 'signed': True, 'decimals': 3},
  278. {'name': 'power_factor_l2', 'start': 23356, 'length': 1, 'signed': True, 'decimals': 3},
  279. {'name': 'power_factor_l3', 'start': 23357, 'length': 1, 'signed': True, 'decimals': 3},
  280. {'name': 'current_quadrant_total', 'start': 23358, 'length': 1, 'signed': False, 'decimals': 0},
  281. {'name': 'current_quadrant_l1', 'start': 23359, 'length': 1, 'signed': False, 'decimals': 0},
  282. {'name': 'current_quadrant_l2', 'start': 23360, 'length': 1, 'signed': False, 'decimals': 0},
  283. {'name': 'current_quadrant_l3', 'start': 23361, 'length': 1, 'signed': False, 'decimals': 0},
  284. {'name': 'voltage_harmonics_l1_n_thd', 'start': 23808, 'length': 2, 'signed': False, 'decimals': 1},
  285. {'name': 'voltage_harmonics_l1_n_2nd', 'start': 23810, 'length': 2, 'signed': False, 'decimals': 1},
  286. {'name': 'voltage_harmonics_l1_n_3rd', 'start': 23812, 'length': 2, 'signed': False, 'decimals': 1},
  287. {'name': 'voltage_harmonics_l1_n_4th', 'start': 23814, 'length': 2, 'signed': False, 'decimals': 1},
  288. {'name': 'voltage_harmonics_l1_n_5th', 'start': 23816, 'length': 2, 'signed': False, 'decimals': 1},
  289. {'name': 'voltage_harmonics_l1_n_6th', 'start': 23818, 'length': 2, 'signed': False, 'decimals': 1},
  290. {'name': 'voltage_harmonics_l1_n_7th', 'start': 23820, 'length': 2, 'signed': False, 'decimals': 1},
  291. {'name': 'voltage_harmonics_l1_n_8th', 'start': 23822, 'length': 2, 'signed': False, 'decimals': 1},
  292. {'name': 'voltage_harmonics_l1_n_9th', 'start': 23824, 'length': 2, 'signed': False, 'decimals': 1},
  293. {'name': 'voltage_harmonics_l1_n_10th', 'start': 23826, 'length': 2, 'signed': False, 'decimals': 1},
  294. {'name': 'voltage_harmonics_l1_n_11th', 'start': 23828, 'length': 2, 'signed': False, 'decimals': 1},
  295. {'name': 'voltage_harmonics_l1_n_12th', 'start': 23830, 'length': 2, 'signed': False, 'decimals': 1},
  296. {'name': 'voltage_harmonics_l1_n_13th', 'start': 23832, 'length': 2, 'signed': False, 'decimals': 1},
  297. {'name': 'voltage_harmonics_l1_n_14th', 'start': 23834, 'length': 2, 'signed': False, 'decimals': 1},
  298. {'name': 'voltage_harmonics_l1_n_15th', 'start': 23836, 'length': 2, 'signed': False, 'decimals': 1},
  299. {'name': 'voltage_harmonics_l1_n_16th', 'start': 23838, 'length': 2, 'signed': False, 'decimals': 1},
  300. {'name': 'voltage_harmonics_l2_n_thd', 'start': 23936, 'length': 2, 'signed': False, 'decimals': 1},
  301. {'name': 'voltage_harmonics_l2_n_2nd', 'start': 23938, 'length': 2, 'signed': False, 'decimals': 1},
  302. {'name': 'voltage_harmonics_l2_n_3rd', 'start': 23940, 'length': 2, 'signed': False, 'decimals': 1},
  303. {'name': 'voltage_harmonics_l2_n_4th', 'start': 23942, 'length': 2, 'signed': False, 'decimals': 1},
  304. {'name': 'voltage_harmonics_l2_n_5th', 'start': 23944, 'length': 2, 'signed': False, 'decimals': 1},
  305. {'name': 'voltage_harmonics_l2_n_6th', 'start': 23946, 'length': 2, 'signed': False, 'decimals': 1},
  306. {'name': 'voltage_harmonics_l2_n_7th', 'start': 23948, 'length': 2, 'signed': False, 'decimals': 1},
  307. {'name': 'voltage_harmonics_l2_n_8th', 'start': 23950, 'length': 2, 'signed': False, 'decimals': 1},
  308. {'name': 'voltage_harmonics_l2_n_9th', 'start': 23952, 'length': 2, 'signed': False, 'decimals': 1},
  309. {'name': 'voltage_harmonics_l2_n_10th', 'start': 23954, 'length': 2, 'signed': False, 'decimals': 1},
  310. {'name': 'voltage_harmonics_l2_n_11th', 'start': 23956, 'length': 2, 'signed': False, 'decimals': 1},
  311. {'name': 'voltage_harmonics_l2_n_12th', 'start': 23958, 'length': 2, 'signed': False, 'decimals': 1},
  312. {'name': 'voltage_harmonics_l2_n_13th', 'start': 23960, 'length': 2, 'signed': False, 'decimals': 1},
  313. {'name': 'voltage_harmonics_l2_n_14th', 'start': 23962, 'length': 2, 'signed': False, 'decimals': 1},
  314. {'name': 'voltage_harmonics_l2_n_15th', 'start': 23964, 'length': 2, 'signed': False, 'decimals': 1},
  315. {'name': 'voltage_harmonics_l2_n_16th', 'start': 23966, 'length': 2, 'signed': False, 'decimals': 1},
  316. {'name': 'voltage_harmonics_l3_n_thd', 'start': 24064, 'length': 2, 'signed': False, 'decimals': 1},
  317. {'name': 'voltage_harmonics_l3_n_2nd', 'start': 24066, 'length': 2, 'signed': False, 'decimals': 1},
  318. {'name': 'voltage_harmonics_l3_n_3rd', 'start': 24068, 'length': 2, 'signed': False, 'decimals': 1},
  319. {'name': 'voltage_harmonics_l3_n_4th', 'start': 24070, 'length': 2, 'signed': False, 'decimals': 1},
  320. {'name': 'voltage_harmonics_l3_n_5th', 'start': 24072, 'length': 2, 'signed': False, 'decimals': 1},
  321. {'name': 'voltage_harmonics_l3_n_6th', 'start': 24074, 'length': 2, 'signed': False, 'decimals': 1},
  322. {'name': 'voltage_harmonics_l3_n_7th', 'start': 24076, 'length': 2, 'signed': False, 'decimals': 1},
  323. {'name': 'voltage_harmonics_l3_n_8th', 'start': 24078, 'length': 2, 'signed': False, 'decimals': 1},
  324. {'name': 'voltage_harmonics_l3_n_9th', 'start': 24080, 'length': 2, 'signed': False, 'decimals': 1},
  325. {'name': 'voltage_harmonics_l3_n_10th', 'start': 24082, 'length': 2, 'signed': False, 'decimals': 1},
  326. {'name': 'voltage_harmonics_l3_n_11th', 'start': 24084, 'length': 2, 'signed': False, 'decimals': 1},
  327. {'name': 'voltage_harmonics_l3_n_12th', 'start': 24086, 'length': 2, 'signed': False, 'decimals': 1},
  328. {'name': 'voltage_harmonics_l3_n_13th', 'start': 24088, 'length': 2, 'signed': False, 'decimals': 1},
  329. {'name': 'voltage_harmonics_l3_n_14th', 'start': 24090, 'length': 2, 'signed': False, 'decimals': 1},
  330. {'name': 'voltage_harmonics_l3_n_15th', 'start': 24092, 'length': 2, 'signed': False, 'decimals': 1},
  331. {'name': 'voltage_harmonics_l3_n_16th', 'start': 24094, 'length': 2, 'signed': False, 'decimals': 1},
  332. {'name': 'voltage_harmonics_l1_l2_thd', 'start': 24192, 'length': 2, 'signed': False, 'decimals': 1},
  333. {'name': 'voltage_harmonics_l1_l2_2nd', 'start': 24194, 'length': 2, 'signed': False, 'decimals': 1},
  334. {'name': 'voltage_harmonics_l1_l2_3rd', 'start': 24196, 'length': 2, 'signed': False, 'decimals': 1},
  335. {'name': 'voltage_harmonics_l1_l2_4th', 'start': 24198, 'length': 2, 'signed': False, 'decimals': 1},
  336. {'name': 'voltage_harmonics_l1_l2_5th', 'start': 24200, 'length': 2, 'signed': False, 'decimals': 1},
  337. {'name': 'voltage_harmonics_l1_l2_6th', 'start': 24202, 'length': 2, 'signed': False, 'decimals': 1},
  338. {'name': 'voltage_harmonics_l1_l2_7th', 'start': 24204, 'length': 2, 'signed': False, 'decimals': 1},
  339. {'name': 'voltage_harmonics_l1_l2_8th', 'start': 24206, 'length': 2, 'signed': False, 'decimals': 1},
  340. {'name': 'voltage_harmonics_l1_l2_9th', 'start': 24208, 'length': 2, 'signed': False, 'decimals': 1},
  341. {'name': 'voltage_harmonics_l1_l2_10th', 'start': 24210, 'length': 2, 'signed': False, 'decimals': 1},
  342. {'name': 'voltage_harmonics_l1_l2_11th', 'start': 24212, 'length': 2, 'signed': False, 'decimals': 1},
  343. {'name': 'voltage_harmonics_l1_l2_12th', 'start': 24214, 'length': 2, 'signed': False, 'decimals': 1},
  344. {'name': 'voltage_harmonics_l1_l2_13th', 'start': 24216, 'length': 2, 'signed': False, 'decimals': 1},
  345. {'name': 'voltage_harmonics_l1_l2_14th', 'start': 24218, 'length': 2, 'signed': False, 'decimals': 1},
  346. {'name': 'voltage_harmonics_l1_l2_15th', 'start': 24220, 'length': 2, 'signed': False, 'decimals': 1},
  347. {'name': 'voltage_harmonics_l1_l2_16th', 'start': 24222, 'length': 2, 'signed': False, 'decimals': 1},
  348. {'name': 'voltage_harmonics_l3_l2_thd', 'start': 24320, 'length': 2, 'signed': False, 'decimals': 1},
  349. {'name': 'voltage_harmonics_l3_l2_2nd', 'start': 24322, 'length': 2, 'signed': False, 'decimals': 1},
  350. {'name': 'voltage_harmonics_l3_l2_3rd', 'start': 24324, 'length': 2, 'signed': False, 'decimals': 1},
  351. {'name': 'voltage_harmonics_l3_l2_4th', 'start': 24326, 'length': 2, 'signed': False, 'decimals': 1},
  352. {'name': 'voltage_harmonics_l3_l2_5th', 'start': 24328, 'length': 2, 'signed': False, 'decimals': 1},
  353. {'name': 'voltage_harmonics_l3_l2_6th', 'start': 24330, 'length': 2, 'signed': False, 'decimals': 1},
  354. {'name': 'voltage_harmonics_l3_l2_7th', 'start': 24332, 'length': 2, 'signed': False, 'decimals': 1},
  355. {'name': 'voltage_harmonics_l3_l2_8th', 'start': 24334, 'length': 2, 'signed': False, 'decimals': 1},
  356. {'name': 'voltage_harmonics_l3_l2_9th', 'start': 24336, 'length': 2, 'signed': False, 'decimals': 1},
  357. {'name': 'voltage_harmonics_l3_l2_10th', 'start': 24338, 'length': 2, 'signed': False, 'decimals': 1},
  358. {'name': 'voltage_harmonics_l3_l2_11th', 'start': 24340, 'length': 2, 'signed': False, 'decimals': 1},
  359. {'name': 'voltage_harmonics_l3_l2_12th', 'start': 24342, 'length': 2, 'signed': False, 'decimals': 1},
  360. {'name': 'voltage_harmonics_l3_l2_13th', 'start': 24344, 'length': 2, 'signed': False, 'decimals': 1},
  361. {'name': 'voltage_harmonics_l3_l2_14th', 'start': 24346, 'length': 2, 'signed': False, 'decimals': 1},
  362. {'name': 'voltage_harmonics_l3_l2_15th', 'start': 24348, 'length': 2, 'signed': False, 'decimals': 1},
  363. {'name': 'voltage_harmonics_l3_l2_16th', 'start': 24350, 'length': 2, 'signed': False, 'decimals': 1},
  364. {'name': 'voltage_harmonics_l1_l3_thd', 'start': 24448, 'length': 2, 'signed': False, 'decimals': 1},
  365. {'name': 'voltage_harmonics_l1_l3_2nd', 'start': 24450, 'length': 2, 'signed': False, 'decimals': 1},
  366. {'name': 'voltage_harmonics_l1_l3_3rd', 'start': 24452, 'length': 2, 'signed': False, 'decimals': 1},
  367. {'name': 'voltage_harmonics_l1_l3_4th', 'start': 24454, 'length': 2, 'signed': False, 'decimals': 1},
  368. {'name': 'voltage_harmonics_l1_l3_5th', 'start': 24456, 'length': 2, 'signed': False, 'decimals': 1},
  369. {'name': 'voltage_harmonics_l1_l3_6th', 'start': 24458, 'length': 2, 'signed': False, 'decimals': 1},
  370. {'name': 'voltage_harmonics_l1_l3_7th', 'start': 24460, 'length': 2, 'signed': False, 'decimals': 1},
  371. {'name': 'voltage_harmonics_l1_l3_8th', 'start': 24462, 'length': 2, 'signed': False, 'decimals': 1},
  372. {'name': 'voltage_harmonics_l1_l3_9th', 'start': 24464, 'length': 2, 'signed': False, 'decimals': 1},
  373. {'name': 'voltage_harmonics_l1_l3_10th', 'start': 24466, 'length': 2, 'signed': False, 'decimals': 1},
  374. {'name': 'voltage_harmonics_l1_l3_11th', 'start': 24468, 'length': 2, 'signed': False, 'decimals': 1},
  375. {'name': 'voltage_harmonics_l1_l3_12th', 'start': 24470, 'length': 2, 'signed': False, 'decimals': 1},
  376. {'name': 'voltage_harmonics_l1_l3_13th', 'start': 24472, 'length': 2, 'signed': False, 'decimals': 1},
  377. {'name': 'voltage_harmonics_l1_l3_14th', 'start': 24474, 'length': 2, 'signed': False, 'decimals': 1},
  378. {'name': 'voltage_harmonics_l1_l3_15th', 'start': 24476, 'length': 2, 'signed': False, 'decimals': 1},
  379. {'name': 'voltage_harmonics_l1_l3_16th', 'start': 24478, 'length': 2, 'signed': False, 'decimals': 1},
  380. {'name': 'current_harmonics_l1_thd', 'start': 24576, 'length': 2, 'signed': False, 'decimals': 1},
  381. {'name': 'current_harmonics_l1_2nd', 'start': 24578, 'length': 2, 'signed': False, 'decimals': 1},
  382. {'name': 'current_harmonics_l1_3rd', 'start': 24580, 'length': 2, 'signed': False, 'decimals': 1},
  383. {'name': 'current_harmonics_l1_4th', 'start': 24582, 'length': 2, 'signed': False, 'decimals': 1},
  384. {'name': 'current_harmonics_l1_5th', 'start': 24584, 'length': 2, 'signed': False, 'decimals': 1},
  385. {'name': 'current_harmonics_l1_6th', 'start': 24586, 'length': 2, 'signed': False, 'decimals': 1},
  386. {'name': 'current_harmonics_l1_7th', 'start': 24588, 'length': 2, 'signed': False, 'decimals': 1},
  387. {'name': 'current_harmonics_l1_8th', 'start': 24590, 'length': 2, 'signed': False, 'decimals': 1},
  388. {'name': 'current_harmonics_l1_9th', 'start': 24592, 'length': 2, 'signed': False, 'decimals': 1},
  389. {'name': 'current_harmonics_l1_10th', 'start': 24594, 'length': 2, 'signed': False, 'decimals': 1},
  390. {'name': 'current_harmonics_l1_11th', 'start': 24596, 'length': 2, 'signed': False, 'decimals': 1},
  391. {'name': 'current_harmonics_l1_12th', 'start': 24598, 'length': 2, 'signed': False, 'decimals': 1},
  392. {'name': 'current_harmonics_l1_13th', 'start': 24600, 'length': 2, 'signed': False, 'decimals': 1},
  393. {'name': 'current_harmonics_l1_14th', 'start': 24602, 'length': 2, 'signed': False, 'decimals': 1},
  394. {'name': 'current_harmonics_l1_15th', 'start': 24604, 'length': 2, 'signed': False, 'decimals': 1},
  395. {'name': 'current_harmonics_l1_16th', 'start': 24606, 'length': 2, 'signed': False, 'decimals': 1},
  396. {'name': 'current_harmonics_l2_thd', 'start': 24704, 'length': 2, 'signed': False, 'decimals': 1},
  397. {'name': 'current_harmonics_l2_2nd', 'start': 24706, 'length': 2, 'signed': False, 'decimals': 1},
  398. {'name': 'current_harmonics_l2_3rd', 'start': 24708, 'length': 2, 'signed': False, 'decimals': 1},
  399. {'name': 'current_harmonics_l2_4th', 'start': 24710, 'length': 2, 'signed': False, 'decimals': 1},
  400. {'name': 'current_harmonics_l2_5th', 'start': 24712, 'length': 2, 'signed': False, 'decimals': 1},
  401. {'name': 'current_harmonics_l2_6th', 'start': 24714, 'length': 2, 'signed': False, 'decimals': 1},
  402. {'name': 'current_harmonics_l2_7th', 'start': 24716, 'length': 2, 'signed': False, 'decimals': 1},
  403. {'name': 'current_harmonics_l2_8th', 'start': 24718, 'length': 2, 'signed': False, 'decimals': 1},
  404. {'name': 'current_harmonics_l2_9th', 'start': 24720, 'length': 2, 'signed': False, 'decimals': 1},
  405. {'name': 'current_harmonics_l2_10th', 'start': 24722, 'length': 2, 'signed': False, 'decimals': 1},
  406. {'name': 'current_harmonics_l2_11th', 'start': 24724, 'length': 2, 'signed': False, 'decimals': 1},
  407. {'name': 'current_harmonics_l2_12th', 'start': 24726, 'length': 2, 'signed': False, 'decimals': 1},
  408. {'name': 'current_harmonics_l2_13th', 'start': 24728, 'length': 2, 'signed': False, 'decimals': 1},
  409. {'name': 'current_harmonics_l2_14th', 'start': 24730, 'length': 2, 'signed': False, 'decimals': 1},
  410. {'name': 'current_harmonics_l2_15th', 'start': 24732, 'length': 2, 'signed': False, 'decimals': 1},
  411. {'name': 'current_harmonics_l2_16th', 'start': 24734, 'length': 2, 'signed': False, 'decimals': 1},
  412. {'name': 'current_harmonics_l3_thd', 'start': 24832, 'length': 2, 'signed': False, 'decimals': 1},
  413. {'name': 'current_harmonics_l3_2nd', 'start': 24834, 'length': 2, 'signed': False, 'decimals': 1},
  414. {'name': 'current_harmonics_l3_3rd', 'start': 24836, 'length': 2, 'signed': False, 'decimals': 1},
  415. {'name': 'current_harmonics_l3_4th', 'start': 24838, 'length': 2, 'signed': False, 'decimals': 1},
  416. {'name': 'current_harmonics_l3_5th', 'start': 24840, 'length': 2, 'signed': False, 'decimals': 1},
  417. {'name': 'current_harmonics_l3_6th', 'start': 24842, 'length': 2, 'signed': False, 'decimals': 1},
  418. {'name': 'current_harmonics_l3_7th', 'start': 24844, 'length': 2, 'signed': False, 'decimals': 1},
  419. {'name': 'current_harmonics_l3_8th', 'start': 24846, 'length': 2, 'signed': False, 'decimals': 1},
  420. {'name': 'current_harmonics_l3_9th', 'start': 24848, 'length': 2, 'signed': False, 'decimals': 1},
  421. {'name': 'current_harmonics_l3_10th', 'start': 24850, 'length': 2, 'signed': False, 'decimals': 1},
  422. {'name': 'current_harmonics_l3_11th', 'start': 24852, 'length': 2, 'signed': False, 'decimals': 1},
  423. {'name': 'current_harmonics_l3_12th', 'start': 24854, 'length': 2, 'signed': False, 'decimals': 1},
  424. {'name': 'current_harmonics_l3_13th', 'start': 24856, 'length': 2, 'signed': False, 'decimals': 1},
  425. {'name': 'current_harmonics_l3_14th', 'start': 24858, 'length': 2, 'signed': False, 'decimals': 1},
  426. {'name': 'current_harmonics_l3_15th', 'start': 24860, 'length': 2, 'signed': False, 'decimals': 1},
  427. {'name': 'current_harmonics_l3_16th', 'start': 24862, 'length': 2, 'signed': False, 'decimals': 1},
  428. {'name': 'current_harmonics_n_thd', 'start': 24960, 'length': 2, 'signed': False, 'decimals': 1},
  429. {'name': 'current_harmonics_n_2nd', 'start': 24962, 'length': 2, 'signed': False, 'decimals': 1},
  430. {'name': 'current_harmonics_n_3rd', 'start': 24964, 'length': 2, 'signed': False, 'decimals': 1},
  431. {'name': 'current_harmonics_n_4th', 'start': 24966, 'length': 2, 'signed': False, 'decimals': 1},
  432. {'name': 'current_harmonics_n_5th', 'start': 24968, 'length': 2, 'signed': False, 'decimals': 1},
  433. {'name': 'current_harmonics_n_6th', 'start': 24970, 'length': 2, 'signed': False, 'decimals': 1},
  434. {'name': 'current_harmonics_n_7th', 'start': 24972, 'length': 2, 'signed': False, 'decimals': 1},
  435. {'name': 'current_harmonics_n_8th', 'start': 24974, 'length': 2, 'signed': False, 'decimals': 1},
  436. {'name': 'current_harmonics_n_9th', 'start': 24976, 'length': 2, 'signed': False, 'decimals': 1},
  437. {'name': 'current_harmonics_n_10th', 'start': 24978, 'length': 2, 'signed': False, 'decimals': 1},
  438. {'name': 'current_harmonics_n_11th', 'start': 24980, 'length': 2, 'signed': False, 'decimals': 1},
  439. {'name': 'current_harmonics_n_12th', 'start': 24982, 'length': 2, 'signed': False, 'decimals': 1},
  440. {'name': 'current_harmonics_n_13th', 'start': 24984, 'length': 2, 'signed': False, 'decimals': 1},
  441. {'name': 'current_harmonics_n_14th', 'start': 24986, 'length': 2, 'signed': False, 'decimals': 1},
  442. {'name': 'current_harmonics_n_15th', 'start': 24988, 'length': 2, 'signed': False, 'decimals': 1},
  443. {'name': 'current_harmonics_n_16th', 'start': 24990, 'length': 2, 'signed': False, 'decimals': 1}]
  444. REGSETS = {
  445. "A43": ["active_import", "active_export", "active_net",
  446. "reactive_import", "reactive_export", "reactive_net",
  447. "apparent_import", "apparent_export", "apparent_net",
  448. "active_import_co2", "active_import_currency",
  449. "active_import_l1", "active_import_l2", "active_import_l3",
  450. "active_export_l1", "active_export_l2", "active_export_l3",
  451. "active_net_l1", "active_net_l2", "active_net_l3",
  452. "reactive_import_l1", "reactive_import_l2",
  453. "reactive_import_l3", "reactive_export_l1",
  454. "reactive_export_l2", "reactive_export_l3",
  455. "reactive_net_l1", "reactive_net_l2", "reactive_net_l3",
  456. "apparent_import_l1", "apparent_import_l2",
  457. "apparent_import_l3", "apparent_export_l1",
  458. "apparent_export_l2", "apparent_export_l3",
  459. "apparent_net_l1", "apparent_net_l2", "apparent_net_l3",
  460. "voltage_l1_n", "voltage_l2_n", "voltage_l3_n",
  461. "voltage_l1_l2", "voltage_l3_l2", "voltage_l1_l3",
  462. "current_l1", "current_l2", "current_l3",
  463. "active_power_total", "active_power_l1", "active_power_l2",
  464. "active_power_l3", "reactive_power_total",
  465. "reactive_power_l1", "reactive_power_l2",
  466. "reactive_power_l3", "apparent_power_total",
  467. "apparent_power_l1", "apparent_power_l2",
  468. "apparent_power_l3", "frequency", "phase_angle_power_total",
  469. "power_factor_total", "power_factor_l1", "power_factor_l2",
  470. "power_factor_l3", "current_quadrant_total",
  471. "current_quadrant_l1", "current_quadrant_l2",
  472. "current_quadrant_l3"],
  473. "B23": ["active_import", "active_export", "active_net",
  474. "reactive_import", "reactive_export", "reactive_net",
  475. "apparent_import", "apparent_export", "apparent_net",
  476. "active_import_co2", "active_import_currency",
  477. "active_import_l1", "active_import_l2", "active_import_l3",
  478. "active_export_l1", "active_export_l2", "active_export_l3",
  479. "active_net_l1", "active_net_l2", "active_net_l3",
  480. "reactive_import_l1", "reactive_import_l2",
  481. "reactive_import_l3", "reactive_export_l1",
  482. "reactive_export_l2", "reactive_export_l3",
  483. "reactive_net_l1", "reactive_net_l2", "reactive_net_l3",
  484. "apparent_import_l1", "apparent_import_l2",
  485. "apparent_import_l3", "apparent_export_l1",
  486. "apparent_export_l2", "apparent_export_l3",
  487. "apparent_net_l1", "apparent_net_l2", "apparent_net_l3",
  488. "voltage_l1_n", "voltage_l2_n", "voltage_l3_n",
  489. "voltage_l1_l2", "voltage_l3_l2", "voltage_l1_l3",
  490. "current_l1", "current_l2", "current_l3",
  491. "active_power_total", "active_power_l1", "active_power_l2",
  492. "active_power_l3", "reactive_power_total",
  493. "reactive_power_l1", "reactive_power_l2",
  494. "reactive_power_l3", "apparent_power_total",
  495. "apparent_power_l1", "apparent_power_l2",
  496. "apparent_power_l3", "frequency", "phase_angle_power_total",
  497. "power_factor_total", "power_factor_l1", "power_factor_l2",
  498. "power_factor_l3", "current_quadrant_total",
  499. "current_quadrant_l1", "current_quadrant_l2",
  500. "current_quadrant_l3"],
  501. "B21": ["active_import", "active_export", "active_net",
  502. "reactive_import", "reactive_export", "reactive_net",
  503. "apparent_import", "apparent_export", "apparent_net",
  504. "active_import_co2", "active_import_currency",
  505. "voltage_l1_n", "current_l1", "active_power_total",
  506. "reactive_power_total", "apparent_power_total", "frequency",
  507. "phase_angle_power_total", "power_factor_total",
  508. "current_quadrant_total"]
  509. }
  510. NULLS = [pow(2, n) - 1 for n in (64, 63, 32, 31, 16, 15)]
  511. class ModbusTCPMeter:
  512. """
  513. Implementation for a Modbus TCP Energy Meter.
  514. """
  515. def __init__(self, port, tcp_port=502, slaveaddress=126, type=None, baudrate=None):
  516. self.port = port
  517. self.tcp_port = tcp_port
  518. self.device_id = slaveaddress
  519. def read(self, regnames=None):
  520. if regnames is None:
  521. registers = self.REGS
  522. return self._read_multiple(registers)
  523. if type(regnames) is str:
  524. registers = [register for register in self.REGS if register['name'] == regnames]
  525. return self._read_single(registers[0])
  526. if type(regnames) is list:
  527. registers = [register for register in self.REGS if register['name'] in regnames]
  528. return self._read_multiple(registers)
  529. def _read_single(self, register):
  530. message = self._modbus_message(start_reg=register['start'], num_regs=register['length'])
  531. data = self._perform_request(message)
  532. return self._convert_value(data, signed=register['signed'], decimals=register['decimals'])
  533. def _read_multiple(self, registers):
  534. registers.sort(key=lambda reg: reg['start'])
  535. results = {}
  536. for reg_range in self._split_ranges(registers):
  537. first_reg = min([register['start'] for register in reg_range])
  538. num_regs = max([register['start'] + register['length'] for register in reg_range]) - first_reg
  539. message = self._modbus_message(start_reg=first_reg, num_regs=num_regs)
  540. data = self._perform_request(message)
  541. results.update(self._interpret_result(data, reg_range))
  542. return results
  543. def _split_ranges(self, registers):
  544. """
  545. Generator that splits the registers list into continuous parts.
  546. """
  547. reg_list = []
  548. prev_end = registers[0]['start'] - 1
  549. for r in registers:
  550. if r['start'] - prev_end > 1:
  551. yield reg_list
  552. reg_list = []
  553. reg_list.append(r)
  554. prev_end = r['start'] + r['length']
  555. yield reg_list
  556. def _modbus_message(self, start_reg, num_regs):
  557. transaction_id = random.randint(1, 2**16 - 1)
  558. return struct.pack(">HHHBBHH", transaction_id,
  559. self.PROTOCOL_CODE,
  560. 6,
  561. self.device_id,
  562. self.FUNCTION_CODE,
  563. start_reg - self.REG_OFFSET,
  564. num_regs)
  565. def _perform_request(self, message):
  566. if self.device is None:
  567. self._connect()
  568. self.device.send(message)
  569. data = bytes()
  570. expect_bytes = 9 + 2 * struct.unpack(">H", message[-2:])[0]
  571. attempt = 1
  572. while len(data) is not expect_bytes:
  573. time.sleep(0.05)
  574. data += self.device.recv(2048)
  575. if attempt >= 10:
  576. return 2 * struct.unpack(">H", message[-2:])[0] * [0]
  577. attempt += 1
  578. return data[9:]
  579. def _interpret_result(self, data, registers):
  580. """
  581. Pull the returned string apart and package the data back to its
  582. intended form.
  583. Arguments:
  584. * data: list of register values returned from the device
  585. * registers: the original requested set of registers
  586. Returns:
  587. * A dict containing the register names and resulting values
  588. """
  589. first_reg = min([register['start'] for register in registers])
  590. results = {}
  591. for register in registers:
  592. regname = register['name']
  593. start = (register['start'] - first_reg) * 2
  594. end = start + register['length'] * 2
  595. values = data[start:end]
  596. results[regname] = self._convert_value(values=values,
  597. signed=register['signed'],
  598. decimals=register['decimals'],
  599. isFloat=register['isFloat'])
  600. if regname == "power_factor_total" and results[regname] == 0:
  601. results[regname] = 1 # The SMA will send out a 0 when the power factor is 100%
  602. return results
  603. def _convert_value(self, values, signed=False, decimals=0, isFloat=False):
  604. """
  605. Convert a list of returned integers to the intended value.
  606. Arguments:
  607. * bytestring: a list of integers that together represent the value
  608. * signed: whether the value is a signed value
  609. * decimals: number of decimals the return value should contain
  610. * isFloat: whether the valie is a float
  611. """
  612. numberOfBytes = len(values)
  613. formatcode_o = '>'
  614. if isFloat:
  615. formatcode_o += 'f'
  616. elif numberOfBytes == 1:
  617. if signed:
  618. formatcode_o += "b"
  619. else:
  620. formatcode_o += "B"
  621. elif numberOfBytes == 2:
  622. if signed:
  623. formatcode_o += "h"
  624. else:
  625. formatcode_o += "H"
  626. elif numberOfBytes == 4:
  627. if signed:
  628. formatcode_o += "l"
  629. else:
  630. formatcode_o += "L"
  631. value = struct.unpack(formatcode_o, bytes(values))[0]
  632. if value in self.NULLS:
  633. return None
  634. else:
  635. return float(value) / 10 ** decimals
  636. def _connect(self):
  637. self.device = socket.create_connection(address=(self.port, self.tcp_port))
  638. class SMAMeter(ModbusTCPMeter):
  639. def __init__(self, *args, **kwargs):
  640. super().__init__(*args, **kwargs)
  641. REGS = [
  642. {'name': 'current_ac', 'start': 40188, 'length': 1, 'signed': False, 'decimals': 1},
  643. {'name': 'current_l1', 'start': 40189, 'length': 1, 'signed': False, 'decimals': 1},
  644. {'name': 'current_l2', 'start': 40190, 'length': 1, 'signed': False, 'decimals': 1},
  645. {'name': 'current_l3', 'start': 40191, 'length': 1, 'signed': False, 'decimals': 1},
  646. {'name': 'voltage_l1_l2', 'start': 40193, 'length': 1, 'signed': False, 'decimals': 1},
  647. {'name': 'voltage_l2_l3', 'start': 40194, 'length': 1, 'signed': False, 'decimals': 1},
  648. {'name': 'voltage_l3_l1', 'start': 40195, 'length': 1, 'signed': False, 'decimals': 1},
  649. {'name': 'voltage_l1_n', 'start': 40196, 'length': 1, 'signed': False, 'decimals': 1},
  650. {'name': 'voltage_l2_n', 'start': 40197, 'length': 1, 'signed': False, 'decimals': 1},
  651. {'name': 'voltage_l3_n', 'start': 40198, 'length': 1, 'signed': False, 'decimals': 1},
  652. {'name': 'active_power_total', 'start': 40200, 'length': 1, 'signed': True, 'decimals': -1},
  653. {'name': 'frequency', 'start': 40202, 'length': 1, 'signed': False, 'decimals': 2},
  654. {'name': 'apparent_power_total', 'start': 40204, 'length': 1, 'signed': True, 'decimals': -1},
  655. {'name': 'reactive_power_total', 'start': 40206, 'length': 1, 'signed': True, 'decimals': -1},
  656. {'name': 'power_factor_total', 'start': 40208, 'length': 1, 'signed': True, 'decimals': 3},
  657. {'name': 'active_export', 'start': 40210, 'length': 2, 'signed': False, 'decimals': 3},
  658. {'name': 'dc_power', 'start': 40217, 'length': 1, 'signed': False, 'decimals': -1},
  659. {'name': 'temperature_internal', 'start': 40219, 'length': 1, 'signed': False, 'decimals': 0},
  660. {'name': 'temperature_other', 'start': 40222, 'length': 1, 'signed': False, 'decimals': 0},
  661. {'name': 'operating_status', 'start': 40224, 'length': 1, 'signed': False, 'decimals': 0}
  662. ]
  663. REG_OFFSET = 1
  664. PROTOCOL_CODE = 0
  665. FUNCTION_CODE = 3
  666. NULLS = [2**16 - 1, 2**15 - 1, 2**15, -2**15]
  667. class MulticubeMeter(ModbusTCPMeter):
  668. """
  669. Implementation for a Multicube energy meter over Modbus TCP.
  670. """
  671. def __init__(self, port, tcp_port=1502, slaveaddress=1, type=None, baudrate=None, auto_scale=True):
  672. super().__init__(port, tcp_port, slaveaddress, type, baudrate)
  673. self.device = socket.create_connection(address=(port, tcp_port))
  674. self.device_id = slaveaddress
  675. self.REGS = [
  676. {'name': 'energy_scale', 'start': 512, 'length': 2, 'decimals': 0, 'signed': True},
  677. {'name': 'active_net', 'start': 514, 'length': 2, 'decimals': 0, 'signed': True},
  678. {'name': 'apparent_net', 'start': 516, 'length': 2, 'decimals': 0, 'signed': True},
  679. {'name': 'reactive_net', 'start': 518, 'length': 2, 'decimals': 0, 'signed': True},
  680. {'name': 'active_power_total', 'start': 2816, 'length': 1, 'decimals': 0, 'signed': True},
  681. {'name': 'apparent_power_total', 'start': 2817, 'length': 1, 'decimals': 0, 'signed': True},
  682. {'name': 'reactive_power_total', 'start': 2818, 'length': 1, 'decimals': 0, 'signed': True},
  683. {'name': 'power_factor_total', 'start': 2819, 'length': 1, 'decimals': 3, 'signed': True},
  684. {'name': 'frequency', 'start': 2820, 'length': 1, 'decimals': 1, 'signed': True},
  685. {'name': 'voltage_l1_n', 'start': 2821, 'length': 1, 'decimals': 0, 'signed': True},
  686. {'name': 'current_l1', 'start': 2822, 'length': 1, 'decimals': 0, 'signed': True},
  687. {'name': 'active_power_l1', 'start': 2823, 'length': 1, 'decimals': 0, 'signed': True},
  688. {'name': 'voltage_l2_n', 'start': 2824, 'length': 1, 'decimals': 0, 'signed': True},
  689. {'name': 'current_l2', 'start': 2825, 'length': 1, 'decimals': 0, 'signed': True},
  690. {'name': 'active_power_l2', 'start': 2826, 'length': 1, 'decimals': 0, 'signed': True},
  691. {'name': 'voltage_l3_n', 'start': 2827, 'length': 1, 'decimals': 0, 'signed': True},
  692. {'name': 'current_l3', 'start': 2828, 'length': 1, 'decimals': 0, 'signed': True},
  693. {'name': 'active_power_l3', 'start': 2829, 'length': 1, 'decimals': 0, 'signed': True},
  694. {'name': 'power_factor_l1', 'start': 2830, 'length': 1, 'decimals': 0, 'signed': True},
  695. {'name': 'power_factor_l2', 'start': 2831, 'length': 1, 'decimals': 0, 'signed': True},
  696. {'name': 'power_factor_l3', 'start': 2832, 'length': 1, 'decimals': 0, 'signed': True},
  697. {'name': 'voltage_l1_l2', 'start': 2833, 'length': 1, 'decimals': 0, 'signed': True},
  698. {'name': 'voltage_l2_l3', 'start': 2834, 'length': 1, 'decimals': 0, 'signed': True},
  699. {'name': 'voltage_l3_l1', 'start': 2835, 'length': 1, 'decimals': 0, 'signed': True},
  700. {'name': 'current_n', 'start': 2836, 'length': 1, 'decimals': 0, 'signed': True},
  701. {'name': 'amps_scale', 'start': 2837, 'length': 1, 'decimals': 0, 'signed': True},
  702. {'name': 'phase_volts_scale', 'start': 2838, 'length': 1, 'decimals': 0, 'signed': True},
  703. {'name': 'line_volts_scale', 'start': 2839, 'length': 1, 'decimals': 0, 'signed': True},
  704. {'name': 'power_scale', 'start': 2840, 'length': 1, 'decimals': 0, 'signed': True},
  705. {'name': 'apparent_power_l1', 'start': 3072, 'length': 1, 'decimals': 0, 'signed': True},
  706. {'name': 'apparent_power_l2', 'start': 3073, 'length': 1, 'decimals': 0, 'signed': True},
  707. {'name': 'apparent_power_l3', 'start': 3074, 'length': 1, 'decimals': 0, 'signed': True},
  708. {'name': 'reactive_power_l1', 'start': 3075, 'length': 1, 'decimals': 0, 'signed': True},
  709. {'name': 'reactive_power_l2', 'start': 3076, 'length': 1, 'decimals': 0, 'signed': True},
  710. {'name': 'reactive_power_l3', 'start': 3077, 'length': 1, 'decimals': 0, 'signed': True},
  711. {'name': 'peak_current_l1', 'start': 3078, 'length': 1, 'decimals': 0, 'signed': True},
  712. {'name': 'peak_current_l2', 'start': 3079, 'length': 1, 'decimals': 0, 'signed': True},
  713. {'name': 'peak_current_l3', 'start': 3080, 'length': 1, 'decimals': 0, 'signed': True},
  714. {'name': 'current_l1_thd', 'start': 3081, 'length': 1, 'decimals': 3, 'signed': True},
  715. {'name': 'current_l2_thd', 'start': 3082, 'length': 1, 'decimals': 3, 'signed': True},
  716. {'name': 'current_l3_thd', 'start': 3083, 'length': 1, 'decimals': 3, 'signed': True}
  717. ]
  718. if auto_scale:
  719. self.set_scaling()
  720. def set_scaling(self):
  721. """
  722. Call this function before reading anything to set up the correct scaling for this meter.
  723. """
  724. decimals_mapping = {1: 2, 2: 1, 3: 0, 4: -1, 5: -2, 6: -3, 7: -4}
  725. a_registers = ['current_l1', 'current_l2', 'current_l3', 'current_n']
  726. scale = int(self.read('amps_scale'))
  727. for r in self.REGS:
  728. if r['name'] in a_registers:
  729. r['decimals'] = decimals_mapping[scale]
  730. pv_registers = ['voltage_l1_n', 'voltage_l2_n', 'voltage_l3_n']
  731. scale = int(self.read('phase_volts_scale'))
  732. for r in self.REGS:
  733. if r['name'] in pv_registers:
  734. r['decimals'] = decimals_mapping[scale]
  735. lv_registers = ['voltage_l1_l2', 'voltage_l2_l3', 'voltage_l3_l1']
  736. scale = int(self.read('line_volts_scale'))
  737. for r in self.REGS:
  738. if r['name'] in lv_registers:
  739. r['decimals'] = decimals_mapping[scale]
  740. p_registers = ['active_power_total',
  741. 'reactive_power_total',
  742. 'apparent_power_total',
  743. 'active_power_l1',
  744. 'active_power_l2',
  745. 'active_power_l3',
  746. 'apparent_power_l1',
  747. 'apparent_power_l2',
  748. 'apparent_power_l3',
  749. 'reactive_power_l1',
  750. 'reactive_power_l2',
  751. 'reactive_power_l3']
  752. scale = int(self.read('power_scale'))
  753. for r in self.REGS:
  754. if r['name'] in p_registers:
  755. r['decimals'] = decimals_mapping[scale]
  756. e_registers = ['active_net', 'apparent_net', 'reactive_net']
  757. decimals_mapping = {3: 3, 4: 2, 5: 1, 6: 0, 7: -1}
  758. scale = int(self.read('energy_scale'))
  759. for r in self.REGS:
  760. if r['name'] in e_registers:
  761. r['decimals'] = decimals_mapping[scale]
  762. REG_OFFSET = 0
  763. PROTOCOL_CODE = 0
  764. FUNCTION_CODE = 3
  765. NULLS = [2**16 - 1, 2**15 - 1, 2**15, -2**15]
  766. class AsyncModbusTCPMeter(ModbusTCPMeter):
  767. def __init__(self, *args, **kwargs):
  768. super().__init__(*args, **kwargs)
  769. self.reader = self.writer = None
  770. async def _connect(self):
  771. self.reader, self.writer = await asyncio.open_connection(host=self.port, port=self.tcp_port)
  772. async def read(self, regnames=None):
  773. if regnames is None:
  774. registers = self.REGS
  775. return await self._read_multiple(registers)
  776. if isinstance(regnames, str):
  777. registers = [register for register in self.REGS if register['name'] == regnames]
  778. if len(registers) == 0:
  779. return "Register not found on device."
  780. return await self._read_single(registers[0])
  781. if isinstance(regnames, Iterable):
  782. registers = [register for register in self.REGS if register['name'] in regnames]
  783. return await self._read_multiple(registers)
  784. async def _read_single(self, register):
  785. num_regs = register['length']
  786. message = self._modbus_message(register['start'], num_regs)
  787. if self.writer is None:
  788. await self._connect()
  789. self.writer.write(message)
  790. await self.writer.drain()
  791. data = await self.reader.readexactly(9 + 2 * num_regs)
  792. return self._convert_value(data[9:], signed=register['signed'], decimals=register['decimals'], isFloat=register['isFloat']|False)
  793. async def _read_multiple(self, registers):
  794. registers.sort(key=lambda reg: reg['start'])
  795. results = {}
  796. for reg_range in self._split_ranges(registers):
  797. # Prepare the request
  798. first_reg = min([register['start'] for register in reg_range])
  799. num_regs = max([register['start'] + register['length'] for register in reg_range]) - first_reg
  800. if self.writer is None:
  801. await self._connect()
  802. self.writer.write(self._modbus_message(first_reg, num_regs))
  803. await self.writer.drain()
  804. # Receive the response
  805. data = await self.reader.readexactly(9 + 2 * num_regs)
  806. results.update(self._interpret_result(data[9:], reg_range))
  807. return results
  808. class AsyncABBTCPMeter(AsyncModbusTCPMeter):
  809. def __init__(self, *args, **kwargs):
  810. super().__init__(*args, **kwargs)
  811. if type in ABBMeter.REGSETS:
  812. self.registers = [register for register in ABBMeter.REGS if register['name'] in ABBMeter.REGSETS[type]]
  813. else:
  814. self.registers = ABBMeter.REGS
  815. REGS = ABBMeter.REGS
  816. REGSETS = ABBMeter.REGSETS
  817. NULLS = ABBMeter.NULLS
  818. PROTOCOL_CODE = 0
  819. FUNCTION_CODE = 3
  820. REG_OFFSET = 0
  821. class SaiaMeter:
  822. def __init__(self, port, baudrate=38400, slaveaddress=1, type=None):
  823. """ Initialize the ABBMeter object.
  824. Arguments:
  825. * port: a serial port (string)
  826. * baudrate: the baudrate to use (integer)
  827. * slaveaddress: the address of the modbus device (integer)
  828. * Specification of the type. Used to limit the registers to a specific set.
  829. Returns:
  830. * An ABBMeter object
  831. """
  832. minimalmodbus.BAUDRATE = baudrate
  833. minimalmodbus.TIMEOUT = 0.5
  834. self.instrument = sbus.Instrument(address=slaveaddress, serial_port=port, baudrate=baudrate)
  835. def read(self, regnames=None):
  836. """ Read one, many or all registers from the device
  837. Args:
  838. * regnames (str or list). If None, read all. If string, read
  839. single register. If list, read all registers from list.
  840. Returns:
  841. * If single register, it returns a single value. If all or list,
  842. return a dict with the keys and values.
  843. Raises:
  844. * KeyError, TypeError, IOError
  845. """
  846. if regnames is None:
  847. return self._batch_read(self.REGS)
  848. if type(regnames) is list:
  849. registers = [register for register in self.REGS if register['name'] in regnames]
  850. if len(registers) < len(regnames):
  851. regs_not_available = [regname for regname in regnames if regname not in \
  852. [register['name'] for register in self.REGS]]
  853. print("Warning: the following registers are not available on this device: " +
  854. ", ".join(regs_not_available))
  855. print("The available registers are: %s" +
  856. ", ".join(register['name'] for register in self.REGS))
  857. if len(registers) == 0:
  858. return {}
  859. registers.sort(key=lambda reg: reg['start'])
  860. return self._batch_read(registers)
  861. elif type(regnames) is str:
  862. registers = [register for register in self.REGS if register['name'] == regnames]
  863. if len(registers) == 0:
  864. return "Register not found on device."
  865. return self._read_single(registers[0])
  866. else:
  867. raise TypeError
  868. def _read_single(self, register):
  869. """
  870. Read a single register and return the value. Not to be called directly.
  871. Arguments:
  872. * register: a 'register' dict that contains info on the register.
  873. Returns:
  874. * The interpreted value from the meter.
  875. """
  876. if register['length'] is 1:
  877. return self.instrument.read_register(registeraddress=register['start'],
  878. number_of_decimals=register['decimals'],
  879. signed=register['signed'])
  880. if register['length'] is 2:
  881. value = self.instrument.read_long(registeraddress=register['start'],
  882. signed=register['signed'])
  883. return value / 10 ** register['decimals']
  884. if register['length'] is 4:
  885. value = self.instrument.read_registers(registeraddress=register['start'],
  886. number_of_registers=register['length'])
  887. return self._convert_value(values=value,
  888. signed=register['signed'],
  889. number_of_decimals=register['decimals'])
  890. def _read_multiple(self, registers):
  891. """
  892. Read multiple registers from the slave device and return their values as a dict.
  893. Arguments:
  894. * A list of registers (complete structs)
  895. Returns:
  896. * A dict containing all keys and values
  897. """
  898. first_reg = min([register['start'] for register in registers])
  899. num_regs = max([register['start'] + register['length'] for register in registers]) - first_reg
  900. values = self.instrument.read_registers(start_register=first_reg,
  901. num_registers=num_regs)
  902. return self._interpret_result(values, registers)
  903. def _batch_read(self, registers):
  904. """
  905. Read multiple registers in batches, limiting each batch to at most 10 registers.
  906. Arguments:
  907. * A list of registers (complete structs)
  908. Returns:
  909. * A dict containing all keys and values
  910. """
  911. # Count up to at most 10 registers:
  912. start_reg = registers[0]['start']
  913. batch = []
  914. results = {}
  915. for register in registers:
  916. if register['start'] + register['length'] - start_reg <= 10:
  917. batch.append(register)
  918. else:
  919. results.update(self._read_multiple(batch))
  920. batch = []
  921. batch.append(register)
  922. start_reg = register['start']
  923. results.update(self._read_multiple(batch))
  924. return results
  925. def _interpret_result(self, data, registers):
  926. """
  927. Pull the returned string apart and package the data back to its
  928. intended form.
  929. Arguments:
  930. * data: list of register values returned from the device
  931. * registers: the original requested set of registers
  932. Returns:
  933. * A dict containing the register names and resulting values
  934. """
  935. first_reg = min([register['start'] for register in registers])
  936. results = {}
  937. for register in registers:
  938. regname = register['name']
  939. start = register['start'] - first_reg
  940. end = start + register['length']
  941. values = data[start:end]
  942. results[regname] = self._convert_value(values=values,
  943. signed=register['signed'],
  944. number_of_decimals=register['decimals'])
  945. return results
  946. def _convert_value(self, values, signed=False, number_of_decimals=0):
  947. """
  948. Convert a list of returned integers to the intended value.
  949. Arguments:
  950. * bytestring: a list of integers that together represent the value
  951. * signed: whether the value is a signed value
  952. * decimals: number of decimals the return value should contain
  953. """
  954. number_of_registers = len(values)
  955. formatcode_i = '>'
  956. formatcode_o = '>'
  957. if number_of_registers == 1:
  958. formatcode_i += "L"
  959. if signed:
  960. formatcode_o += "l"
  961. else:
  962. formatcode_o += "L"
  963. if number_of_registers == 2:
  964. formatcode_i += 'LL'
  965. if signed:
  966. formatcode_o += "q"
  967. else:
  968. formatcode_o += "Q"
  969. bytestring = struct.pack(formatcode_i, *values)
  970. value = struct.unpack(formatcode_o, bytestring)[0]
  971. return float(value) / 10 ** number_of_decimals
  972. REGISTER_BYTES = 4
  973. REGS = [{'name': 'firmware_version', 'start': 0, 'length': 1, 'signed': True, 'decimals': 0},
  974. {'name': 'num_registers', 'start': 1, 'length': 1, 'signed': False, 'decimals': 0},
  975. {'name': 'num_flags', 'start': 2, 'length': 1, 'signed': False, 'decimals': 0},
  976. {'name': 'baudrate', 'start': 3, 'length': 1, 'signed': False, 'decimals': 0},
  977. {'name': 'serial_number', 'start': 11, 'length': 2, 'signed': False, 'decimals': 0},
  978. {'name': 'status_protect', 'start': 14, 'length': 1, 'signed': False, 'decimals': 0},
  979. {'name': 'sbus_timeout', 'start': 15, 'length': 1, 'signed': False, 'decimals': 0},
  980. {'name': 'sbus_address', 'start': 16, 'length': 1, 'signed': False, 'decimals': 0},
  981. {'name': 'error_flags', 'start': 17, 'length': 1, 'signed': False, 'decimals': 0},
  982. {'name': 'tariff', 'start': 19, 'length': 1, 'signed': False, 'decimals': 0},
  983. {'name': 'active_import_tariff_1', 'start': 20, 'length': 1, 'signed': False, 'decimals': 2},
  984. {'name': 'resettable_active_import_t1','start': 21, 'length': 1, 'signed': False, 'decimals': 2},
  985. {'name': 'active_import_tariff_2', 'start': 22, 'length': 1, 'signed': False, 'decimals': 2},
  986. {'name': 'resettable_active_import_t2','start': 23, 'length': 1, 'signed': False, 'decimals': 2},
  987. {'name': 'voltage_l1_n', 'start': 24, 'length': 1, 'signed': False, 'decimals': 0},
  988. {'name': 'current_l1', 'start': 25, 'length': 1, 'signed': False, 'decimals': 1},
  989. {'name': 'active_power_l1', 'start': 26, 'length': 1, 'signed': True, 'decimals': 2},
  990. {'name': 'reactive_power_l1', 'start': 27, 'length': 1, 'signed': True, 'decimals': 2},
  991. {'name': 'power_factor_l1', 'start': 28, 'length': 1, 'signed': True, 'decimals': 2},
  992. {'name': 'voltage_l2_n', 'start': 29, 'length': 1, 'signed': False, 'decimals': 0},
  993. {'name': 'current_l2', 'start': 30, 'length': 1, 'signed': False, 'decimals': 1},
  994. {'name': 'active_power_l2', 'start': 31, 'length': 1, 'signed': True, 'decimals': 2},
  995. {'name': 'reactive_power_l2', 'start': 32, 'length': 1, 'signed': True, 'decimals': 2},
  996. {'name': 'power_factor_l2', 'start': 33, 'length': 1, 'signed': True, 'decimals': 2},
  997. {'name': 'voltage_l3_n', 'start': 34, 'length': 1, 'signed': False, 'decimals': 0},
  998. {'name': 'current_l3', 'start': 35, 'length': 1, 'signed': False, 'decimals': 1},
  999. {'name': 'active_power_l3', 'start': 36, 'length': 1, 'signed': True, 'decimals': 2},
  1000. {'name': 'reactive_power_l3', 'start': 37, 'length': 1, 'signed': True, 'decimals': 2},
  1001. {'name': 'power_factor_l3', 'start': 38, 'length': 1, 'signed': True, 'decimals': 2},
  1002. {'name': 'active_power_total', 'start': 39, 'length': 1, 'signed': True, 'decimals': 2},
  1003. {'name': 'reactive_power_total', 'start': 40, 'length': 1, 'signed': True, 'decimals': 2}]