Compare commits
2 Commits
24815edd34
...
f577e9bfa5
Author | SHA1 | Date | |
---|---|---|---|
f577e9bfa5 | |||
90a94934fb |
|
@ -3,7 +3,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::action_callback::ActionCallback;
|
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::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::error::DeviceConfigError;
|
use automation_lib::error::DeviceConfigError;
|
||||||
use automation_lib::event::{OnMqtt, OnPresence};
|
use automation_lib::event::{OnMqtt, OnPresence};
|
||||||
|
@ -11,10 +11,22 @@ use automation_lib::messages::{ContactMessage, PresenceMessage};
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_lib::presence::DEFAULT_PRESENCE;
|
use automation_lib::presence::DEFAULT_PRESENCE;
|
||||||
use automation_macro::LuaDeviceConfig;
|
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::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error, trace, warn};
|
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
|
// NOTE: If we add more presence devices we might need to move this out of here
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct PresenceDeviceConfig {
|
pub struct PresenceDeviceConfig {
|
||||||
|
@ -26,11 +38,16 @@ pub struct PresenceDeviceConfig {
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub identifier: String,
|
#[device_config(flatten)]
|
||||||
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub presence: Option<PresenceDeviceConfig>,
|
pub presence: Option<PresenceDeviceConfig>,
|
||||||
|
|
||||||
|
#[device_config(default(SensorType::Window))]
|
||||||
|
pub sensor_type: SensorType,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub callback: ActionCallback<ContactSensor, bool>,
|
pub callback: ActionCallback<ContactSensor, bool>,
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
|
@ -66,7 +83,7 @@ impl LuaDeviceCreate for ContactSensor {
|
||||||
type Error = DeviceConfigError;
|
type Error = DeviceConfigError;
|
||||||
|
|
||||||
async fn create(config: Self::Config) -> Result<Self, Self::Error> {
|
async fn create(config: Self::Config) -> Result<Self, Self::Error> {
|
||||||
trace!(id = config.identifier, "Setting up ContactSensor");
|
trace!(id = config.info.identifier(), "Setting up ContactSensor");
|
||||||
|
|
||||||
config
|
config
|
||||||
.client
|
.client
|
||||||
|
@ -86,7 +103,60 @@ impl LuaDeviceCreate for ContactSensor {
|
||||||
|
|
||||||
impl Device for ContactSensor {
|
impl Device for ContactSensor {
|
||||||
fn get_id(&self) -> String {
|
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<bool> {
|
||||||
|
Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_only_open_close(&self) -> Option<bool> {
|
||||||
|
Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_percent(&self) -> Result<u8, ErrorCode> {
|
||||||
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,28 @@ macro_rules! impl_device {
|
||||||
.unwrap())
|
.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())
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
73
config.lua
73
config.lua
|
@ -86,7 +86,7 @@ automation.device_manager:add(living_speakers)
|
||||||
|
|
||||||
automation.device_manager:add(IkeaRemote.new({
|
automation.device_manager:add(IkeaRemote.new({
|
||||||
name = "Remote",
|
name = "Remote",
|
||||||
room = "Living",
|
room = "Living Room",
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
topic = mqtt_z2m("living/remote"),
|
topic = mqtt_z2m("living/remote"),
|
||||||
single_button = true,
|
single_button = true,
|
||||||
|
@ -216,48 +216,42 @@ automation.device_manager:add(HueSwitch.new({
|
||||||
|
|
||||||
local hallway_light_automation = {
|
local hallway_light_automation = {
|
||||||
timeout = Timeout.new(),
|
timeout = Timeout.new(),
|
||||||
state = {
|
forced = false,
|
||||||
door_open = false,
|
|
||||||
trash_open = false,
|
|
||||||
forced = false,
|
|
||||||
},
|
|
||||||
switch_callback = function(self, on)
|
switch_callback = function(self, on)
|
||||||
self.timeout:cancel()
|
self.timeout:cancel()
|
||||||
self.group.set_on(on)
|
self.group.set_on(on)
|
||||||
self.state.forced = on
|
self.forced = on
|
||||||
end,
|
end,
|
||||||
door_callback = function(self, open)
|
door_callback = function(self, open)
|
||||||
self.state.door_open = open
|
|
||||||
if open then
|
if open then
|
||||||
self.timeout:cancel()
|
self.timeout:cancel()
|
||||||
|
|
||||||
self.group.set_on(true)
|
self.group.set_on(true)
|
||||||
elseif not self.state.forced then
|
elseif not self.forced then
|
||||||
self.timeout:start(debug and 10 or 2 * 60, function()
|
self.timeout:start(debug and 10 or 2 * 60, function()
|
||||||
if not self.state.trash_open then
|
if self.trash:is_open_percent() == 0 then
|
||||||
self.group.set_on(false)
|
self.group.set_on(false)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
trash_callback = function(self, open)
|
trash_callback = function(self, open)
|
||||||
self.state.trash_open = open
|
|
||||||
if open then
|
if open then
|
||||||
self.group.set_on(true)
|
self.group.set_on(true)
|
||||||
else
|
else
|
||||||
if not self.timeout:is_waiting() and not self.state.door_open and not self.state.forced then
|
if not self.timeout:is_waiting() and self.door:is_open_percent() == 0 and not self.forced then
|
||||||
self.group.set_on(false)
|
self.group.set_on(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
light_callback = function(self, on)
|
light_callback = function(self, on)
|
||||||
if on and not self.state.trash_open and not self.state.door_open then
|
if on and self.trash:is_open_percent() == 0 and self.door:is_open_percent() == 0 then
|
||||||
-- If the door and trash are not open, that means the light got turned on manually
|
-- If the door and trash are not open, that means the light got turned on manually
|
||||||
self.timeout:cancel()
|
self.timeout:cancel()
|
||||||
self.state.forced = true
|
self.orced = true
|
||||||
elseif not on then
|
elseif not on then
|
||||||
-- The light is never forced when it is off
|
-- The light is never forced when it is off
|
||||||
self.state.forced = false
|
self.forced = false
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
@ -303,8 +297,10 @@ automation.device_manager:add(IkeaRemote.new({
|
||||||
hallway_light_automation:switch_callback(on)
|
hallway_light_automation:switch_callback(on)
|
||||||
end,
|
end,
|
||||||
}))
|
}))
|
||||||
automation.device_manager:add(ContactSensor.new({
|
local hallway_frontdoor = ContactSensor.new({
|
||||||
identifier = "hallway_frontdoor",
|
name = "Frontdoor",
|
||||||
|
room = "Hallway",
|
||||||
|
sensor_type = "Door",
|
||||||
topic = mqtt_z2m("hallway/frontdoor"),
|
topic = mqtt_z2m("hallway/frontdoor"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
presence = {
|
presence = {
|
||||||
|
@ -314,19 +310,26 @@ automation.device_manager:add(ContactSensor.new({
|
||||||
callback = function(_, open)
|
callback = function(_, open)
|
||||||
hallway_light_automation:door_callback(open)
|
hallway_light_automation:door_callback(open)
|
||||||
end,
|
end,
|
||||||
}))
|
})
|
||||||
automation.device_manager:add(ContactSensor.new({
|
automation.device_manager:add(hallway_frontdoor)
|
||||||
identifier = "hallway_trash",
|
hallway_lights_automation.door = hallway_frontdoor
|
||||||
|
|
||||||
|
local hallway_trash = ContactSensor.new({
|
||||||
|
name = "Trash",
|
||||||
|
room = "Hallway",
|
||||||
|
sensor_type = "Drawer",
|
||||||
topic = mqtt_z2m("hallway/trash"),
|
topic = mqtt_z2m("hallway/trash"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
callback = function(_, open)
|
callback = function(_, open)
|
||||||
hallway_light_automation:trash_callback(open)
|
hallway_light_automation:trash_callback(open)
|
||||||
end,
|
end,
|
||||||
}))
|
})
|
||||||
|
automation.device_manager:add(hallway_trash)
|
||||||
|
hallway_lights_automation.trash = hallway_frontdoor
|
||||||
|
|
||||||
automation.device_manager:add(LightOnOff.new({
|
automation.device_manager:add(LightOnOff.new({
|
||||||
name = "Light",
|
name = "Light",
|
||||||
room = "Guest",
|
room = "Guest Room",
|
||||||
topic = mqtt_z2m("guest/light"),
|
topic = mqtt_z2m("guest/light"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
}))
|
}))
|
||||||
|
@ -339,6 +342,32 @@ local bedroom_air_filter = AirFilter.new({
|
||||||
})
|
})
|
||||||
automation.device_manager:add(bedroom_air_filter)
|
automation.device_manager:add(bedroom_air_filter)
|
||||||
|
|
||||||
|
automation.device_manager:add(ContactSensor.new({
|
||||||
|
name = "Balcony",
|
||||||
|
room = "Living Room",
|
||||||
|
sensor_type = "Door",
|
||||||
|
topic = mqtt_z2m("living/balcony"),
|
||||||
|
client = mqtt_client,
|
||||||
|
}))
|
||||||
|
automation.device_manager:add(ContactSensor.new({
|
||||||
|
name = "Window",
|
||||||
|
room = "Living Room",
|
||||||
|
topic = mqtt_z2m("living/window"),
|
||||||
|
client = mqtt_client,
|
||||||
|
}))
|
||||||
|
automation.device_manager:add(ContactSensor.new({
|
||||||
|
name = "Window",
|
||||||
|
room = "Bedroom",
|
||||||
|
topic = mqtt_z2m("bedroom/window"),
|
||||||
|
client = mqtt_client,
|
||||||
|
}))
|
||||||
|
automation.device_manager:add(ContactSensor.new({
|
||||||
|
name = "Window",
|
||||||
|
room = "Guest Room",
|
||||||
|
topic = mqtt_z2m("guest/window"),
|
||||||
|
client = mqtt_client,
|
||||||
|
}))
|
||||||
|
|
||||||
automation.device_manager:schedule("0 0 19 * * *", function()
|
automation.device_manager:schedule("0 0 19 * * *", function()
|
||||||
bedroom_air_filter:set_on(true)
|
bedroom_air_filter:set_on(true)
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -14,6 +14,13 @@ traits! {
|
||||||
async fn on(&self) -> Result<bool, ErrorCode>,
|
async fn on(&self) -> Result<bool, ErrorCode>,
|
||||||
"action.devices.commands.OnOff" => async fn set_on(&self, on: bool) -> Result<(), ErrorCode>,
|
"action.devices.commands.OnOff" => async fn set_on(&self, on: bool) -> Result<(), ErrorCode>,
|
||||||
},
|
},
|
||||||
|
"action.devices.traits.OpenClose" => trait OpenClose {
|
||||||
|
discrete_only_open_close: Option<bool>,
|
||||||
|
command_only_open_close: Option<bool>,
|
||||||
|
query_only_open_close: Option<bool>,
|
||||||
|
async fn open_percent(&self) -> Result<u8, ErrorCode>,
|
||||||
|
"action.devices.commands.OpenClose" => async fn set_open_percent(&self, open_percent: u8) -> Result<(), ErrorCode>,
|
||||||
|
},
|
||||||
"action.devices.traits.Brightness" => trait Brightness {
|
"action.devices.traits.Brightness" => trait Brightness {
|
||||||
command_only_brightness: Option<bool>,
|
command_only_brightness: Option<bool>,
|
||||||
async fn brightness(&self) -> Result<u8, ErrorCode>,
|
async fn brightness(&self) -> Result<u8, ErrorCode>,
|
||||||
|
|
|
@ -12,4 +12,10 @@ pub enum Type {
|
||||||
Scene,
|
Scene,
|
||||||
#[serde(rename = "action.devices.types.AIRPURIFIER")]
|
#[serde(rename = "action.devices.types.AIRPURIFIER")]
|
||||||
AirPurifier,
|
AirPurifier,
|
||||||
|
#[serde(rename = "action.devices.types.DOOR")]
|
||||||
|
Door,
|
||||||
|
#[serde(rename = "action.devices.types.WINDOW")]
|
||||||
|
Window,
|
||||||
|
#[serde(rename = "action.devices.types.DRAWER")]
|
||||||
|
Drawer,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user