Added hue wall switches
This commit is contained in:
parent
5185b0d3ba
commit
6b8d0b7d56
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -127,6 +127,7 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wakey",
|
"wakey",
|
||||||
|
"zigbee2mqtt-types",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2618,3 +2619,13 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.60",
|
"syn 2.0.60",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zigbee2mqtt-types"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0ebe51c23f1de3efd64cca1a2176a060e8c90e72070e9ce9f0c023e514e08ac"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
|
@ -61,6 +61,7 @@ tokio-util = { version = "0.7.11", features = ["full"] }
|
||||||
uuid = "1.8.0"
|
uuid = "1.8.0"
|
||||||
dyn-clone = "1.0.17"
|
dyn-clone = "1.0.17"
|
||||||
impls = "1.0.3"
|
impls = "1.0.3"
|
||||||
|
zigbee2mqtt-types = { version = "0.2.0", features = ["debug", "philips"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
wakey = { git = "https://git.huizinga.dev/Dreaded_X/wakey" }
|
wakey = { git = "https://git.huizinga.dev/Dreaded_X/wakey" }
|
||||||
|
|
39
config.lua
39
config.lua
|
@ -173,8 +173,8 @@ automation.device_manager:add(IkeaOutlet.new({
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
local hallway_lights = HueGroup.new({
|
local hallway_bottom_lights = HueGroup.new({
|
||||||
identifier = "hallway_lights",
|
identifier = "hallway_bottom_lights",
|
||||||
ip = hue_ip,
|
ip = hue_ip,
|
||||||
login = hue_token,
|
login = hue_token,
|
||||||
group_id = 81,
|
group_id = 81,
|
||||||
|
@ -182,14 +182,41 @@ local hallway_lights = HueGroup.new({
|
||||||
timer_id = 1,
|
timer_id = 1,
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
})
|
})
|
||||||
automation.device_manager:add(hallway_lights)
|
automation.device_manager:add(hallway_bottom_lights)
|
||||||
automation.device_manager:add(IkeaRemote.new({
|
automation.device_manager:add(IkeaRemote.new({
|
||||||
name = "Remote",
|
name = "Remote",
|
||||||
room = "Hallway",
|
room = "Hallway",
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
topic = mqtt_z2m("hallway/remote"),
|
topic = mqtt_z2m("hallway/remote"),
|
||||||
callback = function(on)
|
callback = function(on)
|
||||||
hallway_lights:set_on(on)
|
hallway_bottom_lights:set_on(on)
|
||||||
|
end,
|
||||||
|
}))
|
||||||
|
|
||||||
|
local hallway_top_light = HueGroup.new({
|
||||||
|
identifier = "hallway_top_light",
|
||||||
|
ip = hue_ip,
|
||||||
|
login = hue_token,
|
||||||
|
group_id = 83,
|
||||||
|
scene_id = "QeufkFDICEHWeKJ7",
|
||||||
|
client = mqtt_client,
|
||||||
|
})
|
||||||
|
automation.device_manager:add(HueSwitch.new({
|
||||||
|
name = "SwitchBottom",
|
||||||
|
room = "Hallway",
|
||||||
|
client = mqtt_client,
|
||||||
|
topic = mqtt_z2m("hallway/switchbottom"),
|
||||||
|
left_callback = function()
|
||||||
|
hallway_top_light:set_on(not hallway_top_light:is_on())
|
||||||
|
end,
|
||||||
|
}))
|
||||||
|
automation.device_manager:add(HueSwitch.new({
|
||||||
|
name = "SwitchTop",
|
||||||
|
room = "Hallway",
|
||||||
|
client = mqtt_client,
|
||||||
|
topic = mqtt_z2m("hallway/switchtop"),
|
||||||
|
left_callback = function()
|
||||||
|
hallway_top_light:set_on(not hallway_top_light:is_on())
|
||||||
end,
|
end,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -202,7 +229,7 @@ automation.device_manager:add(ContactSensor.new({
|
||||||
timeout = debug and 10 or 15 * 60,
|
timeout = debug and 10 or 15 * 60,
|
||||||
},
|
},
|
||||||
trigger = {
|
trigger = {
|
||||||
devices = { hallway_lights },
|
devices = { hallway_bottom_lights },
|
||||||
timeout = debug and 10 or 2 * 60,
|
timeout = debug and 10 or 2 * 60,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -212,7 +239,7 @@ automation.device_manager:add(ContactSensor.new({
|
||||||
topic = mqtt_z2m("hallway/trash"),
|
topic = mqtt_z2m("hallway/trash"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
trigger = {
|
trigger = {
|
||||||
devices = { hallway_lights },
|
devices = { hallway_bottom_lights },
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,14 @@ use std::marker::PhantomData;
|
||||||
use mlua::{FromLua, IntoLua};
|
use mlua::{FromLua, IntoLua};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ActionCallback<T> {
|
struct Internal {
|
||||||
uuid: uuid::Uuid,
|
uuid: uuid::Uuid,
|
||||||
lua: mlua::Lua,
|
lua: mlua::Lua,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ActionCallback<T> {
|
||||||
|
internal: Option<Internal>,
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +20,10 @@ impl<T> FromLua for ActionCallback<T> {
|
||||||
lua.set_named_registry_value(&uuid.to_string(), value)?;
|
lua.set_named_registry_value(&uuid.to_string(), value)?;
|
||||||
|
|
||||||
Ok(ActionCallback {
|
Ok(ActionCallback {
|
||||||
uuid,
|
internal: Some(Internal {
|
||||||
lua: lua.clone(),
|
uuid,
|
||||||
|
lua: lua.clone(),
|
||||||
|
}),
|
||||||
phantom: PhantomData::<T>,
|
phantom: PhantomData::<T>,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -28,9 +35,14 @@ where
|
||||||
T: IntoLua + Sync + Send + Clone + Copy + 'static,
|
T: IntoLua + Sync + Send + Clone + Copy + 'static,
|
||||||
{
|
{
|
||||||
pub async fn call(&self, state: T) {
|
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 {
|
match callback {
|
||||||
mlua::Value::Function(f) => f.call_async::<()>(state).await.unwrap(),
|
mlua::Value::Function(f) => f.call_async::<()>(state).await.unwrap(),
|
||||||
_ => todo!("Only functions are currently supported"),
|
_ => todo!("Only functions are currently supported"),
|
||||||
|
|
|
@ -19,7 +19,8 @@ pub struct Config {
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub group_id: isize,
|
pub group_id: isize,
|
||||||
pub timer_id: isize,
|
#[device_config(default)]
|
||||||
|
pub timer_id: Option<isize>,
|
||||||
pub scene_id: String,
|
pub scene_id: String,
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
|
@ -48,8 +49,9 @@ impl HueGroup {
|
||||||
format!("http://{}/api/{}", self.config.addr, self.config.login)
|
format!("http://{}/api/{}", self.config.addr, self.config.login)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url_set_schedule(&self) -> String {
|
fn url_set_schedule(&self) -> Option<String> {
|
||||||
format!("{}/schedules/{}", self.url_base(), self.config.timer_id)
|
let timer_id = self.config.timer_id?;
|
||||||
|
Some(format!("{}/schedules/{}", self.url_base(), timer_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url_set_action(&self) -> String {
|
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
|
// 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 message = message::Timeout::new(Some(timeout));
|
||||||
|
let Some(url) = self.url_set_schedule() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.put(self.url_set_schedule())
|
.put(url)
|
||||||
.json(&message)
|
.json(&message)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
@ -156,8 +161,11 @@ impl Timeout for HueGroup {
|
||||||
|
|
||||||
async fn stop_timeout(&self) -> Result<()> {
|
async fn stop_timeout(&self) -> Result<()> {
|
||||||
let message = message::Timeout::new(None);
|
let message = message::Timeout::new(None);
|
||||||
|
let Some(url) = self.url_set_schedule() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.put(self.url_set_schedule())
|
.put(url)
|
||||||
.json(&message)
|
.json(&message)
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
|
|
87
src/devices/hue_switch.rs
Normal file
87
src/devices/hue_switch.rs
Normal 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
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ mod contact_sensor;
|
||||||
mod debug_bridge;
|
mod debug_bridge;
|
||||||
mod hue_bridge;
|
mod hue_bridge;
|
||||||
mod hue_group;
|
mod hue_group;
|
||||||
|
mod hue_switch;
|
||||||
mod ikea_outlet;
|
mod ikea_outlet;
|
||||||
mod ikea_remote;
|
mod ikea_remote;
|
||||||
mod kasa_outlet;
|
mod kasa_outlet;
|
||||||
|
@ -26,6 +27,7 @@ pub use self::contact_sensor::ContactSensor;
|
||||||
pub use self::debug_bridge::DebugBridge;
|
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::ikea_outlet::IkeaOutlet;
|
pub use self::ikea_outlet::IkeaOutlet;
|
||||||
pub use self::ikea_remote::IkeaRemote;
|
pub use self::ikea_remote::IkeaRemote;
|
||||||
pub use self::kasa_outlet::KasaOutlet;
|
pub use self::kasa_outlet::KasaOutlet;
|
||||||
|
@ -102,6 +104,7 @@ impl_device!(ContactSensor);
|
||||||
impl_device!(DebugBridge);
|
impl_device!(DebugBridge);
|
||||||
impl_device!(HueBridge);
|
impl_device!(HueBridge);
|
||||||
impl_device!(HueGroup);
|
impl_device!(HueGroup);
|
||||||
|
impl_device!(HueSwitch);
|
||||||
impl_device!(IkeaOutlet);
|
impl_device!(IkeaOutlet);
|
||||||
impl_device!(IkeaRemote);
|
impl_device!(IkeaRemote);
|
||||||
impl_device!(KasaOutlet);
|
impl_device!(KasaOutlet);
|
||||||
|
@ -117,6 +120,7 @@ pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||||
register_device!(lua, DebugBridge);
|
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, IkeaOutlet);
|
register_device!(lua, IkeaOutlet);
|
||||||
register_device!(lua, IkeaRemote);
|
register_device!(lua, IkeaRemote);
|
||||||
register_device!(lua, KasaOutlet);
|
register_device!(lua, KasaOutlet);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user