reading out registers corrected

This commit is contained in:
Nils Reiners
2025-09-16 22:46:42 +02:00
parent b9cba11be7
commit 8eda3bc954
7 changed files with 150 additions and 40 deletions

View File

@@ -1,13 +1,12 @@
import time import time
from datetime import datetime from datetime import datetime
from data_base_csv import DataBaseCsv
from data_base_influx import DataBaseInflux from data_base_influx import DataBaseInflux
from heat_pump import HeatPump from heat_pump import HeatPump
from pv_inverter import PvInverter from pv_inverter import PvInverter
from shelly_pro_3m import ShellyPro3m 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 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 # For productive-System change IP-adress in heatpump to '10.0.0.10' and port to 502
interval_seconds = 10 interval_seconds = 10
@@ -18,7 +17,7 @@ db = DataBaseInflux(
bucket="allmende_db" bucket="allmende_db"
) )
hp = HeatPump(device_name='hp_master', ip_address='localhost', port=8111) hp = HeatPump(device_name='hp_master', ip_address='10.0.0.10', port=502)
shelly = ShellyPro3m(device_name='wohnung_2_6', ip_address='192.168.1.121') shelly = ShellyPro3m(device_name='wohnung_2_6', ip_address='192.168.1.121')
wr = PvInverter(device_name='wr_master', ip_address='192.168.1.112') wr = PvInverter(device_name='wr_master', ip_address='192.168.1.112')
@@ -31,5 +30,6 @@ while True:
db.store_data(shelly.device_name, shelly.get_state()) db.store_data(shelly.device_name, shelly.get_state())
db.store_data(wr.device_name, wr.get_state()) db.store_data(wr.device_name, wr.get_state())
#controller.perform_action() #controller.perform_action()
time.sleep(0.1) time.sleep(0.1)

View File

@@ -1,68 +1,117 @@
import time import time
import struct
import pandas as pd import pandas as pd
import matplotlib.pyplot as plt
from collections import deque
from typing import Dict, Any, List, Tuple, Optional
from pymodbus.client import ModbusTcpClient from pymodbus.client import ModbusTcpClient
EXCEL_PATH = "modbus_registers/pv_inverter_registers.xlsx"
class PvInverter: class PvInverter:
def __init__(self, device_name: str, ip_address: str, port: int = 502, unit: int = 1): def __init__(self, device_name: str, ip_address: str, port: int = 502, unit: int = 1):
self.device_name = device_name self.device_name = device_name
self.ip = ip_address self.ip = ip_address
self.port = port self.port = port
self.unit = unit self.unit = unit
self.client = None self.client: Optional[ModbusTcpClient] = None
self.registers = None self.registers: Dict[int, Dict[str, Any]] = {} # addr -> {"desc":..., "type":...}
self.connect_to_modbus() self.connect_to_modbus()
self.get_registers() self.load_registers(EXCEL_PATH)
# ---------- Verbindung ----------
def connect_to_modbus(self): def connect_to_modbus(self):
# Timeout & retries optional, aber hilfreich:
self.client = ModbusTcpClient(self.ip, port=self.port, timeout=3.0, retries=3) self.client = ModbusTcpClient(self.ip, port=self.port, timeout=3.0, retries=3)
if not self.client.connect(): if not self.client.connect():
print("Verbindung zu Wechselrichter fehlgeschlagen.") print("Verbindung zu Wechselrichter fehlgeschlagen.")
raise SystemExit(1) raise SystemExit(1)
print("Verbindung zu Wechselrichter erfolgreich.") print("Verbindung zu Wechselrichter hergestellt.")
# WICHTIG: NICHT hier schließen!
# finally: self.client.close() <-- entfernen
def close(self): def close(self):
if self.client: if self.client:
self.client.close() self.client.close()
self.client = None self.client = None
def get_registers(self): # ---------- Register-Liste ----------
excel_path = "modbus_registers/pv_inverter_registers.xlsx" def load_registers(self, excel_path: str):
xls = pd.ExcelFile(excel_path) xls = pd.ExcelFile(excel_path)
df_input_registers = xls.parse() df = xls.parse()
df_clean = df_input_registers[['MB Adresse', 'Beschreibung', 'Variabel Typ']].dropna() # Passen die Spaltennamen bei dir anders, bitte hier anpassen:
df_clean['MB Adresse'] = df_clean['MB Adresse'].astype(int) cols = ["MB Adresse", "Beschreibung", "Variabel Typ"]
for c in cols:
if c not in df.columns:
raise ValueError(f"Spalte '{c}' fehlt in {excel_path}")
df = df[cols].dropna()
df["MB Adresse"] = df["MB Adresse"].astype(int)
# NORMALISIERE TYP
def norm_type(x: Any) -> str:
s = str(x).strip().upper()
return "REAL" if s == "REAL" else "INT"
self.registers = { self.registers = {
row['MB Adresse']: { int(row["MB Adresse"]): {
'desc': row['Beschreibung'], "desc": str(row["Beschreibung"]).strip(),
'type': 'REAL' if str(row['Variabel Typ']).upper() == 'REAL' else 'INT' "type": norm_type(row["Variabel Typ"])
} }
for _, row in df_clean.iterrows() for _, row in df.iterrows()
} }
print(f" {len(self.registers)} Register aus Excel geladen.")
def get_state(self): # ---------- Low-Level Lesen ----------
data = {'Zeit': time.strftime('%Y-%m-%d %H:%M:%S')} def _try_read(self, fn_name: str, address: int, count: int) -> Optional[List[int]]:
for address, info in self.registers.items(): fn = getattr(self.client, fn_name)
reg_type = info['type'] # pymodbus 3.8.x hat 'slave='; Fallbacks schaden nicht
# Unit-ID mitgeben (wichtig bei pymodbus>=3) for kwargs in (dict(address=address, count=count, slave=self.unit),
result = self.client.read_holding_registers( dict(address=address, count=count),
address=address, ):
count=2 if reg_type == 'REAL' else 1, try:
slave=self.unit # pymodbus 2.x -> 'slave', nicht 'unit' res = fn(**kwargs)
) if res is None or (hasattr(res, "isError") and res.isError()):
if result.isError(): continue
print(f"Fehler beim Lesen von Adresse {address}: {result}") return res.registers
except TypeError:
continue continue
return None
# Minimal invasiv: wie bei dir erstes Register verwenden def _read_any(self, address: int, count: int) -> Optional[List[int]]:
value = result.registers[0] regs = self._try_read("read_holding_registers", address, count)
print(f"Adresse {address} - {info['desc']}: {value}") if regs is None:
data[f"{address} - {info['desc']}"] = value regs = self._try_read("read_input_registers", address, count)
return regs
# ---------- Decoding ----------
@staticmethod
def _to_i16(u16: int) -> int:
return struct.unpack(">h", struct.pack(">H", u16))[0]
@staticmethod
def _to_f32_from_two(u16_hi: int, u16_lo: int, msw_first: bool = True) -> float:
if msw_first:
b = struct.pack(">HH", u16_hi, u16_lo)
else:
b = struct.pack(">HH", u16_lo, u16_hi)
return struct.unpack(">f", b)[0]
def read_one(self, address_excel: int, rtype: str) -> Optional[float]:
"""Liest einen Wert nach Typ ('INT' oder 'REAL') unter Berücksichtigung Base-1."""
addr = address_excel
if rtype == "REAL":
regs = self._read_any(addr, 2)
if not regs or len(regs) < 2:
return None
return self._to_f32_from_two(regs[0], regs[1])
else: # INT
regs = self._read_any(addr, 1)
if not regs:
return None
return float(self._to_i16(regs[0]))
def get_state(self) -> Dict[str, Any]:
"""Liest ALLE Register aus self.registers und gibt dict zurück."""
data = {"Zeit": time.strftime("%Y-%m-%d %H:%M:%S")}
for address, meta in self.registers.items():
val = self.read_one(address, meta["type"])
if val is None:
continue
key = f"{address} - {meta['desc']}"
data[key] = val
return data return data

