Files
battery-charging-optimizer/docs/EMS_OpenEMS_HomeAssistant_Dokumentation.md

11 KiB

EMS mit openEMS und Home Assistant - Projektdokumentation

Projektübersicht

Intelligentes Batterie-Optimierungssystem für eine Wohnanlage mit dynamischer Strompreissteuerung und Solarprognose-Integration.

Hardware-Setup

  • Batterie: GoodWe 10 kWh
  • Wechselrichter: 10 kW GoodWe
  • PV-Anlage: 9,2 kWp (Ost-West-Ausrichtung auf Flachdach)
  • Steuerung: BeagleBone mit openEMS
  • Kommunikation: Modbus TCP (IP: 192.168.89.144, Port: 502)

Stromtarif & APIs

  • Tarif: haStrom FLEX PRO (dynamische Preise)
  • Preisabruf: Täglich um 14:00 Uhr für Folgetag
  • PV-Prognose: Forecast.Solar API
  • API-Feldname: t_price_has_pro_incl_vat (spezifisch für FLEX PRO)

Systemarchitektur

Komponenten-Übersicht

┌─────────────────────────────────────────────────┐
│ Home Assistant + PyScript                       │
│ - Optimierungsalgorithmus (täglich 14:05)      │
│ - Stündliche Ausführung (xx:05)                │
│ - Zeitzonenhandling (UTC → Local)              │
└──────────────────┬──────────────────────────────┘
                   │
         ┌─────────┴─────────┐
         │                   │
┌────────▼────────┐  ┌──────▼───────────────┐
│ Modbus TCP      │  │ JSON-RPC API         │
│ Port 502        │  │ Port 8074            │
│ (Batterie-      │  │ (ESS Mode Switch)    │
│  steuerung)     │  │                      │
└────────┬────────┘  └──────┬───────────────┘
         │                   │
         └─────────┬─────────┘
                   │
         ┌─────────▼──────────┐
         │ openEMS            │
         │ - Controller-Prio  │
         │ - ESS Management   │
         │ - GoodWe Integration│
         └────────────────────┘

Kritische technische Details

1. Modbus-Kommunikation

Register-Format

  • Datentyp: IEEE 754 FLOAT32
  • Register-Paare: Verwenden 2 aufeinanderfolgende Register
  • Beispiel: Register 2752/2753 für SET_GRID_ACTIVE_POWER

Wichtige Register (aus Modbustcp0_3.xlsx)

  • Batteriesteuerung: Register 2752/2753 (SET_GRID_ACTIVE_POWER)
  • SOC-Abfrage: Register für State of Charge
  • Leistungswerte: FLOAT32-codiert

Python-Implementierung für Register-Schreiben

import struct
from pymodbus.client import ModbusTcpClient

def write_float32_register(client, address, value):
    """
    Schreibt einen FLOAT32-Wert in zwei Modbus-Register
    """
    # FLOAT32 in zwei 16-bit Register konvertieren
    bytes_value = struct.pack('>f', value)  # Big-Endian
    registers = [
        int.from_bytes(bytes_value[0:2], 'big'),
        int.from_bytes(bytes_value[2:4], 'big')
    ]
    client.write_registers(address, registers)

2. openEMS Controller-Prioritäten

Kritisches Verhalten

  • Alphabetische Ausführungsreihenfolge: Controller werden alphabetisch sortiert ausgeführt
  • Override-Problem: Spätere Controller können frühere überschreiben
  • Lösung: ctrlBalancing0 mit SET_GRID_ACTIVE_POWER nutzt

Controller-Hierarchie

1. ctrlBalancing0 (SET_GRID_ACTIVE_POWER) ← HÖCHSTE PRIORITÄT
2. Andere Controller (können nicht überschreiben)
3. ESS-Register (können überschrieben werden)

ESS-Modi

  • REMOTE: Externe Steuerung via Modbus aktiv
  • INTERNAL: openEMS-interne Steuerung
  • Mode-Switch: Via JSON-RPC API Port 8074

