60b622c6be
This commit fixes the MQTT discovery payload structure to match the working
v1 implementation and comply with Home Assistant's MQTT discovery specification.
Key changes:
- Add "type" field to discovery payload (sensor/switch)
- Update discovery topic format: homeassistant/{component}/{node_id}/{entity_name}/config
- Fix entity naming: {metric}_{device} instead of descriptive names
- Separate state topics for sensors vs switches:
* Sensors: pilot/{device}/{metric} (no /state suffix)
* Switches: pilot/{device}/{metric}/state (with /state suffix)
- Add per-entity availability topics: pilot/{device}/{metric}/available
- Add publish_switch_state() function for proper switch state publishing
Discovery topic examples:
- homeassistant/sensor/asus/cpu_usage_asus/config
- homeassistant/switch/asus/shutdown_asus/config
State topic examples:
- pilot/asus/cpu_usage (sensor)
- pilot/asus/shutdown/state (switch)
This matches the Dell 5520 v1 configuration that works correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
148 lines
4.2 KiB
Rust
148 lines
4.2 KiB
Rust
// Ce module gere la connexion MQTT et les publications de base.
|
|
use anyhow::{Context, Result};
|
|
use rumqttc::{AsyncClient, EventLoop, LastWill, MqttOptions, QoS};
|
|
use serde::Serialize;
|
|
use std::time::Duration;
|
|
|
|
use crate::config::{base_device_topic, Config};
|
|
|
|
pub struct MqttHandle {
|
|
pub client: AsyncClient,
|
|
pub event_loop: EventLoop,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct Status {
|
|
pub version: String,
|
|
pub os: String,
|
|
pub uptime_s: u64,
|
|
pub last_error: String,
|
|
pub backends: Backends,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct Backends {
|
|
pub power: String,
|
|
pub screen: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct Capabilities {
|
|
pub telemetry: Vec<String>,
|
|
pub commands: Vec<String>,
|
|
pub gpu: bool,
|
|
}
|
|
|
|
// Cree un client MQTT configure selon le YAML.
|
|
pub fn connect(cfg: &Config) -> Result<MqttHandle> {
|
|
let client_id = if cfg.mqtt.client_id.trim().is_empty() {
|
|
cfg.device.name.clone()
|
|
} else {
|
|
cfg.mqtt.client_id.clone()
|
|
};
|
|
|
|
let mut options = MqttOptions::new(client_id, cfg.mqtt.host.clone(), cfg.mqtt.port);
|
|
options.set_keep_alive(Duration::from_secs(cfg.mqtt.keepalive_s));
|
|
|
|
if !cfg.mqtt.username.trim().is_empty() || !cfg.mqtt.password.trim().is_empty() {
|
|
options.set_credentials(cfg.mqtt.username.clone(), cfg.mqtt.password.clone());
|
|
}
|
|
|
|
let will_topic = format!("{}/availability", base_device_topic(cfg));
|
|
let will = LastWill::new(will_topic, "offline", qos(cfg), true);
|
|
options.set_last_will(will);
|
|
|
|
let (client, event_loop) = AsyncClient::new(options, 10);
|
|
Ok(MqttHandle { client, event_loop })
|
|
}
|
|
|
|
// Publie availability en retained pour indiquer online/offline.
|
|
pub async fn publish_availability(client: &AsyncClient, cfg: &Config, online: bool) -> Result<()> {
|
|
let topic = format!("{}/availability", base_device_topic(cfg));
|
|
let payload = if online { "online" } else { "offline" };
|
|
client
|
|
.publish(topic, qos(cfg), true, payload)
|
|
.await
|
|
.context("publish availability")?;
|
|
Ok(())
|
|
}
|
|
|
|
// Publie un status JSON (version, OS, backends, etc.).
|
|
pub async fn publish_status(
|
|
client: &AsyncClient,
|
|
cfg: &Config,
|
|
status: &Status,
|
|
) -> Result<()> {
|
|
let topic = format!("{}/status", base_device_topic(cfg));
|
|
let payload = serde_json::to_vec(status).context("serialize status")?;
|
|
client
|
|
.publish(topic, qos(cfg), true, payload)
|
|
.await
|
|
.context("publish status")?;
|
|
Ok(())
|
|
}
|
|
|
|
// Publie les capacites actives (telemetrie/commandes).
|
|
pub async fn publish_capabilities(
|
|
client: &AsyncClient,
|
|
cfg: &Config,
|
|
capabilities: &Capabilities,
|
|
) -> Result<()> {
|
|
let topic = format!("{}/capabilities", base_device_topic(cfg));
|
|
let payload = serde_json::to_vec(capabilities).context("serialize capabilities")?;
|
|
client
|
|
.publish(topic, qos(cfg), true, payload)
|
|
.await
|
|
.context("publish capabilities")?;
|
|
Ok(())
|
|
}
|
|
|
|
// Publie une valeur de capteur (sensors: sans /state à la fin).
|
|
pub async fn publish_state(
|
|
client: &AsyncClient,
|
|
cfg: &Config,
|
|
name: &str,
|
|
value: &str,
|
|
) -> Result<()> {
|
|
let topic = format!("{}/{}", base_device_topic(cfg), name);
|
|
client
|
|
.publish(topic, qos(cfg), cfg.mqtt.retain_states, value)
|
|
.await
|
|
.context("publish state")?;
|
|
Ok(())
|
|
}
|
|
|
|
// Publie l'état d'un switch (avec /state à la fin).
|
|
pub async fn publish_switch_state(
|
|
client: &AsyncClient,
|
|
cfg: &Config,
|
|
name: &str,
|
|
value: &str,
|
|
) -> Result<()> {
|
|
let topic = format!("{}/{}/state", base_device_topic(cfg), name);
|
|
client
|
|
.publish(topic, qos(cfg), cfg.mqtt.retain_states, value)
|
|
.await
|
|
.context("publish switch state")?;
|
|
Ok(())
|
|
}
|
|
|
|
// S'abonne aux commandes standard (cmd/<action>/set).
|
|
pub async fn subscribe_commands(client: &AsyncClient, cfg: &Config) -> Result<()> {
|
|
let topic = format!("{}/cmd/+/set", base_device_topic(cfg));
|
|
client
|
|
.subscribe(topic, qos(cfg))
|
|
.await
|
|
.context("subscribe commands")?;
|
|
Ok(())
|
|
}
|
|
|
|
// Convertit le QoS configure (0/1/2) en enum rumqttc.
|
|
fn qos(cfg: &Config) -> QoS {
|
|
match cfg.mqtt.qos {
|
|
1 => QoS::AtLeastOnce,
|
|
2 => QoS::ExactlyOnce,
|
|
_ => QoS::AtMostOnce,
|
|
}
|
|
}
|