Files
battery-charging-optimizer/EMS_OpenEMS_HomeAssistant_Dokumentation.md

360 lines
11 KiB
Markdown

# 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
```python
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
```python
# 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
```python
# 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
```python
# 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
```python
# 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
```python
# 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
```python
# 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
- openEMS Docs: https://openems.github.io/openems.io/
- Home Assistant Modbus: https://www.home-assistant.io/integrations/modbus/
- PyScript Docs: https://hacs-pyscript.readthedocs.io/
### 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
```bash
# Via HA Developer Tools > States
sensor.openems_battery_soc
sensor.openems_grid_power
```
### PyScript neu laden
```bash
# HA Developer Tools > Services
service: pyscript.reload
```
### openEMS Logs prüfen
```bash
# Auf BeagleBone
tail -f /var/log/openems/openems.log
```
### Manueller Ladetest
```yaml
# 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