61
test_meter.py Normal file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pymodbus.client import ModbusTcpClient
import struct, sys
MODBUS_IP = "192.168.1.112"
MODBUS_PORT = 502
UNIT_ID = 1
METER_START = 40240 # Startadresse Model 203-Felder
def to_i16(u16): # unsigned 16 → signed 16
return struct.unpack(">h", struct.pack(">H", u16))[0]
def read_regs(client, addr, count):
rr = client.read_holding_registers(address=addr, count=count, slave=UNIT_ID)
if rr.isError():
return None
return rr.registers
def read_meter_power(client):
base = METER_START
p = read_regs(client, base + 16, 1) # M_AC_Power
pa = read_regs(client, base + 17, 1) # Phase A
pb = read_regs(client, base + 18, 1) # Phase B
pc = read_regs(client, base + 19, 1) # Phase C
sf = read_regs(client, base + 20, 1) # Scale Factor
if not p or not sf:
return None
sff = to_i16(sf[0])
return {
"total": to_i16(p[0]) * (10 ** sff),
"A": to_i16(pa[0]) * (10 ** sff) if pa else None,
"B": to_i16(pb[0]) * (10 ** sff) if pb else None,
"C": to_i16(pc[0]) * (10 ** sff) if pc else None,
"sf": sff
}
def fmt_w(v):
if v is None: return "-"
neg = v < 0
v = abs(v)
return f"{'-' if neg else ''}{v/1000:.2f} kW" if v >= 1000 else f"{'-' if neg else ''}{v:.0f} W"
def main():
client = ModbusTcpClient(MODBUS_IP, port=MODBUS_PORT)
if not client.connect():
print("❌ Verbindung fehlgeschlagen."); sys.exit(1)
try:
m = read_meter_power(client)
if m:
print(f"Meter-Leistung: {fmt_w(m['total'])} "
f"(A {fmt_w(m['A'])}, B {fmt_w(m['B'])}, C {fmt_w(m['C'])}) [SF={m['sf']}]")
else:
print("Meter-Leistung konnte nicht gelesen werden.")
finally:
client.close()
if __name__ == "__main__":
main()