Setting the presence mqtt topic is now optional, if not set it will generate an appropriate value automatically

This commit is contained in:
Dreaded_X 2023-01-06 04:42:42 +01:00
parent cbcbd05613
commit 27a63b1a79
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
4 changed files with 50 additions and 28 deletions

View File

@ -52,5 +52,4 @@ speakers = "10.0.0.182"
[devices.hallway_frontdoor]
type = "ContactSensor"
topic = "zigbee2mqtt/hallway/frontdoor"
# @TODO This should be automatically constructed from the identifier and presence topic
presence = { topic = "automation_dev/presence/frontdoor", timeout = 10 }
presence = { timeout = 10 }

View File

@ -28,7 +28,7 @@ pub struct OpenIDConfig {
pub base_url: String
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct MqttConfig {
pub host: String,
pub port: u16,
@ -96,32 +96,43 @@ pub struct HueBridgeConfig {
pub flags: Flags,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct InfoConfig {
pub name: String,
pub room: Option<String>,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct MqttDeviceConfig {
pub topic: String,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct KettleConfig {
pub timeout: Option<u64>, // Timeout in seconds
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Clone, Deserialize)]
pub struct PresenceDeviceConfig {
#[serde(flatten)]
pub mqtt: MqttDeviceConfig,
pub mqtt: Option<MqttDeviceConfig>,
// @TODO Maybe make this an option? That way if no timeout is set it will immediately turn the
// device off again?
pub timeout: u64 // Timeout in seconds
}
#[derive(Debug, Deserialize)]
impl PresenceDeviceConfig {
/// Set the mqtt topic to an appropriate value if it is not already set
fn generate_topic(&mut self, identifier: &str, config: &Config) {
if self.mqtt.is_none() {
let topic = config.presence.topic.clone() + "/" + identifier;
trace!("Setting presence mqtt topic: {topic}");
self.mqtt = Some(MqttDeviceConfig { topic });
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
pub enum Device {
IkeaOutlet {
@ -182,7 +193,7 @@ impl Config {
}
impl Device {
pub fn into(self, identifier: String, client: AsyncClient) -> DeviceBox {
pub fn into(self, identifier: String, config: &Config, client: AsyncClient) -> DeviceBox {
match self {
Device::IkeaOutlet { info, mqtt, kettle } => {
trace!(id = identifier, "IkeaOutlet [{} in {:?}]", info.name, info.room);
@ -196,8 +207,11 @@ impl Device {
trace!(id = identifier, "AudioSetup [{}]", identifier);
Box::new(AudioSetup::new(identifier, mqtt, mixer, speakers, client))
},
Device::ContactSensor { mqtt, presence } => {
Device::ContactSensor { mqtt, mut presence } => {
trace!(id = identifier, "ContactSensor [{}]", identifier);
if let Some(presence) = &mut presence {
presence.generate_topic(&identifier, &config);
}
Box::new(ContactSensor::new(identifier, mqtt, presence, client))
},
}

View File

@ -3,7 +3,7 @@ use std::time::Duration;
use pollster::FutureExt;
use rumqttc::AsyncClient;
use tokio::task::JoinHandle;
use tracing::{error, debug};
use tracing::{error, debug, warn};
use crate::{config::{MqttDeviceConfig, PresenceDeviceConfig}, mqtt::{OnMqtt, ContactMessage, PresenceMessage}};
@ -68,17 +68,25 @@ impl OnMqtt for ContactSensor {
None => return,
};
let topic = match &presence.mqtt {
Some(mqtt) => mqtt.topic.clone(),
None => {
warn!("Contact sensors is configured as a presence sensor, but no mqtt topic is specified");
return;
}
};
if !is_closed {
// Activate presence and stop any timeout once we open the door
if let Some(handle) = self.handle.take() {
handle.abort();
}
self.client.publish(presence.mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&PresenceMessage::new(true)).unwrap()).block_on().unwrap();
self.client.publish(topic, rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&PresenceMessage::new(true)).unwrap()).block_on().unwrap();
} else {
// Once the door is closed again we start a timeout for removing the presence
let client = self.client.clone();
let topic = presence.mqtt.topic.clone();
let id = self.identifier.clone();
let timeout = Duration::from_secs(presence.timeout);
self.handle = Some(

View File

@ -44,8 +44,9 @@ async fn main() {
info!("Starting automation_rs...");
// Configure MQTT
let mut mqttoptions = MqttOptions::new("rust-test", config.mqtt.host, config.mqtt.port);
mqttoptions.set_credentials(config.mqtt.username, config.mqtt.password);
let mqtt = config.mqtt.clone();
let mut mqttoptions = MqttOptions::new("rust-test", mqtt.host, mqtt.port);
mqttoptions.set_credentials(mqtt.username, mqtt.password);
mqttoptions.set_keep_alive(Duration::from_secs(5));
mqttoptions.set_transport(Transport::tls_with_default_config());
@ -57,6 +58,18 @@ async fn main() {
let devices = Arc::new(RwLock::new(Devices::new()));
mqtt.add_listener(Arc::downgrade(&devices));
// Turn the config into actual devices and add them
config.devices.clone()
.into_iter()
.map(|(identifier, device_config)| {
// This can technically block, but this only happens during start-up, so should not be
// a problem
device_config.into(identifier, &config, client.clone())
})
.for_each(|device| {
devices.write().unwrap().add_device(device);
});
// Setup presence system
let mut presence = Presence::new(config.presence, client.clone());
// Register devices as presence listener
@ -82,18 +95,6 @@ async fn main() {
// Start mqtt, this spawns a seperate async task
mqtt.start();
// Turn the config into actual devices and add them
config.devices
.into_iter()
.map(|(identifier, device_config)| {
// This can technically block, but this only happens during start-up, so should not be
// a problem
device_config.into(identifier, client.clone())
})
.for_each(|device| {
devices.write().unwrap().add_device(device);
});
// Create google home fullfillment route
let fullfillment = Router::new()
.route("/google_home", post(async move |user: User, Json(payload): Json<Request>| {