314 lines
14 KiB
Markdown
314 lines
14 KiB
Markdown
# 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)
|