initial commit v0.2.1
This commit is contained in:
commit
2b9a10979b
19 files changed
+1665
No files matched your search
@@ -0,0 +1,104 @@
|
||||
"""Read-only weather source selection and unit normalization.
|
||||
|
||||
Pure-Python helpers so source and unit behavior can be tested without HAOS.
|
||||
Optional weather variables are observability inputs in v0.2, not MPC features.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from math import isfinite
|
||||
|
||||
from .const import CONF_OUTSIDE, WEATHER_SOURCES
|
||||
|
||||
|
||||
LIMITS = {
|
||||
"temperature": (-45.0, 55.0),
|
||||
"feels_like": (-65.0, 65.0),
|
||||
"wind": (0.0, 90.0), # m/s
|
||||
"clouds": (0.0, 100.0), # percentage
|
||||
"humidity": (0.0, 100.0), # percentage
|
||||
"pressure": (800.0, 1100.0), # hPa
|
||||
"visibility": (0.0, 100000.0), # metres
|
||||
"rain": (0.0, 500.0), # mm (sampling period defined by upstream sensor)
|
||||
"snow": (0.0, 500.0),
|
||||
}
|
||||
|
||||
|
||||
def sources_for(config: dict, metric: str) -> list[str]:
|
||||
"""Return ordered de-duplicated entity IDs, including v0.1 migration."""
|
||||
key = WEATHER_SOURCES[metric]
|
||||
sources = config.get(key)
|
||||
if sources is None and metric == "temperature":
|
||||
sources = config.get(CONF_OUTSIDE)
|
||||
if isinstance(sources, str):
|
||||
sources = [sources]
|
||||
if not isinstance(sources, (list, tuple)):
|
||||
return []
|
||||
return list(dict.fromkeys(s for s in sources if isinstance(s, str) and s.startswith("sensor.")))
|
||||
|
||||
|
||||
def normalize(metric: str, raw: object, unit: str | None = None) -> float | None:
|
||||
"""Validate and normalize common HA units; reject unknown/mismatched units."""
|
||||
try:
|
||||
value = float(raw)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
if not isfinite(value):
|
||||
return None
|
||||
unit = (unit or "").strip().lower().replace(" ", "")
|
||||
if metric in ("temperature", "feels_like"):
|
||||
if unit in ("°f", "f", "fahrenheit"):
|
||||
value = (value - 32.0) * 5.0 / 9.0
|
||||
elif unit not in ("", "°c", "c", "celsius"):
|
||||
return None
|
||||
elif metric == "wind":
|
||||
if unit in ("km/h", "kmh", "kph"):
|
||||
value /= 3.6
|
||||
elif unit in ("mph",):
|
||||
value *= 0.44704
|
||||
elif unit not in ("", "m/s", "mps"):
|
||||
return None
|
||||
elif metric == "pressure":
|
||||
if unit == "pa":
|
||||
value /= 100.0
|
||||
elif unit == "kpa":
|
||||
value *= 10.0
|
||||
elif unit in ("inhg",):
|
||||
value *= 33.8638867
|
||||
elif unit not in ("", "hpa", "mbar"):
|
||||
return None
|
||||
elif metric == "visibility":
|
||||
if unit in ("km",):
|
||||
value *= 1000.0
|
||||
elif unit not in ("", "m"):
|
||||
return None
|
||||
elif metric in ("humidity", "clouds"):
|
||||
if unit not in ("", "%"):
|
||||
return None
|
||||
elif metric in ("rain", "snow"):
|
||||
if unit not in ("", "mm", "mm/h"):
|
||||
return None
|
||||
lo, hi = LIMITS[metric]
|
||||
return round(value, 4) if lo <= value <= hi else None
|
||||
|
||||
|
||||
def choose_weather_sources(
|
||||
config: dict, states: dict[str, tuple[object, str | None]]
|
||||
) -> tuple[dict[str, float | None], dict[str, str | None], dict[str, dict[str, float]]]:
|
||||
"""Choose first valid sensor per metric, expose alternatives for comparisons.
|
||||
|
||||
`states` maps entity ID -> (value, unit). In HA these are current state snapshots.
|
||||
"""
|
||||
values: dict[str, float | None] = {}
|
||||
active: dict[str, str | None] = {}
|
||||
comparisons: dict[str, dict[str, float]] = {}
|
||||
for metric in WEATHER_SOURCES:
|
||||
valid: dict[str, float] = {}
|
||||
for entity_id in sources_for(config, metric):
|
||||
raw, unit = states.get(entity_id, (None, None))
|
||||
value = normalize(metric, raw, unit)
|
||||
if value is not None:
|
||||
valid[entity_id] = value
|
||||
comparisons[metric] = valid
|
||||
active[metric] = next(iter(valid), None)
|
||||
values[metric] = valid[active[metric]] if active[metric] else None
|
||||
return values, active, comparisons
|
||||
Reference in new issue
Block a user