Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- EM24.py:
- [code]
- import struct
- class EM24:
- modbus_data_const = {
- 0x000b: 1653, # Identification code: Carlo Gavazzi EM24DINAV53XE1PFB
- 0x0302: 1, # Hardware version
- 0x0304: 1, # Firmware version
- 0x1002: 0, # PhaseConfig (0 = 3 phase + neutral)
- 0x0032: 0, # Phase sequence ( 0 = L1/L2/L3, -1 = L1/L3/L2 )
- 0x0033: 500, # Fequency (Hz *10)
- 0xa100: 1, # Switch Position (kVARh, 2, 1, Locked)
- 0xa000: 7, # Application "H"
- 0x0040: 0, # L1 Energy Forward (kWh *10) [NOT AVAILABLE on P1]
- 0x0042: 0, # L2 Energy Forward (kWh *10) [NOT AVAILABLE on P1]
- 0x0044: 0, # L3 Energy Forward (kWh *10) [NOT AVAILABLE on P1]
- #0x5000: [0x3030,0x3030,0x3030,0x3030,0x3030,0x3030,0x3030] # Serial
- }
- buffer = []
- def get_p1_int(self, field_name, factor):
- return int(float(self._p1_data[field_name])*factor)
- def set_int16(self, index, value):
- if self._min <= index <= self._max:
- self.buffer[index - self._min] = value
- def set_int32(self, index, value):
- if self._min <= index <= self._max:
- bytes = struct.pack('>i', value)
- self.set_int16(index+0, int.from_bytes(bytes[2:4], byteorder='big'))
- self.set_int16(index+1, int.from_bytes(bytes[0:2], byteorder='big'))
- def calc_ampere(self, power, volt, factor):
- if volt == 0:
- return 0
- return round(factor * power / volt)
- def __init__(self, p1_data, start_index, count):
- print(p1_data)
- self._p1_data = p1_data
- self._min = start_index
- self._max = start_index + count - 1
- self.buffer = [0] * count
- # Set all default values
- for i,v in self.modbus_data_const.items():
- self.set_int16(i, v)
- # Power: L1, L2, L3, Total (used or supplied)
- 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)
- 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)
- 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)
- 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)
- self.set_int32(0x0012, power_l1 )
- self.set_int32(0x0014, power_l2 )
- self.set_int32(0x0016, power_l3 )
- self.set_int32(0x0028, power_total)
- # Energy: Total (used and supplied)
- # Create aggregate field for the low(1) and high(2) tarif energy counters.
- energy_used = self.get_p1_int("1-0:1.8.1", 10) + self.get_p1_int("1-0:1.8.2", 10)
- energy_supplied = self.get_p1_int("1-0:2.8.1", 10) + self.get_p1_int("1-0:2.8.2", 10)
- self.set_int32(0x0034, energy_used)
- self.set_int32(0x004e, energy_supplied)
- # Voltage: L1, L2, L3
- self.set_int32(0x0000, self.get_p1_int("1-0:32.7.0", 10) )
- self.set_int32(0x0002, self.get_p1_int("1-0:52.7.0", 10) )
- self.set_int32(0x0004, self.get_p1_int("1-0:72.7.0", 10) )
- # Current: L1, L2, L3
- # self.set_int32(0x000c, self.get_p1_int("1-0:31.7.0", 1000) )
- # self.set_int32(0x000e, self.get_p1_int("1-0:51.7.0", 1000) )
- # self.set_int32(0x0010, self.get_p1_int("1-0:71.7.0", 1000) )
- self.set_int32(0x000c, self.calc_ampere(power_l1, self.get_p1_int("1-0:32.7.0", 10),1000) )
- self.set_int32(0x000e, self.calc_ampere(power_l2, self.get_p1_int("1-0:52.7.0", 10),1000) )
- self.set_int32(0x0010, self.calc_ampere(power_l3, self.get_p1_int("1-0:72.7.0", 10),1000) )
- [/code]
- HouseP1.py
- [code]
- import os
- import serial
- import threading
- class HouseP1:
- _serial_error = False
- _serial_device = None
- _must_stop = False
- _thread = None
- p1_data_incomplete = {}
- p1_data_complete = None
- def start(self, devicename = None, baudrate = 115200, timeout=60):
- # Auto detect port?
- if devicename is None:
- device_list = os.listdir("/dev/serial/by-id")
- for dev in device_list:
- if "P1_Converter_Cable" in dev:
- usb_link = os.readlink("/dev/serial/by-id/"+dev)
- devicename = os.path.abspath("/dev/serial/by-id/"+usb_link)
- # Still no port found?
- if devicename is None:
- raise Exception("Unable to determine P1-Converter-Cable usb port.")
- print(f"Reading P1 data from {devicename}")
- self.stop_and_wait()
- self._serial_device = serial.Serial(devicename, baudrate, timeout=timeout)
- self._thread = threading.Thread(target=self.on_p1_data, name="P1-Thread")
- self._thread.setDaemon(True)
- self._serial_error = False
- self._must_stop = False
- self._thread.start()
- def terminate(self):
- self._must_stop = True
- def stop_and_wait(self):
- self._must_stop = True
- if self._thread:
- print("Waiting for background reader to stop...")
- self._thread.join()
- self._thread = None
- def on_p1_data(self):
- print("Start background P1 reader.")
- while not self._must_stop:
- try:
- raw_data = self._serial_device.readline().decode()
- # (Atomic) Complete set of P1 data is ready
- if raw_data.startswith("!"):
- self.p1_data_complete = self.p1_data_incomplete
- self.p1_data_incomplete = {}
- #print(self.p1_data_complete)
- # Split the data string in such a way, that we take the first part (the key code)
- # and the second part (value). We ignore any other data (e.g. "kWh" and additional values.)
- a = raw_data.replace(")","(").replace("*","(").split("(",2)
- if len(a) > 1:
- key = a[0]
- value = a[1]
- self.p1_data_incomplete[key] = value
- p1_in_error = False
- except Exception as e:
- if not self._serial_error:
- self._serial_error = True
- print("Error during on_p1_data: "+str(e))
- print("Subsequent P1 error messages will be suppressed until error is resolved.")
- print("Stopped background P1 reader")
- [/code]
- ModbusServer.py:
- [code]
- import socket
- import struct
- import threading
- STRUCT_MODBUS = struct.Struct('>HHHBBHH')
- class InvalidModbusRequest(Exception):
- """Invalid modbus request"""
- class ModbusServer:
- _must_stop = False
- def __init__(self, bind_host: str, bind_port: int, on_read_holding_registers):
- self._host = bind_host
- self._port = bind_port
- self._on_read_holding_registers = on_read_holding_registers
- def start(self):
- self._thread = threading.Thread(target=self.__listen, name="Modbus-listen")
- self._thread.setDaemon(True)
- self._thread.start()
- def terminate(self):
- self._must_stop = True
- def __listen(self):
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind((self._host, self._port))
- s.listen()
- s.settimeout(3)
- print(f"Listening on {(self._host, self._port)}")
- while not self._must_stop:
- try:
- conn, addr = s.accept()
- print(f"New modbus client {addr}")
- t = threading.Thread(target=self.__process_client, args=(conn,), name="Modbus-client")
- t.setDaemon(True)
- t.start()
- except socket.timeout:
- pass
- def __process_client(self, client_connection):
- with client_connection:
- client_connection.settimeout(3)
- buffer = bytearray()
- while not self._must_stop:
- try:
- # Wait for request data to arrive from client
- data = client_connection.recv(1024)
- # If there is no data, the connection is lost.
- if not data:
- break
- #print(data)
- buffer.extend(data)
- # loop while the buffer contains atleast one complete modbus request
- while len(buffer) >= STRUCT_MODBUS.size:
- # process a packet
- b = self.__process_modbus_packet(buffer)
- # return the response
- client_connection.sendall(b)
- except socket.timeout:
- pass
- def __process_modbus_packet(self, buffer):
- # Extract a modbus packet
- tid, pid, length, uid, funccode, register_index, register_count = STRUCT_MODBUS.unpack(buffer[:STRUCT_MODBUS.size])
- # We only support Procol ID 0.
- if pid != 0x00:
- raise InvalidModbusRequest()
- # We only support reading from holding registers.
- if funccode != 0x03:
- raise InvalidModbusRequest()
- # Sanitity check on length field.
- if length != 6:
- raise InvalidModbusRequest()
- # log debug info
- print(f"Received TransacionID={tid} ProtocolId={pid} Len={length} UnitID={uid} funccode={funccode} reg_index=0x{register_index:04x} reg_count={register_count}")
- # remove packet from buffer
- del buffer[:STRUCT_MODBUS.size]
- output = self._on_read_holding_registers(uid, register_index, register_count)
- output_len = len(output)
- print(f"- output: {output}")
- print(f"- output_len = {output_len}")
- b = struct.pack('>HHHBBB' + ('H' * output_len), tid, pid, output_len*2+3, uid, funccode, output_len*2, *output)
- print("- reply: "+b.hex())
- return b
- [/code]
- En de file die het aan elkaar knoopt Power3.py:
- [code]
- import os
- import serial
- import signal
- import time
- import struct
- import threading
- from ModbusServer import ModbusServer
- from HouseP1 import HouseP1
- from EM24 import EM24
- class App:
- p1 = HouseP1()
- _must_stop = False
- def __init__(self):
- signal.signal(signal.SIGINT, self.on_stop_signal)
- signal.signal(signal.SIGTERM, self.on_stop_signal)
- def on_stop_signal(self, signum, frame):
- self._must_stop = True
- def on_read_holding_registers(self, unit_id: int, index: int, count: int):
- # Get reference to set of complete unchanging set of P1 data
- # It is important to get the reference before actually using the data
- # to ensure the data does not change while processing it here.
- em24 = EM24(self.p1.p1_data_complete, index, count)
- return em24.buffer
- def main(self):
- # Create an instance of ModbusServer
- self.modbus_server = ModbusServer('0.0.0.0', 5020, self.on_read_holding_registers)
- self.p1.start()
- # Wait for first P1 data to be complete
- while not self._must_stop and self.p1.p1_data_complete == None:
- time.sleep(0.1)
- # We can now start the modbus server
- self.modbus_server.start()
- while not self._must_stop:
- time.sleep(1)
- #print(self.p1.data)
- #self.p1.stop_and_wait()
- self.p1.terminate()
- self.modbus_server.terminate()
- print("\nStopped.")
- if __name__ == "__main__":
- App().main()
- [/code]
Advertisement
Add Comment
Please, Sign In to add comment