This commit is contained in:
2026-02-07 16:57:37 +01:00
parent 8383104454
commit dff1b03e42
129 changed files with 19769 additions and 0 deletions

View File

@@ -0,0 +1,935 @@
# 🏠 Spécifications Home Assistant pour IPWatch MQTT
Ce document définit les spécifications pour intégrer les équipements IPWatch dans Home Assistant via MQTT Discovery.
## 1. Vue d'ensemble
L'intégration Home Assistant permet de :
- **Détecter automatiquement** les équipements IPWatch via MQTT Discovery
- **Visualiser** l'état des équipements (online/offline)
- **Contrôler** les équipements (shutdown, reboot)
- **Monitorer** les métriques système (CPU, RAM, Disk, Uptime)
- **Créer des automatisations** basées sur l'état des équipements
## 2. MQTT Discovery
### 2.1 Principe
MQTT Discovery permet à Home Assistant de détecter automatiquement les entités sans configuration manuelle.
**Topic de découverte** : `homeassistant/{component}/ipwatch_{unique_id}/{object_id}/config`
**Composants supportés** :
- `sensor` : Métriques système
- `binary_sensor` : État de disponibilité
- `button` : Actions (shutdown, reboot)
### 2.2 Configuration Discovery pour Sensor
L'agent MQTT publie automatiquement sa configuration au démarrage :
**Topic** : `homeassistant/sensor/ipwatch_10_0_0_100/system/config`
**Payload** :
```json
{
"name": "Server 01 - Système",
"unique_id": "ipwatch_10_0_0_100_system",
"state_topic": "ipwatch/device/10.0.0.100/status",
"value_template": "{{ value_json.hostname }}",
"json_attributes_topic": "ipwatch/device/10.0.0.100/status",
"availability": {
"topic": "ipwatch/device/10.0.0.100/availability",
"payload_available": "online",
"payload_not_available": "offline"
},
"device": {
"identifiers": ["ipwatch_10_0_0_100"],
"name": "Server 01",
"model": "IPWatch MQTT Agent v1.0",
"manufacturer": "IPWatch",
"sw_version": "1.0.0"
},
"icon": "mdi:desktop-tower",
"qos": 1
}
```
### 2.3 Configuration Discovery pour Binary Sensor (Availability)
**Topic** : `homeassistant/binary_sensor/ipwatch_10_0_0_100/availability/config`
**Payload** :
```json
{
"name": "Server 01 - Disponibilité",
"unique_id": "ipwatch_10_0_0_100_availability",
"state_topic": "ipwatch/device/10.0.0.100/availability",
"payload_on": "online",
"payload_off": "offline",
"device_class": "connectivity",
"device": {
"identifiers": ["ipwatch_10_0_0_100"],
"name": "Server 01"
},
"icon": "mdi:lan-connect",
"qos": 1
}
```
### 2.4 Configuration Discovery pour Buttons
**Shutdown Button** :
**Topic** : `homeassistant/button/ipwatch_10_0_0_100/shutdown/config`
**Payload** :
```json
{
"name": "Server 01 - Shutdown",
"unique_id": "ipwatch_10_0_0_100_shutdown",
"command_topic": "ipwatch/device/10.0.0.100/command",
"payload_press": "{\"command\":\"shutdown\",\"timestamp\":\"{{ now().isoformat() }}\"}",
"availability": {
"topic": "ipwatch/device/10.0.0.100/availability",
"payload_available": "online",
"payload_not_available": "offline"
},
"device": {
"identifiers": ["ipwatch_10_0_0_100"],
"name": "Server 01"
},
"icon": "mdi:power-off",
"qos": 1
}
```
**Reboot Button** :
**Topic** : `homeassistant/button/ipwatch_10_0_0_100/reboot/config`
**Payload** :
```json
{
"name": "Server 01 - Reboot",
"unique_id": "ipwatch_10_0_0_100_reboot",
"command_topic": "ipwatch/device/10.0.0.100/command",
"payload_press": "{\"command\":\"reboot\",\"timestamp\":\"{{ now().isoformat() }}\"}",
"availability": {
"topic": "ipwatch/device/10.0.0.100/availability",
"payload_available": "online",
"payload_not_available": "offline"
},
"device": {
"identifiers": ["ipwatch_10_0_0_100"],
"name": "Server 01"
},
"icon": "mdi:restart",
"qos": 1
}
```
## 3. Entités Créées dans Home Assistant
### 3.1 Sensors (Métriques Système)
Chaque équipement IPWatch crée automatiquement ces sensors :
| Entity ID | Nom | Description | Attribut JSON |
|-----------|-----|-------------|---------------|
| `sensor.server_01_systeme` | Server 01 - Système | État général | Tous les attributs |
| `sensor.server_01_cpu` | Server 01 - CPU | Usage CPU | `cpu_percent` |
| `sensor.server_01_memory` | Server 01 - RAM | Usage mémoire | `memory_percent` |
| `sensor.server_01_disk` | Server 01 - Disk | Usage disque | `disk_percent` |
| `sensor.server_01_uptime` | Server 01 - Uptime | Temps de fonctionnement | `uptime` |
**Exemple de valeur du sensor système** :
```json
{
"hostname": "server-01",
"ip": "10.0.0.100",
"platform": "Linux",
"platform_version": "5.15.0-91-generic",
"uptime": 86400,
"cpu_percent": 45.2,
"memory_percent": 62.5,
"disk_percent": 78.3,
"timestamp": "2025-12-23T10:30:00Z"
}
```
### 3.2 Binary Sensors
| Entity ID | Nom | État | Icon |
|-----------|-----|------|------|
| `binary_sensor.server_01_disponibilite` | Server 01 - Disponibilité | on/off | `mdi:lan-connect` |
### 3.3 Buttons
| Entity ID | Nom | Action | Icon |
|-----------|-----|--------|------|
| `button.server_01_shutdown` | Server 01 - Shutdown | Éteindre | `mdi:power-off` |
| `button.server_01_reboot` | Server 01 - Reboot | Redémarrer | `mdi:restart` |
## 4. Configuration Home Assistant
### 4.1 Configuration MQTT
Dans `configuration.yaml` :
```yaml
# Configuration MQTT
mqtt:
broker: localhost
port: 1883
username: !secret mqtt_username
password: !secret mqtt_password
discovery: true
discovery_prefix: homeassistant
birth_message:
topic: 'homeassistant/status'
payload: 'online'
will_message:
topic: 'homeassistant/status'
payload: 'offline'
```
### 4.2 Secrets
Dans `secrets.yaml` :
```yaml
mqtt_username: ipwatch
mqtt_password: VotreMotDePasse
```
### 4.3 Templates pour Sensors Individuels (Optionnel)
Si vous souhaitez créer des sensors séparés pour chaque métrique :
```yaml
# Sensor CPU
mqtt:
- sensor:
name: "Server 01 CPU"
unique_id: ipwatch_10_0_0_100_cpu
state_topic: "ipwatch/device/10.0.0.100/status"
value_template: "{{ value_json.cpu_percent }}"
unit_of_measurement: "%"
icon: mdi:cpu-64-bit
availability:
topic: "ipwatch/device/10.0.0.100/availability"
payload_available: "online"
payload_not_available: "offline"
device:
identifiers: ["ipwatch_10_0_0_100"]
name: "Server 01"
# Sensor RAM
- sensor:
name: "Server 01 Memory"
unique_id: ipwatch_10_0_0_100_memory
state_topic: "ipwatch/device/10.0.0.100/status"
value_template: "{{ value_json.memory_percent }}"
unit_of_measurement: "%"
icon: mdi:memory
availability:
topic: "ipwatch/device/10.0.0.100/availability"
device:
identifiers: ["ipwatch_10_0_0_100"]
# Sensor Disk
- sensor:
name: "Server 01 Disk"
unique_id: ipwatch_10_0_0_100_disk
state_topic: "ipwatch/device/10.0.0.100/status"
value_template: "{{ value_json.disk_percent }}"
unit_of_measurement: "%"
icon: mdi:harddisk
availability:
topic: "ipwatch/device/10.0.0.100/availability"
device:
identifiers: ["ipwatch_10_0_0_100"]
# Sensor Uptime
- sensor:
name: "Server 01 Uptime"
unique_id: ipwatch_10_0_0_100_uptime
state_topic: "ipwatch/device/10.0.0.100/status"
value_template: "{{ (value_json.uptime / 3600) | round(1) }}"
unit_of_measurement: "h"
icon: mdi:clock-outline
availability:
topic: "ipwatch/device/10.0.0.100/availability"
device:
identifiers: ["ipwatch_10_0_0_100"]
```
## 5. Lovelace UI Cards
### 5.1 Entity Card (Simple)
```yaml
type: entities
title: Server 01
entities:
- entity: binary_sensor.server_01_disponibilite
name: Disponibilité
- entity: sensor.server_01_cpu
name: CPU
- entity: sensor.server_01_memory
name: RAM
- entity: sensor.server_01_disk
name: Disque
- entity: sensor.server_01_uptime
name: Uptime
- entity: button.server_01_shutdown
name: Éteindre
- entity: button.server_01_reboot
name: Redémarrer
```
### 5.2 Glance Card (Compact)
```yaml
type: glance
title: Server 01
entities:
- entity: binary_sensor.server_01_disponibilite
name: État
- entity: sensor.server_01_cpu
name: CPU
- entity: sensor.server_01_memory
name: RAM
- entity: sensor.server_01_disk
name: Disque
```
### 5.3 Gauge Card (Métriques visuelles)
```yaml
type: vertical-stack
cards:
- type: gauge
entity: sensor.server_01_cpu
name: CPU
min: 0
max: 100
severity:
green: 0
yellow: 60
red: 80
- type: gauge
entity: sensor.server_01_memory
name: RAM
min: 0
max: 100
severity:
green: 0
yellow: 70
red: 90
- type: gauge
entity: sensor.server_01_disk
name: Disque
min: 0
max: 100
severity:
green: 0
yellow: 80
red: 95
```
### 5.4 Custom Card (Markdown)
```yaml
type: markdown
content: |
## 🖥️ Server 01
**État** : {{ states('binary_sensor.server_01_disponibilite') }}
**CPU** : {{ states('sensor.server_01_cpu') }}%
**RAM** : {{ states('sensor.server_01_memory') }}%
**Disque** : {{ states('sensor.server_01_disk') }}%
**Uptime** : {{ states('sensor.server_01_uptime') }}h
{% if is_state('binary_sensor.server_01_disponibilite', 'on') %}
✅ Serveur en ligne
{% else %}
❌ Serveur hors ligne
{% endif %}
```
### 5.5 Button Card (Actions rapides)
```yaml
type: horizontal-stack
cards:
- type: button
entity: button.server_01_shutdown
name: Éteindre
icon: mdi:power-off
tap_action:
action: call-service
service: button.press
service_data:
entity_id: button.server_01_shutdown
hold_action:
action: none
- type: button
entity: button.server_01_reboot
name: Redémarrer
icon: mdi:restart
tap_action:
action: call-service
service: button.press
service_data:
entity_id: button.server_01_reboot
```
## 6. Automatisations
### 6.1 Alerte si serveur offline
```yaml
alias: "Alert - Server 01 Offline"
description: Notification si serveur hors ligne
trigger:
- platform: state
entity_id: binary_sensor.server_01_disponibilite
to: "off"
for:
minutes: 5
action:
- service: notify.mobile_app
data:
title: "⚠️ Serveur Offline"
message: "Server 01 (10.0.0.100) est hors ligne depuis 5 minutes"
data:
priority: high
mode: single
```
### 6.2 Alerte CPU élevé
```yaml
alias: "Alert - Server 01 High CPU"
description: Notification si CPU > 90%
trigger:
- platform: numeric_state
entity_id: sensor.server_01_cpu
above: 90
for:
minutes: 10
condition:
- condition: state
entity_id: binary_sensor.server_01_disponibilite
state: "on"
action:
- service: notify.mobile_app
data:
title: "🔥 CPU élevé"
message: "Server 01 - CPU à {{ states('sensor.server_01_cpu') }}%"
mode: single
```
### 6.3 Shutdown programmé (extinction nocturne)
```yaml
alias: "Scheduled Shutdown - Server 01"
description: Éteindre le serveur à 23h chaque soir
trigger:
- platform: time
at: "23:00:00"
condition:
- condition: state
entity_id: binary_sensor.server_01_disponibilite
state: "on"
action:
- service: button.press
target:
entity_id: button.server_01_shutdown
mode: single
```
### 6.4 Wake-on-LAN au démarrage de HA
```yaml
alias: "WOL - Server 01 on HA Start"
description: Démarrer le serveur quand Home Assistant démarre
trigger:
- platform: homeassistant
event: start
action:
- service: wake_on_lan.send_magic_packet
data:
mac: "AA:BB:CC:DD:EE:FF"
broadcast_address: "192.168.1.255"
mode: single
```
## 7. Intégration Personnalisée (Custom Integration)
### 7.1 Structure du Custom Component
Pour créer une intégration personnalisée plus avancée :
```
custom_components/
└── ipwatch/
├── __init__.py
├── manifest.json
├── config_flow.py
├── const.py
├── sensor.py
├── binary_sensor.py
├── button.py
└── strings.json
```
### 7.2 Manifest
**`manifest.json`** :
```json
{
"domain": "ipwatch",
"name": "IPWatch Network Monitor",
"version": "1.0.0",
"documentation": "https://github.com/your-repo/ipwatch-ha",
"requirements": ["paho-mqtt==1.6.1"],
"dependencies": ["mqtt"],
"codeowners": ["@yourusername"],
"iot_class": "local_push",
"config_flow": true
}
```
### 7.3 Constants
**`const.py`** :
```python
"""Constants for IPWatch integration."""
DOMAIN = "ipwatch"
CONF_MQTT_BROKER = "mqtt_broker"
CONF_MQTT_PORT = "mqtt_port"
CONF_DEVICE_IP = "device_ip"
CONF_DEVICE_NAME = "device_name"
# MQTT Topics
TOPIC_PREFIX = "ipwatch/device"
TOPIC_COMMAND = "command"
TOPIC_STATUS = "status"
TOPIC_AVAILABILITY = "availability"
TOPIC_RESPONSE = "response"
# Commands
COMMAND_SHUTDOWN = "shutdown"
COMMAND_REBOOT = "reboot"
COMMAND_STATUS = "status"
# Platforms
PLATFORMS = ["sensor", "binary_sensor", "button"]
```
### 7.4 Config Flow (UI Configuration)
**`config_flow.py`** :
```python
"""Config flow for IPWatch integration."""
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import callback
from .const import DOMAIN, CONF_DEVICE_IP, CONF_DEVICE_NAME
class IPWatchConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for IPWatch."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}
if user_input is not None:
# Validate user input
await self.async_set_unique_id(user_input[CONF_DEVICE_IP])
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_input[CONF_DEVICE_NAME],
data=user_input
)
data_schema = vol.Schema({
vol.Required(CONF_DEVICE_IP): str,
vol.Required(CONF_DEVICE_NAME): str,
})
return self.async_show_form(
step_id="user",
data_schema=data_schema,
errors=errors
)
```
### 7.5 Sensor Platform
**`sensor.py`** :
```python
"""Sensor platform for IPWatch."""
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import callback
from .const import DOMAIN
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up IPWatch sensor based on a config entry."""
device_ip = config_entry.data[CONF_DEVICE_IP]
device_name = config_entry.data[CONF_DEVICE_NAME]
sensors = [
IPWatchCPUSensor(device_ip, device_name),
IPWatchMemorySensor(device_ip, device_name),
IPWatchDiskSensor(device_ip, device_name),
IPWatchUptimeSensor(device_ip, device_name),
]
async_add_entities(sensors)
class IPWatchCPUSensor(SensorEntity):
"""Representation of IPWatch CPU sensor."""
def __init__(self, device_ip, device_name):
"""Initialize the sensor."""
self._device_ip = device_ip
self._device_name = device_name
self._state = None
self._available = False
@property
def name(self):
"""Return the name of the sensor."""
return f"{self._device_name} CPU"
@property
def unique_id(self):
"""Return a unique ID."""
return f"ipwatch_{self._device_ip.replace('.', '_')}_cpu"
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return "%"
@property
def icon(self):
"""Return the icon."""
return "mdi:cpu-64-bit"
@property
def available(self):
"""Return True if entity is available."""
return self._available
@callback
def _handle_status_update(self, msg):
"""Handle MQTT status updates."""
import json
payload = json.loads(msg.payload)
self._state = payload.get("cpu_percent")
self._available = True
self.async_write_ha_state()
async def async_added_to_hass(self):
"""Subscribe to MQTT topics."""
await self.hass.components.mqtt.async_subscribe(
f"ipwatch/device/{self._device_ip}/status",
self._handle_status_update
)
```
### 7.6 Button Platform
**`button.py`** :
```python
"""Button platform for IPWatch."""
from homeassistant.components.button import ButtonEntity
from .const import DOMAIN, COMMAND_SHUTDOWN, COMMAND_REBOOT
import json
from datetime import datetime
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up IPWatch buttons."""
device_ip = config_entry.data[CONF_DEVICE_IP]
device_name = config_entry.data[CONF_DEVICE_NAME]
buttons = [
IPWatchShutdownButton(hass, device_ip, device_name),
IPWatchRebootButton(hass, device_ip, device_name),
]
async_add_entities(buttons)
class IPWatchShutdownButton(ButtonEntity):
"""Shutdown button for IPWatch device."""
def __init__(self, hass, device_ip, device_name):
"""Initialize the button."""
self.hass = hass
self._device_ip = device_ip
self._device_name = device_name
@property
def name(self):
"""Return the name."""
return f"{self._device_name} Shutdown"
@property
def unique_id(self):
"""Return unique ID."""
return f"ipwatch_{self._device_ip.replace('.', '_')}_shutdown"
@property
def icon(self):
"""Return icon."""
return "mdi:power-off"
async def async_press(self):
"""Handle button press."""
topic = f"ipwatch/device/{self._device_ip}/command"
payload = json.dumps({
"command": COMMAND_SHUTDOWN,
"timestamp": datetime.now().isoformat()
})
await self.hass.components.mqtt.async_publish(
topic,
payload,
qos=1
)
```
## 8. Scripts Python pour Administration
### 8.1 Script de Découverte Automatique
**`scripts/publish_discovery.py`** :
```python
#!/usr/bin/env python3
"""
Publie la configuration MQTT Discovery pour tous les équipements IPWatch
"""
import paho.mqtt.client as mqtt
import json
import sys
def publish_discovery(broker, port, device_ip, device_name):
"""Publie les configs Discovery pour un équipement"""
client = mqtt.Client()
client.connect(broker, port)
unique_id = f"ipwatch_{device_ip.replace('.', '_')}"
# Sensor système
config = {
"name": f"{device_name} - Système",
"unique_id": f"{unique_id}_system",
"state_topic": f"ipwatch/device/{device_ip}/status",
"value_template": "{{ value_json.hostname }}",
"json_attributes_topic": f"ipwatch/device/{device_ip}/status",
"availability": {
"topic": f"ipwatch/device/{device_ip}/availability",
"payload_available": "online",
"payload_not_available": "offline"
},
"device": {
"identifiers": [unique_id],
"name": device_name,
"model": "IPWatch MQTT Agent v1.0",
"manufacturer": "IPWatch"
},
"icon": "mdi:desktop-tower",
"qos": 1
}
topic = f"homeassistant/sensor/{unique_id}/system/config"
client.publish(topic, json.dumps(config), qos=1, retain=True)
print(f"✓ Published sensor config for {device_name}")
# Binary sensor availability
config = {
"name": f"{device_name} - Disponibilité",
"unique_id": f"{unique_id}_availability",
"state_topic": f"ipwatch/device/{device_ip}/availability",
"payload_on": "online",
"payload_off": "offline",
"device_class": "connectivity",
"device": {"identifiers": [unique_id]},
"icon": "mdi:lan-connect",
"qos": 1
}
topic = f"homeassistant/binary_sensor/{unique_id}/availability/config"
client.publish(topic, json.dumps(config), qos=1, retain=True)
print(f"✓ Published binary_sensor config for {device_name}")
# Buttons
for cmd in ["shutdown", "reboot"]:
config = {
"name": f"{device_name} - {cmd.capitalize()}",
"unique_id": f"{unique_id}_{cmd}",
"command_topic": f"ipwatch/device/{device_ip}/command",
"payload_press": json.dumps({"command": cmd}),
"availability": {
"topic": f"ipwatch/device/{device_ip}/availability",
"payload_available": "online",
"payload_not_available": "offline"
},
"device": {"identifiers": [unique_id]},
"icon": f"mdi:{'power-off' if cmd == 'shutdown' else 'restart'}",
"qos": 1
}
topic = f"homeassistant/button/{unique_id}/{cmd}/config"
client.publish(topic, json.dumps(config), qos=1, retain=True)
print(f"✓ Published button {cmd} config for {device_name}")
client.disconnect()
if __name__ == "__main__":
if len(sys.argv) != 5:
print("Usage: python publish_discovery.py <broker> <port> <device_ip> <device_name>")
sys.exit(1)
publish_discovery(sys.argv[1], int(sys.argv[2]), sys.argv[3], sys.argv[4])
```
**Utilisation** :
```bash
python3 scripts/publish_discovery.py localhost 1883 10.0.0.100 "Server 01"
```
## 9. Développement de l'App Home Assistant
### 9.1 Recommandations
Pour développer une application Home Assistant dédiée à IPWatch :
1. **Utiliser MQTT Discovery** : Simplifie l'ajout d'équipements
2. **Créer un Custom Component** : Intégration native dans Home Assistant
3. **Implémenter Config Flow** : Configuration via UI (Settings → Integrations)
4. **Supporter Multiple Devices** : Un équipement IPWatch = un device HA
5. **Gérer les états** : Mapping clair entre MQTT et entités HA
6. **Ajouter des diagnostics** : Logs et debugging pour faciliter le support
### 9.2 Fonctionnalités Avancées
**WOL depuis Home Assistant** :
```yaml
# Configuration Wake-on-LAN
wake_on_lan:
# Service call
service: wake_on_lan.send_magic_packet
data:
mac: "AA:BB:CC:DD:EE:FF"
broadcast_address: "192.168.1.255"
```
**Intégration avec Lovelace Dashboard** :
- Card personnalisée pour afficher grille IPWatch
- Actions rapides (shutdown, reboot, WOL)
- Graphiques historiques des métriques
**Notifications Push** :
- Alertes sur changements d'état
- Notifications sur nouveaux équipements détectés
## 10. Tests et Validation
### 10.1 Tester Discovery
```bash
# Écouter les messages Discovery
mosquitto_sub -h localhost -t "homeassistant/#" -v
# Vérifier que Home Assistant a créé les entités
# Developer Tools → States
# Rechercher : sensor.server_01, binary_sensor.server_01, button.server_01
```
### 10.2 Tester les Commandes
```bash
# Tester shutdown via MQTT
mosquitto_pub -h localhost \
-t "ipwatch/device/10.0.0.100/command" \
-m '{"command":"shutdown","timestamp":"2025-12-23T10:30:00Z"}'
# Vérifier la réponse
mosquitto_sub -h localhost \
-t "ipwatch/device/10.0.0.100/response" -v
```
### 10.3 Tester les Automatisations
1. Créer une automatisation simple
2. Déclencher manuellement
3. Vérifier les logs HA : Settings → System → Logs
## 11. Dépannage
### Entités non découvertes
```bash
# Vérifier que Discovery est activé
# configuration.yaml
mqtt:
discovery: true
# Republier les configs
python3 scripts/publish_discovery.py localhost 1883 10.0.0.100 "Server 01"
# Redémarrer Home Assistant
```
### Boutons ne fonctionnent pas
- Vérifier que l'agent MQTT est en ligne
- Vérifier les permissions sudo sur le client
- Consulter les logs de l'agent : `sudo journalctl -u ipwatch-mqtt-agent -f`
### Sensors non mis à jour
- Vérifier que le topic status est publié toutes les 30 secondes
- Vérifier le QoS (doit être 1)
- Vérifier la disponibilité du broker MQTT
## 12. Références
- **Home Assistant MQTT Discovery** : https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery
- **Home Assistant Developer Docs** : https://developers.home-assistant.io/
- **MQTT Sensor** : https://www.home-assistant.io/integrations/sensor.mqtt/
- **MQTT Button** : https://www.home-assistant.io/integrations/button.mqtt/
- **Config Flow** : https://developers.home-assistant.io/docs/config_entries_config_flow_handler/
## 13. Checklist de Développement
Avant de publier l'intégration Home Assistant :
- [ ] MQTT Discovery implémenté pour tous les composants
- [ ] Config Flow pour configuration via UI
- [ ] Gestion des erreurs et retry automatique
- [ ] Documentation complète (README, HACS)
- [ ] Tests unitaires et d'intégration
- [ ] Validation HACS (Home Assistant Community Store)
- [ ] Icônes et traductions
- [ ] Changelog et versioning sémantique
- [ ] CI/CD pour tests automatisés
- [ ] Support multi-langues (i18n)

