feat(trigger): entered_near + left_near — drei Modi fuer near()-Watcher
Stefan: bei aktuellen near()-Watcher gibt's nur "solange drin". Reale Szenarien wollen aber differenzieren: - VORWARNUNG vor Ziel (Blitzer-Warner 2 km vorher) → entered_near mit grossem r - ANKUNFT exakt am Ziel → entered_near mit kleinem r - VERLASSEN (Parkplatz, hast du was vergessen) → left_near - KONTINUIERLICH-DRIN (bin noch in der Naehe?) → near (Default, throttled) Zwei neue Funktionen in der Condition-Whitelist: - entered_near(lat, lon, r): True NUR im Moment des Uebergangs draussen → innen. Fires einmal pro Eintritt. - left_near(lat, lon, r): True NUR im Moment des Uebergangs innen → draussen. Fires einmal pro Austritt. State-Tracking: - pro Trigger pro near-Aufruf wird der letzte Auswertungs-Wert (true/ false) im Watcher-Manifest gespeichert (Field "near_states", Key "lat.6,lon.6,radius"). Background-Loop liest's vor dem Eval, gibt's per collect_variables(prev_near_states=...) in die Closure, schreibt nach dem Eval die neuen Werte zurueck — UNABHAENGIG ob gefeuert wurde, sonst greift die Uebergangs-Erkennung nicht. Background _tick: - Aufteilung in Watcher-Pass (mit prev_near_states pro Trigger) und Timer-Pass (ohne State, gemeinsame vars). Bisher war collect_variables einmal pro Tick — jetzt einmal pro Watcher. Disk-Stats sind teuer aber unter 30 Watchern unkritisch; bei mehr koennen wir cachen. ARIA-Tool-Description erweitert (trigger_watcher): erklaert die drei Modi mit Use-Cases und empfohlenen Throttle-Werten (kurz fuer entered/ left, lang fuer near). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+70
-9
@@ -25,7 +25,7 @@ import shutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -125,8 +125,22 @@ def _user_activity_age() -> int:
|
||||
return int(time.time() - ts)
|
||||
|
||||
|
||||
def collect_variables() -> dict[str, Any]:
|
||||
"""Liefert aktuellen Snapshot aller Built-in-Variablen + near()-Helper."""
|
||||
def _near_key(lat: float, lon: float, radius_m: float) -> str:
|
||||
"""Stabiler Schluessel pro near()-Aufruf — fuer entered_near/left_near
|
||||
State-Tracking pro Trigger pro Aufrufstelle."""
|
||||
return f"{float(lat):.6f},{float(lon):.6f},{int(float(radius_m))}"
|
||||
|
||||
|
||||
def collect_variables(prev_near_states: Optional[Dict[str, bool]] = None) -> Dict[str, Any]:
|
||||
"""Liefert aktuellen Snapshot aller Built-in-Variablen + near()-Helper.
|
||||
|
||||
prev_near_states: pro Trigger gespeicherter Zustand vom letzten Eval
|
||||
(für entered_near/left_near). Wird vom background-Loop reingegeben.
|
||||
Nach dem Eval kann man `vars_['_new_near_states']` auslesen, um den
|
||||
Update-Snapshot zurueck ins Trigger-Manifest zu schreiben."""
|
||||
if prev_near_states is None:
|
||||
prev_near_states = {}
|
||||
new_near_states: Dict[str, bool] = {}
|
||||
free_gb, free_pct = _disk_stats()
|
||||
now = datetime.now()
|
||||
gps = _gps_state()
|
||||
@@ -182,12 +196,10 @@ def collect_variables() -> dict[str, Any]:
|
||||
|
||||
# Funktion-Helper — wird vom Parser als ast.Call mit Name "near" erkannt.
|
||||
# Closure ueber die GPS-Werte, damit eval keine extra Variablen braucht.
|
||||
def _near(lat: float, lon: float, radius_m: float) -> bool:
|
||||
def _compute_near(lat: float, lon: float, radius_m: float) -> bool:
|
||||
"""Haversine-Distanz: True wenn aktuelle Position < radius_m vom Punkt.
|
||||
Plus Age-Schutz: GPS-Daten aelter als NEAR_MAX_AGE_SEC werden als
|
||||
veraltet betrachtet → False. Sonst wuerde near() bei abgeschaltetem
|
||||
Tracking weiter die letzte bekannte Position nutzen und potentiell
|
||||
Phantom-Fires werfen wenn der User mal in der Naehe war."""
|
||||
veraltet betrachtet → False."""
|
||||
cur_lat = vars_.get("current_lat")
|
||||
cur_lon = vars_.get("current_lon")
|
||||
if cur_lat is None or cur_lon is None:
|
||||
@@ -207,7 +219,39 @@ def collect_variables() -> dict[str, Any]:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _near(lat: float, lon: float, radius_m: float) -> bool:
|
||||
"""True solange im Radius drin. Plus State-Tracking fuer
|
||||
entered_near/left_near — wir merken uns das letzte Ergebnis
|
||||
damit Uebergaenge erkannt werden koennen."""
|
||||
current = _compute_near(lat, lon, radius_m)
|
||||
new_near_states[_near_key(lat, lon, radius_m)] = current
|
||||
return current
|
||||
|
||||
def _entered_near(lat: float, lon: float, radius_m: float) -> bool:
|
||||
"""True NUR beim Uebergang draussen → innen. Use-Case: einmal
|
||||
feuern wenn der User in den Radius reinfaehrt (Blitzer-Warner,
|
||||
Ankunft-Erinnerung). Bei groesserem Radius = Vorwarnung."""
|
||||
current = _compute_near(lat, lon, radius_m)
|
||||
key = _near_key(lat, lon, radius_m)
|
||||
new_near_states[key] = current
|
||||
prev = bool(prev_near_states.get(key, False))
|
||||
return current and not prev
|
||||
|
||||
def _left_near(lat: float, lon: float, radius_m: float) -> bool:
|
||||
"""True NUR beim Uebergang innen → draussen. Use-Case: 'Hast
|
||||
du am Parkplatz X was vergessen?' beim Verlassen."""
|
||||
current = _compute_near(lat, lon, radius_m)
|
||||
key = _near_key(lat, lon, radius_m)
|
||||
new_near_states[key] = current
|
||||
prev = bool(prev_near_states.get(key, False))
|
||||
return prev and not current
|
||||
|
||||
vars_["near"] = _near
|
||||
vars_["entered_near"] = _entered_near
|
||||
vars_["left_near"] = _left_near
|
||||
# Update-Snapshot fuer den Caller (background-Loop schreibt das pro
|
||||
# Trigger zurueck damit beim naechsten Tick prev_near_states stimmt)
|
||||
vars_["_new_near_states"] = new_near_states
|
||||
return vars_
|
||||
|
||||
|
||||
@@ -249,8 +293,25 @@ def describe_functions() -> list[dict]:
|
||||
{
|
||||
"name": "near",
|
||||
"signature": "near(lat, lon, radius_m)",
|
||||
"desc": "True wenn die aktuelle GPS-Position innerhalb von radius_m Metern "
|
||||
"vom Punkt (lat, lon) liegt. Haversine. Bei unbekannter Position: False.",
|
||||
"desc": "True SOLANGE die aktuelle GPS-Position innerhalb von radius_m "
|
||||
"Metern vom Punkt (lat, lon) liegt. Feuert wiederholt (mit throttle). "
|
||||
"Use-Case: 'bin noch in der Naehe von X?'. "
|
||||
"Haversine. Bei unbekannter oder > 5min alter Position: False.",
|
||||
},
|
||||
{
|
||||
"name": "entered_near",
|
||||
"signature": "entered_near(lat, lon, radius_m)",
|
||||
"desc": "True NUR im Moment des Eintritts in den Radius (Uebergang "
|
||||
"draussen → innen). Use-Case: einmaliger Fire bei Ankunft / "
|
||||
"Blitzer-Warnung. Mit grossem Radius (z.B. 2000) wird das zur "
|
||||
"Vorwarnung bevor man am Punkt ist.",
|
||||
},
|
||||
{
|
||||
"name": "left_near",
|
||||
"signature": "left_near(lat, lon, radius_m)",
|
||||
"desc": "True NUR im Moment des Verlassens des Radius (Uebergang "
|
||||
"innen → draussen). Use-Case: 'Hast du am Parkplatz X was "
|
||||
"vergessen?' beim Wegfahren.",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user