# Intégration Ray-Ban Meta — mymuseum-visitapp ## Vue d'ensemble L'intégration Ray-Ban Meta transforme `mymuseum-visitapp` en guide audio mains-libres. Le visiteur porte les lunettes ; son téléphone est en poche. Il peut : - Poser une question à voix haute à l'assistant sans toucher le téléphone - Scanner un QR code en appuyant sur le bouton des lunettes → entendre l'explication - Recevoir automatiquement des informations contextuelles en approchant d'une œuvre (beacon ou géolocalisation) L'architecture existante (AssistantService, QR scanner, beacons, géoloc) est **inchangée**. Les lunettes sont une nouvelle surface d'entrée/sortie branchée dessus. --- ## Prérequis ### Matériel & apps - Ray-Ban Meta Gen 2 (~380€, 8h autonomie) - **App Meta AI installée sur le téléphone** — bridge Bluetooth obligatoire - Compte Meta connecté dans l'app Meta AI ### Comptes & clés API | Service | Usage | Où obtenir | |---|---|---| | **Picovoice Console** | Générer le wake word .ppn | https://console.picovoice.ai/ — gratuit jusqu'à 3 keywords | | **ElevenLabs** | Synthèse vocale TTS | https://elevenlabs.io — tier gratuit ou payant selon volume | ### SDK Flutter (developer preview) Les packages Meta SDK doivent être ajoutés à `pubspec.yaml` quand publiés : ```yaml # Décommenter quand disponibles : # meta_wearables_dat: any # Android # meta_wearables: any # iOS (ChunkyTofuStudios) ``` Vérifier les versions actuelles sur pub.dev : `meta_wearables_dat`, `meta_wearables`. --- ## Setup initial ### 1. Générer les fichiers wake word (.ppn) 1. Aller sur https://console.picovoice.ai/ 2. Créer un compte → **Wake Word** → **Create a keyword** 3. Keyword : `Hey MyVisit` (ou autre phrase de votre choix) 4. Exporter pour **Android** → sauvegarder dans `assets/wake_words/hey_myvisit_android.ppn` 5. Exporter pour **iOS** → sauvegarder dans `assets/wake_words/hey_myvisit_ios.ppn` 6. Copier l'**Access Key** affiché dans le dashboard ### 2. Configurer les variables de build (--dart-define) Toutes les clés et URLs sont injectées via `--dart-define` au moment du build/run. Aucune clé en dur dans le code. #### Référence complète des `--dart-define` | Variable | Obligatoire | Exemple | Description | |---|---|---|---| | `API_BASE_URL` | ✅ prod | `https://api.mymuseum.be` | URL du backend MyInfoMate | | `API_KEY` | ✅ | `abc123` | Clé API MyInfoMate (bootstrappe la clé depuis le backend au 1er run) | | `INSTANCE_ID` | ✅ | `63514fd67ed8c735aaa4b8f2` | ID de l'instance (fixe par flavor) | | `WHISPER_API_KEY` | ⚡ recommandé | `sk-xxxx` | Clé OpenAI Whisper **ou** n'importe quelle valeur si endpoint auto-hébergé | | `WHISPER_ENDPOINT` | ⚡ si auto-hébergé | `http://192.168.1.x:8000/v1/audio/transcriptions` | Endpoint Whisper compatible OpenAI (défaut: api.openai.com) | | `ELEVENLABS_API_KEY` | ❌ optionnel | `sk_xxxx` | TTS ElevenLabs — si vide, utilise flutter_tts (on-device gratuit) | | `ELEVENLABS_VOICE_ID` | ❌ optionnel | `EXAVITQu4vr4xnSDxMaL` | ID voix ElevenLabs | | `PICOVOICE_ACCESS_KEY` | ❌ optionnel | `xxxx` | Wake word Porcupine — si vide, utilise speech_to_text | | `FLAVOR` | ✅ build | `dev` / `mdlf` / `fortsaintheribert` | Détermine les couleurs et l'app name | #### Commandes de lancement **Dev minimal (speech_to_text + flutter_tts) :** ```bash flutter run --flavor dev \ --dart-define=API_BASE_URL=http://192.168.x.x:5000 \ --dart-define=API_KEY=ta_clé \ --dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2 ``` **Dev avec Whisper OpenAI :** ```bash flutter run --flavor dev \ --dart-define=API_BASE_URL=https://api.mymuseum.be \ --dart-define=API_KEY=ta_clé \ --dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2 \ --dart-define=WHISPER_API_KEY=sk-xxxx ``` **Dev avec Whisper auto-hébergé (faster-whisper-server Docker) :** ```bash flutter run --flavor dev \ --dart-define=API_BASE_URL=https://api.mymuseum.be \ --dart-define=API_KEY=ta_clé \ --dart-define=INSTANCE_ID=63514fd67ed8c735aaa4b8f2 \ --dart-define=WHISPER_API_KEY=anykey \ --dart-define=WHISPER_ENDPOINT=http://192.168.x.x:8000/v1/audio/transcriptions ``` **Production complète (Porcupine + Whisper auto-hébergé + flutter_tts) :** ```bash flutter build appbundle --flavor mdlf \ --dart-define=API_BASE_URL=https://api.mymuseum.be \ --dart-define=API_KEY=ta_clé \ --dart-define=INSTANCE_ID=xxx \ --dart-define=FLAVOR=mdlf \ --dart-define=PICOVOICE_ACCESS_KEY=xxx \ --dart-define=WHISPER_API_KEY=anykey \ --dart-define=WHISPER_ENDPOINT=https://whisper.myinfomate.be/v1/audio/transcriptions ``` #### Démarrer faster-whisper-server (Docker) ```bash docker run -p 8000:8000 fedirz/faster-whisper-server:latest-cpu # Avec GPU : docker run --gpus all -p 8000:8000 fedirz/faster-whisper-server:latest-cuda ``` ### 3. Activer le mode lunettes dans l'app Dans `VisitAppContext`, passer `glassesEnabled = true`. Pour l'instant c'est à faire dans le code ou via un toggle UI à créer dans les paramètres de la configuration. ### 4. Lancer l'app sur le device ```bash flutter run --dart-define=ELEVENLABS_API_KEY=xxx --dart-define=PICOVOICE_ACCESS_KEY=xxx ``` --- ## Architecture technique ``` Lunettes Ray-Ban Meta │ Bluetooth (HFP mic, A2DP speakers, camera stream, button) ▼ [MetaGlassesService] ← SDK DAT lifecycle + photo capture button │ ├── [WakeWordService] ← Porcupine détecte "Hey MyVisit" (on-device) │ └── speech_to_text ← transcrit la commande après wake word │ ├── "scanne ce QR" → MetaGlassesService.requestPhotoCapture() │ ├── "répète" → GlassesTtsService.replay() │ └── autre → AssistantService.chat() → GlassesTtsService │ ├── [GlassesQrScannerService] ← photo → mobile_scanner → regex URL → AssistantService │ ├── [GeoBeaconTriggerService] ← geolocator GPS + beacon_scanner BLE → AssistantService │ └── [GlassesTtsService] ← ElevenLabs API → just_audio → AudioRoutingChannel └── [AudioRoutingPlugin] ← natif iOS (AVAudioSession) / Android (AudioManager) force sortie A2DP vers lunettes ``` ### Flux de données type (question vocale) 1. Porcupine détecte "Hey MyVisit" 2. `speech_to_text` transcrit : "Qui a peint ce tableau ?" 3. `AssistantService.chat("Qui a peint ce tableau ?")` → backend MyInfoMate 4. Réponse texte → `GlassesTtsService.speak()` 5. ElevenLabs → MP3 → `just_audio` → AudioRoutingPlugin → lunettes Ray-Ban --- ## Fonctionnalités ### 1. Wake word + conversation vocale **Déclencheur :** "Hey MyVisit" **Commandes reconnues :** | Phrase | Action | |---|---| | "Hey MyVisit" + question | Pose la question à l'assistant, entend la réponse | | "Hey MyVisit, scanne ce QR" | Déclenche la capture photo → pipeline QR | | "Hey MyVisit, répète" | Rejoue la dernière synthèse vocale | Le wake word fonctionne en permanence en background (Foreground Service sur Android). ### 2. Scanner QR via lunettes **Déclencheurs :** - Pression courte sur le bouton hardware des lunettes - Commande vocale "Hey MyVisit, scanne ce QR" **Fonctionnement :** 1. Les lunettes capturent une photo 2. `GlassesQrScannerService` décode le QR (même regex que le scanner téléphone) 3. Si la section appartient à la configuration courante, l'assistant la résume en 3 phrases 4. La réponse est lue dans les lunettes **Anti-spam :** 10s de cooldown entre deux scans du même QR. ### 3. Géolocalisation → TTS automatique (mode proactif) Activer `VisitAppContext.proactiveModeEnabled = true` pour que l'assistant parle spontanément. **GPS :** Le service surveille en continu la position GPS. Quand le visiteur entre dans le rayon d'un `GeoTriggerPoint` (défini par lat/lng + rayon en mètres), l'assistant propose une présentation du lieu. **Beacons BLE :** Quand la précision d'un beacon connu passe sous `3m`, l'assistant présente l'œuvre ou l'espace associé. Les deux fonctionnent en parallèle, avec un cooldown de 30s par point pour éviter le spam. ### 4. Navigation guidée mains-libres Dans un `GuidedPath`, chaque étape peut déclencher un TTS automatique à l'entrée de sa zone GPS ou à la détection de son beacon. Le visiteur progresse sans jamais toucher son téléphone. ### 5. Mode "Quiet" La sortie audio est forcée sur les lunettes via `AudioRoutingPlugin`, pas sur le haut-parleur téléphone. Respecte le silence des musées. --- ## Mode proactif — comportement | `proactiveModeEnabled` | `glassesEnabled` | Comportement | |---|---|---| | `false` | `false` | Comportement app normal, pas de lunettes | | `false` | `true` | Lunettes actives, TTS sur questions vocales et QR, **pas** de déclenchements auto | | `true` | `true` | Tout actif : wake word + QR + géoloc/beacon auto | Recommandation : proposer le toggle du mode proactif à l'écran de démarrage de visite. --- ## Multi-visiteur broadcast (V2) Plusieurs visiteurs avec des lunettes dans la même instance peuvent recevoir simultanément un message du guide via Firebase. **Comment ça marchera :** 1. Le guide envoie un message depuis `manager-app` → push notification Firebase 2. `PushNotificationService` reçoit la notification 3. Un handler à ajouter dans `initialize()` pipe le corps du message vers `GlassesTtsService` 4. Tous les visiteurs connectés entendent le message dans leurs lunettes Le topic Firebase `instance_{instanceId}` est déjà en place. --- ## Limitations SDK (developer preview, mai 2026) | Limitation | Détail | |---|---| | **App Meta AI requise** | Doit être installée et active — bridge Bluetooth obligatoire | | **Résolution caméra** | Max 720p/30fps via Bluetooth (contrainte protocole, pas hardware) | | **Publication App Store/Play Store** | Réservée aux partenaires Meta sélectionnés pour l'instant — distribution interne possible | | **Android uniquement** pour `meta_wearables_dat` | Le plugin Flutter iOS (`meta_wearables`) est en cours | | **iOS 26.0+** pour le SDK natif | Si implémentation Swift directe (comme OpenGlasses) | --- ## Troubleshooting **Son dans le téléphone au lieu des lunettes** - Vérifier que les lunettes sont bien en Bluetooth actif et appairées - Sur Android API < 31 : vérifier que `startBluetoothSco()` ne génère pas d'erreur de permission - Sur iOS : vérifier que `AVAudioSession.setCategory` est appelé avant `just_audio.play()` **Wake word ne répond pas** - Vérifier que `kPicovoiceAccessKey` est bien injecté (non vide) - Vérifier que les fichiers `.ppn` sont bien dans `assets/wake_words/` et déclarés dans `pubspec.yaml` - Les fichiers `.ppn` sont spécifiques Android/iOS — ne pas les inverser **Faux positifs wake word fréquents** - Télécharger un modèle plus sensible (paramètre `sensitivity` de `PorcupineManager`) - Choisir une phrase wake word plus longue sur Picovoice Console **Scan QR échoue depuis les lunettes** - Vérifier que le QR code est bien visible (lumière suffisante) - Le SDK DAT doit être en état `connected` ou `streaming` — vérifier `MetaGlassesService.instance.state` - Vérifier que la section scannée appartient bien à la configuration active (`visitContext.sectionIds`) **ElevenLabs retourne 401** - Clé API expirée ou quota dépassé — vérifier sur https://elevenlabs.io/speech-synthesis --- ## Fichiers clés | Fichier | Rôle | |---|---| | `lib/Services/meta_glasses_service.dart` | SDK DAT lifecycle, photo capture | | `lib/Services/glasses_tts_service.dart` | ElevenLabs → audio Bluetooth | | `lib/Services/wake_word_service.dart` | Porcupine + dispatch commandes vocales | | `lib/Services/geo_beacon_trigger_service.dart` | Géofence GPS + beacons → TTS auto | | `lib/Services/glasses_qr_scanner_service.dart` | QR decode depuis photo capture lunettes | | `lib/PlatformChannels/audio_routing_channel.dart` | Canal Dart → natif pour routage Bluetooth | | `android/…/MainActivity.kt` | Plugin Android : AudioManager A2DP | | `ios/Runner/AudioRoutingPlugin.swift` | Plugin iOS : AVAudioSession Bluetooth | | `assets/wake_words/` | Fichiers .ppn Porcupine (à générer) |