Compare commits
No commits in common. "9d4b52b51150d673d0702a82085c9a98901c0439" and "6b8d0b7d566c9cbcc80817a5ee367401e667c05e" have entirely different histories.
9d4b52b511
...
6b8d0b7d56
23
config.lua
23
config.lua
|
@ -109,31 +109,16 @@ automation.device_manager:add(IkeaRemote.new({
|
||||||
end,
|
end,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
local function off_timeout(duration)
|
|
||||||
local timeout = Timeout.new()
|
|
||||||
|
|
||||||
return function(this, on)
|
|
||||||
if on then
|
|
||||||
timeout:start(duration, function()
|
|
||||||
this:set_on(false)
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
timeout:cancel()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local kettle = IkeaOutlet.new({
|
local kettle = IkeaOutlet.new({
|
||||||
outlet_type = "Kettle",
|
outlet_type = "Kettle",
|
||||||
name = "Kettle",
|
name = "Kettle",
|
||||||
room = "Kitchen",
|
room = "Kitchen",
|
||||||
topic = mqtt_z2m("kitchen/kettle"),
|
topic = mqtt_z2m("kitchen/kettle"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
callback = off_timeout(debug and 5 or 300),
|
timeout = debug and 5 or 300,
|
||||||
})
|
})
|
||||||
automation.device_manager:add(kettle)
|
automation.device_manager:add(kettle)
|
||||||
|
function set_kettle(on)
|
||||||
local function set_kettle(on)
|
|
||||||
kettle:set_on(on)
|
kettle:set_on(on)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -161,7 +146,7 @@ automation.device_manager:add(IkeaOutlet.new({
|
||||||
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),
|
timeout = debug and 60 or 45 * 60,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
automation.device_manager:add(Washer.new({
|
automation.device_manager:add(Washer.new({
|
||||||
|
@ -178,7 +163,7 @@ automation.device_manager:add(IkeaOutlet.new({
|
||||||
room = "Workbench",
|
room = "Workbench",
|
||||||
topic = mqtt_z2m("workbench/charger"),
|
topic = mqtt_z2m("workbench/charger"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
callback = off_timeout(debug and 5 or 20 * 3600),
|
timeout = debug and 5 or 20 * 3600,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
automation.device_manager:add(IkeaOutlet.new({
|
automation.device_manager:add(IkeaOutlet.new({
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use mlua::{FromLua, IntoLuaMulti};
|
use mlua::{FromLua, IntoLua};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Internal {
|
struct Internal {
|
||||||
|
@ -8,21 +8,12 @@ struct Internal {
|
||||||
lua: mlua::Lua,
|
lua: mlua::Lua,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ActionCallback<T> {
|
pub struct ActionCallback<T> {
|
||||||
internal: Option<Internal>,
|
internal: Option<Internal>,
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for ActionCallback<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
internal: None,
|
|
||||||
phantom: PhantomData::<T>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> FromLua for ActionCallback<T> {
|
impl<T> FromLua for ActionCallback<T> {
|
||||||
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
|
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
|
||||||
let uuid = uuid::Uuid::new_v4();
|
let uuid = uuid::Uuid::new_v4();
|
||||||
|
@ -41,7 +32,7 @@ impl<T> FromLua for ActionCallback<T> {
|
||||||
// TODO: Return proper error here
|
// TODO: Return proper error here
|
||||||
impl<T> ActionCallback<T>
|
impl<T> ActionCallback<T>
|
||||||
where
|
where
|
||||||
T: IntoLuaMulti + Sync + Send + Clone + 'static,
|
T: IntoLua + Sync + Send + Clone + Copy + 'static,
|
||||||
{
|
{
|
||||||
pub async fn call(&self, state: T) {
|
pub async fn call(&self, state: T) {
|
||||||
let Some(internal) = self.internal.as_ref() else {
|
let Some(internal) = self.internal.as_ref() else {
|
||||||
|
|
|
@ -22,11 +22,12 @@ pub struct Config {
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
|
|
||||||
|
// TODO: IntoLua is not implemented for unit type ()
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub left_callback: ActionCallback<()>,
|
pub left_callback: ActionCallback<bool>,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
pub right_callback: ActionCallback<()>,
|
pub right_callback: ActionCallback<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -61,6 +62,7 @@ impl LuaDeviceCreate for HueSwitch {
|
||||||
impl OnMqtt for HueSwitch {
|
impl OnMqtt for HueSwitch {
|
||||||
async fn on_mqtt(&self, message: Publish) {
|
async fn on_mqtt(&self, message: Publish) {
|
||||||
// Check if the message is from the deviec itself or from a remote
|
// Check if the message is from the deviec itself or from a remote
|
||||||
|
debug!(id = Device::get_id(self), "Mqtt message received");
|
||||||
if matches(&message.topic, &self.config.mqtt.topic) {
|
if matches(&message.topic, &self.config.mqtt.topic) {
|
||||||
let action = match serde_json::from_slice::<Zigbee929003017102>(&message.payload) {
|
let action = match serde_json::from_slice::<Zigbee929003017102>(&message.payload) {
|
||||||
Ok(message) => message.action,
|
Ok(message) => message.action,
|
||||||
|
@ -73,10 +75,10 @@ impl OnMqtt for HueSwitch {
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
zigbee2mqtt_types::vendors::philips::Zigbee929003017102Action::Leftpress => {
|
zigbee2mqtt_types::vendors::philips::Zigbee929003017102Action::Leftpress => {
|
||||||
self.config.left_callback.call(()).await
|
self.config.left_callback.call(true).await
|
||||||
}
|
}
|
||||||
zigbee2mqtt_types::vendors::philips::Zigbee929003017102Action::Rightpress => {
|
zigbee2mqtt_types::vendors::philips::Zigbee929003017102Action::Rightpress => {
|
||||||
self.config.right_callback.call(()).await
|
self.config.right_callback.call(true).await
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -10,15 +11,16 @@ use google_home::types::Type;
|
||||||
use rumqttc::{matches, Publish};
|
use rumqttc::{matches, Publish};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
use super::LuaDeviceCreate;
|
use super::LuaDeviceCreate;
|
||||||
use crate::action_callback::ActionCallback;
|
|
||||||
use crate::config::{InfoConfig, MqttDeviceConfig};
|
use crate::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use crate::devices::Device;
|
use crate::devices::Device;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::messages::OnOffMessage;
|
use crate::messages::OnOffMessage;
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
use crate::mqtt::WrappedAsyncClient;
|
||||||
|
use crate::traits::Timeout;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
||||||
pub enum OutletType {
|
pub enum OutletType {
|
||||||
|
@ -36,17 +38,17 @@ pub struct Config {
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(default(OutletType::Outlet))]
|
#[device_config(default(OutletType::Outlet))]
|
||||||
pub outlet_type: OutletType,
|
pub outlet_type: OutletType,
|
||||||
|
#[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))]
|
||||||
#[device_config(from_lua, default)]
|
pub timeout: Option<Duration>,
|
||||||
pub callback: ActionCallback<(IkeaOutlet, bool)>,
|
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
last_known_state: bool,
|
last_known_state: bool,
|
||||||
|
handle: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -79,10 +81,13 @@ impl LuaDeviceCreate for IkeaOutlet {
|
||||||
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
|
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Self {
|
let state = State {
|
||||||
config,
|
last_known_state: false,
|
||||||
state: Default::default(),
|
handle: None,
|
||||||
})
|
};
|
||||||
|
let state = Arc::new(RwLock::new(state));
|
||||||
|
|
||||||
|
Ok(Self { config, state })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,10 +116,16 @@ impl OnMqtt for IkeaOutlet {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config.callback.call((self.clone(), state)).await;
|
// Abort any timer that is currently running
|
||||||
|
self.stop_timeout().await.unwrap();
|
||||||
|
|
||||||
debug!(id = Device::get_id(self), "Updating state to {state}");
|
debug!(id = Device::get_id(self), "Updating state to {state}");
|
||||||
self.state_mut().await.last_known_state = state;
|
self.state_mut().await.last_known_state = state;
|
||||||
|
|
||||||
|
// If this is a kettle start a timeout for turning it of again
|
||||||
|
if state && let Some(timeout) = self.config.timeout {
|
||||||
|
self.start_timeout(timeout).await.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,3 +199,29 @@ impl traits::OnOff for IkeaOutlet {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl crate::traits::Timeout for IkeaOutlet {
|
||||||
|
async fn start_timeout(&self, timeout: Duration) -> Result<()> {
|
||||||
|
// Abort any timer that is currently running
|
||||||
|
self.stop_timeout().await?;
|
||||||
|
|
||||||
|
let device = self.clone();
|
||||||
|
self.state_mut().await.handle = Some(tokio::spawn(async move {
|
||||||
|
debug!(id = device.get_id(), "Starting timeout ({timeout:?})...");
|
||||||
|
tokio::time::sleep(timeout).await;
|
||||||
|
debug!(id = device.get_id(), "Turning outlet off!");
|
||||||
|
device.set_on(false).await.unwrap();
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop_timeout(&self) -> Result<()> {
|
||||||
|
if let Some(handle) = self.state_mut().await.handle.take() {
|
||||||
|
handle.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ impl LuaDeviceCreate for IkeaRemote {
|
||||||
impl OnMqtt for IkeaRemote {
|
impl OnMqtt for IkeaRemote {
|
||||||
async fn on_mqtt(&self, message: Publish) {
|
async fn on_mqtt(&self, message: Publish) {
|
||||||
// Check if the message is from the deviec itself or from a remote
|
// Check if the message is from the deviec itself or from a remote
|
||||||
|
debug!(id = Device::get_id(self), "Mqtt message received");
|
||||||
if matches(&message.topic, &self.config.mqtt.topic) {
|
if matches(&message.topic, &self.config.mqtt.topic) {
|
||||||
let action = match RemoteMessage::try_from(message) {
|
let action = match RemoteMessage::try_from(message) {
|
||||||
Ok(message) => message.action(),
|
Ok(message) => message.action(),
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
mod timeout;
|
|
||||||
|
|
||||||
pub use timeout::Timeout;
|
|
||||||
|
|
||||||
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
|
||||||
lua.globals()
|
|
||||||
.set("Timeout", lua.create_proxy::<Timeout>()?)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tokio::task::JoinHandle;
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
use crate::action_callback::ActionCallback;
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct State {
|
|
||||||
handle: Option<JoinHandle<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Timeout {
|
|
||||||
state: Arc<RwLock<State>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for Timeout {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_function("new", |_lua, ()| {
|
|
||||||
let device = Self {
|
|
||||||
state: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(device)
|
|
||||||
});
|
|
||||||
|
|
||||||
methods.add_async_method(
|
|
||||||
"start",
|
|
||||||
|_lua, this, (timeout, callback): (u64, ActionCallback<bool>)| async move {
|
|
||||||
if let Some(handle) = this.state.write().await.handle.take() {
|
|
||||||
handle.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Running timeout callback after {timeout}s");
|
|
||||||
|
|
||||||
let timeout = Duration::from_secs(timeout);
|
|
||||||
|
|
||||||
this.state.write().await.handle = Some(tokio::spawn({
|
|
||||||
async move {
|
|
||||||
tokio::time::sleep(timeout).await;
|
|
||||||
|
|
||||||
callback.call(false).await;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
methods.add_async_method("cancel", |_lua, this, ()| async move {
|
|
||||||
debug!("Canceling timeout callback");
|
|
||||||
|
|
||||||
if let Some(handle) = this.state.write().await.handle.take() {
|
|
||||||
handle.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@ pub mod device_manager;
|
||||||
pub mod devices;
|
pub mod devices;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod helpers;
|
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod mqtt;
|
pub mod mqtt;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
|
|
|
@ -5,9 +5,9 @@ use anyhow::anyhow;
|
||||||
use automation::auth::User;
|
use automation::auth::User;
|
||||||
use automation::config::{FulfillmentConfig, MqttConfig};
|
use automation::config::{FulfillmentConfig, MqttConfig};
|
||||||
use automation::device_manager::DeviceManager;
|
use automation::device_manager::DeviceManager;
|
||||||
|
use automation::devices;
|
||||||
use automation::error::ApiError;
|
use automation::error::ApiError;
|
||||||
use automation::mqtt::{self, WrappedAsyncClient};
|
use automation::mqtt::{self, WrappedAsyncClient};
|
||||||
use automation::{devices, helpers};
|
|
||||||
use axum::extract::{FromRef, State};
|
use axum::extract::{FromRef, State};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
|
@ -112,7 +112,6 @@ async fn app() -> anyhow::Result<()> {
|
||||||
lua.globals().set("automation", automation)?;
|
lua.globals().set("automation", automation)?;
|
||||||
|
|
||||||
devices::register_with_lua(&lua)?;
|
devices::register_with_lua(&lua)?;
|
||||||
helpers::register_with_lua(&lua)?;
|
|
||||||
|
|
||||||
// TODO: Make this not hardcoded
|
// TODO: Make this not hardcoded
|
||||||
let config_filename = std::env::var("AUTOMATION_CONFIG").unwrap_or("./config.lua".into());
|
let config_filename = std::env::var("AUTOMATION_CONFIG").unwrap_or("./config.lua".into());
|
||||||
|
|
Loading…
Reference in New Issue
Block a user