Guest User

Untitled

a guest
Apr 5th, 2024
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.77 KB | None | 0 0
  1.  
  2. EM24.py:
  3. [code]
  4.  
  5. import struct
  6.  
  7. class EM24:
  8.  
  9. modbus_data_const = {
  10. 0x000b: 1653, # Identification code: Carlo Gavazzi EM24DINAV53XE1PFB
  11. 0x0302: 1, # Hardware version
  12. 0x0304: 1, # Firmware version
  13. 0x1002: 0, # PhaseConfig (0 = 3 phase + neutral)
  14. 0x0032: 0, # Phase sequence ( 0 = L1/L2/L3, -1 = L1/L3/L2 )
  15. 0x0033: 500, # Fequency (Hz *10)
  16. 0xa100: 1, # Switch Position (kVARh, 2, 1, Locked)
  17. 0xa000: 7, # Application "H"
  18. 0x0040: 0, # L1 Energy Forward (kWh *10) [NOT AVAILABLE on P1]
  19. 0x0042: 0, # L2 Energy Forward (kWh *10) [NOT AVAILABLE on P1]
  20. 0x0044: 0, # L3 Energy Forward (kWh *10) [NOT AVAILABLE on P1]
  21. #0x5000: [0x3030,0x3030,0x3030,0x3030,0x3030,0x3030,0x3030] # Serial
  22. }
  23.  
  24. buffer = []
  25.  
  26. def get_p1_int(self, field_name, factor):
  27. return int(float(self._p1_data[field_name])*factor)
  28.  
  29. def set_int16(self, index, value):
  30. if self._min <= index <= self._max:
  31. self.buffer[index - self._min] = value
  32.  
  33. def set_int32(self, index, value):
  34. if self._min <= index <= self._max:
  35. bytes = struct.pack('>i', value)
  36. self.set_int16(index+0, int.from_bytes(bytes[2:4], byteorder='big'))
  37. self.set_int16(index+1, int.from_bytes(bytes[0:2], byteorder='big'))
  38.  
  39. def calc_ampere(self, power, volt, factor):
  40. if volt == 0:
  41. return 0
  42. return round(factor * power / volt)
  43.  
  44. def __init__(self, p1_data, start_index, count):
  45. print(p1_data)
  46. self._p1_data = p1_data
  47. self._min = start_index
  48. self._max = start_index + count - 1
  49. self.buffer = [0] * count
  50.  
  51. # Set all default values
  52. for i,v in self.modbus_data_const.items():
  53. self.set_int16(i, v)
  54.  
  55. # Power: L1, L2, L3, Total (used or supplied)
  56. power_l1 = self.get_p1_int("1-0:21.7.0", 1000*10) or self.get_p1_int("1-0:22.7.0", -1000*10)
  57. power_l2 = self.get_p1_int("1-0:41.7.0", 1000*10) or self.get_p1_int("1-0:42.7.0", -1000*10)
  58. power_l3 = self.get_p1_int("1-0:61.7.0", 1000*10) or self.get_p1_int("1-0:62.7.0", -1000*10)
  59. power_total = self.get_p1_int("1-0:1.7.0", 1000*10) or self.get_p1_int("1-0:2.7.0", -1000*10)
  60. self.set_int32(0x0012, power_l1 )
  61. self.set_int32(0x0014, power_l2 )
  62. self.set_int32(0x0016, power_l3 )
  63. self.set_int32(0x0028, power_total)
  64.  
  65. # Energy: Total (used and supplied)
  66. # Create aggregate field for the low(1) and high(2) tarif energy counters.
  67. energy_used = self.get_p1_int("1-0:1.8.1", 10) + self.get_p1_int("1-0:1.8.2", 10)
  68. energy_supplied = self.get_p1_int("1-0:2.8.1", 10) + self.get_p1_int("1-0:2.8.2", 10)
  69. self.set_int32(0x0034, energy_used)
  70. self.set_int32(0x004e, energy_supplied)
  71.  
  72. # Voltage: L1, L2, L3
  73. self.set_int32(0x0000, self.get_p1_int("1-0:32.7.0", 10) )
  74. self.set_int32(0x0002, self.get_p1_int("1-0:52.7.0", 10) )
  75. self.set_int32(0x0004, self.get_p1_int("1-0:72.7.0", 10) )
  76.  
  77. # Current: L1, L2, L3
  78. # self.set_int32(0x000c, self.get_p1_int("1-0:31.7.0", 1000) )
  79. # self.set_int32(0x000e, self.get_p1_int("1-0:51.7.0", 1000) )
  80. # self.set_int32(0x0010, self.get_p1_int("1-0:71.7.0", 1000) )
  81. self.set_int32(0x000c, self.calc_ampere(power_l1, self.get_p1_int("1-0:32.7.0", 10),1000) )
  82. self.set_int32(0x000e, self.calc_ampere(power_l2, self.get_p1_int("1-0:52.7.0", 10),1000) )
  83. self.set_int32(0x0010, self.calc_ampere(power_l3, self.get_p1_int("1-0:72.7.0", 10),1000) )
  84. [/code]
  85.  
  86. HouseP1.py
  87. [code]
  88.  
  89. import os
  90. import serial
  91. import threading
  92.  
  93. class HouseP1:
  94.  
  95. _serial_error = False
  96. _serial_device = None
  97. _must_stop = False
  98. _thread = None
  99.  
  100. p1_data_incomplete = {}
  101.  
  102. p1_data_complete = None
  103.  
  104. def start(self, devicename = None, baudrate = 115200, timeout=60):
  105. # Auto detect port?
  106. if devicename is None:
  107. device_list = os.listdir("/dev/serial/by-id")
  108. for dev in device_list:
  109. if "P1_Converter_Cable" in dev:
  110. usb_link = os.readlink("/dev/serial/by-id/"+dev)
  111. devicename = os.path.abspath("/dev/serial/by-id/"+usb_link)
  112. # Still no port found?
  113. if devicename is None:
  114. raise Exception("Unable to determine P1-Converter-Cable usb port.")
  115. print(f"Reading P1 data from {devicename}")
  116.  
  117. self.stop_and_wait()
  118.  
  119. self._serial_device = serial.Serial(devicename, baudrate, timeout=timeout)
  120. self._thread = threading.Thread(target=self.on_p1_data, name="P1-Thread")
  121. self._thread.setDaemon(True)
  122.  
  123. self._serial_error = False
  124. self._must_stop = False
  125. self._thread.start()
  126.  
  127.  
  128. def terminate(self):
  129. self._must_stop = True
  130.  
  131.  
  132. def stop_and_wait(self):
  133. self._must_stop = True
  134. if self._thread:
  135. print("Waiting for background reader to stop...")
  136. self._thread.join()
  137. self._thread = None
  138.  
  139.  
  140. def on_p1_data(self):
  141. print("Start background P1 reader.")
  142. while not self._must_stop:
  143. try:
  144. raw_data = self._serial_device.readline().decode()
  145.  
  146. # (Atomic) Complete set of P1 data is ready
  147. if raw_data.startswith("!"):
  148. self.p1_data_complete = self.p1_data_incomplete
  149. self.p1_data_incomplete = {}
  150. #print(self.p1_data_complete)
  151.  
  152. # Split the data string in such a way, that we take the first part (the key code)
  153. # and the second part (value). We ignore any other data (e.g. "kWh" and additional values.)
  154. a = raw_data.replace(")","(").replace("*","(").split("(",2)
  155. if len(a) > 1:
  156. key = a[0]
  157. value = a[1]
  158. self.p1_data_incomplete[key] = value
  159.  
  160. p1_in_error = False
  161. except Exception as e:
  162. if not self._serial_error:
  163. self._serial_error = True
  164. print("Error during on_p1_data: "+str(e))
  165. print("Subsequent P1 error messages will be suppressed until error is resolved.")
  166. print("Stopped background P1 reader")
  167. [/code]
  168.  
  169. ModbusServer.py:
  170. [code]
  171. import socket
  172. import struct
  173. import threading
  174.  
  175.  
  176. STRUCT_MODBUS = struct.Struct('>HHHBBHH')
  177.  
  178. class InvalidModbusRequest(Exception):
  179. """Invalid modbus request"""
  180.  
  181. class ModbusServer:
  182.  
  183. _must_stop = False
  184.  
  185. def __init__(self, bind_host: str, bind_port: int, on_read_holding_registers):
  186. self._host = bind_host
  187. self._port = bind_port
  188. self._on_read_holding_registers = on_read_holding_registers
  189.  
  190. def start(self):
  191. self._thread = threading.Thread(target=self.__listen, name="Modbus-listen")
  192. self._thread.setDaemon(True)
  193. self._thread.start()
  194.  
  195. def terminate(self):
  196. self._must_stop = True
  197.  
  198. def __listen(self):
  199. with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
  200. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  201. s.bind((self._host, self._port))
  202. s.listen()
  203. s.settimeout(3)
  204. print(f"Listening on {(self._host, self._port)}")
  205. while not self._must_stop:
  206. try:
  207. conn, addr = s.accept()
  208. print(f"New modbus client {addr}")
  209. t = threading.Thread(target=self.__process_client, args=(conn,), name="Modbus-client")
  210. t.setDaemon(True)
  211. t.start()
  212.  
  213. except socket.timeout:
  214. pass
  215.  
  216. def __process_client(self, client_connection):
  217. with client_connection:
  218. client_connection.settimeout(3)
  219. buffer = bytearray()
  220. while not self._must_stop:
  221. try:
  222. # Wait for request data to arrive from client
  223. data = client_connection.recv(1024)
  224. # If there is no data, the connection is lost.
  225. if not data:
  226. break
  227.  
  228. #print(data)
  229. buffer.extend(data)
  230.  
  231. # loop while the buffer contains atleast one complete modbus request
  232. while len(buffer) >= STRUCT_MODBUS.size:
  233. # process a packet
  234. b = self.__process_modbus_packet(buffer)
  235. # return the response
  236. client_connection.sendall(b)
  237.  
  238. except socket.timeout:
  239. pass
  240.  
  241. def __process_modbus_packet(self, buffer):
  242. # Extract a modbus packet
  243. tid, pid, length, uid, funccode, register_index, register_count = STRUCT_MODBUS.unpack(buffer[:STRUCT_MODBUS.size])
  244.  
  245. # We only support Procol ID 0.
  246. if pid != 0x00:
  247. raise InvalidModbusRequest()
  248.  
  249. # We only support reading from holding registers.
  250. if funccode != 0x03:
  251. raise InvalidModbusRequest()
  252.  
  253. # Sanitity check on length field.
  254. if length != 6:
  255. raise InvalidModbusRequest()
  256.  
  257. # log debug info
  258. print(f"Received TransacionID={tid} ProtocolId={pid} Len={length} UnitID={uid} funccode={funccode} reg_index=0x{register_index:04x} reg_count={register_count}")
  259.  
  260. # remove packet from buffer
  261. del buffer[:STRUCT_MODBUS.size]
  262.  
  263. output = self._on_read_holding_registers(uid, register_index, register_count)
  264. output_len = len(output)
  265. print(f"- output: {output}")
  266. print(f"- output_len = {output_len}")
  267. b = struct.pack('>HHHBBB' + ('H' * output_len), tid, pid, output_len*2+3, uid, funccode, output_len*2, *output)
  268. print("- reply: "+b.hex())
  269.  
  270. return b
  271. [/code]
  272.  
  273. En de file die het aan elkaar knoopt Power3.py:
  274. [code]
  275.  
  276. import os
  277. import serial
  278. import signal
  279. import time
  280. import struct
  281.  
  282. import threading
  283. from ModbusServer import ModbusServer
  284. from HouseP1 import HouseP1
  285. from EM24 import EM24
  286.  
  287. class App:
  288.  
  289. p1 = HouseP1()
  290. _must_stop = False
  291.  
  292. def __init__(self):
  293. signal.signal(signal.SIGINT, self.on_stop_signal)
  294. signal.signal(signal.SIGTERM, self.on_stop_signal)
  295.  
  296. def on_stop_signal(self, signum, frame):
  297. self._must_stop = True
  298.  
  299. def on_read_holding_registers(self, unit_id: int, index: int, count: int):
  300. # Get reference to set of complete unchanging set of P1 data
  301. # It is important to get the reference before actually using the data
  302. # to ensure the data does not change while processing it here.
  303. em24 = EM24(self.p1.p1_data_complete, index, count)
  304. return em24.buffer
  305.  
  306.  
  307. def main(self):
  308. # Create an instance of ModbusServer
  309. self.modbus_server = ModbusServer('0.0.0.0', 5020, self.on_read_holding_registers)
  310.  
  311. self.p1.start()
  312.  
  313. # Wait for first P1 data to be complete
  314. while not self._must_stop and self.p1.p1_data_complete == None:
  315. time.sleep(0.1)
  316.  
  317. # We can now start the modbus server
  318. self.modbus_server.start()
  319.  
  320. while not self._must_stop:
  321. time.sleep(1)
  322. #print(self.p1.data)
  323.  
  324. #self.p1.stop_and_wait()
  325. self.p1.terminate()
  326. self.modbus_server.terminate()
  327. print("\nStopped.")
  328.  
  329.  
  330. if __name__ == "__main__":
  331. App().main()
  332. [/code]
Advertisement
Add Comment
Please, Sign In to add comment