// Ce module regroupe la publication Home Assistant discovery. use anyhow::{Context, Result}; use rumqttc::AsyncClient; use serde::Serialize; use crate::config::{base_device_topic, Config}; #[derive(Clone, Serialize)] struct DeviceInfo { identifiers: Vec, name: String, manufacturer: String, model: String, sw_version: String, #[serde(skip_serializing_if = "Option::is_none")] suggested_area: Option, } #[derive(Serialize)] struct EntityConfig<'a> { name: &'a str, unique_id: String, state_topic: String, availability_topic: String, payload_available: &'a str, payload_not_available: &'a str, device: DeviceInfo, #[serde(skip_serializing_if = "Option::is_none")] command_topic: Option, #[serde(skip_serializing_if = "Option::is_none")] payload_on: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] payload_off: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] unit_of_measurement: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] device_class: Option<&'a str>, #[serde(skip_serializing_if = "Option::is_none")] icon: Option<&'a str>, } // Publie les entites HA discovery pour les capteurs et commandes standard. pub async fn publish_all(client: &AsyncClient, cfg: &Config) -> Result<()> { let base = base_device_topic(cfg); let prefix = cfg.mqtt.discovery_prefix.trim_end_matches('/'); let device = DeviceInfo { identifiers: cfg.device.identifiers.clone(), name: cfg.device.name.clone(), manufacturer: cfg.device.manufacturer.clone(), model: cfg.device.model.clone(), sw_version: cfg.device.sw_version.clone(), suggested_area: cfg.device.suggested_area.clone(), }; let sensors = vec![ ("cpu_usage", "CPU Usage", Some("%"), None, Some("mdi:chip")), ("memory_used_mb", "Memory Used", Some("MB"), None, Some("mdi:memory")), ("memory_total_mb", "Memory Total", Some("MB"), None, Some("mdi:memory")), ("ip_address", "IP Address", None, None, Some("mdi:ip")), ("power_state", "Power State", None, None, Some("mdi:power")), ("battery_level", "Battery Level", Some("%"), Some("battery"), Some("mdi:battery")), ("battery_state", "Battery State", None, None, Some("mdi:battery-charging")), ]; for (key, name, unit, class, icon) in sensors { let entity_name = format!("{}_{}", key, cfg.device.name); let entity = EntityConfig { name: &entity_name, unique_id: format!("{}_{}", cfg.device.name, key), state_topic: format!("{}/{}", base, key), availability_topic: format!("{}/{}/available", base, key), payload_available: "online", payload_not_available: "offline", device: DeviceInfo { ..device.clone() }, command_topic: None, payload_on: None, payload_off: None, unit_of_measurement: unit, device_class: class, icon, }; let topic = format!("{}/sensor/{}/{}/config", prefix, cfg.device.name, entity_name); publish_discovery(client, &topic, &entity).await?; } let switches = vec![ ("shutdown", "Shutdown", "cmd/shutdown/set"), ("reboot", "Reboot", "cmd/reboot/set"), ("sleep", "Sleep", "cmd/sleep/set"), ("screen", "Screen", "cmd/screen/set"), ]; for (key, name, cmd) in switches { let entity_name = format!("{}_{}", key, cfg.device.name); let entity = EntityConfig { name: &entity_name, unique_id: format!("{}_{}", cfg.device.name, key), state_topic: format!("{}/{}/state", base, key), availability_topic: format!("{}/{}/available", base, key), payload_available: "online", payload_not_available: "offline", device: DeviceInfo { ..device.clone() }, command_topic: Some(format!("{}/{}", base, cmd)), payload_on: Some("ON"), payload_off: Some("OFF"), unit_of_measurement: None, device_class: Some("switch"), icon: Some("mdi:power"), }; let topic = format!("{}/switch/{}/{}/config", prefix, cfg.device.name, entity_name); publish_discovery(client, &topic, &entity).await?; } Ok(()) } async fn publish_discovery(client: &AsyncClient, topic: &str, payload: &T) -> Result<()> { let data = serde_json::to_vec(payload).context("serialize discovery")?; client .publish(topic, rumqttc::QoS::AtLeastOnce, true, data) .await .context("publish discovery")?; Ok(()) }