diff --git a/README.md b/README.md
index cc30907..463d7a6 100644
--- a/README.md
+++ b/README.md
@@ -851,6 +851,8 @@ docker exec aria-core ssh aria-wohnung hostname
- [x] Sprachnachrichten-Bubble: audioRequestId statt Substring-Match — keine vertauschten Bubbles mehr bei parallelen Aufnahmen
- [x] Bereit-Sound (Airplane Ding-Dong) wenn Mikro nach Wake-Word offen ist — akustische Bestaetigung, in Settings abschaltbar
- [x] Wake-Word parallel zu TTS mit AcousticEchoCanceler — "Computer" sagen waehrend ARIA spricht stoppt sie und oeffnet Mikro
+- [x] GPS-Position mit Nachrichten mitsenden (Toggle in Settings) — ARIA nutzt sie nur bei standortbezogenen Fragen, im Chat sichtbar nur in ihrer Antwort
+- [x] Sprachnachrichten ohne STT-Result werden nach Timeout automatisch entfernt (skaliert mit Aufnahmedauer)
- [x] Disk-Voll Banner in Diagnostic mit copy-baren Cleanup-Befehlen
- [x] Wake-Word on-device via openWakeWord (ONNX Runtime, kein API-Key) + State-Icon
diff --git a/android/src/screens/ChatScreen.tsx b/android/src/screens/ChatScreen.tsx
index 1b66e90..562a516 100644
--- a/android/src/screens/ChatScreen.tsx
+++ b/android/src/screens/ChatScreen.tsx
@@ -142,9 +142,10 @@ const ChatScreen: React.FC = () => {
return `msg_${Date.now()}_${messageIdCounter.current}`;
};
- // TTS-Settings beim Mount + bei Screen-Fokus neu laden (damit Settings-Toggle sofort greift)
+ // TTS- + GPS-Settings beim Mount + alle 2s neu laden (damit Settings-Toggle
+ // sofort greift, ohne Context- oder Event-System)
useEffect(() => {
- const loadTtsSettings = async () => {
+ const loadSettings = async () => {
const enabled = await AsyncStorage.getItem('aria_tts_enabled');
setTtsDeviceEnabled(enabled !== 'false'); // default true
const muted = await AsyncStorage.getItem('aria_tts_muted');
@@ -152,10 +153,11 @@ const ChatScreen: React.FC = () => {
const voice = await AsyncStorage.getItem('aria_xtts_voice');
localXttsVoiceRef.current = voice || '';
ttsSpeedRef.current = await loadTtsSpeed();
+ const gps = await AsyncStorage.getItem('aria_gps_enabled');
+ setGpsEnabled(gps === 'true');
};
- loadTtsSettings();
- // Poll alle 2s um Settings-Aenderung mitzubekommen (einfache Loesung ohne Context)
- const interval = setInterval(loadTtsSettings, 2000);
+ loadSettings();
+ const interval = setInterval(loadSettings, 2000);
return () => clearInterval(interval);
}, []);
diff --git a/android/src/screens/SettingsScreen.tsx b/android/src/screens/SettingsScreen.tsx
index 068c138..5121632 100644
--- a/android/src/screens/SettingsScreen.tsx
+++ b/android/src/screens/SettingsScreen.tsx
@@ -159,6 +159,9 @@ const SettingsScreen: React.FC = () => {
AsyncStorage.getItem('aria_tts_enabled').then(saved => {
if (saved !== null) setTtsEnabled(saved === 'true');
});
+ AsyncStorage.getItem('aria_gps_enabled').then(saved => {
+ if (saved !== null) setGpsEnabled(saved === 'true');
+ });
AsyncStorage.getItem(TTS_PREROLL_STORAGE_KEY).then(saved => {
if (saved != null) {
const n = parseFloat(saved);
@@ -437,7 +440,7 @@ const SettingsScreen: React.FC = () => {
const handleGPSToggle = useCallback((value: boolean) => {
setGpsEnabled(value);
- // In Produktion: Wert in AsyncStorage persistieren
+ AsyncStorage.setItem('aria_gps_enabled', String(value)).catch(() => {});
}, []);
// --- XTTS Voice ---
@@ -661,7 +664,11 @@ const SettingsScreen: React.FC = () => {
GPS-Position mitsenden
- Standort wird automatisch an Nachrichten angehaengt
+ Position (lat/lon) wird mit jeder Nachricht an ARIA mitgeschickt.
+ Sie sieht's nur intern und nutzt es bei standortbezogenen Fragen
+ ("wo bin ich?", "Wetter hier?"), erwaehnt es sonst nicht.
+ Im Chat-Verlauf bleibt die Bubble unveraendert — nur ARIAs
+ Antwort kann darauf eingehen.
str:
+ """Baut den Text fuer aria-core mit allen relevanten Hints (Barge-In,
+ GPS-Position). Hints sind in eckigen Klammern, der eigentliche User-
+ Text folgt unverandert."""
+ parts: list[str] = []
+ if interrupted:
+ parts.append(
+ "[Hinweis: Stefan hat dich gerade unterbrochen waehrend du noch "
+ "gesprochen oder gearbeitet hast. Folgendes ist eine Korrektur, "
+ "Ergaenzung oder ein Themenwechsel zu deiner letzten Antwort.]"
+ )
+ if location and isinstance(location, dict):
+ lat = location.get("lat")
+ lon = location.get("lon") or location.get("lng")
+ if lat is not None and lon is not None:
+ parts.append(
+ f"[Stefans aktuelle GPS-Position: {float(lat):.6f}, {float(lon):.6f}. "
+ f"Nutze die nur wenn die Frage sich auf seinen Standort bezieht. "
+ f"Erwaehne sie nicht von dir aus, ausser er fragt explizit danach.]"
+ )
+ if parts:
+ return " ".join(parts) + " " + text
+ return text
+
def _build_pending_files_message(self, user_text: str) -> str:
"""Baut eine Anweisung an aria-core aus den gepufferten Files + optionalem
User-Text. user_text leer → 'warte auf Anweisung'-Variante."""
@@ -1236,6 +1261,7 @@ class ARIABridge:
self._next_speed_override = None
if text:
interrupted = bool(payload.get("interrupted", False))
+ location = payload.get("location") or None
# Wenn Files gerade gepuffert sind (Bild + Text gleichzeitig
# gesendet), mergen wir sie zu einer einzigen Anfrage statt
# zwei separater send_to_core-Calls.
@@ -1243,15 +1269,11 @@ class ARIABridge:
if merged:
logger.info("[rvs] App-Chat (mit Anhaengen): '%s'", text[:80])
else:
- core_text = (
- f"[Hinweis: Stefan hat dich gerade unterbrochen waehrend du noch "
- f"gesprochen oder gearbeitet hast. Folgendes ist eine Korrektur, "
- f"Ergaenzung oder ein Themenwechsel zu deiner letzten Antwort.] "
- f"{text}"
- if interrupted else text
- )
- logger.info("[rvs] App-Chat%s: '%s'",
- " [BARGE-IN]" if interrupted else "", text[:80])
+ core_text = self._build_core_text(text, interrupted, location)
+ logger.info("[rvs] App-Chat%s%s: '%s'",
+ " [BARGE-IN]" if interrupted else "",
+ " [GPS]" if location else "",
+ text[:80])
await self.send_to_core(core_text, source="app" + (" [barge-in]" if interrupted else ""))
return
@@ -1511,11 +1533,14 @@ class ARIABridge:
self._next_speed_override = None
interrupted = bool(payload.get("interrupted", False))
audio_request_id = payload.get("audioRequestId", "") or ""
- logger.info("[rvs] Audio empfangen: %s, %dms, %dKB%s%s",
+ location = payload.get("location") or None
+ logger.info("[rvs] Audio empfangen: %s, %dms, %dKB%s%s%s",
mime_type, duration_ms, len(audio_b64) // 1365,
" [BARGE-IN]" if interrupted else "",
+ " [GPS]" if location else "",
f" reqId={audio_request_id[:16]}" if audio_request_id else "")
- asyncio.create_task(self._process_app_audio(audio_b64, mime_type, interrupted, audio_request_id))
+ asyncio.create_task(self._process_app_audio(
+ audio_b64, mime_type, interrupted, audio_request_id, location))
elif msg_type == "stt_response":
# Antwort der whisper-bridge auf unseren stt_request
@@ -1573,7 +1598,8 @@ class ARIABridge:
async def _process_app_audio(self, audio_b64: str, mime_type: str,
interrupted: bool = False,
- audio_request_id: str = "") -> None:
+ audio_request_id: str = "",
+ location: Optional[dict] = None) -> None:
"""App-Audio → STT → aria-core. Primaer via whisper-bridge (RVS), Fallback lokal.
interrupted=True wenn der User waehrend ARIA noch sprach/dachte aufgenommen hat
@@ -1583,7 +1609,10 @@ class ARIABridge:
audio_request_id: Korrelations-ID die die App im audio-Event mitschickt — wird
unveraendert ans STT-Result zurueckgegeben damit die App die EXAKT richtige
- 'wird verarbeitet'-Bubble ersetzen kann (auch bei mehreren parallelen Aufnahmen)."""
+ 'wird verarbeitet'-Bubble ersetzen kann (auch bei mehreren parallelen Aufnahmen).
+
+ location: Optional GPS-Position {lat, lon} — wird als Hinweis-Praefix mitgegeben
+ damit ARIA bei standortbezogenen Fragen sie nutzen kann."""
# Erst Remote versuchen
text = await self._stt_remote(audio_b64, mime_type)
if text is None:
@@ -1595,15 +1624,9 @@ class ARIABridge:
if text.strip():
logger.info("[rvs] STT Ergebnis: '%s'", text[:80])
- # Barge-In-Hinweis: gibt ARIA den Kontext dass sie unterbrochen wurde
- # und dies eine Korrektur/Aenderung der vorherigen Anweisung sein kann.
- core_text = (
- f"[Hinweis: Stefan hat dich gerade unterbrochen waehrend du noch "
- f"gesprochen oder gearbeitet hast. Folgendes ist eine Korrektur, "
- f"Ergaenzung oder ein Themenwechsel zu deiner letzten Antwort.] "
- f"{text}"
- if interrupted else text
- )
+ # Hints (Barge-In, GPS) als Praefix vorschalten — gemeinsamer Helper
+ # mit dem chat-Pfad damit das Verhalten konsistent ist.
+ core_text = self._build_core_text(text, interrupted, location)
# ERST an aria-core senden (wichtigster Schritt)
await self.send_to_core(core_text, source="app-voice" + (" [barge-in]" if interrupted else ""))
# STT-Text an RVS senden (fuer Anzeige in App + Diagnostic)
diff --git a/issue.md b/issue.md
index 830d9d8..1e6eea2 100644
--- a/issue.md
+++ b/issue.md
@@ -108,6 +108,9 @@
- [x] Mikro-Offen-Toast "🎤 sprich jetzt" erscheint erst wenn audioService.startRecording wirklich erfolgreich war (statt ~400ms vorher beim Wake-Word-Detect)
- [x] **Bereit-Sound (Airplane Ding-Dong) wenn Mikro nach Wake-Word offen** — akustische Bestaetigung statt nur Toast. Toggle in Settings → Wake-Word, default aktiv
- [x] **Wake-Word parallel zu TTS** mit AcousticEchoCanceler: User sagt "Computer" waehrend ARIA spricht → TTS verstummt sofort, neue Aufnahme startet. Native AEC verhindert dass ARIAs eigene Stimme das Wake-Word triggert. Audio-Source ist VOICE_COMMUNICATION + zusaetzlich AEC/NS/AGC-Effekte aktiviert
+- [x] **GPS-Position mitsenden**: Toggle in Settings → Allgemein → Standort, persistiert in AsyncStorage, ChatScreen pollt den Wert. Wenn aktiv wird lat/lon mit jeder chat/audio-Message mitgegeben. Bridge prefixed den Text fuer aria-core mit GPS-Hint (mit Anweisung dass die Position nur bei Bedarf erwaehnt wird, nicht automatisch). Im App-Chat sieht man die Position nicht, nur ARIAs Antwort kann darauf eingehen
+- [x] Sprachnachrichten ohne STT-Result werden nach 60s+Aufnahmedauer automatisch entfernt (sicher genug fuer 5-30min-Aufnahmen, schnell genug fuer leere Wake-Word-Echos)
+- [x] VAD adaptive Baseline robuster: minimum statt avg + Cap auf -50dB bis -28dB (Stille) / -40dB bis -18dB (Speech) — keine "tote" VAD-Konfiguration mehr bei lauter Umgebung oder Wake-Word-Echo
## Offen