diff --git a/google-home/src/attributes.rs b/google-home/src/attributes.rs index 523d5db..3463e14 100644 --- a/google-home/src/attributes.rs +++ b/google-home/src/attributes.rs @@ -17,4 +17,6 @@ pub struct Attributes { pub command_only_fan_speed: Option, #[serde(skip_serializing_if = "Option::is_none")] pub available_fan_speeds: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub query_only_humidity_setting: Option, } diff --git a/google-home/src/device.rs b/google-home/src/device.rs index 26c93c0..0cbc6bc 100644 --- a/google-home/src/device.rs +++ b/google-home/src/device.rs @@ -4,7 +4,7 @@ use serde::Serialize; use crate::errors::{DeviceError, ErrorCode}; use crate::request::execute::CommandType; use crate::response; -use crate::traits::{FanSpeed, OnOff, Scene, Trait}; +use crate::traits::{FanSpeed, HumiditySetting, OnOff, Scene, Trait}; use crate::types::Type; // TODO: Find a more elegant way to do this @@ -42,7 +42,7 @@ where } #[async_trait] -#[impl_cast::device(As: OnOff + Scene + FanSpeed)] +#[impl_cast::device(As: OnOff + Scene + FanSpeed + HumiditySetting)] pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { fn get_device_type(&self) -> Type; fn get_device_name(&self) -> Name; @@ -95,6 +95,12 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { device.attributes.available_fan_speeds = Some(fan_speed.available_speeds()); } + if let Some(humidity_setting) = As::::cast(self) { + traits.push(Trait::HumiditySetting); + device.attributes.query_only_humidity_setting = + humidity_setting.query_only_humidity_setting(); + } + device.traits = traits; device @@ -120,6 +126,11 @@ pub trait GoogleHomeDevice: AsGoogleHomeDevice + Sync + Send + 'static { device.state.current_fan_speed_setting = Some(fan_speed.current_speed().await); } + if let Some(humidity_setting) = As::::cast(self) { + device.state.humidity_ambient_percent = + Some(humidity_setting.humidity_ambient_percent().await); + } + device } diff --git a/google-home/src/response.rs b/google-home/src/response.rs index dfb68b4..448c094 100644 --- a/google-home/src/response.rs +++ b/google-home/src/response.rs @@ -36,4 +36,7 @@ pub struct State { #[serde(skip_serializing_if = "Option::is_none")] pub current_fan_speed_setting: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub humidity_ambient_percent: Option, } diff --git a/google-home/src/response/execute.rs b/google-home/src/response/execute.rs index 92badf9..9e5cbe1 100644 --- a/google-home/src/response/execute.rs +++ b/google-home/src/response/execute.rs @@ -98,6 +98,7 @@ mod tests { let state = State { on: Some(true), current_fan_speed_setting: None, + humidity_ambient_percent: None, }; let mut command = Command::new(Status::Success); command.states = Some(States { diff --git a/google-home/src/traits.rs b/google-home/src/traits.rs index c38100d..cae0340 100644 --- a/google-home/src/traits.rs +++ b/google-home/src/traits.rs @@ -11,6 +11,8 @@ pub enum Trait { Scene, #[serde(rename = "action.devices.traits.FanSpeed")] FanSpeed, + #[serde(rename = "action.devices.traits.HumiditySetting")] + HumiditySetting, } #[async_trait] @@ -72,3 +74,14 @@ pub trait FanSpeed { async fn current_speed(&self) -> String; async fn set_speed(&self, speed: &str) -> Result<(), ErrorCode>; } + +#[async_trait] +#[impl_cast::device_trait] +pub trait HumiditySetting { + // TODO: This implementation is not complete, I have only implemented what I need right now + fn query_only_humidity_setting(&self) -> Option { + None + } + + async fn humidity_ambient_percent(&self) -> isize; +} diff --git a/src/devices/air_filter.rs b/src/devices/air_filter.rs index ac9ec89..3a016fe 100644 --- a/src/devices/air_filter.rs +++ b/src/devices/air_filter.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; use google_home::device::Name; use google_home::errors::ErrorCode; -use google_home::traits::{AvailableSpeeds, FanSpeed, OnOff, Speed, SpeedValues}; +use google_home::traits::{AvailableSpeeds, FanSpeed, HumiditySetting, OnOff, Speed, SpeedValues}; use google_home::types::Type; use google_home::GoogleHomeDevice; use rumqttc::{AsyncClient, Publish}; @@ -13,7 +13,7 @@ use crate::device_manager::{ConfigExternal, DeviceConfig}; use crate::devices::Device; use crate::error::DeviceConfigError; use crate::event::OnMqtt; -use crate::messages::{AirFilterMessage, AirFilterState}; +use crate::messages::{AirFilterFanState, AirFilterState, SetAirFilterFanState}; #[derive(Debug, Deserialize)] pub struct AirFilterConfig { @@ -35,7 +35,10 @@ impl DeviceConfig for AirFilterConfig { info: self.info, mqtt: self.mqtt, client: ext.client.clone(), - last_known_state: AirFilterState::Off, + last_known_state: AirFilterState { + state: AirFilterFanState::Off, + humidity: 0.0, + }, }; Ok(Box::new(device)) @@ -53,8 +56,8 @@ pub struct AirFilter { } impl AirFilter { - async fn set_speed(&self, state: AirFilterState) { - let message = AirFilterMessage::new(state); + async fn set_speed(&self, state: AirFilterFanState) { + let message = SetAirFilterFanState::new(state); let topic = format!("{}/set", self.mqtt.topic); // TODO: Handle potential errors here @@ -84,8 +87,8 @@ impl OnMqtt for AirFilter { } async fn on_mqtt(&mut self, message: Publish) { - let state = match AirFilterMessage::try_from(message) { - Ok(state) => state.state(), + let state = match AirFilterState::try_from(message) { + Ok(state) => state, Err(err) => { error!(id = self.identifier, "Failed to parse message: {err}"); return; @@ -131,16 +134,16 @@ impl GoogleHomeDevice for AirFilter { #[async_trait] impl OnOff for AirFilter { async fn is_on(&self) -> Result { - Ok(self.last_known_state != AirFilterState::Off) + Ok(self.last_known_state.state != AirFilterFanState::Off) } async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> { debug!("Turning on air filter: {on}"); if on { - self.set_speed(AirFilterState::High).await; + self.set_speed(AirFilterFanState::High).await; } else { - self.set_speed(AirFilterState::Off).await; + self.set_speed(AirFilterFanState::Off).await; } Ok(()) @@ -186,11 +189,11 @@ impl FanSpeed for AirFilter { } async fn current_speed(&self) -> String { - let speed = match self.last_known_state { - AirFilterState::Off => "off", - AirFilterState::Low => "low", - AirFilterState::Medium => "medium", - AirFilterState::High => "high", + let speed = match self.last_known_state.state { + AirFilterFanState::Off => "off", + AirFilterFanState::Low => "low", + AirFilterFanState::Medium => "medium", + AirFilterFanState::High => "high", }; speed.into() @@ -198,13 +201,13 @@ impl FanSpeed for AirFilter { async fn set_speed(&self, speed: &str) -> Result<(), ErrorCode> { let state = if speed == "off" { - AirFilterState::Off + AirFilterFanState::Off } else if speed == "low" { - AirFilterState::Low + AirFilterFanState::Low } else if speed == "medium" { - AirFilterState::Medium + AirFilterFanState::Medium } else if speed == "high" { - AirFilterState::High + AirFilterFanState::High } else { return Err(google_home::errors::DeviceError::TransientError.into()); }; @@ -214,3 +217,14 @@ impl FanSpeed for AirFilter { Ok(()) } } + +#[async_trait] +impl HumiditySetting for AirFilter { + fn query_only_humidity_setting(&self) -> Option { + Some(true) + } + + async fn humidity_ambient_percent(&self) -> isize { + self.last_known_state.humidity.round() as isize + } +} diff --git a/src/messages.rs b/src/messages.rs index 724eabb..822036e 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -245,7 +245,7 @@ impl TryFrom for HueMessage { // TODO: Import this from the air_filter code itself instead of copying #[derive(PartialEq, Eq, Debug, Clone, Copy, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] -pub enum AirFilterState { +pub enum AirFilterFanState { Off, Low, Medium, @@ -253,21 +253,23 @@ pub enum AirFilterState { } #[derive(Debug, Clone, Copy, Deserialize, Serialize)] -pub struct AirFilterMessage { - state: AirFilterState, +pub struct SetAirFilterFanState { + state: AirFilterFanState, } -impl AirFilterMessage { - pub fn state(&self) -> AirFilterState { - self.state - } +#[derive(PartialEq, Debug, Clone, Copy, Deserialize, Serialize)] +pub struct AirFilterState { + pub state: AirFilterFanState, + pub humidity: f32, +} - pub fn new(state: AirFilterState) -> Self { +impl SetAirFilterFanState { + pub fn new(state: AirFilterFanState) -> Self { Self { state } } } -impl TryFrom for AirFilterMessage { +impl TryFrom for AirFilterState { type Error = ParseError; fn try_from(message: Publish) -> Result {