This commit is contained in:
ordinarthur 2026-04-01 17:10:18 +02:00
parent 92605d728b
commit 4ac7bd16d2
6 changed files with 794 additions and 9 deletions

View File

@ -10,6 +10,36 @@ Le projet intègre de nombreux services externes (STT, TTS, LLM, Google Calendar
## Vue d'ensemble
### Architecture globale (robot + cloud)
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ ROBOT PHYSIQUE │
│ │
│ ┌──────────────┐ UART ┌──────────────────────────┐ │
│ │ ESP32-S3 │ ◄────────────►│ robot-client (Pi Zero) │ │
│ │ • Audio I2S │ │ • WebSocket cloud │ │
│ │ • Servos │ │ • Wake word │ │
│ │ • LEDs │ │ • Domotique (local) │ │
│ │ • Capteurs │ │ • WiFi/BLE setup │ │
│ └──────────────┘ └────────────┬─────────────┘ │
│ │ │
└──────────────────────────────────────────────┼───────────────────────────────┘
│ WebSocket (WSS)
┌──────────────────────────────────────────────────────────────────────────────┐
│ BACKEND CLOUD (NestJS) │
│ │
│ Adaptateurs entrants → Core (services) → Adaptateurs sortants │
│ (WebSocket, REST) (Conversation, (STT, TTS, LLM, │
│ Calendar, etc.) Storage, etc.) │
└──────────────────────────────────────────────────────────────────────────────┘
```
> **Note :** Le robot-client gère la domotique en local (Hue, Home Assistant, MQTT) pour la latence et la résilience. Les commandes domotiques sont décidées par le LLM côté cloud, puis transmises au robot-client qui les exécute sur le réseau local. Voir [home-automation.md](home-automation.md) et [robot-client.md](robot-client.md) pour le détail.
### Architecture du backend cloud
```
┌─────────────────────────────────────────────┐
│ ADAPTATEURS ENTRANTS │
@ -297,4 +327,20 @@ Utilisée par l'app web/mobile pour toute la configuration :
- **Circuit breaker** sur les appels aux services externes (STT, TTS, LLM) — si un provider tombe, le système peut fallback sur un autre (ex: Deepgram → Whisper local).
- **Retry avec backoff exponentiel** sur les appels API.
- **Queue de messages** pour les function calls non critiques (ex: envoi d'email) — si ça échoue, on retry sans bloquer la conversation.
- **Mode dégradé offline** — le robot peut toujours gérer les timers, alarmes, et commandes basiques en local si le core est injoignable.
- **Mode dégradé offline** — le robot peut toujours gérer les timers, alarmes, commandes domotiques basiques, et commandes locales si le core est injoignable.
## Robot Client
Le robot-client (`apps/robot-client`) est l'application Node.js/TypeScript qui tourne sur le Raspberry Pi Zero 2 W. Il orchestre toute la logique locale du robot : communication UART avec l'ESP32-S3, connexion WebSocket vers le backend cloud, détection du wake word, provisioning WiFi (BLE + AP mode), et exécution des commandes domotiques sur le réseau local.
En développement, le robot-client tourne sur le PC du développeur en mode simulateur (`ROBOT_MODE=simulator`), les modules hardware étant remplacés par des mocks. Le simulateur web (`apps/simulator`) sert alors de frontend de debug.
Voir [robot-client.md](robot-client.md) pour l'architecture détaillée.
## Couche Domotique
L'intégration domotique est gérée **localement par le robot-client**, pas par le backend cloud. Chaque écosystème (Philips Hue, Home Assistant, MQTT…) est encapsulé dans un bridge qui implémente une interface `IAutomationBridge`. L'`AutomationManager` orchestre les bridges et expose une API unifiée.
Le flux : le LLM côté cloud décide d'appeler un tool domotique → le backend transmet la commande au robot-client via WebSocket → le robot-client exécute la commande localement sur le réseau.
Voir [home-automation.md](home-automation.md) pour le détail complet.

View File

@ -258,7 +258,63 @@ Une application web (et mobile à terme) pour configurer Ti-Pote et gérer son c
---
## 12. Mode offline dégradé
## 12. Domotique (contrôle de la maison)
### Description
Ti-Pote peut contrôler les appareils domotiques de la maison par la voix : lumières, thermostats, volets, media players, et tout appareil connecté via les écosystèmes supportés. Le contrôle est exécuté localement depuis le robot (pas via le cloud) pour la latence et la résilience.
### Cas d'usage
- "Allume la lumière du salon"
- "Mets le salon en ambiance cinéma"
- "Quelle température il fait dans la chambre ?"
- "Monte le chauffage à 22 degrés"
- "Ferme les volets"
- "Active la scène bonne nuit"
### Écosystèmes supportés
- **Philips Hue** — contrôle direct via l'API REST locale du Hue Bridge
- **Home Assistant** — accès à tous les appareils gérés par HA via son WebSocket API
- **MQTT** — support Zigbee2MQTT, Tasmota, et tout appareil publiant sur MQTT
- **Futurs** : Matter/Thread, Apple HomeKit, Tuya, KNX
### Architecture
Chaque écosystème est encapsulé dans un bridge avec une interface commune (`IAutomationBridge`). Un `AutomationManager` orchestre les bridges et expose une API unifiée. La découverte des hubs se fait par mDNS.
Voir [home-automation.md](home-automation.md) pour le détail complet.
### Functions exposées au LLM
- `list_smart_devices` — Lister les appareils disponibles (par pièce, par type)
- `control_device` — Contrôler un appareil (on/off, brightness, color, temperature…)
- `get_device_state` — Lire l'état d'un appareil
- `execute_scene` — Activer une scène prédéfinie
- `get_sensor_value` — Lire un capteur (température, humidité, mouvement…)
---
## 13. Provisioning WiFi et setup initial
### Description
Le robot doit pouvoir être configuré sur n'importe quel réseau WiFi par un utilisateur non-technique. Deux mécanismes complémentaires sont prévus.
### Cas d'usage
- Premier démarrage du robot : l'utilisateur le connecte à son WiFi via l'app mobile (BLE) ou via le captive portal (AP mode)
- Changement de réseau WiFi (déménagement, nouveau routeur)
- Reset réseau (bouton physique sur le robot)
### Méthode principale : BLE Provisioning
L'app Ti-Pote (web/mobile) scanne les robots en BLE, envoie les credentials WiFi. Le robot se connecte et confirme. L'utilisateur n'a pas besoin de changer de réseau sur son téléphone.
### Méthode fallback : AP Mode + Captive Portal
Le robot crée un hotspot WiFi (`Ti-Pote-XXXX`). L'utilisateur s'y connecte, un portail web s'ouvre pour choisir le réseau et entrer le mot de passe. Le robot se connecte et désactive le hotspot.
### Implémentation
- BLE : librairie `noble` (npm) sur le Pi Zero 2 W
- AP mode : `hostapd` + `dnsmasq` + serveur HTTP local (Express/Fastify)
- Voir [robot-client.md](robot-client.md) pour le détail du flux
---
## 14. Mode offline dégradé
### Description
Si le robot perd la connexion internet ou que le core est injoignable, certaines fonctionnalités de base restent disponibles localement.
@ -266,6 +322,7 @@ Si le robot perd la connexion internet ou que le core est injoignable, certaines
### Fonctionnalités offline
- Timers et alarmes (gérés localement)
- Heure et date
- Commandes domotiques basiques (allumer/éteindre — via les bridges locaux, pas besoin du cloud)
- Commandes de base pré-enregistrées
- Feedback audio (sons, bips) pour confirmer les actions

313
docs/home-automation.md Normal file
View File

@ -0,0 +1,313 @@
# Intégration Domotique
## Vue d'ensemble
Ti-Pote peut interagir avec les équipements domotiques de la maison. L'utilisateur contrôle ses lampes, thermostats, volets, et autres appareils connectés par la voix. L'architecture est conçue pour être extensible : chaque écosystème domotique est encapsulé dans un **bridge** (adaptateur) avec une interface commune.
### Philosophie
Le contrôle domotique est exécuté **localement depuis le robot-client** (sur le Pi Zero 2 W), pas depuis le backend cloud. C'est un choix délibéré :
- **Latence** : les commandes domotiques doivent être quasi-instantanées. Passer par le cloud ajouterait 100-300ms inutiles.
- **Résilience** : si la connexion internet tombe, le robot peut encore contrôler les lumières et les appareils locaux.
- **Privacy** : les commandes domotiques ne transitent pas par des serveurs distants.
Le flux est le suivant : l'utilisateur parle → le backend cloud traite la requête (STT → LLM) → le LLM décide d'appeler une function domotique → le backend envoie la commande au robot-client via WebSocket → le robot-client exécute la commande sur le réseau local.
```
Utilisateur : "Allume la lumière du salon"
┌─────────────┐ audio ┌──────────────┐ function call ┌──────────────────┐
│ Robot │ ──────────►│ Backend │ ──────────────►│ Robot-client │
│ (micro) │ │ Cloud │ "set_light" │ (Pi Zero) │
└─────────────┘ │ │ │ │
│ STT → LLM │ │ ┌──────────────┐ │
│ │ │ │ Hue Bridge │ │
│ │ │ │ (REST local) │ │
└──────────────┘ │ └──────┬───────┘ │
└────────┼─────────┘
┌──────▼───────┐
│ Lampe Hue │
│ (salon) │
└──────────────┘
```
## Architecture des bridges
Chaque écosystème domotique est implémenté comme un **bridge** qui respecte une interface TypeScript commune. Cela permet d'ajouter de nouveaux écosystèmes sans modifier le reste du code.
```typescript
// bridge.interface.ts
interface IAutomationBridge {
/** Nom du bridge (ex: "hue", "homeassistant", "mqtt") */
readonly name: string;
/** Initialise la connexion avec le hub/controller */
connect(): Promise<void>;
/** Déconnexion propre */
disconnect(): Promise<void>;
/** Vérifie si le bridge est connecté et fonctionnel */
isConnected(): boolean;
/** Liste tous les appareils découverts */
listDevices(): Promise<AutomationDevice[]>;
/** Récupère l'état d'un appareil */
getDeviceState(deviceId: string): Promise<DeviceState>;
/** Exécute une commande sur un appareil */
executeCommand(deviceId: string, command: DeviceCommand): Promise<void>;
/** S'abonne aux changements d'état (optionnel) */
onStateChange?(callback: (deviceId: string, state: DeviceState) => void): void;
}
interface AutomationDevice {
id: string;
name: string; // Nom lisible ("Lampe salon", "Thermostat chambre")
type: DeviceType; // "light" | "switch" | "thermostat" | "cover" | "sensor" | "media" | ...
room?: string; // Pièce (si disponible)
capabilities: string[]; // ["on_off", "brightness", "color", "temperature", ...]
bridge: string; // Nom du bridge source
}
interface DeviceState {
on?: boolean;
brightness?: number; // 0-100
color?: { r: number; g: number; b: number };
colorTemp?: number; // en Kelvin
temperature?: number; // en °C (thermostat/capteur)
targetTemperature?: number; // consigne (thermostat)
position?: number; // 0-100 (volets)
[key: string]: unknown; // Propriétés spécifiques au bridge
}
type DeviceCommand =
| { action: 'turn_on'; brightness?: number; color?: string; colorTemp?: number }
| { action: 'turn_off' }
| { action: 'set_brightness'; value: number }
| { action: 'set_color'; r: number; g: number; b: number }
| { action: 'set_color_temp'; kelvin: number }
| { action: 'set_temperature'; value: number }
| { action: 'set_position'; value: number } // volets
| { action: 'toggle' }
| { action: 'custom'; payload: Record<string, unknown> };
```
### AutomationManager
Le `AutomationManager` orchestre tous les bridges actifs. Il maintient un registre unifié des appareils, quelle que soit leur source, et expose une API unique au reste du robot-client.
```typescript
class AutomationManager {
private bridges: Map<string, IAutomationBridge>;
private devices: Map<string, AutomationDevice>; // Cache unifié
/** Enregistre un nouveau bridge */
registerBridge(bridge: IAutomationBridge): void;
/** Connecte tous les bridges enregistrés */
connectAll(): Promise<void>;
/** Liste tous les appareils de tous les bridges */
listAllDevices(): AutomationDevice[];
/** Cherche des appareils par nom ou pièce (fuzzy matching) */
findDevices(query: string): AutomationDevice[];
/** Exécute une commande (résout le bridge automatiquement) */
executeCommand(deviceId: string, command: DeviceCommand): Promise<void>;
/** Crée des scènes (groupes de commandes) */
executeScene(scene: AutomationScene): Promise<void>;
}
```
## Bridges supportés
### 1. Philips Hue
Communication directe avec le Hue Bridge via son API REST locale. Pas besoin du cloud Philips — tout se passe sur le réseau local.
| Paramètre | Détail |
|-----------|--------|
| Protocole | HTTP REST (API v2 CLIP) |
| Découverte | mDNS (`_hue._tcp.local`) ou IP manuelle |
| Auth | Appui physique sur le bridge + échange de clé API |
| Appareils | Lampes, prises, capteurs de mouvement, boutons |
| Capacités | on/off, brightness, color (RGB + CT), groupes, scènes |
**Découverte et appairage :**
1. Le robot-client scanne le réseau local pour trouver le Hue Bridge (mDNS ou SSDP).
2. L'utilisateur est invité à appuyer sur le bouton du bridge (pour autoriser l'accès).
3. Le robot-client enregistre une clé API et la stocke localement (chiffrée).
4. Les lampes et groupes sont découverts automatiquement.
**Exemples de commandes vocales :**
- "Allume la lumière du salon"
- "Mets le salon en rouge à 50%"
- "Éteins toutes les lumières"
- "Active la scène cinéma"
### 2. Home Assistant
Communication avec une instance Home Assistant via son WebSocket API. Home Assistant est un hub domotique open source qui supporte des centaines de protocoles (Zigbee, Z-Wave, WiFi, Bluetooth, Matter...). C'est le bridge le plus polyvalent : en connectant Ti-Pote à Home Assistant, on accède automatiquement à tous les appareils gérés par HA.
| Paramètre | Détail |
|-----------|--------|
| Protocole | WebSocket (+ REST API pour certaines opérations) |
| Découverte | mDNS (`_home-assistant._tcp.local`) ou URL manuelle |
| Auth | Long-lived access token (généré dans l'interface HA) |
| Appareils | Tout ce que Home Assistant gère (lumières, thermostats, volets, media players, capteurs, aspirateurs, caméras...) |
| Capacités | Dépend des appareils — HA expose les entités avec leurs services |
**Intégration :**
Le bridge Home Assistant est particulièrement puissant car il permet aussi :
- D'exécuter des **automatisations HA** via la voix ("Lance le scénario bonne nuit").
- De lire les **capteurs** ("Quelle est la température dans la chambre ?").
- D'interagir avec des **media players** ("Mets la musique dans la cuisine").
- D'accéder à l'**historique** des états ("À quelle heure la porte du garage a été ouverte ?").
### 3. MQTT (Zigbee2MQTT, Tasmota...)
Pour les utilisateurs qui utilisent un broker MQTT directement (sans Home Assistant). Permet de contrôler des appareils Zigbee (via Zigbee2MQTT), des prises Tasmota, ou tout appareil publiant sur MQTT.
| Paramètre | Détail |
|-----------|--------|
| Protocole | MQTT (via broker Mosquitto ou autre) |
| Découverte | Topics de discovery (`homeassistant/+/+/config` ou custom) |
| Auth | Credentials MQTT (user/password) |
| Appareils | Dépend des topics publiés |
### 4. Bridges futurs (Phase 4+)
L'interface `IAutomationBridge` permet d'ajouter facilement :
- **Matter/Thread** — le nouveau standard IoT (quand l'écosystème sera plus mature)
- **Apple HomeKit** — via le protocole HAP
- **Google Home** — via l'API locale (si disponible)
- **Tuya/Smart Life** — protocole local Tuya
- **KNX** — domotique filaire haut de gamme
## Découverte automatique
Au démarrage (et périodiquement), le robot-client scanne le réseau local pour découvrir les hubs domotiques disponibles :
```
1. Scan mDNS
├── _hue._tcp.local → Hue Bridge trouvé à 192.168.1.42
├── _home-assistant._tcp.local → HA trouvé à 192.168.1.100
└── (autres services...)
2. Notification à l'utilisateur
"J'ai détecté un pont Philips Hue et une instance Home Assistant
sur ton réseau. Tu veux que je m'y connecte ?"
3. Configuration via l'app frontend
└── L'utilisateur entre les credentials nécessaires (clé API Hue, token HA...)
```
## Functions LLM (tools pour le backend)
Le backend cloud expose ces tools au LLM. Quand le LLM les appelle, le backend transmet la commande au robot-client qui l'exécute localement.
```typescript
const automationTools = [
{
name: 'list_smart_devices',
description: 'Liste les appareils domotiques disponibles dans la maison',
parameters: {
type: 'object',
properties: {
room: { type: 'string', description: 'Filtrer par pièce (optionnel)' },
type: { type: 'string', description: 'Filtrer par type: light, switch, thermostat, cover, sensor, media' },
},
},
},
{
name: 'control_device',
description: 'Contrôle un appareil domotique (allumer, éteindre, régler luminosité, couleur, température...)',
parameters: {
type: 'object',
properties: {
device_name: { type: 'string', description: 'Nom de l\'appareil (ex: "lampe salon")' },
action: { type: 'string', enum: ['turn_on', 'turn_off', 'toggle', 'set_brightness', 'set_color', 'set_color_temp', 'set_temperature', 'set_position'] },
value: { type: 'number', description: 'Valeur numérique selon l\'action (brightness 0-100, temperature en °C, etc.)' },
color: { type: 'string', description: 'Couleur (nom ou hex, ex: "rouge", "#FF0000")' },
},
required: ['device_name', 'action'],
},
},
{
name: 'get_device_state',
description: 'Récupère l\'état actuel d\'un appareil (allumé/éteint, température, luminosité...)',
parameters: {
type: 'object',
properties: {
device_name: { type: 'string', description: 'Nom de l\'appareil' },
},
required: ['device_name'],
},
},
{
name: 'execute_scene',
description: 'Active une scène ou un scénario domotique prédéfini',
parameters: {
type: 'object',
properties: {
scene_name: { type: 'string', description: 'Nom de la scène (ex: "cinéma", "bonne nuit", "matin")' },
},
required: ['scene_name'],
},
},
{
name: 'get_sensor_value',
description: 'Lit la valeur d\'un capteur domotique (température, humidité, mouvement, ouverture...)',
parameters: {
type: 'object',
properties: {
sensor_name: { type: 'string', description: 'Nom du capteur (ex: "température chambre", "capteur porte garage")' },
},
required: ['sensor_name'],
},
},
];
```
## Exemples de conversations
**Contrôle basique :**
> "Hey Ti-Pote, allume la lumière du salon"
> → LLM appelle `control_device("lampe salon", "turn_on")` → robot-client → Hue Bridge → lampe s'allume
> "C'est fait, la lampe du salon est allumée."
**Contrôle avancé :**
> "Mets le salon en ambiance cinéma"
> → LLM appelle `execute_scene("cinéma")` → robot-client → Hue Bridge → scène activée
> "Scène cinéma activée. Bon film !"
**Lecture de capteur :**
> "Quelle température il fait dans la chambre ?"
> → LLM appelle `get_sensor_value("température chambre")` → robot-client → Home Assistant → 21.5°C
> "Il fait 21 degrés et demi dans la chambre."
**Combinaison avec d'autres services :**
> "Il fait froid, monte le chauffage et rappelle-moi de baisser dans 2 heures"
> → LLM appelle `control_device("thermostat salon", "set_temperature", 22)` + `set_timer("Baisser le chauffage", 7200)`
> "J'ai monté le thermostat à 22 degrés. Je te rappellerai de baisser dans 2 heures."
## Configuration dans le frontend
L'interface web (Next.js) propose une section "Domotique" dans les paramètres :
- **Bridges détectés** : liste des hubs trouvés sur le réseau, avec statut de connexion
- **Appairage** : bouton pour initier l'appairage (Hue : appui sur le bridge, HA : saisie du token)
- **Appareils** : liste des devices découverts avec possibilité de renommer, assigner à une pièce, masquer
- **Scènes** : création de scènes personnalisées (groupes de commandes)
- **Test** : bouton pour tester une commande sur un appareil (allumer/éteindre)

View File

@ -5,11 +5,11 @@
Le développement de Ti-Pote est découpé en phases progressives. Chaque phase produit un livrable fonctionnel et testable. L'objectif est de valider chaque brique avant d'empiler la suivante.
```
Phase 0 Phase 1 Phase 2 Phase 3 Phase 4
Setup & Conversation Services Intelligence Expansion
Infra vocale métier avancée
─────────────────────────────────────────────────────────────────────────────────►
[2-3 sem] [3-4 sem] [4-6 sem] [4-6 sem] [Continu]
Phase 0 Phase 1 Phase 1.5 Phase 2 Phase 3 Phase 4
Setup & Conversation Robot Client Services Intelligence Expansion
Infra vocale & Domotique métier avancée
──────────────────────────────────────────────────────────────────────────────────────────────────
[2-3 sem] [3-4 sem] [3-4 sem] [4-6 sem] [4-6 sem] [Continu]
```
---
@ -82,6 +82,48 @@ L'utilisateur dit "Hey Ti-Pote, raconte-moi une blague" → le robot répond voc
---
## Phase 1.5 — Robot Client & Domotique (3-4 semaines)
### Objectif
Remplacer le simulateur comme client direct du backend par un vrai **robot-client** TypeScript (`apps/robot-client`) qui reflète l'architecture cible du robot physique. Ajouter le provisioning WiFi et les premières intégrations domotiques.
### Livrables
**Robot Client (`apps/robot-client`)**
- Application Node.js TypeScript qui tourne sur le Pi (ou en mode simulateur sur PC de dev)
- Communication WebSocket bidirectionnelle avec le backend cloud (même protocole que le simulateur)
- Bridge UART avec l'ESP32-S3 via `serialport` (mode physical) ou mock (mode simulator)
- Flag `ROBOT_MODE=simulator|physical` pour switcher entre les deux modes
- Intégration du wake word (OpenWakeWord via subprocess Python)
- Système de health check et heartbeat avec le backend
**WiFi Provisioning**
- Setup WiFi par BLE (méthode principale) : le robot advertise en BLE, l'app envoie les credentials
- Setup WiFi par AP mode + captive portal (fallback) : hotspot `Ti-Pote-XXXX`, portail web local
- Flux de premier démarrage (onboarding) complet : power on → WiFi → cloud → prêt
**Domotique — première intégration**
- Architecture de bridges domotiques avec interface `IAutomationBridge`
- `AutomationManager` pour orchestrer plusieurs bridges
- Bridge **Philips Hue** : découverte mDNS, appairage, contrôle des lampes (on/off, brightness, color)
- Bridge **Home Assistant** : connexion WebSocket API, listing des entités, exécution de commandes
- Bridge **MQTT** : connexion broker, support Zigbee2MQTT / Tasmota
- Découverte automatique des hubs sur le réseau local (mDNS)
- Tools LLM : `list_smart_devices`, `control_device`, `get_device_state`, `execute_scene`, `get_sensor_value`
**Simulateur (évolution)**
- Le simulateur web (`apps/simulator`) devient un frontend de debug pour le robot-client en mode simulator
- Nouvelle topologie : Browser ↔ robot-client (PC) ↔ Backend cloud
**Frontend**
- Page de configuration WiFi (pour le setup initial)
- Section domotique : bridges détectés, appairage, liste des appareils, test de commande
### Critère de validation
Le robot-client tourne sur un PC de dev en mode simulator. L'utilisateur peut parler via le simulateur web, la requête transite par le robot-client avant d'atteindre le backend. Depuis la voix, l'utilisateur peut allumer/éteindre une lampe Hue ou un appareil Home Assistant. Le setup WiFi fonctionne en AP mode sur un Pi physique.
---
## Phase 2 — Services métier & Function calling (4-6 semaines)
### Objectif
@ -175,7 +217,7 @@ Améliorer, étendre et polish le produit. Cette phase est continue et n'a pas d
- WhatsApp Business API
- Telegram Bot API
- Spotify / services de musique
- Domotique (Home Assistant, Philips Hue…)
- Nouveaux bridges domotiques (Matter/Thread, Apple HomeKit, Tuya, KNX…)
**Mobilité**
- Firmware pour le module base mobile
@ -212,6 +254,9 @@ Améliorer, étendre et polish le produit. Cette phase est continue et n'a pas d
| Jalon | Phase | Description |
|-------|-------|-------------|
| "Hello World" vocal | Phase 1 | Premier aller-retour audio complet |
| Robot-client opérationnel | Phase 1.5 | Le robot-client fait le pont simulateur ↔ backend |
| "Allume la lumière" | Phase 1.5 | Ti-Pote contrôle une lampe Hue ou un device HA par la voix |
| Setup WiFi autonome | Phase 1.5 | Le robot se configure sur un réseau WiFi via AP mode ou BLE |
| Premier function call | Phase 2 | Ti-Pote crée un événement dans Google Calendar |
| "Tu te souviens ?" | Phase 3 | Ti-Pote retrouve un souvenir d'une conversation passée |
| Notification proactive | Phase 3 | Ti-Pote rappelle un RDV sans qu'on lui demande |

284
docs/robot-client.md Normal file
View File

@ -0,0 +1,284 @@
# Robot Client (`apps/robot-client`)
## Vue d'ensemble
Le **robot-client** est l'application TypeScript qui tourne directement sur le Raspberry Pi Zero 2 W du robot. C'est le "cerveau logiciel local" de Ti-Pote : il fait le pont entre le hardware (ESP32-S3 via UART) et le backend cloud (via WebSocket). En phase de développement, cette même app est simulée sur un PC de dev, permettant de travailler sans le robot physique.
### Positionnement dans l'architecture
```
┌──────────────┐ UART ┌──────────────────────┐ WebSocket ┌──────────────┐
│ ESP32-S3 │ ◄────────────►│ robot-client │ ◄─────────────►│ Backend │
│ (firmware) │ │ (Node.js / Pi) │ │ Cloud │
│ │ │ │ │ (NestJS) │
│ • Audio I2S │ │ • Orchestration │ │ │
│ • Servos │ │ • Wake word │ │ • STT/TTS │
│ • LEDs │ │ • WiFi management │ │ • LLM │
│ • Capteurs │ │ • Config locale │ │ • Services │
└──────────────┘ │ • Domotique bridge │ └──────────────┘
│ • OTA updates │
└──────────────────────┘
```
## Runtime et contraintes
| Paramètre | Choix |
|-----------|-------|
| Runtime | **Node.js** (LTS) |
| Langage | TypeScript strict |
| OS | Raspberry Pi OS Lite (headless) |
| RAM disponible | ~300 Mo (sur 512 Mo, après OS) |
| Stockage | microSD 32 Go |
| Réseau | WiFi 802.11 b/g/n + BLE 4.2 |
### Pourquoi Node.js ?
Node.js est le choix le plus cohérent avec le reste du stack (backend NestJS, simulateur). L'écosystème npm offre des librairies matures pour tout ce dont on a besoin : WebSocket (socket.io-client), serialport (UART), mDNS, BLE, HTTP serveur local. La consommation mémoire est gérable sur le Pi Zero 2 W si on fait attention aux dépendances.
## Architecture interne
Le robot-client suit lui aussi une architecture modulaire avec des services découplés. Chaque module peut être activé/désactivé selon la configuration du robot.
```
apps/robot-client/
├── src/
│ ├── main.ts # Point d'entrée, bootstrap
│ ├── config/
│ │ ├── robot.config.ts # Config locale (device ID, tokens, WiFi...)
│ │ └── hardware.config.ts # Config hardware (UART port, baudrate...)
│ │
│ ├── transport/
│ │ ├── cloud-socket.ts # Client WebSocket vers le backend cloud
│ │ ├── uart-bridge.ts # Communication UART avec l'ESP32
│ │ └── local-server.ts # Serveur HTTP/WS local pour le setup
│ │
│ ├── services/
│ │ ├── wake-word.service.ts # Détection wake word (OpenWakeWord via subprocess)
│ │ ├── wifi.service.ts # Gestion WiFi (scan, connect, AP mode)
│ │ ├── bluetooth.service.ts # BLE pour le provisioning
│ │ ├── camera.service.ts # Capture image (CSI camera)
│ │ ├── ota.service.ts # Mises à jour OTA (self + ESP32)
│ │ ├── health.service.ts # Monitoring, heartbeat, diagnostics
│ │ └── automation/
│ │ ├── automation.manager.ts # Gestionnaire de bridges domotiques
│ │ ├── bridges/
│ │ │ ├── bridge.interface.ts # Interface commune pour tous les bridges
│ │ │ ├── hue.bridge.ts # Philips Hue (API REST locale)
│ │ │ ├── homeassistant.bridge.ts # Home Assistant (WebSocket API)
│ │ │ └── mqtt.bridge.ts # MQTT générique (Zigbee2MQTT, etc.)
│ │ └── discovery.service.ts # Découverte mDNS des hubs domotiques
│ │
│ ├── setup/
│ │ ├── captive-portal.ts # AP mode + portail captif pour config WiFi
│ │ ├── ble-provisioning.ts # Provisioning BLE (méthode principale)
│ │ └── setup-flow.ts # Orchestration du flux de premier setup
│ │
│ └── utils/
│ ├── logger.ts # Logging local + remote
│ ├── network.ts # Utilitaires réseau (IP, connectivity check)
│ └── system.ts # Infos système (CPU, RAM, température)
├── scripts/
│ ├── install.sh # Installation des dépendances système (Node, OpenWakeWord...)
│ └── setup-ap.sh # Configuration du point d'accès WiFi
├── package.json
└── tsconfig.json
```
## Fonctionnalités clés
### 1. Communication cloud (WebSocket)
Le robot-client maintient une connexion WebSocket persistante avec le backend cloud. C'est le même protocole que le simulateur web — le backend ne fait pas la différence. En cas de déconnexion, le client tente automatiquement de se reconnecter avec un backoff exponentiel.
Le client gère aussi la bufferisation : si la connexion tombe pendant un échange audio, il peut informer l'utilisateur via les LEDs (passage en orange) et basculer en mode dégradé local.
### 2. Communication hardware (UART)
Le robot-client communique avec l'ESP32-S3 via le port série UART à 921600 baud. Il implémente le protocole de frames binaire décrit dans [hardware.md](hardware.md) : réception des chunks audio du micro (AUDIO_UP), envoi des chunks TTS (AUDIO_DOWN), commandes servo/LED, et heartbeat.
La librairie `serialport` npm est utilisée pour l'accès au port série.
### 3. Configuration WiFi
Le robot doit pouvoir être configuré sur n'importe quel réseau WiFi, même par un utilisateur non-technique. Deux mécanismes complémentaires sont prévus.
#### Méthode principale : BLE Provisioning
Le mécanisme de setup préféré. L'utilisateur ouvre l'app web/mobile Ti-Pote sur son téléphone et scanne les robots disponibles en BLE. L'app envoie les credentials WiFi (SSID + mot de passe) au robot via BLE. Le robot se connecte au WiFi et confirme la connexion.
Avantage : l'utilisateur n'a pas besoin de changer de réseau WiFi sur son téléphone pendant le setup.
```
Téléphone (App Ti-Pote) Robot (Pi Zero 2 W)
│ │
│ ─── BLE scan ──────────────────► │
│ ◄── BLE advertise (Ti-Pote-XXXX) │
│ │
│ ─── Connect BLE ──────────────► │
│ ─── Send WiFi credentials ────► │
│ │ → connecte au WiFi
│ ◄── WiFi connected (IP, status) │ → contacte le backend cloud
│ │
│ ─── Send device token ─────────► │
│ ◄── Setup complete ──────────── │
```
#### Méthode fallback : AP Mode + Captive Portal
Si le BLE n'est pas disponible (navigateur desktop, BLE désactivé), le robot peut créer son propre point d'accès WiFi.
1. Au premier démarrage (ou après un reset réseau), le Pi crée un hotspot WiFi : `Ti-Pote-XXXX` (où XXXX est un identifiant unique).
2. L'utilisateur se connecte à ce hotspot depuis son téléphone/PC.
3. Un captive portal s'ouvre automatiquement (ou manuellement à `http://192.168.4.1`).
4. Le portail affiche les réseaux WiFi disponibles. L'utilisateur choisit son réseau et entre le mot de passe.
5. Le robot se connecte au réseau, désactive le hotspot, et confirme via une page de succès.
```
┌────────────────────┐
│ Captive Portal │
│ (page web locale) │
│ │
│ ┌──────────────┐ │
│ │ WiFi: Maison │ │
│ │ MDP: ******* │ │
│ │ [Connecter] │ │
│ └──────────────┘ │
│ │
│ ► Réseau 1 │
│ ► Réseau 2 │
│ ► Réseau 3 │
└────────────────────┘
```
Technologies utilisées : `hostapd` (AP mode), `dnsmasq` (DHCP + DNS redirect), serveur HTTP local Express/Fastify pour le portail.
### 4. Mode simulation (développement)
En phase de développement, le robot-client tourne sur un PC de dev au lieu du Pi physique. Les modules hardware (UART, WiFi, BLE, caméra) sont remplacés par des mocks :
- **UART** → mockée par le simulateur web (qui joue le rôle de l'ESP32 : capture micro via WebRTC, playback via Web Audio API)
- **WiFi** → pas nécessaire en mode dev (le PC est déjà sur le réseau)
- **BLE** → désactivé en mode dev
- **Caméra** → remplacée par la webcam du PC ou des images statiques
Un flag d'environnement `ROBOT_MODE=simulator|physical` détermine quels modules sont chargés.
```bash
# Mode développement (sur PC, avec le simulateur web comme frontend)
ROBOT_MODE=simulator pnpm dev:robot
# Mode production (sur le Pi physique)
ROBOT_MODE=physical pnpm start:robot
```
### 5. Premier setup (onboarding)
Le flux de premier démarrage du robot :
```
┌─────────────────────────────────────────────────────────────────────┐
│ PREMIER DÉMARRAGE │
│ │
│ 1. Power on │
│ └─► LEDs en respiration violette (mode setup) │
│ │
│ 2. WiFi pas configuré ? │
│ ├─► Active BLE advertising ("Ti-Pote-XXXX") │
│ └─► Active AP mode en fallback (30s sans connexion BLE) │
│ │
│ 3. Credentials WiFi reçus (BLE ou captive portal) │
│ └─► Tente la connexion WiFi │
│ ├─► Succès → LEDs en vert │
│ └─► Échec → LEDs en rouge, retour étape 2 │
│ │
│ 4. Connexion au backend cloud │
│ └─► Envoie device_id + firmware version │
│ ├─► Nouveau device → le backend génère un device token │
│ └─► Device connu → reprise de session │
│ │
│ 5. Setup terminé │
│ └─► LEDs en bleu, robot prêt │
│ └─► "Bonjour, je suis Ti-Pote ! Je suis prêt." │
└─────────────────────────────────────────────────────────────────────┘
```
### 6. OTA Updates
Le robot-client supporte les mises à jour over-the-air :
- **Self-update** : le backend notifie le client qu'une nouvelle version est disponible. Le client télécharge le nouveau package, le vérifie (checksum), et se redéploie (process manager type PM2 ou systemd).
- **ESP32 update** : le Pi peut flasher l'ESP32 via UART (bootloader ESP32). Le firmware est téléchargé depuis le cloud et poussé vers l'ESP32.
- **Rollback** : si le nouveau firmware ne démarre pas correctement (pas de heartbeat dans les 60 secondes), le système revient à la version précédente.
## Transition depuis le simulateur
Le `apps/simulator` actuel (React + Vite) reste en place comme outil de développement. Il joue désormais le rôle d'interface visuelle pour le robot-client en mode simulateur :
```
Mode développement actuel :
Browser (simulator) ──WebSocket──► Backend cloud
Mode développement futur :
Browser (simulator) ──WebSocket──► robot-client (PC) ──WebSocket──► Backend cloud
(simule le Pi)
Mode production :
ESP32 (hardware) ───UART──► robot-client (Pi) ──WebSocket──► Backend cloud
```
Le simulateur web évolue pour devenir un "frontend de debug" du robot-client, plutôt qu'un client direct du backend. Cela permet de tester la logique complète du robot-client (wake word, bufferisation, mode dégradé, domotique) sans hardware.
## Dépendances système (Pi Zero 2 W)
```bash
# Runtime
node >= 20 LTS
npm / pnpm
# Wake word
python3
pip: openwakeword
# WiFi AP mode
hostapd
dnsmasq
# Bluetooth
bluez
noble (npm, bindings BLE)
# Port série
librairie native serialport (npm)
# Caméra
libcamera-apps (rpicam-still, rpicam-vid)
```
## Variables d'environnement
```bash
# Mode de fonctionnement
ROBOT_MODE=simulator|physical
# Backend cloud
CLOUD_URL=wss://api.tipote.dev/ws/robot
DEVICE_TOKEN=eyJ...
# Hardware (mode physical uniquement)
UART_PORT=/dev/ttyS0
UART_BAUDRATE=921600
# WiFi AP (mode physical uniquement)
AP_SSID=Ti-Pote-XXXX
AP_CHANNEL=6
# Domotique
HUE_BRIDGE_IP=192.168.1.x
HUE_API_KEY=xxxxx
HOMEASSISTANT_URL=http://homeassistant.local:8123
HOMEASSISTANT_TOKEN=xxxxx
MQTT_BROKER_URL=mqtt://192.168.1.x:1883
```

View File

@ -259,11 +259,51 @@ Le simulateur web affiche les timings de chaque étape :
Ces timings permettent d'identifier les goulots d'étranglement et d'optimiser la latence.
## Transition vers le robot-client
> **Important :** À partir de la Phase 1.5, le simulateur web n'est plus un client direct du backend cloud. Il devient un **frontend de debug** pour le robot-client (`apps/robot-client`).
### Avant (Phase 0-1)
```
Browser (simulator) ──WebSocket──► Backend cloud (NestJS)
```
Le simulateur se connecte directement au backend, exactement comme le ferait un robot. C'est simple et suffisant pour développer les premières features.
### Après (Phase 1.5+)
```
Browser (simulator) ──WebSocket──► robot-client (PC, mode simulator) ──WebSocket──► Backend cloud
```
Le robot-client tourne sur le PC du développeur en mode `ROBOT_MODE=simulator`. Le simulateur web lui envoie l'audio (comme l'ESP32 enverrait des frames UART). Le robot-client fait transiter vers le backend. Cela permet de tester la logique complète du robot-client (wake word, bufferisation, mode dégradé, domotique) sans hardware.
### En production
```
ESP32-S3 ──UART──► robot-client (Pi Zero) ──WebSocket──► Backend cloud
```
Le simulateur n'est plus utilisé. Le robot physique parle directement au robot-client via UART.
### Ce qui change pour le simulateur
Le simulateur web reste en place et conserve son interface actuelle (statut, bouton wake word, transcript, logs). Ce qui change :
- Il se connecte au **robot-client local** (ex: `ws://localhost:3001`) au lieu du backend cloud directement.
- Il peut afficher des informations supplémentaires : état des bridges domotiques, statut WiFi, devices connectés.
- Il sert aussi de frontend pour tester le **captive portal** en mode dev.
Voir [robot-client.md](robot-client.md) pour l'architecture du robot-client.
## Évolution progressive
Le simulateur évolue avec le projet :
**Phase 1 (MVP)** — Simulateur web avec micro + speaker uniquement. Bouton wake word. Transcript et logs. CLI avec mode texte interactif.
**Phase 1 (MVP)** — Simulateur web avec micro + speaker uniquement. Bouton wake word. Transcript et logs. CLI avec mode texte interactif. Connexion directe au backend.
**Phase 1.5** — Le simulateur se connecte au robot-client (mode simulator) au lieu du backend. Ajout d'un panneau domotique (état des devices). Test du flux complet robot-client ↔ backend.
**Phase 2** — Ajout de la webcam pour simuler le module caméra. Interface pour afficher ce que le "robot voit". CLI avec mode audio (envoi de fichiers .wav).