Update: Battery Optimizer v3.4.0 mit allen Fixes und Features
This commit is contained in:
359
docs/EMS_OpenEMS_HomeAssistant_Dokumentation.md
Normal file
359
docs/EMS_OpenEMS_HomeAssistant_Dokumentation.md
Normal file
@@ -0,0 +1,359 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user