Compare commits
1 Commits
203c341863
...
3af618e40f
| Author | SHA1 | Date | |
|---|---|---|---|
|
3af618e40f
|
@@ -1,12 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
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::{InfoConfig, 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;
|
use automation_lib::event::{OnMqtt, OnPresence};
|
||||||
use automation_lib::messages::ContactMessage;
|
use automation_lib::messages::{ContactMessage, PresenceMessage};
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
use google_home::device;
|
use google_home::device;
|
||||||
@@ -15,7 +16,10 @@ use google_home::traits::OpenClose;
|
|||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, error, trace};
|
use tokio::task::JoinHandle;
|
||||||
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
|
use crate::presence::DEFAULT_PRESENCE;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
||||||
pub enum SensorType {
|
pub enum SensorType {
|
||||||
@@ -24,12 +28,23 @@ pub enum SensorType {
|
|||||||
Window,
|
Window,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: If we add more presence devices we might need to move this out of here
|
||||||
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
|
pub struct PresenceDeviceConfig {
|
||||||
|
#[device_config(flatten)]
|
||||||
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
#[device_config(with(Duration::from_secs))]
|
||||||
|
pub timeout: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
#[device_config(from_lua, default)]
|
||||||
|
pub presence: Option<PresenceDeviceConfig>,
|
||||||
|
|
||||||
#[device_config(default(SensorType::Window))]
|
#[device_config(default(SensorType::Window))]
|
||||||
pub sensor_type: SensorType,
|
pub sensor_type: SensorType,
|
||||||
@@ -42,7 +57,9 @@ pub struct Config {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
|
overall_presence: bool,
|
||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
|
handle: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, LuaDevice)]
|
||||||
@@ -75,7 +92,11 @@ impl LuaDeviceCreate for ContactSensor {
|
|||||||
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
|
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let state = State { is_closed: true };
|
let state = State {
|
||||||
|
overall_presence: DEFAULT_PRESENCE,
|
||||||
|
is_closed: true,
|
||||||
|
handle: None,
|
||||||
|
};
|
||||||
let state = Arc::new(RwLock::new(state));
|
let state = Arc::new(RwLock::new(state));
|
||||||
|
|
||||||
Ok(Self { config, state })
|
Ok(Self { config, state })
|
||||||
@@ -142,6 +163,13 @@ impl OpenClose for ContactSensor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl OnPresence for ContactSensor {
|
||||||
|
async fn on_presence(&self, presence: bool) {
|
||||||
|
self.state_mut().await.overall_presence = presence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnMqtt for ContactSensor {
|
impl OnMqtt for ContactSensor {
|
||||||
async fn on_mqtt(&self, message: rumqttc::Publish) {
|
async fn on_mqtt(&self, message: rumqttc::Publish) {
|
||||||
@@ -165,5 +193,64 @@ impl OnMqtt for ContactSensor {
|
|||||||
|
|
||||||
debug!(id = self.get_id(), "Updating state to {is_closed}");
|
debug!(id = self.get_id(), "Updating state to {is_closed}");
|
||||||
self.state_mut().await.is_closed = is_closed;
|
self.state_mut().await.is_closed = is_closed;
|
||||||
|
|
||||||
|
// Check if this contact sensor works as a presence device
|
||||||
|
// If not we are done here
|
||||||
|
let presence = match &self.config.presence {
|
||||||
|
Some(presence) => presence.clone(),
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !is_closed {
|
||||||
|
// Activate presence and stop any timeout once we open the door
|
||||||
|
if let Some(handle) = self.state_mut().await.handle.take() {
|
||||||
|
handle.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only use the door as an presence sensor if there the current presence is set false
|
||||||
|
// This is to prevent the house from being marked as present for however long the
|
||||||
|
// timeout is set when leaving the house
|
||||||
|
if !self.state().await.overall_presence {
|
||||||
|
self.config
|
||||||
|
.client
|
||||||
|
.publish(
|
||||||
|
&presence.mqtt.topic,
|
||||||
|
rumqttc::QoS::AtLeastOnce,
|
||||||
|
false,
|
||||||
|
serde_json::to_string(&PresenceMessage::new(true)).unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(
|
||||||
|
"Failed to publish presence on {}: {err}",
|
||||||
|
presence.mqtt.topic
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Once the door is closed again we start a timeout for removing the presence
|
||||||
|
let device = self.clone();
|
||||||
|
self.state_mut().await.handle = Some(tokio::spawn(async move {
|
||||||
|
debug!(
|
||||||
|
id = device.get_id(),
|
||||||
|
"Starting timeout ({:?}) for contact sensor...", presence.timeout
|
||||||
|
);
|
||||||
|
tokio::time::sleep(presence.timeout).await;
|
||||||
|
debug!(id = device.get_id(), "Removing door device!");
|
||||||
|
device
|
||||||
|
.config
|
||||||
|
.client
|
||||||
|
.publish(&presence.mqtt.topic, rumqttc::QoS::AtLeastOnce, false, "")
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(
|
||||||
|
"Failed to publish presence on {}: {err}",
|
||||||
|
presence.mqtt.topic
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
65
automation_devices/src/debug_bridge.rs
Normal file
65
automation_devices/src/debug_bridge.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use automation_lib::config::MqttDeviceConfig;
|
||||||
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
|
use automation_lib::event::OnPresence;
|
||||||
|
use automation_lib::messages::PresenceMessage;
|
||||||
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
|
use tracing::{trace, warn};
|
||||||
|
|
||||||
|
#[derive(Debug, LuaDeviceConfig, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
pub identifier: String,
|
||||||
|
#[device_config(flatten)]
|
||||||
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
#[device_config(from_lua)]
|
||||||
|
pub client: WrappedAsyncClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, LuaDevice)]
|
||||||
|
pub struct DebugBridge {
|
||||||
|
config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LuaDeviceCreate for DebugBridge {
|
||||||
|
type Config = Config;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
async fn create(config: Self::Config) -> Result<Self, Self::Error> {
|
||||||
|
trace!(id = config.identifier, "Setting up DebugBridge");
|
||||||
|
Ok(Self { config })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device for DebugBridge {
|
||||||
|
fn get_id(&self) -> String {
|
||||||
|
self.config.identifier.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl OnPresence for DebugBridge {
|
||||||
|
async fn on_presence(&self, presence: bool) {
|
||||||
|
let message = PresenceMessage::new(presence);
|
||||||
|
let topic = format!("{}/presence", self.config.mqtt.topic);
|
||||||
|
self.config
|
||||||
|
.client
|
||||||
|
.publish(
|
||||||
|
topic,
|
||||||
|
rumqttc::QoS::AtLeastOnce,
|
||||||
|
true,
|
||||||
|
serde_json::to_string(&message).expect("Serialization should not fail"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
warn!(
|
||||||
|
"Failed to update presence on {}/presence: {err}",
|
||||||
|
self.config.mqtt.topic
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ use std::net::SocketAddr;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
|
use automation_lib::event::OnPresence;
|
||||||
use automation_lib::lua::traits::AddAdditionalMethods;
|
use automation_lib::lua::traits::AddAdditionalMethods;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
@@ -109,3 +110,11 @@ impl AddAdditionalMethods for HueBridge {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl OnPresence for HueBridge {
|
||||||
|
async fn on_presence(&self, presence: bool) {
|
||||||
|
trace!("Bridging presence to hue");
|
||||||
|
self.set_flag(Flag::Presence, presence).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::str::Utf8Error;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
|
use automation_lib::event::OnPresence;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
use bytes::{Buf, BufMut};
|
use bytes::{Buf, BufMut};
|
||||||
use google_home::errors::{self, DeviceError};
|
use google_home::errors::{self, DeviceError};
|
||||||
@@ -12,7 +13,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tracing::trace;
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
@@ -275,3 +276,13 @@ impl OnOff for KasaOutlet {
|
|||||||
.or(Err(DeviceError::TransientError.into()))
|
.or(Err(DeviceError::TransientError.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl OnPresence for KasaOutlet {
|
||||||
|
async fn on_presence(&self, presence: bool) {
|
||||||
|
if !presence {
|
||||||
|
debug!(id = Device::get_id(self), "Turning device off");
|
||||||
|
self.set_on(false).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod air_filter;
|
mod air_filter;
|
||||||
mod contact_sensor;
|
mod contact_sensor;
|
||||||
|
mod debug_bridge;
|
||||||
mod hue_bridge;
|
mod hue_bridge;
|
||||||
mod hue_group;
|
mod hue_group;
|
||||||
mod hue_switch;
|
mod hue_switch;
|
||||||
@@ -18,6 +19,7 @@ use zigbee::outlet::{OutletOnOff, OutletPower};
|
|||||||
|
|
||||||
pub use self::air_filter::AirFilter;
|
pub use self::air_filter::AirFilter;
|
||||||
pub use self::contact_sensor::ContactSensor;
|
pub use self::contact_sensor::ContactSensor;
|
||||||
|
pub use self::debug_bridge::DebugBridge;
|
||||||
pub use self::hue_bridge::HueBridge;
|
pub use self::hue_bridge::HueBridge;
|
||||||
pub use self::hue_group::HueGroup;
|
pub use self::hue_group::HueGroup;
|
||||||
pub use self::hue_switch::HueSwitch;
|
pub use self::hue_switch::HueSwitch;
|
||||||
@@ -39,6 +41,7 @@ macro_rules! register_device {
|
|||||||
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||||
register_device!(lua, AirFilter);
|
register_device!(lua, AirFilter);
|
||||||
register_device!(lua, ContactSensor);
|
register_device!(lua, ContactSensor);
|
||||||
|
register_device!(lua, DebugBridge);
|
||||||
register_device!(lua, HueBridge);
|
register_device!(lua, HueBridge);
|
||||||
register_device!(lua, HueGroup);
|
register_device!(lua, HueGroup);
|
||||||
register_device!(lua, HueSwitch);
|
register_device!(lua, HueSwitch);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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::MqttDeviceConfig;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::{self, EventChannel, OnMqtt};
|
||||||
use automation_lib::messages::BrightnessMessage;
|
use automation_lib::messages::BrightnessMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
@@ -19,6 +19,8 @@ pub struct Config {
|
|||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
pub min: isize,
|
pub min: isize,
|
||||||
pub max: isize,
|
pub max: isize,
|
||||||
|
#[device_config(rename("event_channel"), from_lua, with(|ec: EventChannel| ec.get_tx()))]
|
||||||
|
pub tx: event::Sender,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub callback: ActionCallback<LightSensor, bool>,
|
pub callback: ActionCallback<LightSensor, bool>,
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ 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::MqttDeviceConfig;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::{self, Event, EventChannel, OnMqtt};
|
||||||
use automation_lib::lua::traits::AddAdditionalMethods;
|
|
||||||
use automation_lib::messages::PresenceMessage;
|
use automation_lib::messages::PresenceMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
@@ -18,6 +17,8 @@ use tracing::{debug, trace, warn};
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
#[device_config(from_lua, rename("event_channel"), with(|ec: EventChannel| ec.get_tx()))]
|
||||||
|
pub tx: event::Sender,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub callback: ActionCallback<Presence, bool>,
|
pub callback: ActionCallback<Presence, bool>,
|
||||||
@@ -35,7 +36,6 @@ pub struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDevice)]
|
#[derive(Debug, Clone, LuaDevice)]
|
||||||
#[traits(AddAdditionalMethods)]
|
|
||||||
pub struct Presence {
|
pub struct Presence {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
state: Arc<RwLock<State>>,
|
||||||
@@ -118,18 +118,17 @@ impl OnMqtt for Presence {
|
|||||||
debug!("Overall presence updated: {overall_presence}");
|
debug!("Overall presence updated: {overall_presence}");
|
||||||
self.state_mut().await.current_overall_presence = overall_presence;
|
self.state_mut().await.current_overall_presence = overall_presence;
|
||||||
|
|
||||||
|
if self
|
||||||
|
.config
|
||||||
|
.tx
|
||||||
|
.send(Event::Presence(overall_presence))
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
warn!("There are no receivers on the event channel");
|
||||||
|
}
|
||||||
|
|
||||||
self.config.callback.call(self, &overall_presence).await;
|
self.config.callback.call(self, &overall_presence).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAdditionalMethods for Presence {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
|
|
||||||
where
|
|
||||||
Self: Sized + 'static,
|
|
||||||
{
|
|
||||||
methods.add_async_method("overall_presence", async |_lua, this, ()| {
|
|
||||||
Ok(this.state().await.current_overall_presence)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use async_trait::async_trait;
|
|||||||
use automation_lib::action_callback::ActionCallback;
|
use automation_lib::action_callback::ActionCallback;
|
||||||
use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::{OnMqtt, OnPresence};
|
||||||
use automation_lib::helpers::serialization::state_deserializer;
|
use automation_lib::helpers::serialization::state_deserializer;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
@@ -251,6 +251,16 @@ impl OnMqtt for Light<StateColorTemperature> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: LightState> OnPresence for Light<T> {
|
||||||
|
async fn on_presence(&self, presence: bool) {
|
||||||
|
if !presence {
|
||||||
|
debug!(id = Device::get_id(self), "Turning device off");
|
||||||
|
self.set_on(false).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: LightState> google_home::Device for Light<T> {
|
impl<T: LightState> google_home::Device for Light<T> {
|
||||||
fn get_device_type(&self) -> Type {
|
fn get_device_type(&self) -> Type {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use async_trait::async_trait;
|
|||||||
use automation_lib::action_callback::ActionCallback;
|
use automation_lib::action_callback::ActionCallback;
|
||||||
use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
use automation_lib::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::{OnMqtt, OnPresence};
|
||||||
use automation_lib::helpers::serialization::state_deserializer;
|
use automation_lib::helpers::serialization::state_deserializer;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
||||||
@@ -50,6 +50,10 @@ pub struct Config<T: OutletState> {
|
|||||||
#[device_config(default(OutletType::Outlet))]
|
#[device_config(default(OutletType::Outlet))]
|
||||||
pub outlet_type: OutletType,
|
pub outlet_type: OutletType,
|
||||||
|
|
||||||
|
// TODO: One presence is reworked, this should be removed!
|
||||||
|
#[device_config(default(true))]
|
||||||
|
pub presence_auto_off: bool,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub callback: ActionCallback<Outlet<T>, T>,
|
pub callback: ActionCallback<Outlet<T>, T>,
|
||||||
|
|
||||||
@@ -198,6 +202,16 @@ impl OnMqtt for Outlet<StatePower> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<T: OutletState> OnPresence for Outlet<T> {
|
||||||
|
async fn on_presence(&self, presence: bool) {
|
||||||
|
if self.config.presence_auto_off && !presence {
|
||||||
|
debug!(id = Device::get_id(self), "Turning device off");
|
||||||
|
self.set_on(false).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: OutletState> google_home::Device for Outlet<T> {
|
impl<T: OutletState> google_home::Device for Outlet<T> {
|
||||||
fn get_device_type(&self) -> Type {
|
fn get_device_type(&self) -> Type {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use dyn_clone::DynClone;
|
|||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
use mlua::ObjectLike;
|
use mlua::ObjectLike;
|
||||||
|
|
||||||
use crate::event::OnMqtt;
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait LuaDeviceCreate {
|
pub trait LuaDeviceCreate {
|
||||||
@@ -18,7 +18,14 @@ pub trait LuaDeviceCreate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Device:
|
pub trait Device:
|
||||||
Debug + DynClone + Sync + Send + Cast<dyn google_home::Device> + Cast<dyn OnMqtt> + Cast<dyn OnOff>
|
Debug
|
||||||
|
+ DynClone
|
||||||
|
+ Sync
|
||||||
|
+ Send
|
||||||
|
+ Cast<dyn google_home::Device>
|
||||||
|
+ Cast<dyn OnMqtt>
|
||||||
|
+ Cast<dyn OnPresence>
|
||||||
|
+ Cast<dyn OnOff>
|
||||||
{
|
{
|
||||||
fn get_id(&self) -> String;
|
fn get_id(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use tokio_cron_scheduler::{Job, JobScheduler};
|
|||||||
use tracing::{debug, instrument, trace};
|
use tracing::{debug, instrument, trace};
|
||||||
|
|
||||||
use crate::device::Device;
|
use crate::device::Device;
|
||||||
use crate::event::{Event, EventChannel, OnMqtt};
|
use crate::event::{Event, EventChannel, OnMqtt, OnPresence};
|
||||||
|
|
||||||
pub type DeviceMap = HashMap<String, Box<dyn Device>>;
|
pub type DeviceMap = HashMap<String, Box<dyn Device>>;
|
||||||
|
|
||||||
@@ -92,6 +92,19 @@ impl DeviceManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
join_all(iter).await;
|
||||||
|
}
|
||||||
|
Event::Presence(presence) => {
|
||||||
|
let devices = self.devices.read().await;
|
||||||
|
let iter = devices.iter().map(|(id, device)| async move {
|
||||||
|
let device: Option<&dyn OnPresence> = device.cast();
|
||||||
|
if let Some(device) = device {
|
||||||
|
trace!(id, "Handling");
|
||||||
|
device.on_presence(presence).await;
|
||||||
|
trace!(id, "Done");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
join_all(iter).await;
|
join_all(iter).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use tokio::sync::mpsc;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
MqttMessage(Publish),
|
MqttMessage(Publish),
|
||||||
|
Presence(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Sender = mpsc::Sender<Event>;
|
pub type Sender = mpsc::Sender<Event>;
|
||||||
@@ -33,3 +34,8 @@ pub trait OnMqtt: Sync + Send {
|
|||||||
// fn topics(&self) -> Vec<&str>;
|
// fn topics(&self) -> Vec<&str>;
|
||||||
async fn on_mqtt(&self, message: Publish);
|
async fn on_mqtt(&self, message: Publish);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait OnPresence: Sync + Send {
|
||||||
|
async fn on_presence(&self, presence: bool);
|
||||||
|
}
|
||||||
|
|||||||
164
config.lua
164
config.lua
@@ -1,4 +1,4 @@
|
|||||||
print(_VERSION)
|
print("Hello from lua")
|
||||||
|
|
||||||
local host = automation.util.get_hostname()
|
local host = automation.util.get_hostname()
|
||||||
print("Running @" .. host)
|
print("Running @" .. host)
|
||||||
@@ -34,85 +34,36 @@ local ntfy = Ntfy.new({
|
|||||||
})
|
})
|
||||||
automation.device_manager:add(ntfy)
|
automation.device_manager:add(ntfy)
|
||||||
|
|
||||||
local on_presence = {
|
automation.device_manager:add(Presence.new({
|
||||||
add = function(self, f)
|
|
||||||
self[#self + 1] = f
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
|
|
||||||
local presence_system = Presence.new({
|
|
||||||
topic = mqtt_automation("presence/+/#"),
|
topic = mqtt_automation("presence/+/#"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
event_channel = automation.device_manager:event_channel(),
|
event_channel = automation.device_manager:event_channel(),
|
||||||
callback = function(_, presence)
|
callback = function(_, presence)
|
||||||
for _, f in ipairs(on_presence) do
|
ntfy:send_notification({
|
||||||
if type(f) == "function" then
|
title = "Presence",
|
||||||
f(presence)
|
message = presence and "Home" or "Away",
|
||||||
end
|
tags = { "house" },
|
||||||
end
|
priority = "low",
|
||||||
end,
|
actions = {
|
||||||
})
|
{
|
||||||
automation.device_manager:add(presence_system)
|
action = "broadcast",
|
||||||
on_presence:add(function(presence)
|
extras = {
|
||||||
ntfy:send_notification({
|
cmd = "presence",
|
||||||
title = "Presence",
|
state = presence and "0" or "1",
|
||||||
message = presence and "Home" or "Away",
|
},
|
||||||
tags = { "house" },
|
label = presence and "Set away" or "Set home",
|
||||||
priority = "low",
|
clear = true,
|
||||||
actions = {
|
|
||||||
{
|
|
||||||
action = "broadcast",
|
|
||||||
extras = {
|
|
||||||
cmd = "presence",
|
|
||||||
state = presence and "0" or "1",
|
|
||||||
},
|
},
|
||||||
label = presence and "Set away" or "Set home",
|
|
||||||
clear = true,
|
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
})
|
|
||||||
end)
|
|
||||||
on_presence:add(function(presence)
|
|
||||||
mqtt_client:send_message(mqtt_automation("debug") .. "/presence", {
|
|
||||||
state = presence,
|
|
||||||
updated = automation.util.get_epoch(),
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
|
|
||||||
local function turn_off_when_away(device)
|
|
||||||
on_presence:add(function(presence)
|
|
||||||
if not presence then
|
|
||||||
device:set_on(false)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local on_light = {
|
|
||||||
add = function(self, f)
|
|
||||||
self[#self + 1] = f
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
automation.device_manager:add(LightSensor.new({
|
|
||||||
identifier = "living_light_sensor",
|
|
||||||
topic = mqtt_z2m("living/light"),
|
|
||||||
client = mqtt_client,
|
|
||||||
min = 22000,
|
|
||||||
max = 23500,
|
|
||||||
event_channel = automation.device_manager:event_channel(),
|
|
||||||
callback = function(_, light)
|
|
||||||
for _, f in ipairs(on_light) do
|
|
||||||
if type(f) == "function" then
|
|
||||||
f(light)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
}))
|
}))
|
||||||
on_light:add(function(light)
|
|
||||||
mqtt_client:send_message(mqtt_automation("debug") .. "/darkness", {
|
automation.device_manager:add(DebugBridge.new({
|
||||||
state = not light,
|
identifier = "debug_bridge",
|
||||||
updated = automation.util.get_epoch(),
|
topic = mqtt_automation("debug"),
|
||||||
})
|
client = mqtt_client,
|
||||||
end)
|
}))
|
||||||
|
|
||||||
local hue_ip = "10.0.0.102"
|
local hue_ip = "10.0.0.102"
|
||||||
local hue_token = automation.util.get_env("HUE_TOKEN")
|
local hue_token = automation.util.get_env("HUE_TOKEN")
|
||||||
@@ -127,12 +78,6 @@ local hue_bridge = HueBridge.new({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
automation.device_manager:add(hue_bridge)
|
automation.device_manager:add(hue_bridge)
|
||||||
on_light:add(function(light)
|
|
||||||
hue_bridge:set_flag("darkness", not light)
|
|
||||||
end)
|
|
||||||
on_presence:add(function(presence)
|
|
||||||
hue_bridge:set_flag("presence", presence)
|
|
||||||
end)
|
|
||||||
|
|
||||||
local kitchen_lights = HueGroup.new({
|
local kitchen_lights = HueGroup.new({
|
||||||
identifier = "kitchen_lights",
|
identifier = "kitchen_lights",
|
||||||
@@ -175,6 +120,22 @@ automation.device_manager:add(HueSwitch.new({
|
|||||||
end,
|
end,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
automation.device_manager:add(LightSensor.new({
|
||||||
|
identifier = "living_light_sensor",
|
||||||
|
topic = mqtt_z2m("living/light"),
|
||||||
|
client = mqtt_client,
|
||||||
|
min = 22000,
|
||||||
|
max = 23500,
|
||||||
|
event_channel = automation.device_manager:event_channel(),
|
||||||
|
callback = function(_, light)
|
||||||
|
hue_bridge:set_flag("darkness", not light)
|
||||||
|
mqtt_client:send_message(mqtt_automation("debug") .. "/darkness", {
|
||||||
|
state = not light,
|
||||||
|
updated = automation.util.get_epoch(),
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
}))
|
||||||
|
|
||||||
automation.device_manager:add(WakeOnLAN.new({
|
automation.device_manager:add(WakeOnLAN.new({
|
||||||
name = "Zeus",
|
name = "Zeus",
|
||||||
room = "Living Room",
|
room = "Living Room",
|
||||||
@@ -190,7 +151,6 @@ local living_mixer = OutletOnOff.new({
|
|||||||
topic = mqtt_z2m("living/mixer"),
|
topic = mqtt_z2m("living/mixer"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
})
|
||||||
turn_off_when_away(living_mixer)
|
|
||||||
automation.device_manager:add(living_mixer)
|
automation.device_manager:add(living_mixer)
|
||||||
local living_speakers = OutletOnOff.new({
|
local living_speakers = OutletOnOff.new({
|
||||||
name = "Speakers",
|
name = "Speakers",
|
||||||
@@ -198,7 +158,6 @@ local living_speakers = OutletOnOff.new({
|
|||||||
topic = mqtt_z2m("living/speakers"),
|
topic = mqtt_z2m("living/speakers"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
})
|
||||||
turn_off_when_away(living_speakers)
|
|
||||||
automation.device_manager:add(living_speakers)
|
automation.device_manager:add(living_speakers)
|
||||||
|
|
||||||
automation.device_manager:add(IkeaRemote.new({
|
automation.device_manager:add(IkeaRemote.new({
|
||||||
@@ -248,7 +207,6 @@ local kettle = OutletPower.new({
|
|||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
callback = kettle_timeout(),
|
callback = kettle_timeout(),
|
||||||
})
|
})
|
||||||
turn_off_when_away(kettle)
|
|
||||||
automation.device_manager:add(kettle)
|
automation.device_manager:add(kettle)
|
||||||
|
|
||||||
local function set_kettle(_, on)
|
local function set_kettle(_, on)
|
||||||
@@ -287,14 +245,13 @@ local function off_timeout(duration)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local bathroom_light = LightOnOff.new({
|
automation.device_manager:add(LightOnOff.new({
|
||||||
name = "Light",
|
name = "Light",
|
||||||
room = "Bathroom",
|
room = "Bathroom",
|
||||||
topic = mqtt_z2m("bathroom/light"),
|
topic = mqtt_z2m("bathroom/light"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
callback = off_timeout(debug and 60 or 45 * 60),
|
callback = off_timeout(debug and 60 or 45 * 60),
|
||||||
})
|
}))
|
||||||
automation.device_manager:add(bathroom_light)
|
|
||||||
|
|
||||||
automation.device_manager:add(Washer.new({
|
automation.device_manager:add(Washer.new({
|
||||||
identifier = "bathroom_washer",
|
identifier = "bathroom_washer",
|
||||||
@@ -312,6 +269,7 @@ automation.device_manager:add(Washer.new({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
automation.device_manager:add(OutletOnOff.new({
|
automation.device_manager:add(OutletOnOff.new({
|
||||||
|
presence_auto_off = false,
|
||||||
name = "Charger",
|
name = "Charger",
|
||||||
room = "Workbench",
|
room = "Workbench",
|
||||||
topic = mqtt_z2m("workbench/charger"),
|
topic = mqtt_z2m("workbench/charger"),
|
||||||
@@ -319,14 +277,12 @@ automation.device_manager:add(OutletOnOff.new({
|
|||||||
callback = off_timeout(debug and 5 or 20 * 3600),
|
callback = off_timeout(debug and 5 or 20 * 3600),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
local workbench_outlet = OutletOnOff.new({
|
automation.device_manager:add(OutletOnOff.new({
|
||||||
name = "Outlet",
|
name = "Outlet",
|
||||||
room = "Workbench",
|
room = "Workbench",
|
||||||
topic = mqtt_z2m("workbench/outlet"),
|
topic = mqtt_z2m("workbench/outlet"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
}))
|
||||||
turn_off_when_away(workbench_outlet)
|
|
||||||
automation.device_manager:add(workbench_outlet)
|
|
||||||
|
|
||||||
local workbench_light = LightColorTemperature.new({
|
local workbench_light = LightColorTemperature.new({
|
||||||
name = "Light",
|
name = "Light",
|
||||||
@@ -334,7 +290,6 @@ local workbench_light = LightColorTemperature.new({
|
|||||||
topic = mqtt_z2m("workbench/light"),
|
topic = mqtt_z2m("workbench/light"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
})
|
||||||
turn_off_when_away(workbench_light)
|
|
||||||
automation.device_manager:add(workbench_light)
|
automation.device_manager:add(workbench_light)
|
||||||
|
|
||||||
local delay_color_temp = Timeout.new()
|
local delay_color_temp = Timeout.new()
|
||||||
@@ -437,7 +392,6 @@ local hallway_storage = LightBrightness.new({
|
|||||||
hallway_light_automation:light_callback(state.state)
|
hallway_light_automation:light_callback(state.state)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
turn_off_when_away(hallway_storage)
|
|
||||||
automation.device_manager:add(hallway_storage)
|
automation.device_manager:add(hallway_storage)
|
||||||
|
|
||||||
local hallway_bottom_lights = HueGroup.new({
|
local hallway_bottom_lights = HueGroup.new({
|
||||||
@@ -460,28 +414,6 @@ hallway_light_automation.group = {
|
|||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
|
|
||||||
local frontdoor_presence = {
|
|
||||||
timeout = Timeout.new(),
|
|
||||||
}
|
|
||||||
setmetatable(frontdoor_presence, {
|
|
||||||
__call = function(self, open)
|
|
||||||
if open then
|
|
||||||
self.timeout:cancel()
|
|
||||||
|
|
||||||
if not presence_system:overall_presence() then
|
|
||||||
mqtt_client:send_message(mqtt_automation("presence/contact/frontdoor"), {
|
|
||||||
state = true,
|
|
||||||
updated = automation.util.get_epoch(),
|
|
||||||
})
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.timeout:start(debug and 10 or 15 * 60, function()
|
|
||||||
mqtt_client:send_message(mqtt_automation("presence/contact/frontdoor"), {})
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
automation.device_manager:add(IkeaRemote.new({
|
automation.device_manager:add(IkeaRemote.new({
|
||||||
name = "Remote",
|
name = "Remote",
|
||||||
room = "Hallway",
|
room = "Hallway",
|
||||||
@@ -503,7 +435,6 @@ local hallway_frontdoor = ContactSensor.new({
|
|||||||
},
|
},
|
||||||
callback = function(_, open)
|
callback = function(_, open)
|
||||||
hallway_light_automation:door_callback(open)
|
hallway_light_automation:door_callback(open)
|
||||||
frontdoor_presence(open)
|
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
automation.device_manager:add(hallway_frontdoor)
|
automation.device_manager:add(hallway_frontdoor)
|
||||||
@@ -522,14 +453,12 @@ local hallway_trash = ContactSensor.new({
|
|||||||
automation.device_manager:add(hallway_trash)
|
automation.device_manager:add(hallway_trash)
|
||||||
hallway_light_automation.trash = hallway_trash
|
hallway_light_automation.trash = hallway_trash
|
||||||
|
|
||||||
local guest_light = LightOnOff.new({
|
automation.device_manager:add(LightOnOff.new({
|
||||||
name = "Light",
|
name = "Light",
|
||||||
room = "Guest Room",
|
room = "Guest Room",
|
||||||
topic = mqtt_z2m("guest/light"),
|
topic = mqtt_z2m("guest/light"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
}))
|
||||||
turn_off_when_away(guest_light)
|
|
||||||
automation.device_manager:add(guest_light)
|
|
||||||
|
|
||||||
local bedroom_air_filter = AirFilter.new({
|
local bedroom_air_filter = AirFilter.new({
|
||||||
name = "Air Filter",
|
name = "Air Filter",
|
||||||
@@ -600,7 +529,6 @@ local storage_light = LightBrightness.new({
|
|||||||
topic = mqtt_z2m("storage/light"),
|
topic = mqtt_z2m("storage/light"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
})
|
||||||
turn_off_when_away(storage_light)
|
|
||||||
automation.device_manager:add(storage_light)
|
automation.device_manager:add(storage_light)
|
||||||
|
|
||||||
automation.device_manager:add(ContactSensor.new({
|
automation.device_manager:add(ContactSensor.new({
|
||||||
|
|||||||
Reference in New Issue
Block a user