You can now add remotes to IkeaOutlets
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dreaded_X 2023-08-24 02:21:16 +02:00
parent 1ba20c3390
commit 15cde02a8d
Signed by: Dreaded_X
GPG Key ID: FA5F485356B0D2D4
3 changed files with 52 additions and 21 deletions

View File

@ -62,6 +62,7 @@ name = "Kettle"
room = "Kitchen" room = "Kitchen"
topic = "zigbee2mqtt/kitchen/kettle" topic = "zigbee2mqtt/kitchen/kettle"
timeout = 300 timeout = 300
remotes = [{ topic = "zigbee2mqtt/bedroom/remote" }]
[device.bathroom_light] [device.bathroom_light]

View File

@ -64,7 +64,7 @@ name = "Kettle"
room = "Kitchen" room = "Kitchen"
topic = "zigbee2mqtt/kitchen/kettle" topic = "zigbee2mqtt/kitchen/kettle"
timeout = 5 timeout = 5
remotes = [{ topic = "zigbee2mqtt/bedroom/remote" }]
[device.bathroom_light] [device.bathroom_light]
type = "IkeaOutlet" type = "IkeaOutlet"

View File

@ -7,7 +7,7 @@ use google_home::{
types::Type, types::Type,
GoogleHomeDevice, GoogleHomeDevice,
}; };
use rumqttc::{AsyncClient, Publish}; use rumqttc::{matches, AsyncClient, Publish};
use serde::Deserialize; use serde::Deserialize;
use serde_with::serde_as; use serde_with::serde_as;
use serde_with::DurationSeconds; use serde_with::DurationSeconds;
@ -21,7 +21,7 @@ use crate::devices::Device;
use crate::error::DeviceConfigError; use crate::error::DeviceConfigError;
use crate::event::OnMqtt; use crate::event::OnMqtt;
use crate::event::OnPresence; use crate::event::OnPresence;
use crate::messages::OnOffMessage; use crate::messages::{OnOffMessage, RemoteAction, RemoteMessage};
use crate::traits::Timeout; use crate::traits::Timeout;
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] #[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
@ -43,6 +43,8 @@ pub struct IkeaOutletConfig {
outlet_type: OutletType, outlet_type: OutletType,
#[serde_as(as = "Option<DurationSeconds>")] #[serde_as(as = "Option<DurationSeconds>")]
timeout: Option<Duration>, // Timeout in seconds timeout: Option<Duration>, // Timeout in seconds
#[serde(default)]
pub remotes: Vec<MqttDeviceConfig>,
} }
fn default_outlet_type() -> OutletType { fn default_outlet_type() -> OutletType {
@ -69,6 +71,7 @@ impl DeviceConfig for IkeaOutletConfig {
mqtt: self.mqtt, mqtt: self.mqtt,
outlet_type: self.outlet_type, outlet_type: self.outlet_type,
timeout: self.timeout, timeout: self.timeout,
remotes: self.remotes,
client: ext.client.clone(), client: ext.client.clone(),
last_known_state: false, last_known_state: false,
handle: None, handle: None,
@ -85,6 +88,7 @@ struct IkeaOutlet {
mqtt: MqttDeviceConfig, mqtt: MqttDeviceConfig,
outlet_type: OutletType, outlet_type: OutletType,
timeout: Option<Duration>, timeout: Option<Duration>,
remotes: Vec<MqttDeviceConfig>,
client: AsyncClient, client: AsyncClient,
last_known_state: bool, last_known_state: bool,
@ -117,33 +121,59 @@ impl Device for IkeaOutlet {
#[async_trait] #[async_trait]
impl OnMqtt for IkeaOutlet { impl OnMqtt for IkeaOutlet {
fn topics(&self) -> Vec<&str> { fn topics(&self) -> Vec<&str> {
vec![&self.mqtt.topic] let mut topics: Vec<_> = self
.remotes
.iter()
.map(|mqtt| mqtt.topic.as_str())
.collect();
topics.push(&self.mqtt.topic);
topics
} }
async fn on_mqtt(&mut self, message: Publish) { async fn on_mqtt(&mut self, message: Publish) {
// Update the internal state based on what the device has reported // Check if the message is from the deviec itself or from a remote
let state = match OnOffMessage::try_from(message) { if matches(&message.topic, &self.mqtt.topic) {
Ok(state) => state.state(), // Update the internal state based on what the device has reported
Err(err) => { let state = match OnOffMessage::try_from(message) {
error!(id = self.identifier, "Failed to parse message: {err}"); Ok(state) => state.state(),
Err(err) => {
error!(id = self.identifier, "Failed to parse message: {err}");
return;
}
};
// No need to do anything if the state has not changed
if state == self.last_known_state {
return; return;
} }
};
// No need to do anything if the state has not changed // Abort any timer that is currently running
if state == self.last_known_state { self.stop_timeout().await.unwrap();
return;
}
// Abort any timer that is currently running debug!(id = self.identifier, "Updating state to {state}");
self.stop_timeout().await.unwrap(); self.last_known_state = state;
debug!(id = self.identifier, "Updating state to {state}"); // If this is a kettle start a timeout for turning it of again
self.last_known_state = state; if state && let Some(timeout) = self.timeout {
self.start_timeout(timeout).await.unwrap();
}
} else {
let action = match RemoteMessage::try_from(message) {
Ok(message) => message.action(),
Err(err) => {
error!(id = self.identifier, "Failed to parse message: {err}");
return;
}
};
// If this is a kettle start a timeout for turning it of again match action {
if state && let Some(timeout) = self.timeout { RemoteAction::On => self.set_on(true).await.unwrap(),
self.start_timeout(timeout).await.unwrap(); RemoteAction::BrightnessMoveUp => self.set_on(false).await.unwrap(),
RemoteAction::BrightnessStop => { /* Ignore this action */ },
_ => warn!("Expected ikea shortcut button which only supports 'on' and 'brightness_move_up', got: {action:?}")
}
} }
} }
} }