Files
ipwatch/mqtt/docs/HOMEASSISTANT_SPEC.md
2026-02-07 16:57:37 +01:00

24 KiB

🏠 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 :

{
  "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 :

{
  "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 :

{
  "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 :

{
  "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 :

{
  "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 :

# 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 :

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 :

# 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)

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)

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)

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)

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)

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

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é

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)

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

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 :

{
  "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 :

"""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 :

"""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 :

"""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 :

"""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 :

#!/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 :

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 :

# 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

# É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

# 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

# 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

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)