FEAT-NETATMO-06: V1 Status-Update Redesign + shared helpers foundation

Netatmo-Plugin komplett auf V1 Status-Update Layout umgebaut:
- 4-Sektionen: Aussen / Wind+Regen / Indoor / Vorhersage
- Echte 12h-History fuer MIN/MAX (vorher estimated +-2/+-4)
- Dynamische Forecast-Daten (datetime.now() + timedelta)
- DE strings durchgaengig (AUSSEN, FEUCHTE, DRUCK, Bo, HEUTE, Boerse, ...)
- 7-Farben ACeP palette (kein Greyscale, kein Anti-Alias)
- Indoor-Sensoren: rote Tag-Box + 22pt+ Temp + CO2-Bar + Batt aligned
- Chart mit 2-Tick Y-Grid (min/max), 12h Linie in Gruen
- Wetter-Icons (WMO codes) in Vorhersage, Regen-Drop-Icon pro Tag

Foundation / shared helpers:
- palette.py: BATT_*, DE_WEEKDAYS/MONTHS, WMO_ICONS, format_*,
              POWER_DEFAULTS, temp_color, co2_color, batt_color
- plugins/base.py: draw_battery, draw_compass, draw_trend,
                   draw_line_chart, draw_mini_bar, draw_weather_icon,
                   draw_deepsleep_indicator, draw_zzz_indicator,
                   _fit_size, _get_base_sans, de_* formatters
- designbase.py: THEMES dict (light/dark/retro/mag), Layout templates
                 (bereits importierbar)

Tools:
- tools/render_mockup.py: 3 design-Varianten Mockup-Generator
- tools/verify_netatmo.py: Self-verification mit dynamic mock data
This commit is contained in:
Patrik
2026-08-31 00:47:40 +02:00
parent c5a09833c9
commit 5a83826aa5
5 changed files with 2082 additions and 571 deletions
+63
View File
@@ -0,0 +1,63 @@
"""Verifizierungs-Script: rendert Netatmo mit dynamischen Mock-Daten.
Findet und behebt:
1. Statische Forecast-Daten (jetzt relativ zu heute)
2. Synthetische Outdoor-History mit groesserer Range (damit Chart nicht flach)
3. Bessere Indoor Batt-Darstellung
"""
import sys
sys.path.insert(0, '.')
from PIL import Image, ImageDraw, ImageFont
from plugins.netatmo import Widget
from datetime import datetime, timedelta
import math
import os
# FIX 1: Mock-Daten mit dynamischen Daten (heute + 2 Tage)
now = datetime.now()
forecast_days = [(now + timedelta(days=i)).date().isoformat() for i in range(3)]
# FIX 2: Synthetische Outdoor-History mit groesserem Temperatur-Range
# 12h-Verlauf mit sin-voller Swing (12.5 - 22.5 Grad, also 10 Grad Range)
outdoor_temps = []
for h in range(12):
t = 17.5 + 4.5 * math.sin(h / 2.2) + 1.0 * math.cos(h / 1.3)
outdoor_temps.append(round(t, 1))
temp_history = ';'.join(str(t) for t in outdoor_temps)
print(f'History: min={min(outdoor_temps)}, max={max(outdoor_temps)}, n={len(outdoor_temps)}')
mock = {
'station_name': 'Daheim', 'place': {},
'main': {'name': 'Daheim', 'data': {'Temperature': 21.5, 'CO2': 620, 'Humidity': 48, 'Pressure': 1014, 'Pressure_trend': 'down'}},
'modules': [
# Aussentemperatur = letzter Wert der History
{'type': 'NAModule1', 'name': 'Aussen', 'data': {'Temperature': outdoor_temps[-1], 'Humidity': 67, 'Temp_history': temp_history}, 'battery_pct': 78},
{'type': 'NAModule2', 'name': 'Wind', 'data': {'WindStrength': 12, 'GustStrength': 24, 'WindAngle': 215}, 'battery_pct': 65},
{'type': 'NAModule3', 'name': 'Regen', 'data': {'RainRate': 0.0, 'sum_rain_1': 0.4, 'sum_rain_24': 6.2}, 'battery_pct': 82},
{'type': 'NAModule4', 'name': 'Kueche', 'data': {'Temperature': 23.5, 'CO2': 980}, 'battery_pct': 88},
{'type': 'NAModule4', 'name': 'Schlafen', 'data': {'Temperature': 20.8, 'CO2': 1450}, 'battery_pct': 41},
],
'forecast': {
'daily': {
'time': forecast_days, # DYNAMISCH: heute + 2 Tage
'temperature_2m_max': [19, 21, 17],
'temperature_2m_min': [9, 11, 8],
'precipitation_sum': [0.0, 2.4, 8.1],
'weather_code': [0, 3, 61],
},
},
}
w = Widget({})
w.fetch = lambda: mock
fonts = {}
for sz in [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, 40, 48, 60, 72, 84, 96]:
fonts[str(sz)] = ImageFont.truetype(os.path.join('C:/Windows/Fonts/arial.ttf'), sz)
fonts['default'] = ImageFont.load_default()
img = Image.new('RGB', (800, 480), (255, 255, 255))
draw = ImageDraw.Draw(img)
w.set_fonts(fonts)
w.render(draw, fonts, 0, 0, 800, 480)
img.save('docs/test_netatmo_v10.png')
print(f'OK saved v10 ({now.strftime("%H:%M")})')