energymeter.py 74 KB

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