3. Existierende Home Assistant Automationen

Drei bewährte Automationen für Batteriesteuerung:

  1. Batterieladung starten (ID: 1730457901370)
  2. Batterieladung stoppen (ID: 1730457994517)
  3. Batterieentladung (ID: weitere Details erforderlich)

Wichtig: Diese Automationen werden vom Optimierungssystem gesteuert, nicht ersetzt!

Optimierungsalgorithmus

Strategie

  • Konservativ: Nur in günstigsten Stunden laden
  • SOC-Bereich: 20-100% (2 kWh Reserve für Eigenverbrauch)
  • Ranking-basiert: N günstigste Stunden aus Heute+Morgen
  • Mitternachtsoptimierung: Berücksichtigt Preise über Tageswechsel hinweg

Berechnung

# Benötigte Ladestunden
needed_kwh = (target_soc - current_soc) / 100 * battery_capacity
needed_hours = needed_kwh / charging_power

# Sortierung nach Preis
combined_prices = today_prices + tomorrow_prices
sorted_hours = sorted(combined_prices, key=lambda x: x['price'])

# N günstigste Stunden auswählen
charging_hours = sorted_hours[:needed_hours]

PyScript-Ausführung

Zeitplan

  • Optimierung: Täglich 14:05 (nach Preisveröffentlichung um 14:00)
  • Ausführung: Stündlich xx:05
  • Prüfung: Ist aktuelle Stunde eine Ladestunde?

Zeitzonenhandling

# PROBLEM: PyScript verwendet UTC
from datetime import datetime
import pytz

# UTC datetime von PyScript
utc_now = datetime.now()

# Konvertierung nach deutscher Zeit
berlin_tz = pytz.timezone('Europe/Berlin')
local_now = utc_now.astimezone(berlin_tz)

# Speicherung in Home Assistant (local time)
state.set('sensor.charging_schedule', attributes={'data': schedule_data})

KRITISCH: Home Assistant speichert Zeiten in lokaler Zeit, PyScript arbeitet in UTC!

Datenquellen & Integration

haStrom FLEX PRO API

# Endpoint-Unterschiede beachten!
url = "https://api.hastrom.de/api/prices"
params = {
    'start': '2024-01-01',
    'end': '2024-01-02'  # Unterstützt Datumsbereichsabfragen
}

# Feldname ist spezifisch!
price = data['t_price_has_pro_incl_vat']  # Nicht der Standard-Feldname!

Forecast.Solar

  • Standortdaten: Lat/Lon für Ost-West-Arrays
  • Dachneigung: Flachdach-spezifisch
  • Azimut: Ost (90°) und West (270°)

InfluxDB2

  • Historische Daten: Langzeit-Speicherung
  • Analyse: Performance-Tracking
  • Backup: Datenredundanz

Home Assistant Dashboard

Design-Prinzipien

  • Maximum 4 Spalten: Mobile-First Design
  • Sections Layout: Moderne HA 2024.2+ Standard
  • Keine verschachtelten Listen: Flache Hierarchie bevorzugen

Verwendete Custom Cards (HACS)

  • Mushroom Cards: Basis-UI-Elemente
  • Bubble Card: Erweiterte Visualisierung
  • Plotly Graph Card: Detaillierte Diagramme
  • Power Flow Card Plus: Energiefluss-Darstellung
  • Stack-in-Card: Layout-Organisation

Dashboard-Varianten

  1. Minimal: Schneller Status-Check
  2. Standard: Tägliche Nutzung
  3. Detail: Analyse und Debugging

PyScript-Besonderheiten

Bekannte Limitierungen

# NICHT UNTERSTÜTZT in PyScript:
# - Generator Expressions mit selectattr()
result = [x for x in items if x.attr == value]  # ✗

# STATTDESSEN:
result = []
for x in items:
    if x.attr == value:
        result.append(x)  # ✓

State vs. Attributes

# State Value: Max 255 Zeichen
state.set('sensor.status', 'charging')

