HueLight is now HueGroup and uses a scene to turn the light on, the contact sensor will also not override the current light state if it is already on
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
044c38ba86
commit
9628b8a94b
|
@ -93,15 +93,16 @@ room = "Workbench"
|
|||
topic = "zigbee2mqtt/workbench/outlet"
|
||||
|
||||
|
||||
[device.hallway_light]
|
||||
type = "HueLight"
|
||||
[device.hallway_lights]
|
||||
type = "HueGroup"
|
||||
ip = "10.0.0.146"
|
||||
login = "${HUE_TOKEN}"
|
||||
light_id = 16
|
||||
group_id = 81
|
||||
scene_id = "3qWKxGVadXFFG4o"
|
||||
timer_id = 1
|
||||
|
||||
[device.hallway_frontdoor]
|
||||
type = "ContactSensor"
|
||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||
presence = { topic = "automation/presence/contact/frontdoor", timeout = 900 }
|
||||
trigger = { devices = ["hallway_light"], timeout = 60 }
|
||||
trigger = { devices = ["hallway_lights"], timeout = 60 }
|
||||
|
|
|
@ -95,15 +95,16 @@ room = "Workbench"
|
|||
topic = "zigbee2mqtt/workbench/outlet"
|
||||
|
||||
|
||||
[device.hallway_light]
|
||||
type = "HueLight"
|
||||
[device.hallway_lights]
|
||||
type = "HueGroup"
|
||||
ip = "10.0.0.146"
|
||||
login = "${HUE_TOKEN}"
|
||||
light_id = 16
|
||||
group_id = 81
|
||||
scene_id = "3qWKxGVadXFFG4o"
|
||||
timer_id = 1
|
||||
|
||||
[device.hallway_frontdoor]
|
||||
type = "ContactSensor"
|
||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||
presence = { topic = "automation_dev/presence/contact/frontdoor", timeout = 10 }
|
||||
trigger = { devices = ["hallway_light"], timeout = 10 }
|
||||
trigger = { devices = ["hallway_lights"], timeout = 10 }
|
||||
|
|
|
@ -13,7 +13,7 @@ use tracing::debug;
|
|||
use crate::{
|
||||
auth::OpenIDConfig,
|
||||
device_manager::DeviceConfigs,
|
||||
devices::{DebugBridgeConfig, PresenceConfig},
|
||||
devices::PresenceConfig,
|
||||
error::{ConfigParseError, MissingEnv},
|
||||
};
|
||||
|
||||
|
@ -26,7 +26,6 @@ pub struct Config {
|
|||
pub fullfillment: FullfillmentConfig,
|
||||
pub ntfy: Option<NtfyConfig>,
|
||||
pub presence: PresenceConfig,
|
||||
pub debug_bridge: Option<DebugBridgeConfig>,
|
||||
#[serde(rename = "device")]
|
||||
pub devices: IndexMap<String, DeviceConfigs>,
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ use tracing::{debug, error, instrument, trace};
|
|||
use crate::{
|
||||
devices::{
|
||||
As, AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device, HueBridgeConfig,
|
||||
HueLightConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig, WakeOnLANConfig,
|
||||
HueGroupConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig, WakeOnLANConfig,
|
||||
WasherConfig,
|
||||
},
|
||||
error::DeviceConfigError,
|
||||
|
@ -50,7 +50,7 @@ pub enum DeviceConfigs {
|
|||
WakeOnLAN(WakeOnLANConfig),
|
||||
Washer(WasherConfig),
|
||||
HueBridge(HueBridgeConfig),
|
||||
HueLight(HueLightConfig),
|
||||
HueGroup(HueGroupConfig),
|
||||
LightSensor(LightSensorConfig),
|
||||
}
|
||||
|
||||
|
|
|
@ -167,7 +167,13 @@ impl OnMqtt for ContactSensor {
|
|||
let mut light = light.write().await;
|
||||
if let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
||||
*previous = light.is_on().await.unwrap();
|
||||
light.set_on(true).await.ok();
|
||||
// Only turn the light on when it is currently off
|
||||
// This is done such that if the light is on but dimmed for example it
|
||||
// won't suddenly blast at full brightness but instead retain the current
|
||||
// state
|
||||
if !*previous {
|
||||
light.set_on(true).await.ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -18,25 +18,27 @@ use crate::{
|
|||
use super::Device;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct HueLightConfig {
|
||||
pub struct HueGroupConfig {
|
||||
pub ip: Ipv4Addr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub group_id: isize,
|
||||
pub timer_id: isize,
|
||||
pub scene_id: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DeviceConfig for HueLightConfig {
|
||||
impl DeviceConfig for HueGroupConfig {
|
||||
async fn create(
|
||||
self,
|
||||
identifier: &str,
|
||||
_ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = HueLight {
|
||||
let device = HueGroup {
|
||||
identifier: identifier.into(),
|
||||
addr: (self.ip, 80).into(),
|
||||
login: self.login,
|
||||
light_id: self.light_id,
|
||||
group_id: self.group_id,
|
||||
scene_id: self.scene_id,
|
||||
timer_id: self.timer_id,
|
||||
};
|
||||
|
||||
|
@ -45,16 +47,17 @@ impl DeviceConfig for HueLightConfig {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HueLight {
|
||||
struct HueGroup {
|
||||
pub identifier: String,
|
||||
pub addr: SocketAddr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub group_id: isize,
|
||||
pub timer_id: isize,
|
||||
pub scene_id: String,
|
||||
}
|
||||
|
||||
// Couple of helper function to get the correct urls
|
||||
impl HueLight {
|
||||
impl HueGroup {
|
||||
fn url_base(&self) -> String {
|
||||
format!("http://{}/api/{}", self.addr, self.login)
|
||||
}
|
||||
|
@ -63,30 +66,35 @@ impl HueLight {
|
|||
format!("{}/schedules/{}", self.url_base(), self.timer_id)
|
||||
}
|
||||
|
||||
fn url_set_state(&self) -> String {
|
||||
format!("{}/lights/{}/state", self.url_base(), self.light_id)
|
||||
fn url_set_action(&self) -> String {
|
||||
format!("{}/groups/{}/action", self.url_base(), self.group_id)
|
||||
}
|
||||
|
||||
fn url_get_state(&self) -> String {
|
||||
format!("{}/lights/{}", self.url_base(), self.light_id)
|
||||
format!("{}/groups/{}", self.url_base(), self.group_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for HueLight {
|
||||
impl Device for HueGroup {
|
||||
fn get_id(&self) -> &str {
|
||||
&self.identifier
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnOff for HueLight {
|
||||
impl OnOff for HueGroup {
|
||||
async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> {
|
||||
// Abort any timer that is currently running
|
||||
self.stop_timeout().await.unwrap();
|
||||
|
||||
let message = message::State::new(on);
|
||||
let message = if on {
|
||||
message::Action::scene(self.scene_id.clone())
|
||||
} else {
|
||||
message::Action::on(true)
|
||||
};
|
||||
|
||||
let res = reqwest::Client::new()
|
||||
.put(self.url_set_state())
|
||||
.put(self.url_set_action())
|
||||
.json(&message)
|
||||
.send()
|
||||
.await;
|
||||
|
@ -118,7 +126,7 @@ impl OnOff for HueLight {
|
|||
}
|
||||
|
||||
let on = match res.json::<message::Info>().await {
|
||||
Ok(info) => info.is_on(),
|
||||
Ok(info) => info.any_on(),
|
||||
Err(err) => {
|
||||
error!(id = self.identifier, "Failed to parse message: {err}");
|
||||
// TODO: Error code
|
||||
|
@ -136,11 +144,12 @@ impl OnOff for HueLight {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Timeout for HueLight {
|
||||
impl Timeout for HueGroup {
|
||||
async fn start_timeout(&mut 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 res = reqwest::Client::new()
|
||||
.put(self.url_set_schedule())
|
||||
|
@ -185,14 +194,33 @@ mod message {
|
|||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct State {
|
||||
on: bool,
|
||||
pub struct Action {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
on: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
scene: Option<String>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn new(on: bool) -> Self {
|
||||
Self { on }
|
||||
impl Action {
|
||||
pub fn on(on: bool) -> Self {
|
||||
Self {
|
||||
on: Some(on),
|
||||
scene: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scene(scene: String) -> Self {
|
||||
Self {
|
||||
on: None,
|
||||
scene: Some(scene),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct State {
|
||||
all_on: bool,
|
||||
any_on: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -201,9 +229,13 @@ mod message {
|
|||
}
|
||||
|
||||
impl Info {
|
||||
pub fn is_on(&self) -> bool {
|
||||
self.state.on
|
||||
pub fn any_on(&self) -> bool {
|
||||
self.state.any_on
|
||||
}
|
||||
|
||||
// pub fn all_on(&self) -> bool {
|
||||
// self.state.all_on
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -15,7 +15,7 @@ pub use self::audio_setup::AudioSetupConfig;
|
|||
pub use self::contact_sensor::ContactSensorConfig;
|
||||
pub use self::debug_bridge::DebugBridgeConfig;
|
||||
pub use self::hue_bridge::HueBridgeConfig;
|
||||
pub use self::hue_light::HueLightConfig;
|
||||
pub use self::hue_light::HueGroupConfig;
|
||||
pub use self::ikea_outlet::IkeaOutletConfig;
|
||||
pub use self::kasa_outlet::KasaOutletConfig;
|
||||
pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
||||
|
|
Loading…
Reference in New Issue
Block a user