Files
battery-charging-optimizer/validate_pyscript.py
felix.zoesch 0fa03a566a feat: Major update - Battery Optimizer v3.4.0 with comprehensive fixes
## 🎯 Hauptänderungen

### Version 3.4.0 - SOC-Drift & Charging Capacity
-  Sicherheitspuffer (20-50% konfigurierbar) für untertägige SOC-Schwankungen
-  Monatliche automatische Batterie-Kalibrierung
- 🐛 SOC-Plausibilitäts-Check (filtert 65535% Spikes beim Modus-Wechsel)
- 🐛 Zeitabhängige API-Abfrage (vor/nach 14:00 Uhr)

### Neue Features
- 🔋 **Safety Buffer**: Kompensiert SOC-Drift und Eigenverbrauch
- 🔋 **Auto-Calibration**: Monatlicher Vollzyklus für SOC-Genauigkeit
- 🔋 **Spike Protection**: 4-fach Schutz gegen ungültige SOC-Werte
- 🔋 **Smart API**: Verhindert HTTP 500 Errors bei fehlenden Tomorrow-Preisen

### Dokumentation
- 📚 SOC_CALIBRATION_GUIDE.md - Umfassender Kalibrierungs-Guide
- 📚 FIX_CHARGING_CAPACITY.md - Sicherheitspuffer-Dokumentation
- 📚 FIX_SOC_SPIKE_PROBLEM.md - Spike-Protection-Lösung
- 📚 FIX_API_TIMING.md - Zeitabhängige API-Abfrage
- 📚 DIAGNOSE_LADE_PROBLEM.md - Debug-Guide

### Neue Dateien
- battery_calibration_automation.yaml - 4 Automations für Kalibrierung
- battery_calibration_input_helper.yaml - Input Helper Config
- battery_optimizer_input_helper_safety_buffer.yaml - Puffer Config
- debug_schedule.py - Umfassendes Debug-Script

### Scripts
- battery_charging_optimizer.py v3.4.0
- hastrom_flex_extended.py v1.1.0
- debug_schedule.py v1.0.0

### Fixes
- 🐛 SOC springt auf 65535% beim ESS-Modus-Wechsel → Debounce + Plausibilitäts-Check
- 🐛 API-HTTP-500 vor 14:00 → Zeitabhängige Abfrage
- 🐛 Batterie nicht bis 100% geladen → Sicherheitspuffer
- 🐛 SOC driftet ohne Vollzyklen → Automatische Kalibrierung

## 🚀 Installation

1. Input Helper erstellen (siehe battery_optimizer_input_helper_safety_buffer.yaml)
2. Automations installieren (siehe battery_calibration_automation.yaml)
3. Scripts aktualisieren (battery_charging_optimizer.py v3.4.0)
4. PyScript neu laden

## 📊 Verbesserungen

- Präzisere Ladeplanung durch Sicherheitspuffer
- Robustheit gegen SOC-Drift
- Keine API-Fehler mehr vor 14:00
- Hardware-Stopp bei 100% wird respektiert
- Bessere Batterie-Gesundheit durch regelmäßige Kalibrierung

