Improved how mqtt topics are handled

This commit is contained in:
Dreaded_X 2023-01-06 05:25:39 +01:00
parent 1326a8878c
commit 47afda8dee
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
8 changed files with 58 additions and 48 deletions

View File

@ -11,7 +11,7 @@ password="${MQTT_PASSWORD}"
topic = "${NTFY_TOPIC}" topic = "${NTFY_TOPIC}"
[presence] [presence]
topic = "automation_dev/presence" topic = "automation_dev/presence/+"
[hue_bridge] [hue_bridge]
ip = "10.0.0.146" ip = "10.0.0.146"

View File

@ -2,7 +2,7 @@ use std::{fs, error::Error, collections::HashMap, net::{Ipv4Addr, SocketAddr}};
use regex::{Regex, Captures}; use regex::{Regex, Captures};
use tracing::{debug, trace, error}; use tracing::{debug, trace, error};
use rumqttc::AsyncClient; use rumqttc::{AsyncClient, has_wildcards};
use serde::Deserialize; use serde::Deserialize;
use crate::devices::{DeviceBox, IkeaOutlet, WakeOnLAN, AudioSetup, ContactSensor}; use crate::devices::{DeviceBox, IkeaOutlet, WakeOnLAN, AudioSetup, ContactSensor};
@ -125,7 +125,7 @@ impl PresenceDeviceConfig {
/// Set the mqtt topic to an appropriate value if it is not already set /// Set the mqtt topic to an appropriate value if it is not already set
fn generate_topic(&mut self, identifier: &str, config: &Config) { fn generate_topic(&mut self, identifier: &str, config: &Config) {
if self.mqtt.is_none() { if self.mqtt.is_none() {
let topic = config.presence.topic.clone() + "/" + identifier; let topic = config.presence.topic.replace('+', identifier).replace('#', identifier);
trace!("Setting presence mqtt topic: {topic}"); trace!("Setting presence mqtt topic: {topic}");
self.mqtt = Some(MqttDeviceConfig { topic }); self.mqtt = Some(MqttDeviceConfig { topic });
} }
@ -187,7 +187,16 @@ impl Config {
return Err("Missing environment variables".into()); return Err("Missing environment variables".into());
} }
let config = toml::from_str(&file)?; let config: Config = toml::from_str(&file)?;
// Some extra config validation
if !has_wildcards(&config.presence.topic) {
return Err(format!("Invalid presence topic '{}', needs to contain a wildcard (+/#) in order to listen to presence devices", config.presence.topic).into());
}
// @TODO It would be nice it was possible to add validation to serde,
// that way we can check that the provided mqtt topics are actually valid
Ok(config) Ok(config)
} }
} }

View File

