Added hue wall switches
All checks were successful
Build and deploy / Build application (push) Successful in 4m9s
Build and deploy / Build container (push) Successful in 53s
Build and deploy / Deploy container (push) Successful in 32s

This commit is contained in:
2024-11-30 22:16:49 +01:00
parent 5185b0d3ba
commit 6b8d0b7d56
7 changed files with 166 additions and 16 deletions

View File

@@ -3,9 +3,14 @@ use std::marker::PhantomData;
use mlua::{FromLua, IntoLua};
#[derive(Debug, Clone)]
pub struct ActionCallback<T> {
struct Internal {
uuid: uuid::Uuid,
lua: mlua::Lua,
}
#[derive(Debug, Clone, Default)]
pub struct ActionCallback<T> {
internal: Option<Internal>,
phantom: PhantomData<T>,
}
@@ -15,8 +20,10 @@ impl<T> FromLua for ActionCallback<T> {
lua.set_named_registry_value(&uuid.to_string(), value)?;
Ok(ActionCallback {
uuid,
lua: lua.clone(),
internal: Some(Internal {
uuid,
lua: lua.clone(),
}),
phantom: PhantomData::<T>,
})
}
@@ -28,9 +35,14 @@ where
T: IntoLua + Sync + Send + Clone + Copy + 'static,
{
pub async fn call(&self, state: T) {
let uuid = self.uuid;
let Some(internal) = self.internal.as_ref() else {
return;
};
let callback: mlua::Value = self.lua.named_registry_value(&uuid.to_string()).unwrap();
let callback: mlua::Value = internal
.lua
.named_registry_value(&internal.uuid.to_string())
.unwrap();
match callback {
mlua::Value::Function(f) => f.call_async::<()>(state).await.unwrap(),
_ => todo!("Only functions are currently supported"),

View File

@@ -19,7 +19,8 @@ pub struct Config {
pub addr: SocketAddr,
pub login: String,
pub group_id: isize,
pub timer_id: isize,
#[device_config(default)]
pub timer_id: Option<isize>,
pub scene_id: String,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
@@ -48,8 +49,9 @@ impl HueGroup {
format!("http://{}/api/{}", self.config.addr, self.config.login)
}
fn url_set_schedule(&self) -> String {
format!("{}/schedules/{}", self.url_base(), self.config.timer_id)
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 {
@@ -137,8 +139,11 @@ impl Timeout for HueGroup {
// 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(self.url_set_schedule())
.put(url)
.json(&message)
.send()
.await
@@ -156,8 +161,11 @@ impl Timeout for HueGroup {
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(self.url_set_schedule())
.put(url)
.json(&message)
.send()
.await

87
src/devices/hue_switch.rs Normal file
View File

@@ -0,0 +1,87 @@
use automation_macro::LuaDeviceConfig;
use axum::async_trait;
use rumqttc::{matches, Publish};
use tracing::{debug, trace, warn};
use zigbee2mqtt_types::vendors::philips::Zigbee929003017102;
use super::LuaDeviceCreate;
use crate::action_callback::ActionCallback;
use crate::config::{InfoConfig, MqttDeviceConfig};
use crate::devices::Device;
use crate::event::OnMqtt;
use crate::mqtt::WrappedAsyncClient;
#[derive(Debug, Clone, LuaDeviceConfig)]
pub struct Config {
#[device_config(flatten)]
pub info: InfoConfig,
#[device_config(flatten)]
pub mqtt: MqttDeviceConfig,
#[device_config(from_lua)]
pub client: WrappedAsyncClient,
// TODO: IntoLua is not implemented for unit type ()
#[device_config(from_lua, default)]
pub left_callback: ActionCallback<bool>,
#[device_config(from_lua, default)]
pub right_callback: ActionCallback<bool>,
}
#[derive(Debug, Clone)]
pub struct HueSwitch {
config: Config,
}
impl Device for HueSwitch {
fn get_id(&self) -> String {
self.config.info.identifier()
}
}
#[async_trait]
impl LuaDeviceCreate for HueSwitch {
type Config = Config;
type Error = rumqttc::ClientError;
async fn create(config: Self::Config) -> Result<Self, Self::Error> {
trace!(id = config.info.identifier(), "Setting up HueSwitch");
config
.client
.subscribe(&config.mqtt.topic, rumqttc::QoS::AtLeastOnce)
.await?;
Ok(Self { config })
}
}
#[async_trait]
impl OnMqtt for HueSwitch {
async fn on_mqtt(&self, message: Publish) {
// 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) {
let action = match serde_json::from_slice::<Zigbee929003017102>(&message.payload) {
Ok(message) => message.action,
Err(err) => {
warn!(id = Device::get_id(self), "Failed to parse message: {err}");
return;
}
};
debug!(id = Device::get_id(self), "Remote action = {:?}", action);
match action {
zigbee2mqtt_types::vendors::philips::Zigbee929003017102Action::Leftpress => {
self.config.left_callback.call(true).await
}
zigbee2mqtt_types::vendors::philips::Zigbee929003017102Action::Rightpress => {
self.config.right_callback.call(true).await
}
_ => {}
}
}
}
}

View File

@@ -3,6 +3,7 @@ mod contact_sensor;
mod debug_bridge;
mod hue_bridge;
mod hue_group;
mod hue_switch;
mod ikea_outlet;
mod ikea_remote;
mod kasa_outlet;
@@ -26,6 +27,7 @@ pub use self::contact_sensor::ContactSensor;
pub use self::debug_bridge::DebugBridge;
pub use self::hue_bridge::HueBridge;
pub use self::hue_group::HueGroup;
pub use self::hue_switch::HueSwitch;
pub use self::ikea_outlet::IkeaOutlet;
pub use self::ikea_remote::IkeaRemote;
pub use self::kasa_outlet::KasaOutlet;
@@ -102,6 +104,7 @@ impl_device!(ContactSensor);
impl_device!(DebugBridge);
impl_device!(HueBridge);
impl_device!(HueGroup);
impl_device!(HueSwitch);
impl_device!(IkeaOutlet);
impl_device!(IkeaRemote);
impl_device!(KasaOutlet);
@@ -117,6 +120,7 @@ pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
register_device!(lua, DebugBridge);
register_device!(lua, HueBridge);
register_device!(lua, HueGroup);
register_device!(lua, HueSwitch);
register_device!(lua, IkeaOutlet);
register_device!(lua, IkeaRemote);
register_device!(lua, KasaOutlet);