2026.07.25 18:26:45 1: SolarForecast DEBUG> ################### START Consumption forecast ###################
2026.07.25 18:26:45 1: SolarForecast DEBUG> Basics - installed locale: C.UTF-8, used scheme: EN
2026.07.25 18:26:45 1: SolarForecast DEBUG> Need number of stored days: 60, Number of days in History: 31 => can calculate excludes/includes: no
2026.07.25 18:26:45 1: SolarForecast DEBUG> process Today dayname: Sat, Tomorrow dayname: Sun
2026.07.25 18:26:45 1: SolarForecast DEBUG> ################### Consumption forecast for the next 24 Hours ###################
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=00, isToday=1, hod=19, SUMMARY -> estimated CON: 1406 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=01, isToday=1, hod=20, SUMMARY -> estimated CON: 1311 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=02, isToday=1, hod=21, SUMMARY -> estimated CON: 1000 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=03, isToday=1, hod=22, SUMMARY -> estimated CON: 1060 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=04, isToday=1, hod=23, SUMMARY -> estimated CON: 1060 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=05, isToday=1, hod=24, SUMMARY -> estimated CON: 962 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=06, isToday=0, hod=01, SUMMARY -> estimated CON: 842 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=07, isToday=0, hod=02, SUMMARY -> estimated CON: 762 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=08, isToday=0, hod=03, SUMMARY -> estimated CON: 743 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=09, isToday=0, hod=04, SUMMARY -> estimated CON: 741 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=10, isToday=0, hod=05, SUMMARY -> estimated CON: 736 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=11, isToday=0, hod=06, SUMMARY -> estimated CON: 760 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=12, isToday=0, hod=07, SUMMARY -> estimated CON: 636 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=13, isToday=0, hod=08, SUMMARY -> estimated CON: 958 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=14, isToday=0, hod=09, SUMMARY -> estimated CON: 1476 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=15, isToday=0, hod=10, SUMMARY -> estimated CON: 1464 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=16, isToday=0, hod=11, SUMMARY -> estimated CON: 1512 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=17, isToday=0, hod=12, SUMMARY -> estimated CON: 2326 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=18, isToday=0, hod=13, SUMMARY -> estimated CON: 1646 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=19, isToday=0, hod=14, SUMMARY -> estimated CON: 2150 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=20, isToday=0, hod=15, SUMMARY -> estimated CON: 2630 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=21, isToday=0, hod=16, SUMMARY -> estimated CON: 1622 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=22, isToday=0, hod=17, SUMMARY -> estimated CON: 1767 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=23, isToday=0, hod=18, SUMMARY -> estimated CON: 2056 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=24, isToday=0, hod=19, SUMMARY -> estimated CON: 1406 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=25, isToday=0, hod=20, SUMMARY -> estimated CON: 1311 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=26, isToday=0, hod=21, SUMMARY -> estimated CON: 1000 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=27, isToday=0, hod=22, SUMMARY -> estimated CON: 1060 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=28, isToday=0, hod=23, SUMMARY -> estimated CON: 1060 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=29, isToday=0, hod=24, SUMMARY -> estimated CON: 962 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=30, isToday=0, hod=01, SUMMARY -> estimated CON: 842 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=31, isToday=0, hod=02, SUMMARY -> estimated CON: 762 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=32, isToday=0, hod=03, SUMMARY -> estimated CON: 743 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=33, isToday=0, hod=04, SUMMARY -> estimated CON: 741 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=34, isToday=0, hod=05, SUMMARY -> estimated CON: 736 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=35, isToday=0, hod=06, SUMMARY -> estimated CON: 760 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=36, isToday=0, hod=07, SUMMARY -> estimated CON: 636 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=37, isToday=0, hod=08, SUMMARY -> estimated CON: 958 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=38, isToday=0, hod=09, SUMMARY -> estimated CON: 1476 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=39, isToday=0, hod=10, SUMMARY -> estimated CON: 1464 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=40, isToday=0, hod=11, SUMMARY -> estimated CON: 1512 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=41, isToday=0, hod=12, SUMMARY -> estimated CON: 2326 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=42, isToday=0, hod=13, SUMMARY -> estimated CON: 1646 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=43, isToday=0, hod=14, SUMMARY -> estimated CON: 2150 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=44, isToday=0, hod=15, SUMMARY -> estimated CON: 2630 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=45, isToday=0, hod=16, SUMMARY -> estimated CON: 1622 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=46, isToday=0, hod=17, SUMMARY -> estimated CON: 1767 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=47, isToday=0, hod=18, SUMMARY -> estimated CON: 2056 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=48, isToday=0, hod=19, SUMMARY -> estimated CON: 1406 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=49, isToday=0, hod=20, SUMMARY -> estimated CON: 1311 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=50, isToday=0, hod=21, SUMMARY -> estimated CON: 1000 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=51, isToday=0, hod=22, SUMMARY -> estimated CON: 1060 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=52, isToday=0, hod=23, SUMMARY -> estimated CON: 1060 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> NH=53, isToday=0, hod=24, SUMMARY -> estimated CON: 962 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> ################### ENDE Consumption forecast ###################
2026.07.25 18:26:45 1: SolarForecast DEBUG> store AI legacy alpha value to circular/history -> hod=19 confc=1848 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> store AI legacy alpha value to circular/history -> hod=20 confc=895 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> store AI legacy alpha value to circular/history -> hod=21 confc=692 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> store AI legacy alpha value to circular/history -> hod=22 confc=1122 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> store AI legacy alpha value to circular/history -> hod=23 confc=629 Wh
2026.07.25 18:26:45 1: SolarForecast DEBUG> store AI legacy alpha value to circular/history -> hod=24 confc=963 Wh
Diese Prognosen könnten so passen.Zitat von: DS_Starter am 25 Juli 2026, 17:24:52Ach ... ist einfach zu warm![]()
LG,
Heiko
#!/usr/bin/env python3
import asyncio
import json
import logging
import math
import time
from datetime import datetime, timezone
from urllib import parse, request
import websockets
import paho.mqtt.client as mqtt
# ---------------------------------------------------------------------------
# Konfiguration - hier anpassen
# ---------------------------------------------------------------------------
HOME_LAT = 50.815 # eigene Breite
HOME_LON = 10.225 # eigene Laenge
RADIUS_KM = 20 # Umkreis, der als "hier" gilt
MQTT_HOST = "127.0.0.1"
MQTT_PORT = 1883
MQTT_USER = None # z.B. "fhem" oder None wenn kein Login noetig
MQTT_PASS = None
MQTT_TOPIC = "blitz/strike"
MQTT_CLIENT_ID = "blitzortung_bridge"
# Mehrere Blitzortung-Relays, falls eines nicht erreichbar ist wird
# automatisch das naechste probiert
WS_SERVERS = [
"wss://ws1.blitzortung.org/",
"wss://ws2.blitzortung.org/",
"wss://ws3.blitzortung.org/",
"wss://ws4.blitzortung.org/",
"wss://ws5.blitzortung.org/",
"wss://ws6.blitzortung.org/",
"wss://ws7.blitzortung.org/",
"wss://ws8.blitzortung.org/",
]
# Handshake-Nachricht, die der Server erwartet bevor er Daten schickt
HANDSHAKE_MSG = '{"a": 111}'
# User-Agent für Nominatim (bitte aussagekräftig, z.B. mit Projektname + Kontakt)
NOMINATIM_USER_AGENT = "blitzortung_bridge (dein Kontakt oder Projekt-URL)"
# ---------------------------------------------------------------------------
# Reverse Geocoding (Nominatim)
# ---------------------------------------------------------------------------
def get_nearest_town(lat: float, lon: float, language: str = "de") -> str | None:
"""
Ermittelt per Nominatim die nächstgrößere Ortschaft für gegebene Koordinaten.
Gibt Stadt/Town/Village zurück oder None, falls nichts gefunden.
"""
base_url = "https://nominatim.openstreetmap.org/reverse"
params = {
"lat": lat,
"lon": lon,
"format": "json",
"accept-language": language,
"zoom": 10, # Bevorzugt größere Ortschaften
}
url = base_url + "?" + parse.urlencode(params)
headers = {
"User-Agent": NOMINATIM_USER_AGENT,
}
req = request.Request(url, headers=headers)
try:
with request.urlopen(req, timeout=5) as resp:
data = json.loads(resp.read().decode("utf-8"))
except Exception:
logging.debug("Nominatim-Anfrage fehlgeschlagen für %s,%s", lat, lon)
return None
address = data.get("address", {})
# Priorität: city > town > village > municipality > county
for key in ("city", "town", "village", "municipality", "county"):
value = address.get(key)
if value:
return value
# Fallback: 'name' oder 'place' aus dem Top-Level
return data.get("name") or data.get("place")
# ---------------------------------------------------------------------------
# Blitzortung Dekompression & Geodäsie
# ---------------------------------------------------------------------------
def unpack_blitzortung(raw: str) -> str:
"""Entpackt die LZW-aehnliche Kompression, die Blitzortung fuer
die WebSocket-Rohdaten verwendet. Portiert aus einer verifizierten
Referenzimplementierung (gkbrk.com/blitzortung)."""
d = list(raw)
if not d:
return ""
dictionary = {}
c = d[0]
f = c
out = [c]
next_code = 256
for i in range(1, len(d)):
code = ord(d[i])
if code < 256:
a = d[i]
else:
a = dictionary.get(code) or (f + c)
out.append(a)
c = a[0]
dictionary[next_code] = f + c
next_code += 1
f = a
return "".join(out)
def haversine_km(lat1, lon1, lat2, lon2) -> float:
r = 6371.0
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = (
math.sin(dlat / 2) ** 2
+ math.cos(math.radians(lat1))
* math.cos(math.radians(lat2))
* math.sin(dlon / 2) ** 2
)
return r * 2 * math.asin(math.sqrt(a))
def calculate_bearing(lat1, lon1, lat2, lon2) -> float:
"""Berechnet den mathematischen Grosskreiswinkel (Azimut) von Punkt 1 zu Punkt 2.
Ergebnis: 0° = Norden, 90° = Osten, 180° = Sueden, 270° = Westen"""
lat1_rad = math.radians(lat1)
lat2_rad = math.radians(lat2)
diff_lon_rad = math.radians(lon2 - lon1)
x = math.sin(diff_lon_rad) * math.cos(lat2_rad)
y = math.cos(lat1_rad) * math.sin(lat2_rad) - (
math.sin(lat1_rad) * math.cos(lat2_rad) * math.cos(diff_lon_rad)
)
initial_bearing = math.atan2(x, y)
initial_bearing = math.degrees(initial_bearing)
return (initial_bearing + 360) % 360
def bearing_to_text(bearing: float) -> str:
"""Konvertiert Gradzahlen (0-360) in Himmelsrichtungs-Kuerzel (8er-Teilung)."""
directions = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]
index = int((bearing + 22.5) / 45) % 8
return directions[index]
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
log = logging.getLogger("blitz-bridge")
class MqttPublisher:
def __init__(self):
self.client = mqtt.Client(client_id=MQTT_CLIENT_ID)
if MQTT_USER:
self.client.username_pw_set(MQTT_USER, MQTT_PASS)
self.client.connect(MQTT_HOST, MQTT_PORT, keepalive=60)
self.client.loop_start()
def publish(self, payload: dict):
self.client.publish(MQTT_TOPIC, json.dumps(payload, ensure_ascii=False), qos=0, retain=False)
async def listen_once(publisher: MqttPublisher, start_index: int = 0):
"""Ein Verbindungsversuch. Wirft eine Exception bei Verbindungsabbruch."""
servers = WS_SERVERS[start_index:] + WS_SERVERS[:start_index]
for server in servers:
try:
log.debug("Verbinde mit %s ...", server)
async with websockets.connect(server, ping_interval=20, ping_timeout=20) as ws:
await ws.send(HANDSHAKE_MSG)
log.debug("Verbunden, warte auf Blitzdaten ...")
async for raw_msg in ws:
handle_message(raw_msg, publisher)
return # Verbindung sauber beendet -> aeussere Schleife reconnected
except Exception as exc:
log.debug("Verbindung zu %s fehlgeschlagen: %s", server, exc)
continue
# Wenn alle Server fehlgeschlagen sind, kurz warten bevor Retry
await asyncio.sleep(5)
def handle_message(raw_msg: str, publisher: MqttPublisher):
try:
text = unpack_blitzortung(raw_msg)
data = json.loads(text)
except Exception as exc:
log.debug("Konnte Nachricht nicht verarbeiten (%s): %.100s", exc, raw_msg)
return # ungueltige/nicht relevante Nachricht ignorieren
lat = data.get("lat")
lon = data.get("lon")
if lat is None or lon is None:
return
dist = haversine_km(HOME_LAT, HOME_LON, lat, lon)
if dist <= RADIUS_KM:
ts_ns = data.get("time")
ts = (
datetime.fromtimestamp(ts_ns / 1e9, tz=timezone.utc).isoformat()
if ts_ns
else datetime.now(tz=timezone.utc).isoformat()
)
# Berechnung der Richtung (Grad und Text)
bearing = calculate_bearing(HOME_LAT, HOME_LON, lat, lon)
bearing_txt = bearing_to_text(bearing)
# Ortschaft via Nominatim (optional, nicht-blockierend genug für den Use-Case)
location = get_nearest_town(lat, lon)
payload = {
"distance_km": round(dist, 1),
"direction_deg": round(bearing, 1),
"direction_text": bearing_txt,
"lat": lat,
"lon": lon,
"time": ts,
}
if location:
payload["location"] = location
log.info(
"Blitz im Radius: %.1f km entfernt in Richtung %s (%.1f°) bei %s",
dist,
bearing_txt,
bearing,
location or "unbekannter Ort",
)
publisher.publish(payload)
async def main():
publisher = MqttPublisher()
server_index = 0
while True:
try:
await listen_once(publisher, start_index=server_index)
except Exception as exc:
log.error("Unerwarteter Fehler: %s", exc)
server_index = (server_index + 1) % len(WS_SERVERS)
log.debug("Verbindung verloren, versuche erneut in 5s ...")
await asyncio.sleep(5)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
log.info("Beendet durch Benutzer.")
Zitat von: RalfP am 23 Juli 2026, 10:36:18Hallo,
habe seit einigen Tagen diese Meldung im Log:withings: getUserDetail json error Invalid Params: action is specified in URL and in GET/POST dataKennt jemand den Grund dafür?
Zitat von: kurtklaiber am 25 Juli 2026, 15:46:04Das wäre allerdings zur Erprobung der notifys (usw) sehr hilfreich.Du kannst den Event mit dem trigger Befehl simulieren, siehe auch help trigger