# Attributes: Unbegrenzt (JSON)
state.set('sensor.schedule', 
    value='active',
    attributes={
        'schedule': complete_schedule_dict,  # ✓ Kann sehr groß sein
        'prices': all_price_data
    }
)

Debugging & Monitoring

Logging-Strategie

# PyScript Logging
log.info(f"Optimization completed: {len(charging_hours)} hours")
log.debug(f"Price data: {prices}")
log.error(f"Modbus connection failed: {error}")

Transparenz

  • Geplant vs. Ausgeführt: Vergleich in Logs
  • Controller-Execution: openEMS-Logs prüfen
  • Modbus-Traffic: Register-Scan-Tools

Typische Probleme

Problem 1: Controller Override

Symptom: Batterie lädt nicht trotz Befehl Ursache: Anderer Controller überschreibt SET_GRID_ACTIVE_POWER Lösung: ctrlBalancing0 verwenden, nicht direkte ESS-Register

Problem 2: Zeitzone-Mismatch

Symptom: Ladung startet zur falschen Stunde Ursache: UTC/Local-Zeit-Verwechslung Lösung: Explizite Timezone-Konvertierung in PyScript

Problem 3: FLOAT32-Encoding

Symptom: Falsche Werte in Modbus-Registern Ursache: Falsche Byte-Reihenfolge Lösung: Big-Endian IEEE 754 verwenden

Datei-Struktur

/config/
├── pyscripts/
│   ├── battery_optimizer.py          # Hauptoptimierung
│   └── helpers/
│       ├── modbus_client.py          # Modbus-Funktionen
│       └── price_fetcher.py          # API-Aufrufe
├── automations/
│   ├── battery_charge_start.yaml
│   ├── battery_charge_stop.yaml
│   └── battery_discharge.yaml
├── dashboards/
│   ├── energy_overview.yaml          # Hauptdashboard
│   └── energy_detail.yaml            # Detail-Analyse
└── configuration.yaml                # InfluxDB, Modbus, etc.

Nächste Schritte & Erweiterungen

Kurzfristig

  • Dashboard-Feinschliff (Sections Layout)
  • Logging-Verbesserungen
  • Performance-Monitoring

Mittelfristig

  • Erweiterte Algorithmen (ML-basiert?)
  • Wetterprognose-Integration
  • Community-Sharing vorbereiten

Langfristig

  • Multi-Tarif-Support
  • V2G-Integration (Vehicle-to-Grid)
  • Peer-to-Peer-Energiehandel

Wichtige Ressourcen

Dokumentation

Tools

  • Modbus-Scanner: Für Register-Mapping
  • Excel-Export: openEMS Register-Dokumentation (Modbustcp0_3.xlsx)
  • HA Logs: /config/home-assistant.log

Lessons Learned

  1. Controller-Priorität ist kritisch: Immer höchsten verfügbaren Channel nutzen
  2. Zeitzone-Handling explizit: UTC/Local nie vermischen
  3. API-Spezifikationen prüfen: Feldnamen können abweichen (haStrom!)
  4. PyScript-Limitierungen kennen: Keine komplexen Comprehensions
  5. Attributes > State: Für komplexe Datenstrukturen
  6. Bestehende Automationen nutzen: Nicht neuerfinden
  7. Transparent loggen: Nachvollziehbarkeit ist Schlüssel

Quick Reference - Häufige Befehle

Modbus Register auslesen

# Via HA Developer Tools > States
sensor.openems_battery_soc
sensor.openems_grid_power

PyScript neu laden

# HA Developer Tools > Services
service: pyscript.reload

openEMS Logs prüfen

# Auf BeagleBone
tail -f /var/log/openems/openems.log

Manueller Ladetest

# HA Developer Tools > Services
service: automation.trigger
target:
  entity_id: automation.batterieladung_starten

Version: 1.0
Letzte Aktualisierung: November 2024
Autor: Felix
Status: Produktiv im Einsatz