Tesla presence watcher: geofence, ev_presence_obs, 'píchni auto' pobídka
All checks were successful
CI and deploy / migration-check (push) Successful in 47s
CI and deploy / deploy (push) Has been skipped

- V095 ems.ev_presence_obs (state/at_home/distance/charging/shift per ~5 min)
- tesla_client: get_vehicle_api_state (jen /vehicles — nebudí), haversine_m
- collector poll_tesla_presence: online → poloha → geofence 150 m vs GPS site;
  přechod pryč→doma + Disconnected → Discord pobídka s aktuálním přebytkem
  (cooldown 2 h); vše logováno pro budoucí dostupnostní statistiku
- 6 testů (haversine, přechody); docs: zákopy reauth procesu (6 bodů)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dusan Vojacek
2026-06-12 14:14:48 +02:00
parent ea4ca0e3de
commit 2122fa2035
5 changed files with 223 additions and 0 deletions

View File

@@ -166,3 +166,33 @@ async def get_charge_state(
return None
r.raise_for_status()
return parse_charge_state(r.json())
def haversine_m(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Vzdálenost dvou GPS bodů v metrech (čisté, testovatelné)."""
import math
r = 6_371_000.0
p1, p2 = math.radians(lat1), math.radians(lat2)
dp = math.radians(lat2 - lat1)
dl = math.radians(lon2 - lon1)
a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
return 2 * r * math.asin(math.sqrt(a))
async def get_vehicle_api_state(db: asyncpg.Connection, vin: str | None) -> str | None:
"""Jen state z /vehicles (online/asleep/offline) — NIKDY nebudí auto."""
token = await _get_access_token(db)
if token is None:
return None
async with httpx.AsyncClient(
timeout=HTTP_TIMEOUT_S, headers={"Authorization": f"Bearer {token}"}
) as client:
r = await client.get(f"{API_BASE}/api/1/vehicles")
r.raise_for_status()
vehicles = r.json().get("response") or []
if vin:
v = next((x for x in vehicles if x.get("vin") == vin), None)
else:
v = vehicles[0] if len(vehicles) == 1 else None
return str(v["state"]) if v else None