@ -4,7 +4,7 @@ use std::net::{TcpStream, SocketAddr, Ipv4Addr};
use bytes::{BufMut, Buf}; use bytes::{BufMut, Buf};
use google_home::errors::{ErrorCode, DeviceError}; use google_home::errors::{ErrorCode, DeviceError};
use google_home::traits::{self, OnOff}; use google_home::traits::{self, OnOff};
use rumqttc::AsyncClient; use rumqttc::{AsyncClient, matches};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{error, warn}; use tracing::{error, warn};
use pollster::FutureExt as _; use pollster::FutureExt as _;
@ -225,7 +225,7 @@ impl Device for AudioSetup {
impl OnMqtt for AudioSetup { impl OnMqtt for AudioSetup {
fn on_mqtt(&mut self, message: &rumqttc::Publish) { fn on_mqtt(&mut self, message: &rumqttc::Publish) {
if message.topic != self.mqtt.topic { if !matches(&message.topic, &self.mqtt.topic) {
return; return;
} }

View File

@ -1,7 +1,7 @@
use std::time::Duration; use std::time::Duration;
use pollster::FutureExt; use pollster::FutureExt;
use rumqttc::AsyncClient; use rumqttc::{AsyncClient, matches};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use tracing::{error, debug, warn}; use tracing::{error, debug, warn};
@ -50,7 +50,7 @@ impl OnPresence for ContactSensor {
impl OnMqtt for ContactSensor { impl OnMqtt for ContactSensor {
fn on_mqtt(&mut self, message: &rumqttc::Publish) { fn on_mqtt(&mut self, message: &rumqttc::Publish) {
if message.topic != self.mqtt.topic { if !matches(&message.topic, &self.mqtt.topic) {
return; return;
} }

View File

@ -2,7 +2,7 @@ use std::time::Duration;
use google_home::errors::ErrorCode; use google_home::errors::ErrorCode;
use google_home::{GoogleHomeDevice, device, types::Type, traits}; use google_home::{GoogleHomeDevice, device, types::Type, traits};
use rumqttc::{AsyncClient, Publish}; use rumqttc::{AsyncClient, Publish, matches};
use tracing::{debug, trace, error}; use tracing::{debug, trace, error};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use pollster::FutureExt as _; use pollster::FutureExt as _;
@ -48,7 +48,7 @@ impl Device for IkeaOutlet {
impl OnMqtt for IkeaOutlet { impl OnMqtt for IkeaOutlet {
fn on_mqtt(&mut self, message: &Publish) { fn on_mqtt(&mut self, message: &Publish) {
// Update the internal state based on what the device has reported // Update the internal state based on what the device has reported
if message.topic != self.mqtt.topic { if !matches(&message.topic, &self.mqtt.topic) {
return; return;
} }

View File

@ -1,6 +1,6 @@
use google_home::{GoogleHomeDevice, types::Type, device, traits::{self, Scene}, errors::{ErrorCode, DeviceError}}; use google_home::{GoogleHomeDevice, types::Type, device, traits::{self, Scene}, errors::{ErrorCode, DeviceError}};
use tracing::{debug, error}; use tracing::{debug, error};
use rumqttc::{AsyncClient, Publish}; use rumqttc::{AsyncClient, Publish, matches};
use pollster::FutureExt as _; use pollster::FutureExt as _;
use crate::{config::{InfoConfig, MqttDeviceConfig}, mqtt::{OnMqtt, ActivateMessage}}; use crate::{config::{InfoConfig, MqttDeviceConfig}, mqtt::{OnMqtt, ActivateMessage}};
@ -31,7 +31,7 @@ impl Device for WakeOnLAN {
impl OnMqtt for WakeOnLAN { impl OnMqtt for WakeOnLAN {
fn on_mqtt(&mut self, message: &Publish) { fn on_mqtt(&mut self, message: &Publish) {
if message.topic != self.mqtt.topic { if !matches(&message.topic, &self.mqtt.topic) {
return; return;
} }

View File

@ -1,7 +1,7 @@
use std::sync::{Weak, RwLock}; use std::sync::{Weak, RwLock};
use pollster::FutureExt as _; use pollster::FutureExt as _;
use rumqttc::AsyncClient; use rumqttc::{AsyncClient, matches};
use tracing::{span, Level, log::{error, trace}, debug}; use tracing::{span, Level, log::{error, trace}, debug};
use crate::{config::{MqttDeviceConfig, LightSensorConfig}, mqtt::{OnMqtt, BrightnessMessage}}; use crate::{config::{MqttDeviceConfig, LightSensorConfig}, mqtt::{OnMqtt, BrightnessMessage}};
@ -42,7 +42,7 @@ impl LightSensor {
impl OnMqtt for LightSensor { impl OnMqtt for LightSensor {
fn on_mqtt(&mut self, message: &rumqttc::Publish) { fn on_mqtt(&mut self, message: &rumqttc::Publish) {
if message.topic != self.mqtt.topic { if !matches(&message.topic, &self.mqtt.topic) {
return; return;
} }

View File

@ -1,7 +1,7 @@
use std::{sync::{Weak, RwLock}, collections::HashMap}; use std::{sync::{Weak, RwLock}, collections::HashMap};
use tracing::{debug, span, Level, error}; use tracing::{debug, span, Level, error};
use rumqttc::AsyncClient; use rumqttc::{AsyncClient, matches};
use pollster::FutureExt as _; use pollster::FutureExt as _;
use crate::{mqtt::{OnMqtt, PresenceMessage}, config::MqttDeviceConfig}; use crate::{mqtt::{OnMqtt, PresenceMessage}, config::MqttDeviceConfig};
@ -19,9 +19,7 @@ pub struct Presence {
impl Presence { impl Presence {
pub fn new(mqtt: MqttDeviceConfig, client: AsyncClient) -> Self { pub fn new(mqtt: MqttDeviceConfig, client: AsyncClient) -> Self {
// @TODO Handle potential errors here client.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce).block_on().unwrap();
let topic = mqtt.topic.clone() + "/+";
client.subscribe(topic, rumqttc::QoS::AtLeastOnce).block_on().unwrap();
Self { listeners: Vec::new(), devices: HashMap::new(), overall_presence: false, mqtt } Self { listeners: Vec::new(), devices: HashMap::new(), overall_presence: false, mqtt }
} }
@ -42,7 +40,11 @@ impl Presence {
impl OnMqtt for Presence { impl OnMqtt for Presence {
fn on_mqtt(&mut self, message: &rumqttc::Publish) { fn on_mqtt(&mut self, message: &rumqttc::Publish) {
if message.topic.starts_with(&(self.mqtt.topic.clone() + "/")) { if !matches(&message.topic, &self.mqtt.topic) {
return;
}
// @TODO More robust mechanism for splitting
let device_name = message.topic.rsplit_once("/").unwrap().1; let device_name = message.topic.rsplit_once("/").unwrap().1;
if message.payload.len() == 0 { if message.payload.len() == 0 {
@ -79,4 +81,3 @@ impl OnMqtt for Presence {
} }
} }
} }
}