🤖 Generated with Claude Code (claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-12 08:04:07 +01:00

245 lines
7.8 KiB
Python

#!/usr/bin/env python3
"""
PyScript Validation Tool for Battery Charging Optimizer
Checks for common issues without needing Home Assistant runtime
"""
import ast
import re
from pathlib import Path
def check_file(filepath):
"""Analyze a PyScript file for potential issues"""
print(f"\n{'='*60}")
print(f"Analyzing: {filepath.name}")
print('='*60)
with open(filepath, 'r') as f:
content = f.read()
issues = []
warnings = []
# 1. Check imports
print("\n1. Checking imports...")
imports = re.findall(r'^(?:from|import)\s+(\S+)', content, re.MULTILINE)
critical_imports = ['zoneinfo', 'requests', 'json', 'datetime']
for imp in critical_imports:
if any(imp in i for i in imports):
print(f" ✓ Found import: {imp}")
else:
if imp == 'zoneinfo':
issues.append(f"CRITICAL: zoneinfo might not be available in PyScript")
# 2. Check for PyScript-specific constructs
print("\n2. Checking PyScript constructs...")
# @service decorators
services = re.findall(r'@service\s+def\s+(\w+)', content)
if services:
print(f" ✓ Found {len(services)} services: {', '.join(services)}")
else:
warnings.append("No @service decorators found")
# @time_trigger decorators
triggers = re.findall(r'@time_trigger\("([^"]+)"\)', content)
if triggers:
print(f" ✓ Found {len(triggers)} time triggers:")
for t in triggers:
print(f" - {t}")
# Check cron syntax
for trigger in triggers:
if not trigger.startswith('cron('):
issues.append(f"Invalid time_trigger syntax: {trigger}")
# 3. Check state access patterns
print("\n3. Checking state access patterns...")
# state.get() calls
state_gets = re.findall(r"state\.get\(['\"]([^'\"]+)['\"]\)", content)
if state_gets:
print(f" Found {len(state_gets)} state.get() calls")
unique_entities = set(state_gets)
print(f" Unique entities accessed: {len(unique_entities)}")
for entity in sorted(unique_entities)[:5]:
print(f" - {entity}")
if len(unique_entities) > 5:
print(f" ... and {len(unique_entities) - 5} more")
# state.getattr() calls
state_getattrs = re.findall(r"state\.getattr\(['\"]?([^'\"]+)['\"]\)", content)
if state_getattrs:
print(f" Found {len(state_getattrs)} state.getattr() calls")
# Check for missing null checks after getattr
getattr_lines = [i for i, line in enumerate(content.split('\n'), 1)
if 'state.getattr' in line]
for line_num in getattr_lines:
lines = content.split('\n')
next_line = lines[line_num] if line_num < len(lines) else ""
if '.get(' in next_line and 'or {}' not in lines[line_num-1]:
warnings.append(f"Line {line_num}: state.getattr() without null check before .get()")
# 4. Check for datetime handling
print("\n4. Checking datetime handling...")
if 'datetime.now()' in content:
if 'datetime.now().astimezone()' in content or 'datetime.now(TIMEZONE)' in content:
print(" ✓ Timezone-aware datetime usage found")
else:
warnings.append("datetime.now() without timezone - may cause UTC/local issues")
if 'fromisoformat' in content:
print(" ✓ ISO format datetime parsing found")
if 'tzinfo' not in content:
warnings.append("fromisoformat without timezone handling")
# 5. Check for task.executor usage
print("\n5. Checking async task patterns...")
if 'task.executor' in content:
print(" ✓ task.executor found (for blocking I/O)")
if 'requests.get' in content:
if 'task.executor(requests.get' in content:
print(" ✓ Correctly wrapped requests.get in task.executor")
else:
issues.append("CRITICAL: requests.get not wrapped in task.executor")
# 6. Check state.set() calls
print("\n6. Checking state.set() patterns...")
state_sets = re.findall(r"state\.set\(['\"]([^'\"]+)['\"]", content)
if state_sets:
print(f" Found {len(state_sets)} state.set() calls:")
for entity in set(state_sets):
print(f" - {entity}")
# Check for large attribute sets
if 'new_attributes' in content:
print(" ✓ Using new_attributes in state.set()")
if "'schedule':" in content:
warnings.append("Schedule in attributes might be large - monitor state size")
# 7. Check service calls
print("\n7. Checking service calls...")
service_calls = re.findall(r'pyscript\.(\w+)\(', content)
if service_calls:
print(f" Found service calls: {', '.join(set(service_calls))}")
warnings.append("Service-to-service calls in PyScript - verify syntax")
# 8. Check error handling
print("\n8. Checking error handling...")
try_blocks = content.count('try:')
except_blocks = content.count('except')
print(f" Found {try_blocks} try blocks, {except_blocks} except handlers")
if 'traceback' in content:
print(" ✓ traceback module usage found")
# 9. Syntax validation
print("\n9. Validating Python syntax...")
try:
ast.parse(content)
print(" ✓ Python syntax is valid")
except SyntaxError as e:
issues.append(f"CRITICAL: Syntax error at line {e.lineno}: {e.msg}")
# Summary
print("\n" + "="*60)
print("SUMMARY")
print("="*60)
if not issues and not warnings:
print("✅ No issues found!")
else:
if issues:
print(f"\n🔴 CRITICAL ISSUES ({len(issues)}):")
for i, issue in enumerate(issues, 1):
print(f" {i}. {issue}")
if warnings:
print(f"\n⚠️ WARNINGS ({len(warnings)}):")
for i, warning in enumerate(warnings, 1):
print(f" {i}. {warning}")
return issues, warnings
def main():
"""Main validation routine"""
print("PyScript Battery Optimizer Validation Tool")
print("="*60)
base_path = Path(__file__).parent / "openems"
files_to_check = [
base_path / "battery_charging_optimizer.py",
base_path / "hastrom_flex_extended.py"
]
all_issues = []
all_warnings = []
for filepath in files_to_check:
if not filepath.exists():
print(f"\n❌ File not found: {filepath}")
continue
issues, warnings = check_file(filepath)
all_issues.extend(issues)
all_warnings.extend(warnings)
# Final summary
print("\n" + "="*60)
print("OVERALL SUMMARY")
print("="*60)
print(f"\nTotal files checked: {len(files_to_check)}")
print(f"Critical issues: {len(all_issues)}")
print(f"Warnings: {len(all_warnings)}")
if all_issues:
print("\n🔴 ACTION REQUIRED: Fix critical issues before deploying")
elif all_warnings:
print("\n⚠️ REVIEW RECOMMENDED: Check warnings")
else:
print("\n✅ All checks passed!")
print("\n" + "="*60)
print("NEXT STEPS")
print("="*60)
print("""
1. Copy files to Home Assistant:
- /config/pyscript/battery_charging_optimizer.py
- /config/pyscript/hastrom_flex_extended.py
2. Reload PyScript:
- Home Assistant → Developer Tools → Services
- Call: pyscript.reload
3. Check logs:
- Settings → System → Logs
- Look for errors from pyscript or your scripts
4. Test manually:
- Call service: pyscript.getprices_extended
- Call service: pyscript.calculate_charging_schedule
- Check states: pyscript.battery_charging_schedule
5. Monitor execution:
- Enable debug logging for pyscript in configuration.yaml:
logger:
default: info
logs:
custom_components.pyscript: debug
""")
if __name__ == "__main__":
main()