fa47068d6d
ARIA kann jetzt GPS-Watcher mit near() effektiv nutzen: die App liefert
kontinuierliche Position, Brain wertet sie in den Background-Triggers aus.
rvs/server.js
ALLOWED_TYPES: location_update (App→Bridge) + location_tracking (Brain→App).
bridge/aria_bridge.py
location_update Handler: persistiert {lat, lon} via _persist_location in
/shared/state/location.json — selber Pfad wie chat/audio-events, aber als
eigenes Event ohne Chat-Overhead.
aria-brain/agent.py
Neues Meta-Tool request_location_tracking(on, reason). Dispatcher fuegt
{type: "location_tracking", on, reason} zu _pending_events hinzu →
Bridge forwarded als RVS-Message zur App.
aria-brain/prompts.py
Trigger-Section bekam neuen Block "GPS-Watcher mit near()": ARIA wird
angewiesen request_location_tracking(on=true) zu rufen wenn sie einen
near()-Watcher anlegt, und wieder false beim Loeschen des letzten.
android/src/services/gpsTracking.ts (NEU)
Singleton-Service. start(reason) → Geolocation.watchPosition mit
distanceFilter 30m + interval 15s, sendet location_update an RVS.
stop(reason) → clearWatch. Persistiert Status in 'aria_gps_tracking',
restoreFromStorage() beim Settings-Mount. Permission-Request fuer
ACCESS_FINE_LOCATION + Toast-Benachrichtigung bei An/Aus.
android/src/screens/SettingsScreen.tsx
Neuer Switch im "Standort"-Block: "GPS-Tracking (kontinuierlich)" mit
Hinweis-Text. Subscribe auf gpsTrackingService.onChange damit Toggle
reflektiert wenn ARIA das per Tool umschaltet.
RVS-Handler: location_tracking → gpsTrackingService.start/stop mit
Reason aus Brain-Tool.
Ablauf Stefan→ARIA→Blitzer:
1. Stefan: "Warn mich vor Blitzern auf Route nach Rhauderfehn"
2. ARIA: skill_create("blitzer-warner") falls noch nicht da
3. ARIA: run_blitzer-warner → Liste {lat,lon,name}
4. ARIA: pro Eintrag trigger_watcher mit near(lat,lon,500)
5. ARIA: request_location_tracking(on=true, reason="Blitzer-Warner aktiv")
6. App: GPS-Tracking startet, sendet alle 15s location_update
7. Bridge: /shared/state/location.json wird aktuell gehalten
8. Brain-Background-Loop: alle 30s near()-Check pro Trigger
9. Bei Erfolg: ARIA spricht "Blitzer A31 km 12 in 500m"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
/**
|
|
* GPS-Tracking-Service.
|
|
*
|
|
* Wenn aktiv: pushed alle paar Sekunden die aktuelle Position als
|
|
* `location_update {lat, lon}` an den RVS-Server, damit Brain-Watcher
|
|
* mit `near()`-Conditions etwas zum Vergleichen haben.
|
|
*
|
|
* Default: AUS. Wird entweder vom User manuell in Settings angeschaltet
|
|
* oder von ARIA via location_tracking-RVS-Message (Brain-Tool
|
|
* `request_location_tracking`).
|
|
*
|
|
* Energie-Schutz: distanceFilter 30m, interval 15s. Echte Fahrt-Updates
|
|
* (Geschwindigkeit) kommen sauber durch, stationaer wird kaum gesendet.
|
|
*/
|
|
|
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
import { PermissionsAndroid, Platform, ToastAndroid } from 'react-native';
|
|
import Geolocation from '@react-native-community/geolocation';
|
|
import rvs from './rvs';
|
|
|
|
type Listener = (active: boolean) => void;
|
|
|
|
class GpsTrackingService {
|
|
private watchId: number | null = null;
|
|
private active = false;
|
|
private listeners: Set<Listener> = new Set();
|
|
// Defensive: nicht zu schnell oeffentlich togglen
|
|
private lastChangeAt = 0;
|
|
|
|
isActive(): boolean {
|
|
return this.active;
|
|
}
|
|
|
|
onChange(cb: Listener): () => void {
|
|
this.listeners.add(cb);
|
|
return () => { this.listeners.delete(cb); };
|
|
}
|
|
|
|
private notify() {
|
|
for (const cb of this.listeners) {
|
|
try { cb(this.active); } catch {}
|
|
}
|
|
}
|
|
|
|
/** Beim App-Start: gespeicherten Zustand wiederherstellen (Default off). */
|
|
async restoreFromStorage(): Promise<void> {
|
|
try {
|
|
const v = await AsyncStorage.getItem('aria_gps_tracking');
|
|
if (v === 'true') {
|
|
console.log('[gps-track] Restore: war an, starte wieder');
|
|
this.start('Beim Start wiederhergestellt');
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
private async ensurePermission(): Promise<boolean> {
|
|
if (Platform.OS !== 'android') return true;
|
|
try {
|
|
const granted = await PermissionsAndroid.request(
|
|
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
|
|
{
|
|
title: 'GPS-Tracking',
|
|
message: 'ARIA braucht laufende Standort-Updates damit GPS-Watcher (Blitzer-Warner, near()) funktionieren.',
|
|
buttonPositive: 'Erlauben',
|
|
buttonNegative: 'Abbrechen',
|
|
},
|
|
);
|
|
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
|
} catch (e) {
|
|
console.warn('[gps-track] Permission-Fehler:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async start(reason: string = ''): Promise<boolean> {
|
|
if (this.active) return true;
|
|
const ok = await this.ensurePermission();
|
|
if (!ok) {
|
|
ToastAndroid.show('GPS-Tracking: Berechtigung abgelehnt', ToastAndroid.LONG);
|
|
return false;
|
|
}
|
|
try {
|
|
this.watchId = Geolocation.watchPosition(
|
|
(pos) => {
|
|
const lat = pos.coords.latitude;
|
|
const lon = pos.coords.longitude;
|
|
rvs.send('location_update' as any, { lat, lon });
|
|
},
|
|
(err) => {
|
|
console.warn('[gps-track] watchPosition error:', err?.code, err?.message);
|
|
},
|
|
{
|
|
enableHighAccuracy: true,
|
|
distanceFilter: 30, // erst senden wenn 30m gewandert
|
|
interval: 15000, // (Android) gewuenschte Frequenz
|
|
fastestInterval: 10000, // (Android) max Frequenz
|
|
} as any,
|
|
);
|
|
this.active = true;
|
|
this.lastChangeAt = Date.now();
|
|
this.notify();
|
|
AsyncStorage.setItem('aria_gps_tracking', 'true').catch(() => {});
|
|
ToastAndroid.show(
|
|
reason ? `GPS-Tracking aktiv (${reason})` : 'GPS-Tracking aktiv',
|
|
ToastAndroid.SHORT,
|
|
);
|
|
console.log('[gps-track] gestartet', reason ? `(${reason})` : '');
|
|
return true;
|
|
} catch (e: any) {
|
|
console.warn('[gps-track] start fehlgeschlagen:', e?.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
stop(reason: string = ''): void {
|
|
if (!this.active) return;
|
|
if (this.watchId !== null) {
|
|
try { Geolocation.clearWatch(this.watchId); } catch {}
|
|
this.watchId = null;
|
|
}
|
|
this.active = false;
|
|
this.lastChangeAt = Date.now();
|
|
this.notify();
|
|
AsyncStorage.setItem('aria_gps_tracking', 'false').catch(() => {});
|
|
ToastAndroid.show(
|
|
reason ? `GPS-Tracking aus (${reason})` : 'GPS-Tracking aus',
|
|
ToastAndroid.SHORT,
|
|
);
|
|
console.log('[gps-track] gestoppt', reason ? `(${reason})` : '');
|
|
}
|
|
|
|
async toggle(reason: string = ''): Promise<void> {
|
|
if (this.active) this.stop(reason);
|
|
else await this.start(reason);
|
|
}
|
|
}
|
|
|
|
export default new GpsTrackingService();
|