From 90a94934fb7eb0cbb2992b448c4832b723460e87 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Wed, 11 Dec 2024 22:19:31 +0100 Subject: [PATCH] Added open close trait and google home support for contact sensor --- automation_devices/src/contact_sensor.rs | 78 ++++++++++++++++++++++-- automation_devices/src/lib.rs | 22 +++++++ google_home/google_home/src/traits.rs | 7 +++ google_home/google_home/src/types.rs | 6 ++ 4 files changed, 109 insertions(+), 4 deletions(-) diff --git a/automation_devices/src/contact_sensor.rs b/automation_devices/src/contact_sensor.rs index 935b07b..90b1a49 100644 --- a/automation_devices/src/contact_sensor.rs +++ b/automation_devices/src/contact_sensor.rs @@ -3,7 +3,7 @@ use std::time::Duration; use async_trait::async_trait; use automation_lib::action_callback::ActionCallback; -use automation_lib::config::MqttDeviceConfig; +use automation_lib::config::{InfoConfig, MqttDeviceConfig}; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_lib::error::DeviceConfigError; use automation_lib::event::{OnMqtt, OnPresence}; @@ -11,10 +11,22 @@ use automation_lib::messages::{ContactMessage, PresenceMessage}; use automation_lib::mqtt::WrappedAsyncClient; use automation_lib::presence::DEFAULT_PRESENCE; use automation_macro::LuaDeviceConfig; +use google_home::device; +use google_home::errors::{DeviceError, ErrorCode}; +use google_home::traits::OpenClose; +use google_home::types::Type; +use serde::Deserialize; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio::task::JoinHandle; use tracing::{debug, error, trace, warn}; +#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)] +pub enum SensorType { + Door, + Drawer, + Window, +} + // NOTE: If we add more presence devices we might need to move this out of here #[derive(Debug, Clone, LuaDeviceConfig)] pub struct PresenceDeviceConfig { @@ -26,11 +38,16 @@ pub struct PresenceDeviceConfig { #[derive(Debug, Clone, LuaDeviceConfig)] pub struct Config { - pub identifier: String, + #[device_config(flatten)] + pub info: InfoConfig, #[device_config(flatten)] pub mqtt: MqttDeviceConfig, #[device_config(from_lua, default)] pub presence: Option, + + #[device_config(default(SensorType::Window))] + pub sensor_type: SensorType, + #[device_config(from_lua, default)] pub callback: ActionCallback, #[device_config(from_lua)] @@ -66,7 +83,7 @@ impl LuaDeviceCreate for ContactSensor { type Error = DeviceConfigError; async fn create(config: Self::Config) -> Result { - trace!(id = config.identifier, "Setting up ContactSensor"); + trace!(id = config.info.identifier(), "Setting up ContactSensor"); config .client @@ -86,7 +103,60 @@ impl LuaDeviceCreate for ContactSensor { impl Device for ContactSensor { fn get_id(&self) -> String { - self.config.identifier.clone() + self.config.info.identifier() + } +} + +impl google_home::Device for ContactSensor { + fn get_device_type(&self) -> google_home::types::Type { + match self.config.sensor_type { + SensorType::Door => Type::Door, + SensorType::Drawer => Type::Drawer, + SensorType::Window => Type::Window, + } + } + + fn get_id(&self) -> String { + Device::get_id(self) + } + + fn get_device_name(&self) -> google_home::device::Name { + device::Name::new(&self.config.info.name) + } + + fn get_room_hint(&self) -> Option<&str> { + self.config.info.room.as_deref() + } + + fn will_report_state(&self) -> bool { + false + } + + fn is_online(&self) -> bool { + true + } +} + +#[async_trait] +impl OpenClose for ContactSensor { + fn discrete_only_open_close(&self) -> Option { + Some(true) + } + + fn query_only_open_close(&self) -> Option { + Some(true) + } + + async fn open_percent(&self) -> Result { + if self.state().await.is_closed { + Ok(0) + } else { + Ok(100) + } + } + + async fn set_open_percent(&self, _open_percent: u8) -> Result<(), ErrorCode> { + Err(DeviceError::ActionNotAvailable.into()) } } diff --git a/automation_devices/src/lib.rs b/automation_devices/src/lib.rs index 1fae592..e5ab41a 100644 --- a/automation_devices/src/lib.rs +++ b/automation_devices/src/lib.rs @@ -96,6 +96,28 @@ macro_rules! impl_device { .unwrap()) }); } + + if impls::impls!($device: google_home::traits::OpenClose) { + // TODO: Make discrete_only_open_close and query_only_open_close static, that way we can + // add only the supported functions and drop _percet if discrete is true + methods.add_async_method("set_open_percent", |_lua, this, open_percent: u8| async move { + (this.deref().cast() as Option<&dyn google_home::traits::OpenClose>) + .expect("Cast should be valid") + .set_open_percent(open_percent) + .await + .unwrap(); + + Ok(()) + }); + + methods.add_async_method("open_percent", |_lua, this, _: ()| async move { + Ok((this.deref().cast() as Option<&dyn google_home::traits::OpenClose>) + .expect("Cast should be valid") + .open_percent() + .await + .unwrap()) + }); + } } } }; diff --git a/google_home/google_home/src/traits.rs b/google_home/google_home/src/traits.rs index 7137c15..7746462 100644 --- a/google_home/google_home/src/traits.rs +++ b/google_home/google_home/src/traits.rs @@ -14,6 +14,13 @@ traits! { async fn on(&self) -> Result, "action.devices.commands.OnOff" => async fn set_on(&self, on: bool) -> Result<(), ErrorCode>, }, + "action.devices.traits.OpenClose" => trait OpenClose { + discrete_only_open_close: Option, + command_only_open_close: Option, + query_only_open_close: Option, + async fn open_percent(&self) -> Result, + "action.devices.commands.OpenClose" => async fn set_open_percent(&self, open_percent: u8) -> Result<(), ErrorCode>, + }, "action.devices.traits.Brightness" => trait Brightness { command_only_brightness: Option, async fn brightness(&self) -> Result, diff --git a/google_home/google_home/src/types.rs b/google_home/google_home/src/types.rs index d082088..7b46f35 100644 --- a/google_home/google_home/src/types.rs +++ b/google_home/google_home/src/types.rs @@ -12,4 +12,10 @@ pub enum Type { Scene, #[serde(rename = "action.devices.types.AIRPURIFIER")] AirPurifier, + #[serde(rename = "action.devices.types.DOOR")] + Door, + #[serde(rename = "action.devices.types.WINDOW")] + Window, + #[serde(rename = "action.devices.types.DRAWER")] + Drawer, }