Feature: Goodwe GW10KN-ET Support via goodwe UDP/8899
- goodwe_client.py: GoodweReader wraps goodwe library (asyncio → sync) - inverters.py: GW10KN_ET mit 37 ET-Sensoren, protocol=goodwe_udp - main.py: poll-loop verzweigt auf GoodweReader bei goodwe_udp-Geräten; AGG_SENSOR_IDS um Goodwe-Keys erweitert (ppv, soc, e_total, e_total_imp/exp, …) - requirements.txt: goodwe==0.4.10 hinzugefügt Goodwe-Stick (WIFILAN_2.0 v2.4.41) hat eFuse-gesperrten ROM-Download; Kommunikation erfolgt über WiFi-Stick-IP + UDP-Port 8899. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+31
-21
@@ -12,6 +12,7 @@ from flask import Flask, jsonify, request, send_from_directory
|
||||
from inverters import INVERTERS
|
||||
import history
|
||||
from modbus_client import ModbusReader
|
||||
from goodwe_client import GoodweReader
|
||||
from mqtt_publisher import MqttPublisher
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -30,18 +31,18 @@ app = Flask(__name__, static_folder=WEB_DIR)
|
||||
# Welche Sensor-IDs fließen in welchen Aggregat-Bucket (Summe, außer AGG_AVG)
|
||||
|
||||
AGG_SENSOR_IDS: Dict[str, List[str]] = {
|
||||
"total_pv_power": ["pv_power", "pv1_power", "pv2_power"],
|
||||
"total_pv_power": ["pv_power", "pv1_power", "pv2_power", "ppv"],
|
||||
"total_ac_power": ["ac_power", "ac_power_total"],
|
||||
"total_energy_today": ["energy_today"],
|
||||
"total_energy_total": ["energy_total"],
|
||||
"grid_power": ["total_power"],
|
||||
"grid_import_kwh": ["import_kwh"],
|
||||
"grid_export_kwh": ["export_kwh"],
|
||||
"total_energy_today": ["energy_today", "e_day"],
|
||||
"total_energy_total": ["energy_total", "e_total"],
|
||||
"grid_power": ["total_power", "active_power"],
|
||||
"grid_import_kwh": ["import_kwh", "e_total_imp"],
|
||||
"grid_export_kwh": ["export_kwh", "e_total_exp"],
|
||||
"bat_charge_power": ["bat_charge_power"],
|
||||
"bat_discharge_power": ["bat_discharge_power"],
|
||||
"bat_charge_total": ["bat_charge_total"],
|
||||
"bat_discharge_total": ["bat_discharge_total"],
|
||||
"bat_soc": ["bat_soc"],
|
||||
"bat_charge_total": ["bat_charge_total", "e_bat_charge_total"],
|
||||
"bat_discharge_total": ["bat_discharge_total", "e_bat_discharge_total"],
|
||||
"bat_soc": ["bat_soc", "soc"],
|
||||
}
|
||||
AGG_AVG = {"bat_soc"}
|
||||
|
||||
@@ -154,7 +155,15 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
||||
log.error("[%s] Ungültige Konfiguration: %s", inv_id, e)
|
||||
return
|
||||
|
||||
reader = ModbusReader(host=inv_cfg["modbus_ip"], port=port, slave=slave)
|
||||
host = inv_cfg["modbus_ip"]
|
||||
if inverter.protocol == "goodwe_udp":
|
||||
reader = GoodweReader(host=host, family=inverter.goodwe_family)
|
||||
log.info("[%s] Poll-Loop: %s @ %s (Goodwe UDP/8899) alle %ds",
|
||||
inv_id, inverter.name, host, interval)
|
||||
else:
|
||||
reader = ModbusReader(host=host, port=port, slave=slave)
|
||||
log.info("[%s] Poll-Loop: %s @ %s:%s alle %ds",
|
||||
inv_id, inverter.name, host, port, interval)
|
||||
|
||||
with State.lock:
|
||||
if _publisher:
|
||||
@@ -170,9 +179,7 @@ def _poll_loop(inv_cfg: Dict[str, Any], stop: threading.Event):
|
||||
q = hist.setdefault(sid, deque(maxlen=300))
|
||||
for pt in points:
|
||||
q.append(pt)
|
||||
log.info("[%s] Poll-Loop: %s @ %s:%s alle %ds — %d Sensoren aus DB geladen",
|
||||
inv_id, inverter.name, inv_cfg["modbus_ip"],
|
||||
inv_cfg.get("modbus_port", 502), interval, len(hist_data))
|
||||
log.info("[%s] %d Sensoren aus DB geladen", inv_id, len(hist_data))
|
||||
|
||||
while not stop.is_set():
|
||||
t0 = time.time()
|
||||
@@ -284,14 +291,17 @@ def api_save_inverters():
|
||||
for inv in data:
|
||||
if not isinstance(inv, dict):
|
||||
return jsonify({"error": "invalid"}), 400
|
||||
if inv.get("inverter_model") not in INVERTERS:
|
||||
return jsonify({"error": f"unknown model: {inv.get('inverter_model')}"}), 400
|
||||
port = inv.get("modbus_port", 502)
|
||||
if not isinstance(port, int) or not (1 <= port <= 65535):
|
||||
return jsonify({"error": "invalid port"}), 400
|
||||
addr = inv.get("modbus_address", 1)
|
||||
if not isinstance(addr, int) or not (1 <= addr <= 247):
|
||||
return jsonify({"error": "invalid modbus address (1-247)"}), 400
|
||||
model_id = inv.get("inverter_model")
|
||||
if model_id not in INVERTERS:
|
||||
return jsonify({"error": f"unknown model: {model_id}"}), 400
|
||||
inverter_def = INVERTERS[model_id]
|
||||
if inverter_def.protocol == "modbus":
|
||||
port = inv.get("modbus_port", 502)
|
||||
if not isinstance(port, int) or not (1 <= port <= 65535):
|
||||
return jsonify({"error": "invalid port"}), 400
|
||||
addr = inv.get("modbus_address", 1)
|
||||
if not isinstance(addr, int) or not (1 <= addr <= 247):
|
||||
return jsonify({"error": "invalid modbus address (1-247)"}), 400
|
||||
with State.lock:
|
||||
State.inverters_cfg = data
|
||||
save_config()
|
||||
|
||||
Reference in New Issue
Block a user