View File

@@ -0,0 +1,441 @@
# 📐 Architecture MQTT pour IPWatch
## 1. Vue d'ensemble
L'architecture MQTT d'IPWatch implémente un système de contrôle à distance des équipements réseau basé sur le protocole MQTT (Message Queuing Telemetry Transport).
### 1.1 Objectifs
- **Contrôle centralisé** : Gérer shutdown/reboot depuis l'interface IPWatch
- **Légèreté** : Protocol MQTT léger et performant
- **Fiabilité** : QoS MQTT pour garantir la livraison des messages
- **Scalabilité** : Support de centaines d'équipements simultanément
- **Intégration** : Compatible Home Assistant et autres systèmes domotiques
### 1.2 Composants
```
┌─────────────────────────────────────────────────────────────────┐
│ IPWatch Ecosystem │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌──────────────┐ │
│ │ Web Interface │────────►│ Backend │ │
│ │ (Vue.js) │ HTTP │ (FastAPI) │ │
│ └───────────────┘ API └──────┬───────┘ │
│ │ │
│ paho-mqtt (publish) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ MQTT Broker (Mosquitto) │ │
│ │ │ │
│ │ Topics: │ │
│ │ • ipwatch/device/{IP}/command │ │
│ │ • ipwatch/device/{IP}/status │ │
│ │ • ipwatch/device/{IP}/availability │ │
│ └─────────────────┬───────────────────────────────┘ │
│ │ MQTT (subscribe) │
│ │ │
│ ┌──────────┼──────────┬──────────┬──────────┐ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Agent 1 │ │ Agent 2 │ │ Agent 3 │ │ ... │ │ Agent N │ │
│ │ 10.0.0.1│ │ 10.0.0.2│ │ 10.0.0.3│ │ │ │ 10.0.0.N│ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ Python │ │ Python │ │ Python │ │ Python │ │ Python │ │
│ │ systemd │ │ systemd │ │ systemd │ │ systemd │ │ systemd │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
## 2. Topics MQTT
### 2.1 Hiérarchie des Topics
```
ipwatch/
└── device/
└── {IP_ADDRESS}/
├── command # Commandes envoyées par IPWatch
├── status # Statut publié par l'agent
├── availability # Disponibilité de l'agent (online/offline)
└── response # Réponses/acquittements de l'agent
```
### 2.2 Détail des Topics
#### `ipwatch/device/{IP}/command`
- **Direction** : IPWatch Backend → Agent
- **QoS** : 1 (at least once)
- **Retain** : false
- **Payload** : JSON
- **Commandes supportées** :
- `shutdown` : Arrêt de la machine
- `reboot` : Redémarrage de la machine
- `status` : Demande de statut
**Exemple** :
```json
{
"command": "shutdown",
"timestamp": "2025-12-23T10:30:00Z"
}
```
#### `ipwatch/device/{IP}/status`
- **Direction** : Agent → IPWatch Backend
- **QoS** : 1
- **Retain** : false
- **Payload** : JSON avec métriques système
**Exemple** :
```json
{
"hostname": "server-01",
"ip": "10.0.0.100",
"platform": "Linux",
"platform_version": "5.15.0-91-generic",
"uptime": 86400,
"cpu_percent": 45.2,
"memory_percent": 62.5,
"disk_percent": 78.3,
"timestamp": "2025-12-23T10:30:00Z"
}
```
#### `ipwatch/device/{IP}/availability`
- **Direction** : Agent → Tous
- **QoS** : 1
- **Retain** : true (important!)
- **Payload** : Texte simple
- **Valeurs** : `online` | `offline`
#### `ipwatch/device/{IP}/response`
- **Direction** : Agent → IPWatch Backend
- **QoS** : 1
- **Retain** : false
- **Payload** : JSON avec résultat de la commande
**Exemple** :
```json
{
"success": true,
"message": "Shutdown en cours...",
"timestamp": "2025-12-23T10:30:05Z"
}
```
## 3. Flux de Communication
### 3.1 Shutdown/Reboot
```mermaid
sequenceDiagram
participant UI as IPWatch UI
participant BE as Backend
participant BR as MQTT Broker
participant AG as Agent (10.0.0.100)
UI->>BE: POST /api/tracking/shutdown/10.0.0.100
BE->>BR: PUBLISH ipwatch/device/10.0.0.100/command
Note over BR: QoS 1
BR->>AG: {"command": "shutdown"}
AG->>BR: PUBLISH ipwatch/device/10.0.0.100/response
Note over AG: {"success": true, "message": "Shutdown en cours..."}
AG->>BR: PUBLISH ipwatch/device/10.0.0.100/availability
Note over AG: "offline"
AG->>AG: sudo shutdown -h now
BE-->>UI: 200 OK {"message": "Commande envoyée"}
```
### 3.2 Wake-on-LAN
```mermaid
sequenceDiagram
participant UI as IPWatch UI
participant BE as Backend
participant AG as Agent (offline)
UI->>BE: POST /api/tracking/wol/10.0.0.100
BE->>BE: send_magic_packet(MAC)
Note over BE: Broadcast UDP
BE-->>UI: 200 OK
Note over AG: Machine démarre
AG->>AG: Agent systemd démarre
AG->>BR: CONNECT
AG->>BR: PUBLISH availability "online"
AG->>BR: PUBLISH status {...}
```
### 3.3 Monitoring périodique
```mermaid
sequenceDiagram
participant AG as Agent
participant BR as MQTT Broker
participant HA as Home Assistant
loop Chaque 30 secondes
AG->>BR: PUBLISH ipwatch/device/10.0.0.100/status
Note over AG: CPU, RAM, Disk, Uptime
BR->>HA: Forward status
Note over HA: Update sensors
end
```
## 4. Gestion des Erreurs
### 4.1 Agent déconnecté
**Problème** : L'agent perd la connexion MQTT
**Solution** :
- **Last Will Testament** : Message `offline` publié automatiquement sur `availability`
- **Reconnexion automatique** : L'agent tente de se reconnecter (retry avec backoff)
- **QoS 1** : Garantit la livraison du message d'offline
### 4.2 Commande non livrée
**Problème** : La commande n'atteint pas l'agent
**Solution** :
- **QoS 1** : Le broker MQTT garantit au moins une livraison
- **Timeout** : Le backend attend 5 secondes puis retourne une erreur
- **Retry** : L'utilisateur peut retenter l'opération
### 4.3 Agent en erreur
**Problème** : L'agent reçoit la commande mais ne peut l'exécuter
**Solution** :
- **Message de réponse** : L'agent publie sur `response` avec `success: false`
- **Logging** : Logs détaillés dans journalctl
- **Notification** : L'UI affiche l'erreur à l'utilisateur
## 5. Sécurité
### 5.1 Authentification
```
┌──────────────────────┐
│ MQTT Broker │
│ (Mosquitto) │
│ │
│ password_file: │
│ /etc/mosquitto/passwd│
│ │
│ Users: │
│ • ipwatch (backend) │
│ • agent (clients) │
└──────────────────────┘
```
**Configuration** :
```bash
# Créer les utilisateurs
mosquitto_passwd -c /etc/mosquitto/passwd ipwatch
mosquitto_passwd /etc/mosquitto/passwd agent
# Dans mosquitto.conf
allow_anonymous false
password_file /etc/mosquitto/passwd
```
### 5.2 Chiffrement TLS/SSL
```
listener 8883
cafile /etc/mosquitto/ca.crt
certfile /etc/mosquitto/server.crt
keyfile /etc/mosquitto/server.key
require_certificate false
```
### 5.3 ACL (Access Control List)
```
# /etc/mosquitto/acl
# Backend peut publier sur command
user ipwatch
topic write ipwatch/device/+/command
# Agents peuvent publier sur status/response/availability
user agent
topic write ipwatch/device/+/status
topic write ipwatch/device/+/response
topic write ipwatch/device/+/availability
topic read ipwatch/device/+/command
```
## 6. Performance et Scalabilité
### 6.1 Dimensionnement
| Métrique | Valeur | Notes |
|----------|--------|-------|
| Agents max | 500 | Limité par le broker |
| Messages/sec | 1000 | Dépend du hardware |
| Latence | <100ms | Sur réseau local |
| Bande passante | ~10 KB/s par agent | Pour statut 30s |
### 6.2 Optimisations
**Payload compression** :
- JSON minifié (pas d'indentation)
- Champs courts
- Pas de données redondantes
**Keep-alive** :
- 60 secondes (défaut)
- Réduit le trafic réseau
**QoS adaptatif** :
- QoS 1 pour commandes critiques
- QoS 0 pour statut non-critique
## 7. Compatibilité Home Assistant
### 7.1 MQTT Discovery
L'agent publie automatiquement sa configuration pour Home Assistant :
```json
{
"name": "Server 01",
"unique_id": "ipwatch_10_0_0_100",
"state_topic": "ipwatch/device/10.0.0.100/status",
"availability_topic": "ipwatch/device/10.0.0.100/availability",
"json_attributes_topic": "ipwatch/device/10.0.0.100/status",
"device": {
"identifiers": ["ipwatch_10_0_0_100"],
"name": "Server 01",
"model": "IPWatch MQTT Agent",
"manufacturer": "IPWatch"
}
}
```
**Topic Discovery** :
```
homeassistant/sensor/ipwatch_10_0_0_100/config
```
### 7.2 Entités créées
Home Assistant crée automatiquement :
- **Sensor** : CPU, RAM, Disk, Uptime
- **Binary Sensor** : Availability (online/offline)
- **Button** : Shutdown, Reboot
## 8. Monitoring et Logs
### 8.1 Logs Agent
```bash
# Logs en temps réel
sudo journalctl -u ipwatch-mqtt-agent -f
# Logs des dernières 24h
sudo journalctl -u ipwatch-mqtt-agent --since "24 hours ago"
# Recherche d'erreurs
sudo journalctl -u ipwatch-mqtt-agent -p err
```
### 8.2 Logs Broker
```bash
# Logs Mosquitto
sudo journalctl -u mosquitto -f
# Activer le debug dans mosquitto.conf
log_type all
log_dest /var/log/mosquitto/mosquitto.log
```
### 8.3 Métriques
**Collecte via MQTT** :
- Nombre de clients connectés
- Messages publiés/reçus
- Latence des messages
**Grafana + Prometheus** (optionnel) :
- Dashboard pour visualiser les métriques
- Alertes sur déconnexions
## 9. Diagrammes Détaillés
### 9.1 État de l'Agent
```
┌─────────┐
│ Start │
└────┬────┘
┌─────────────────┐
│ Load Config │
└────┬────────────┘
┌─────────────────┐
│ Connect MQTT │──┐
│ Broker │ │
└────┬────────────┘ │
│ │ Retry
│ Success │ on error
▼ │
┌─────────────────┐ │
│ Subscribe to │ │
│ command topic │ │
└────┬────────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ Publish │◄─┘
│ availability │
│ "online" │
└────┬────────────┘
┌─────────────────┐
│ Main Loop │
│ • Listen MQTT │
│ • Publish status│
│ every 30s │
└────┬────────────┘
│ Receive shutdown
┌─────────────────┐
│ Publish │
│ "offline" │
└────┬────────────┘
┌─────────────────┐
│ Execute │
│ shutdown │
└─────────────────┘
```
## 10. Références
- **MQTT v3.1.1 Specification** : http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html
- **Mosquitto Documentation** : https://mosquitto.org/documentation/
- **Paho MQTT Python** : https://github.com/eclipse/paho.mqtt.python
- **Home Assistant MQTT Discovery** : https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery
## 11. TODO / Améliorations Futures
- [ ] Support SSL/TLS par défaut
- [ ] Compression des payloads (gzip)
- [ ] Authentification par certificat client
- [ ] Métriques Prometheus
- [ ] Dashboard Grafana
- [ ] Support MQTT v5
- [ ] Broadcast pour commandes multi-équipements
- [ ] Planification de shutdown/reboot (cron-like)

View File

@@ -0,0 +1,588 @@
# 📝 Consignes de Codage MQTT pour IPWatch
Ce document définit les standards de développement pour l'écosystème MQTT d'IPWatch.
## 1. Conventions de Nommage
### 1.1 Topics MQTT
**Format** : `ipwatch/device/{IP_ADDRESS}/{category}`
**Règles** :
- ✅ Utiliser des minuscules uniquement
- ✅ Utiliser `/` comme séparateur hiérarchique
- ✅ Pas d'espaces ni de caractères spéciaux
- ✅ Maximum 128 caractères
- ❌ Éviter les wildcards (`+`, `#`) dans les noms de topics
**Exemples** :
```
✅ ipwatch/device/192.168.1.100/command
✅ ipwatch/device/10.0.0.50/status
❌ IPWatch/Device/192.168.1.100/Command (majuscules)
❌ ipwatch-device-192.168.1.100-command (mauvais séparateur)
```
### 1.2 Noms de Variables (Python)
```python
# ✅ Bon
mqtt_broker = "localhost"
command_topic = f"ipwatch/device/{ip}/command"
message_payload = json.dumps(data)
# ❌ Mauvais
MQTTBroker = "localhost" # Pas de PascalCase pour variables
commandTopic = f"ipwatch/device/{ip}/command" # Pas de camelCase
msg = json.dumps(data) # Abréviation non claire
```
### 1.3 Constantes
```python
# ✅ Constantes en MAJUSCULES
MQTT_QOS_COMMAND = 1
MQTT_QOS_STATUS = 1
DEFAULT_KEEPALIVE = 60
MAX_RETRY_ATTEMPTS = 3
# Topics en constantes
TOPIC_PREFIX = "ipwatch/device"
TOPIC_COMMAND = "{}/command"
TOPIC_STATUS = "{}/status"
```
## 2. Structure du Code
### 2.1 Organisation des Fichiers
```
mqtt/
├── client/ # Agent MQTT côté client
│ ├── ipwatch_mqtt_agent.py # Agent principal
│ ├── mqtt-agent.conf.example # Configuration exemple
│ └── requirements.txt # Dépendances Python
├── docs/ # Documentation
│ ├── MQTT_ARCHITECTURE.md
│ ├── MQTT_CODING_GUIDELINES.md
│ └── HOMEASSISTANT_SPEC.md
└── systemd/ # Services systemd
└── ipwatch-mqtt-agent.service
```
### 2.2 Structure d'un Module Python
```python
#!/usr/bin/env python3
"""
Titre du module
Description détaillée
Installation:
pip install dependencies
Usage:
python module.py --help
"""
# Imports standard library
import os
import sys
import json
import logging
from pathlib import Path
# Imports third-party
import paho.mqtt.client as mqtt
# Imports locaux
from backend.app.core.config import config_manager
# Configuration du logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Constantes globales
MQTT_BROKER = "localhost"
MQTT_PORT = 1883
# Classes
class MyClass:
"""Docstring de la classe"""
pass
# Fonctions
def my_function():
"""Docstring de la fonction"""
pass
# Point d'entrée
if __name__ == "__main__":
main()
```
## 3. Gestion MQTT
### 3.1 Connexion au Broker
```python
# ✅ Bon - Gestion robuste des erreurs
def connect_mqtt(broker, port, username=None, password=None):
"""
Connecte au broker MQTT avec gestion d'erreurs
Args:
broker (str): Adresse du broker
port (int): Port MQTT
username (str, optional): Nom d'utilisateur
password (str, optional): Mot de passe
Returns:
mqtt.Client: Client MQTT connecté
Raises:
ConnectionError: Si la connexion échoue
"""
try:
client = mqtt.Client(client_id=f"ipwatch-{os.getpid()}")
if username and password:
client.username_pw_set(username, password)
client.connect(broker, port, keepalive=60)
logger.info(f"✓ Connecté au broker {broker}:{port}")
return client
except Exception as e:
logger.error(f"✗ Erreur connexion MQTT: {e}")
raise ConnectionError(f"Impossible de se connecter à {broker}:{port}")
# ❌ Mauvais - Pas de gestion d'erreur
def connect_mqtt(broker, port):
client = mqtt.Client()
client.connect(broker, port)
return client
```
### 3.2 Publication de Messages
```python
# ✅ Bon - QoS et vérification
def publish_command(client, ip_address, command):
"""
Publie une commande MQTT
Args:
client: Client MQTT
ip_address (str): IP de destination
command (str): Commande à envoyer
Returns:
bool: True si publié avec succès
"""
topic = f"ipwatch/device/{ip_address}/command"
payload = json.dumps({
"command": command,
"timestamp": datetime.now().isoformat()
})
result = client.publish(topic, payload, qos=1)
result.wait_for_publish(timeout=5)
if result.is_published():
logger.info(f"✓ Commande '{command}' envoyée à {ip_address}")
return True
else:
logger.error(f"✗ Échec publication commande vers {ip_address}")
return False
# ❌ Mauvais - Pas de QoS, pas de vérification
def publish_command(client, ip, cmd):
client.publish(f"ipwatch/device/{ip}/command", cmd)
```
### 3.3 Souscription et Callbacks
```python
# ✅ Bon - Callbacks avec gestion d'erreur
class MQTTAgent:
def __init__(self):
self.client = mqtt.Client()
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
self.client.on_disconnect = self.on_disconnect
def on_connect(self, client, userdata, flags, rc):
"""Callback lors de la connexion"""
if rc == 0:
logger.info("✓ Connecté au broker")
client.subscribe(self.command_topic, qos=1)
else:
logger.error(f"✗ Échec connexion, code: {rc}")
def on_message(self, client, userdata, msg):
"""Callback réception message"""
try:
payload = json.loads(msg.payload.decode('utf-8'))
command = payload.get('command')
self.execute_command(command)
except json.JSONDecodeError as e:
logger.error(f"✗ JSON invalide: {e}")
except Exception as e:
logger.error(f"✗ Erreur traitement message: {e}")
def on_disconnect(self, client, userdata, rc):
"""Callback déconnexion"""
if rc != 0:
logger.warning(f"⚠ Déconnexion inattendue (code {rc})")
```
## 4. Gestion des Erreurs
### 4.1 Try/Except
```python
# ✅ Bon - Gestion spécifique des erreurs
def execute_shutdown():
"""Exécute la commande shutdown"""
try:
subprocess.run(
["sudo", "shutdown", "-h", "now"],
check=True,
timeout=10
)
logger.info("✓ Shutdown initié")
return True
except subprocess.TimeoutExpired:
logger.error("✗ Timeout exécution shutdown")
return False
except subprocess.CalledProcessError as e:
logger.error(f"✗ Erreur shutdown: {e}")
return False
except PermissionError:
logger.error("✗ Permissions sudo insuffisantes")
return False
except Exception as e:
logger.error(f"✗ Erreur inattendue: {e}")
return False
# ❌ Mauvais - Catch all sans logging
def execute_shutdown():
try:
subprocess.run(["sudo", "shutdown", "-h", "now"])
except:
pass
```
### 4.2 Logging
```python
# ✅ Bon - Niveaux de log appropriés
logger.debug("Payload: %s", payload) # Détails de debug
logger.info("✓ Connexion établie") # Opérations normales
logger.warning("⚠ Retry connexion...") # Avertissements
logger.error("✗ Échec publication") # Erreurs
logger.critical("🔴 Broker inaccessible") # Erreurs critiques
# ❌ Mauvais - Mauvais niveaux
logger.info("Erreur critique!") # Devrait être error/critical
logger.error("Opération réussie") # Devrait être info
```
## 5. Format des Payloads JSON
### 5.1 Commandes
```python
# ✅ Bon - Structure claire
{
"command": "shutdown", # string, required
"timestamp": "2025-12-23T10:30:00Z" # ISO 8601, required
}
# Avec options
{
"command": "shutdown",
"timestamp": "2025-12-23T10:30:00Z",
"delay": 60, # secondes avant exécution
"force": false # shutdown forcé ou non
}
```
### 5.2 Statut
```python
# ✅ Bon - Métriques utiles
{
"hostname": "server-01",
"ip": "10.0.0.100",
"platform": "Linux",
"platform_version": "5.15.0-91-generic",
"uptime": 86400, # secondes
"cpu_percent": 45.2,
"memory_percent": 62.5,
"disk_percent": 78.3,
"timestamp": "2025-12-23T10:30:00Z"
}
# ❌ Mauvais - Données incomplètes
{
"host": "server-01",
"status": "ok"
}
```
### 5.3 Réponses
```python
# ✅ Bon - Succès/échec clair
{
"success": true,
"message": "Shutdown en cours...",
"timestamp": "2025-12-23T10:30:05Z"
}
# En cas d'erreur
{
"success": false,
"message": "Permissions insuffisantes",
"error_code": "PERMISSION_DENIED",
"timestamp": "2025-12-23T10:30:05Z"
}
```
## 6. Tests
### 6.1 Tests Unitaires
```python
# tests/test_mqtt_agent.py
import pytest
from unittest.mock import MagicMock, patch
from ipwatch_mqtt_agent import IPWatchMQTTAgent
def test_get_ip_address():
"""Test récupération adresse IP"""
agent = IPWatchMQTTAgent()
ip = agent.get_ip_address()
assert isinstance(ip, str)
assert len(ip.split('.')) == 4
def test_publish_status():
"""Test publication du statut"""
agent = IPWatchMQTTAgent()
agent.client = MagicMock()
agent.publish_status()
agent.client.publish.assert_called_once()
args = agent.client.publish.call_args
assert "ipwatch/device" in args[0][0]
@patch('subprocess.run')
def test_shutdown(mock_subprocess):
"""Test commande shutdown"""
agent = IPWatchMQTTAgent()
agent.shutdown()
mock_subprocess.assert_called_once()
assert "shutdown" in mock_subprocess.call_args[0][0]
```
### 6.2 Tests d'Intégration
```python
# tests/test_mqtt_integration.py
import time
import paho.mqtt.client as mqtt
def test_command_flow():
"""Test flux complet command → response"""
received_messages = []
def on_message(client, userdata, msg):
received_messages.append(msg.payload.decode())
# Setup client test
client = mqtt.Client("test-client")
client.on_message = on_message
client.connect("localhost", 1883)
client.subscribe("ipwatch/device/+/response")
client.loop_start()
# Envoyer commande
client.publish(
"ipwatch/device/10.0.0.100/command",
'{"command": "status"}',
qos=1
)
# Attendre réponse
time.sleep(2)
assert len(received_messages) > 0
assert "success" in received_messages[0]
client.loop_stop()
client.disconnect()
```
## 7. Sécurité
### 7.1 Credentials
```python
# ✅ Bon - Variables d'environnement
MQTT_USERNAME = os.getenv('MQTT_USERNAME')
MQTT_PASSWORD = os.getenv('MQTT_PASSWORD')
if not MQTT_USERNAME or not MQTT_PASSWORD:
logger.warning("⚠ MQTT sans authentification")
# ❌ Mauvais - Hardcodé
MQTT_USERNAME = "ipwatch"
MQTT_PASSWORD = "password123"
```
### 7.2 Validation des Entrées
```python
# ✅ Bon - Validation stricte
def execute_command(command):
"""Exécute une commande MQTT"""
ALLOWED_COMMANDS = ['shutdown', 'reboot', 'status']
if command not in ALLOWED_COMMANDS:
logger.error(f"✗ Commande refusée: {command}")
return False
# Exécution sécurisée
if command == "shutdown":
return execute_shutdown()
# ...
# ❌ Mauvais - Exécution directe
def execute_command(command):
subprocess.run(command, shell=True) # DANGER: injection!
```
## 8. Performance
### 8.1 Optimisation des Payloads
```python
# ✅ Bon - Payload compact
payload = json.dumps({
"cmd": "status", # Abréviation pour réduire taille
"ts": int(time.time()) # Timestamp Unix
}, separators=(',', ':')) # Pas d'espaces
# ❌ Mauvais - Payload volumineux
payload = json.dumps({
"command": "status",
"timestamp": "2025-12-23T10:30:00.123456Z",
"extra_field_1": None,
"extra_field_2": None
}, indent=2) # Indentation inutile
```
### 8.2 Gestion de la Boucle
```python
# ✅ Bon - Boucle non-bloquante
client.loop_start() # Thread séparé
while True:
time.sleep(30)
publish_status()
# ❌ Mauvais - Boucle bloquante
while True:
client.loop() # Bloque le thread principal
time.sleep(30)
```
## 9. Documentation
### 9.1 Docstrings
```python
# ✅ Bon - Docstring complet
def send_mqtt_command(ip_address, command):
"""
Envoie une commande MQTT à un équipement
Cette fonction publie une commande sur le topic MQTT correspondant
à l'adresse IP fournie. La commande est envoyée avec QoS 1 pour
garantir la livraison.
Args:
ip_address (str): Adresse IP de l'équipement cible
command (str): Commande à envoyer (shutdown, reboot, status)
Returns:
bool: True si la commande a été publiée avec succès
Raises:
ValueError: Si la commande n'est pas valide
ConnectionError: Si le broker MQTT est inaccessible
Example:
>>> send_mqtt_command("192.168.1.100", "status")
True
"""
pass
# ❌ Mauvais - Pas de documentation
def send_mqtt_command(ip, cmd):
pass
```
## 10. Checklist Pré-commit
Avant de commit du code MQTT, vérifier :
- [ ] Code formaté avec `black` (PEP 8)
- [ ] Pas de secrets/credentials hardcodés
- [ ] Logging approprié (info/error/debug)
- [ ] Gestion des erreurs avec try/except
- [ ] Docstrings sur fonctions/classes publiques
- [ ] Tests unitaires passent
- [ ] QoS 1 pour messages critiques
- [ ] Validation des payloads JSON
- [ ] Topics respectent la convention
- [ ] Variables d'environnement documentées
## 11. Outils Recommandés
```bash
# Formatage code
pip install black
black mqtt/client/
# Linting
pip install pylint
pylint mqtt/client/ipwatch_mqtt_agent.py
# Tests
pip install pytest pytest-cov
pytest mqtt/tests/ --cov
# Type checking
pip install mypy
mypy mqtt/client/
```
## 12. Exemples de Code Complet
Voir :
- `mqtt/client/ipwatch_mqtt_agent.py` - Agent MQTT client
- `backend/app/services/mqtt_client.py` - Client MQTT backend
- `backend/app/routers/tracking.py` - Endpoints API MQTT