Moved and improved hallways logic with lua
All checks were successful
Build and deploy / Build application (push) Successful in 4m7s
Build and deploy / Build container (push) Successful in 1m18s
Build and deploy / Deploy container (push) Successful in 21s

This commit is contained in:
2024-12-06 01:27:35 +01:00
parent 9d4b52b511
commit e9f080ef19
7 changed files with 85 additions and 176 deletions

View File

@@ -3,19 +3,18 @@ use std::time::Duration;
use async_trait::async_trait;
use automation_macro::LuaDeviceConfig;
use google_home::traits::OnOff;
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use tokio::task::JoinHandle;
use tracing::{debug, error, trace, warn};
use super::{Device, LuaDeviceCreate};
use crate::action_callback::ActionCallback;
use crate::config::MqttDeviceConfig;
use crate::devices::DEFAULT_PRESENCE;
use crate::error::DeviceConfigError;
use crate::event::{OnMqtt, OnPresence};
use crate::messages::{ContactMessage, PresenceMessage};
use crate::mqtt::WrappedAsyncClient;
use crate::traits::Timeout;
// NOTE: If we add more presence devices we might need to move this out of here
#[derive(Debug, Clone, LuaDeviceConfig)]
@@ -26,14 +25,6 @@ pub struct PresenceDeviceConfig {
pub timeout: Duration,
}
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct TriggerConfig {
#[device_config(from_lua)]
pub devices: Vec<Box<dyn Device>>,
#[device_config(default, with(|t: Option<_>| t.map(Duration::from_secs)))]
pub timeout: Option<Duration>,
}
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct Config {
pub identifier: String,
@@ -41,8 +32,8 @@ pub struct Config {
pub mqtt: MqttDeviceConfig,
#[device_config(from_lua, default)]
pub presence: Option<PresenceDeviceConfig>,
#[device_config(from_lua)]
pub trigger: Option<TriggerConfig>,
#[device_config(from_lua, default)]
pub callback: ActionCallback<bool>,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
}
@@ -51,7 +42,6 @@ pub struct Config {
struct State {
overall_presence: bool,
is_closed: bool,
previous: Vec<bool>,
handle: Option<JoinHandle<()>>,
}
@@ -79,26 +69,6 @@ impl LuaDeviceCreate for ContactSensor {
async fn create(config: Self::Config) -> Result<Self, Self::Error> {
trace!(id = config.identifier, "Setting up ContactSensor");
let mut previous = Vec::new();
// Make sure the devices implement the required traits
if let Some(trigger) = &config.trigger {
for device in &trigger.devices {
{
let id = device.get_id().to_owned();
if (device.cast() as Option<&dyn OnOff>).is_none() {
return Err(DeviceConfigError::MissingTrait(id, "OnOff".into()));
}
if trigger.timeout.is_none()
&& (device.cast() as Option<&dyn Timeout>).is_none()
{
return Err(DeviceConfigError::MissingTrait(id, "Timeout".into()));
}
}
}
previous.resize(trigger.devices.len(), false);
}
config
.client
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
@@ -107,7 +77,6 @@ impl LuaDeviceCreate for ContactSensor {
let state = State {
overall_presence: DEFAULT_PRESENCE,
is_closed: true,
previous,
handle: None,
};
let state = Arc::new(RwLock::new(state));
@@ -148,44 +117,11 @@ impl OnMqtt for ContactSensor {
return;
}
self.config.callback.call(!is_closed).await;
debug!(id = self.get_id(), "Updating state to {is_closed}");
self.state_mut().await.is_closed = is_closed;
if let Some(trigger) = &self.config.trigger {
if !is_closed {
for (light, previous) in trigger
.devices
.iter()
.zip(self.state_mut().await.previous.iter_mut())
{
if let Some(light) = light.cast() as Option<&dyn OnOff> {
*previous = light.on().await.unwrap();
light.set_on(true).await.ok();
}
}
} else {
for (light, previous) in trigger
.devices
.iter()
.zip(self.state_mut().await.previous.iter())
{
if !previous {
// If the timeout is zero just turn the light off directly
if trigger.timeout.is_none()
&& let Some(light) = light.cast() as Option<&dyn OnOff>
{
light.set_on(false).await.ok();
} else if let Some(timeout) = trigger.timeout
&& let Some(light) = light.cast() as Option<&dyn Timeout>
{
light.start_timeout(timeout).await.unwrap();
}
// TODO: Put a warning/error on creation if either of this has to option to fail
}
}
}
}
// Check if this contact sensor works as a presence device
// If not we are done here
let presence = match &self.config.presence {

View File

@@ -1,7 +1,6 @@
use std::net::SocketAddr;
use std::time::Duration;
use anyhow::{anyhow, Context, Result};
use anyhow::Result;
use async_trait::async_trait;
use automation_macro::LuaDeviceConfig;
use google_home::errors::ErrorCode;
@@ -10,7 +9,6 @@ use tracing::{error, trace, warn};
use super::{Device, LuaDeviceCreate};
use crate::mqtt::WrappedAsyncClient;
use crate::traits::Timeout;
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct Config {
@@ -19,8 +17,6 @@ pub struct Config {
pub addr: SocketAddr,
pub login: String,
pub group_id: isize,
#[device_config(default)]
pub timer_id: Option<isize>,
pub scene_id: String,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
@@ -49,11 +45,6 @@ impl HueGroup {
format!("http://{}/api/{}", self.config.addr, self.config.login)
}
fn url_set_schedule(&self) -> Option<String> {
let timer_id = self.config.timer_id?;
Some(format!("{}/schedules/{}", self.url_base(), timer_id))
}
fn url_set_action(&self) -> String {
format!("{}/groups/{}/action", self.url_base(), self.config.group_id)
}
@@ -72,9 +63,6 @@ impl Device for HueGroup {
#[async_trait]
impl OnOff for HueGroup {
async fn set_on(&self, on: bool) -> Result<(), ErrorCode> {
// Abort any timer that is currently running
self.stop_timeout().await.unwrap();
let message = if on {
message::Action::scene(self.config.scene_id.clone())
} else {
@@ -131,57 +119,6 @@ impl OnOff for HueGroup {
}
}
#[async_trait]
impl Timeout for HueGroup {
async fn start_timeout(&self, timeout: Duration) -> Result<()> {
// Abort any timer that is currently running
self.stop_timeout().await?;
// NOTE: This uses an existing timer, as we are unable to cancel it on the hub otherwise
let message = message::Timeout::new(Some(timeout));
let Some(url) = self.url_set_schedule() else {
return Ok(());
};
let res = reqwest::Client::new()
.put(url)
.json(&message)
.send()
.await
.context("Failed to start timeout")?;
let status = res.status();
if !status.is_success() {
return Err(anyhow!(
"Hue bridge returned unsuccessful status '{status}'"
));
}
Ok(())
}
async fn stop_timeout(&self) -> Result<()> {
let message = message::Timeout::new(None);
let Some(url) = self.url_set_schedule() else {
return Ok(());
};
let res = reqwest::Client::new()
.put(url)
.json(&message)
.send()
.await
.context("Failed to stop timeout")?;
let status = res.status();
if !status.is_success() {
return Err(anyhow!(
"Hue bridge returned unsuccessful status '{status}'"
));
}
Ok(())
}
}
mod message {
use std::time::Duration;

View File

@@ -37,7 +37,6 @@ pub use self::presence::{Presence, DEFAULT_PRESENCE};
pub use self::wake_on_lan::WakeOnLAN;
pub use self::washer::Washer;
use crate::event::{OnDarkness, OnMqtt, OnNotification, OnPresence};
use crate::traits::Timeout;
#[async_trait]
pub trait LuaDeviceCreate {
@@ -145,7 +144,6 @@ pub trait Device:
+ Cast<dyn OnDarkness>
+ Cast<dyn OnNotification>
+ Cast<dyn OnOff>
+ Cast<dyn Timeout>
{
fn get_id(&self) -> String;
}

View File

@@ -59,5 +59,18 @@ impl mlua::UserData for Timeout {
Ok(())
});
methods.add_async_method("is_waiting", |_lua, this, ()| async move {
debug!("Canceling timeout callback");
if let Some(handle) = this.state.read().await.handle.as_ref() {
debug!("Join handle: {}", handle.is_finished());
return Ok(!handle.is_finished());
}
debug!("Join handle: None");
Ok(false)
});
}
}

View File

@@ -13,4 +13,3 @@ pub mod helpers;
pub mod messages;
pub mod mqtt;
pub mod schedule;
pub mod traits;

View File

@@ -1,10 +0,0 @@
use std::time::Duration;
use anyhow::Result;
use async_trait::async_trait;
#[async_trait]
pub trait Timeout: Sync + Send {
async fn start_timeout(&self, _timeout: Duration) -> Result<()>;
async fn stop_timeout(&self) -> Result<()>;
}