diff --git a/README b/README index f4d8eb3..3fb597d 100644 --- a/README +++ b/README @@ -11,10 +11,42 @@ Was needs to be done on the Raspberry pi before the tool can run. - pip install -r requirements.txt -How to run the script: +3) How to run the script for testing: -- nohup python main.py > terminal_log 2>&1 & + nohup python main.py > terminal_log 2>&1 & For reading out the terminal_log while script is runing: -- tail -f terminal_log + tail -f terminal_log + + +4) Implement and run the ems as systemd service: +create: + /etc/systemd/system/allmende_ems.service + +insert: + [Unit] + Description=Allmende EMS Python Script + After=network.target + + [Service] + WorkingDirectory=/home/pi/projects/allmende_ems + ExecStart=/home/pi/allmende_ems/bin/python3.11 /home/pi/projects/allmende_ems/main.py + Restart=always + RestartSec=5 + StandardOutput=journal + StandardError=journal + + [Install] + WantedBy=multi-user.target + +manage the service with the following commands: +Once: + sudo systemctl daemon-reload + sudo systemctl start allmende_ems.service + sudo systemctl enable allmende_ems.service +While running: + sudo systemctl status allmende_ems.service + sudo systemctl restart allmende_ems.service + sudo systemctl stop allmende_ems.service + journalctl -u allmende_ems.service diff --git a/__pycache__/data_base_csv.cpython-312.pyc b/__pycache__/data_base_csv.cpython-312.pyc index a9f0803..e276725 100644 Binary files a/__pycache__/data_base_csv.cpython-312.pyc and b/__pycache__/data_base_csv.cpython-312.pyc differ diff --git a/__pycache__/heat_pump.cpython-312.pyc b/__pycache__/heat_pump.cpython-312.pyc index 589611a..6cb73db 100644 Binary files a/__pycache__/heat_pump.cpython-312.pyc and b/__pycache__/heat_pump.cpython-312.pyc differ diff --git a/__pycache__/make_tunnel.cpython-312.pyc b/__pycache__/make_tunnel.cpython-312.pyc new file mode 100644 index 0000000..84e32db Binary files /dev/null and b/__pycache__/make_tunnel.cpython-312.pyc differ diff --git a/__pycache__/pv_inverter.cpython-312.pyc b/__pycache__/pv_inverter.cpython-312.pyc new file mode 100644 index 0000000..9cccd2d Binary files /dev/null and b/__pycache__/pv_inverter.cpython-312.pyc differ diff --git a/__pycache__/shelly_pro_3m.cpython-312.pyc b/__pycache__/shelly_pro_3m.cpython-312.pyc index d2250f0..c74d295 100644 Binary files a/__pycache__/shelly_pro_3m.cpython-312.pyc and b/__pycache__/shelly_pro_3m.cpython-312.pyc differ diff --git a/heat_pump.py b/heat_pump.py index 008dc4c..773e84a 100644 --- a/heat_pump.py +++ b/heat_pump.py @@ -3,16 +3,17 @@ import pandas as pd import time class HeatPump: - def __init__(self, device_name: str, ip_address: str): + def __init__(self, device_name: str, ip_address: str, port: int=502): self.device_name = device_name self.ip = ip_address + self.port = port self.client = None self.connect_to_modbus() self.registers = None self.get_registers() def connect_to_modbus(self): - port = 502 + port = self.port self.client = ModbusTcpClient(self.ip, port=port) try: if not self.client.connect(): diff --git a/main.py b/main.py index 3642708..1df6135 100644 --- a/main.py +++ b/main.py @@ -3,22 +3,26 @@ from datetime import datetime from data_base_csv import DataBaseCsv from data_base_influx import DataBaseInflux from heat_pump import HeatPump +from pv_inverter import PvInverter from shelly_pro_3m import ShellyPro3m +# For dev-System run in terminal: ssh -N -L 127.0.0.1:8111:10.0.0.10:502 pi@192.168.1.146 +# For productive-System change port in heatpump to 502 + interval_seconds = 10 db = DataBaseInflux( - url="http://localhost:8086", + url="http://192.168.1.146:8086", token="Cw_naEZyvJ3isiAh1P4Eq3TsjcHmzzDFS7SlbKDsS6ZWL04fMEYixWqtNxGThDdG27S9aW5g7FP9eiq5z1rsGA==", org="allmende", bucket="allmende_db" ) -hp = HeatPump(device_name='hp_master', ip_address='10.0.0.10') +hp = HeatPump(device_name='hp_master', ip_address='localhost', port=8111) shelly = ShellyPro3m(device_name='wohnung_2_6', ip_address='192.168.1.121') -wr = SolarEdgeWechselrichter(device_name='wr_master', ip_address='192.168.1.112') +wr = PvInverter(device_name='wr_master', ip_address='192.168.1.112') -controller = SgReadyController(hp, wr) +#controller = SgReadyController(hp, wr) while True: now = datetime.now() @@ -26,6 +30,6 @@ while True: db.store_data(hp.device_name, hp.get_state()) db.store_data(shelly.device_name, shelly.get_state()) db.store_data(wr.device_name, wr.get_state()) - controller.perform_action() + #controller.perform_action() time.sleep(0.1) diff --git a/make_tunnel.py b/make_tunnel.py new file mode 100644 index 0000000..3adb257 --- /dev/null +++ b/make_tunnel.py @@ -0,0 +1,22 @@ +from sshtunnel import SSHTunnelForwarder + +# ---- KONFIG ---- +SSH_HOST = "192.168.1.146" # Raspberry Pi im 192.168.1.x Netz +SSH_PORT = 22 +SSH_USER = "pi" +PASSWORD = 'raspberry' # oder Passwort als String + + +REMOTE_IP = "10.0.0.10" # Wärmepumpe im 10.0.0.x Netz +REMOTE_PORT = 502 # Modbus/TCP Port + +def make_tunnel(port): + tunnel = SSHTunnelForwarder( + (SSH_HOST, SSH_PORT), + ssh_username=SSH_USER, + ssh_password=PASSWORD, + remote_bind_address=(REMOTE_IP, REMOTE_PORT), + local_bind_address=("127.0.0.1", port), + ) + tunnel.start() + return tunnel \ No newline at end of file diff --git a/modbus_registers/pv_inverter_registers.xlsx b/modbus_registers/pv_inverter_registers.xlsx new file mode 100644 index 0000000..ed66134 Binary files /dev/null and b/modbus_registers/pv_inverter_registers.xlsx differ diff --git a/pv_inverter.py b/pv_inverter.py new file mode 100644 index 0000000..365d7fb --- /dev/null +++ b/pv_inverter.py @@ -0,0 +1,68 @@ +import time +import pandas as pd +from pymodbus.client import ModbusTcpClient + +class PvInverter: + def __init__(self, device_name: str, ip_address: str, port: int = 502, unit: int = 1): + self.device_name = device_name + self.ip = ip_address + self.port = port + self.unit = unit + self.client = None + self.registers = None + + self.connect_to_modbus() + self.get_registers() + + def connect_to_modbus(self): + # Timeout & retries optional, aber hilfreich: + self.client = ModbusTcpClient(self.ip, port=self.port, timeout=3.0, retries=3) + if not self.client.connect(): + print("Verbindung zu Wechselrichter fehlgeschlagen.") + raise SystemExit(1) + print("Verbindung zu Wechselrichter erfolgreich.") + + # WICHTIG: NICHT hier schließen! + # finally: self.client.close() <-- entfernen + + def close(self): + if self.client: + self.client.close() + self.client = None + + def get_registers(self): + excel_path = "modbus_registers/pv_inverter_registers.xlsx" + xls = pd.ExcelFile(excel_path) + df_input_registers = xls.parse() + df_clean = df_input_registers[['MB Adresse', 'Beschreibung', 'Variabel Typ']].dropna() + df_clean['MB Adresse'] = df_clean['MB Adresse'].astype(int) + + self.registers = { + row['MB Adresse']: { + 'desc': row['Beschreibung'], + 'type': 'REAL' if str(row['Variabel Typ']).upper() == 'REAL' else 'INT' + } + for _, row in df_clean.iterrows() + } + + def get_state(self): + data = {'Zeit': time.strftime('%Y-%m-%d %H:%M:%S')} + for address, info in self.registers.items(): + reg_type = info['type'] + # Unit-ID mitgeben (wichtig bei pymodbus>=3) + result = self.client.read_holding_registers( + address=address, + count=2 if reg_type == 'REAL' else 1, + slave=self.unit # pymodbus 2.x -> 'slave', nicht 'unit' + ) + if result.isError(): + print(f"Fehler beim Lesen von Adresse {address}: {result}") + continue + + # Minimal invasiv: wie bei dir – erstes Register verwenden + value = result.registers[0] + print(f"Adresse {address} - {info['desc']}: {value}") + data[f"{address} - {info['desc']}"] = value + return data + + diff --git a/requirements.txt b/requirements.txt index aa87620..08e3db0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pymodbus~=3.8.6 pandas -openpyxl \ No newline at end of file +openpyxl +sshtunnel \ No newline at end of file diff --git a/shelly_pro_3m.py b/shelly_pro_3m.py index d1cee53..9ba8b76 100644 --- a/shelly_pro_3m.py +++ b/shelly_pro_3m.py @@ -5,16 +5,17 @@ import pandas as pd import time class ShellyPro3m: - def __init__(self, device_name: str, ip_address: str): + def __init__(self, device_name: str, ip_address: str, port: int=502): self.device_name = device_name self.ip = ip_address + self.port = port self.client = None self.connect_to_modbus() self.registers = None self.get_registers() def connect_to_modbus(self): - port = 502 + port = self.port self.client = ModbusTcpClient(self.ip, port=port) try: if not self.client.connect(): diff --git a/test_wr.py b/test_wr.py index 4da815f..26a240b 100644 --- a/test_wr.py +++ b/test_wr.py @@ -4,7 +4,7 @@ import sys from typing import Optional # === Verbindungseinstellungen === -MODBUS_IP = "192.168.1.107" +MODBUS_IP = "192.168.1.112" MODBUS_PORT = 502 # SetApp: 1502; LCD-Menü: 502 -> ggf. anpassen UNIT_ID = 1 # Default laut Doku: 1