Added basic hue light bridge, improved Timeout trait and setup frontdoor to turn on hallway ligh temporarily
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
c584fa014c
commit
bb131f2b1a
|
@ -79,10 +79,18 @@ topic = "zigbee2mqtt/living/remote"
|
|||
mixer = "living_mixer"
|
||||
speakers = "living_speakers"
|
||||
|
||||
[devices.hallway_light]
|
||||
type = "HueLight"
|
||||
ip = "10.0.0.146"
|
||||
login = "${HUE_TOKEN}"
|
||||
light_id = 16
|
||||
timer_id = 1
|
||||
|
||||
[devices.hallway_frontdoor]
|
||||
type = "ContactSensor"
|
||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||
presence = { timeout = 900 }
|
||||
lights = { lights = ["hallway_light"], timeout = 60 }
|
||||
|
||||
[devices.bathroom_washer]
|
||||
type = "Washer"
|
||||
|
|
|
@ -79,11 +79,18 @@ topic = "zigbee2mqtt/living/remote"
|
|||
mixer = "living_mixer"
|
||||
speakers = "living_speakers"
|
||||
|
||||
[devices.hallway_light]
|
||||
type = "HueLight"
|
||||
ip = "10.0.0.146"
|
||||
login = "${HUE_TOKEN}"
|
||||
light_id = 16
|
||||
timer_id = 1
|
||||
|
||||
[devices.hallway_frontdoor]
|
||||
type = "ContactSensor"
|
||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||
presence = { timeout = 10 }
|
||||
lights = { lights = ["bathroom_light"], timeout = 10 }
|
||||
lights = { lights = ["hallway_light"], timeout = 10 }
|
||||
|
||||
[devices.bathroom_washer]
|
||||
type = "Washer"
|
||||
|
|
|
@ -14,8 +14,8 @@ use crate::{
|
|||
auth::OpenIDConfig,
|
||||
device_manager::DeviceManager,
|
||||
devices::{
|
||||
AudioSetup, ContactSensor, DebugBridgeConfig, Device, HueBridgeConfig, IkeaOutlet,
|
||||
KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN, Washer,
|
||||
AudioSetup, ContactSensor, DebugBridgeConfig, Device, HueBridgeConfig, HueLight,
|
||||
IkeaOutlet, KasaOutlet, LightSensorConfig, PresenceConfig, WakeOnLAN, Washer,
|
||||
},
|
||||
error::{ConfigParseError, CreateDeviceError, MissingEnv},
|
||||
event::EventChannel,
|
||||
|
@ -131,6 +131,7 @@ pub enum DeviceConfig {
|
|||
KasaOutlet(<KasaOutlet as CreateDevice>::Config),
|
||||
WakeOnLAN(<WakeOnLAN as CreateDevice>::Config),
|
||||
Washer(<Washer as CreateDevice>::Config),
|
||||
HueLight(<HueLight as CreateDevice>::Config),
|
||||
}
|
||||
|
||||
impl Config {
|
||||
|
@ -202,7 +203,8 @@ impl DeviceConfig {
|
|||
IkeaOutlet,
|
||||
KasaOutlet,
|
||||
WakeOnLAN,
|
||||
Washer
|
||||
Washer,
|
||||
HueLight
|
||||
]
|
||||
})
|
||||
}
|
||||
|
|
|
@ -201,8 +201,9 @@ impl OnMqtt for ContactSensor {
|
|||
if lights.timeout.is_zero() && let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
||||
light.set_on(false).await.ok();
|
||||
} else if let Some(light) = As::<dyn Timeout>::cast_mut(light.as_mut()) {
|
||||
light.start_timeout(lights.timeout);
|
||||
light.start_timeout(lights.timeout).await;
|
||||
}
|
||||
// TODO: Put a warning/error on creation if either of this has to option to fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use google_home::{errors::ErrorCode, traits::OnOff};
|
||||
use rumqttc::AsyncClient;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{error, trace, warn};
|
||||
use serde_json::Value;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{devices::Device, event::OnDarkness, event::OnPresence};
|
||||
use crate::{
|
||||
config::CreateDevice, device_manager::DeviceManager, devices::Device, error::CreateDeviceError,
|
||||
event::EventChannel, event::OnDarkness, event::OnPresence, traits::Timeout,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Flag {
|
||||
|
@ -68,9 +77,7 @@ impl HueBridge {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HueBridge {
|
||||
pub fn new(config: HueBridgeConfig) -> Self {
|
||||
Self {
|
||||
addr: (config.ip, 80).into(),
|
||||
|
@ -101,3 +108,163 @@ impl OnDarkness for HueBridge {
|
|||
self.set_flag(Flag::Darkness, dark).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct HueLightConfig {
|
||||
pub ip: Ipv4Addr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub timer_id: isize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HueLight {
|
||||
pub identifier: String,
|
||||
pub addr: SocketAddr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub timer_id: isize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CreateDevice for HueLight {
|
||||
type Config = HueLightConfig;
|
||||
|
||||
async fn create(
|
||||
identifier: &str,
|
||||
config: Self::Config,
|
||||
_event_channel: &EventChannel,
|
||||
_client: &AsyncClient,
|
||||
_presence_topic: &str,
|
||||
_devices: &DeviceManager,
|
||||
) -> Result<Self, CreateDeviceError> {
|
||||
Ok(Self {
|
||||
identifier: identifier.to_owned(),
|
||||
addr: (config.ip, 80).into(),
|
||||
login: config.login,
|
||||
light_id: config.light_id,
|
||||
timer_id: config.timer_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for HueLight {
|
||||
fn get_id(&self) -> &str {
|
||||
&self.identifier
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnOff for HueLight {
|
||||
async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> {
|
||||
// Abort any timer that is currently running
|
||||
self.stop_timeout().await;
|
||||
|
||||
let url = format!(
|
||||
"http://{}/api/{}/lights/{}/state",
|
||||
self.addr, self.login, self.light_id
|
||||
);
|
||||
|
||||
let res = reqwest::Client::new()
|
||||
.put(url)
|
||||
.body(format!(r#"{{"on": {}}}"#, on))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(res) => {
|
||||
let status = res.status();
|
||||
if !status.is_success() {
|
||||
warn!(self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(self.identifier, "Error: {err}"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_on(&self) -> Result<bool, ErrorCode> {
|
||||
let url = format!(
|
||||
"http://{}/api/{}/lights/{}",
|
||||
self.addr, self.login, self.light_id
|
||||
);
|
||||
|
||||
let res = reqwest::Client::new().get(url).send().await;
|
||||
|
||||
match res {
|
||||
Ok(res) => {
|
||||
let status = res.status();
|
||||
if !status.is_success() {
|
||||
warn!(self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
|
||||
let v: Value = serde_json::from_slice(res.bytes().await.unwrap().as_ref()).unwrap();
|
||||
// TODO: This is not very nice
|
||||
return Ok(v["state"]["on"].as_bool().unwrap());
|
||||
}
|
||||
Err(err) => error!(self.identifier, "Error: {err}"),
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Timeout for HueLight {
|
||||
async fn start_timeout(&mut self, timeout: Duration) {
|
||||
// Abort any timer that is currently running
|
||||
self.stop_timeout().await;
|
||||
|
||||
let url = format!(
|
||||
"http://{}/api/{}/schedules/{}",
|
||||
self.addr, self.login, self.timer_id
|
||||
);
|
||||
|
||||
let seconds = timeout.as_secs() % 60;
|
||||
let minutes = (timeout.as_secs() / 60) % 60;
|
||||
let hours = timeout.as_secs() / 3600;
|
||||
let time = format!("PT{hours:<02}:{minutes:<02}:{seconds:<02}");
|
||||
|
||||
debug!(self.identifier, "Starting timeout ({time})...");
|
||||
|
||||
let res = reqwest::Client::new()
|
||||
.put(url)
|
||||
.body(format!(r#"{{"status": "enabled", "localtime": "{time}"}}"#))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(res) => {
|
||||
let status = res.status();
|
||||
if !status.is_success() {
|
||||
warn!(self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(self.identifier, "Error: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn stop_timeout(&mut self) {
|
||||
let url = format!(
|
||||
"http://{}/api/{}/schedules/{}",
|
||||
self.addr, self.login, self.timer_id
|
||||
);
|
||||
|
||||
let res = reqwest::Client::new()
|
||||
.put(url)
|
||||
.body(format!(r#"{{"status": "disabled"}}"#))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(res) => {
|
||||
let status = res.status();
|
||||
if !status.is_success() {
|
||||
warn!(self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(self.identifier, "Error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -135,16 +135,14 @@ impl OnMqtt for IkeaOutlet {
|
|||
}
|
||||
|
||||
// Abort any timer that is currently running
|
||||
if let Some(handle) = self.handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
self.stop_timeout().await;
|
||||
|
||||
debug!(id = self.identifier, "Updating state to {state}");
|
||||
self.last_known_state = state;
|
||||
|
||||
// If this is a kettle start a timeout for turning it of again
|
||||
if state && let Some(timeout) = self.timeout {
|
||||
self.start_timeout(timeout);
|
||||
self.start_timeout(timeout).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,12 +203,11 @@ impl traits::OnOff for IkeaOutlet {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::traits::Timeout for IkeaOutlet {
|
||||
fn start_timeout(&mut self, timeout: Duration) {
|
||||
async fn start_timeout(&mut self, timeout: Duration) {
|
||||
// Abort any timer that is currently running
|
||||
if let Some(handle) = self.handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
self.stop_timeout().await;
|
||||
|
||||
// Turn the kettle of after the specified timeout
|
||||
// TODO: Impl Drop for IkeaOutlet that will abort the handle if the IkeaOutlet
|
||||
|
@ -228,4 +225,10 @@ impl crate::traits::Timeout for IkeaOutlet {
|
|||
set_on(client, &topic, false).await;
|
||||
}));
|
||||
}
|
||||
|
||||
async fn stop_timeout(&mut self) {
|
||||
if let Some(handle) = self.handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ mod washer;
|
|||
pub use self::audio_setup::AudioSetup;
|
||||
pub use self::contact_sensor::ContactSensor;
|
||||
pub use self::debug_bridge::{DebugBridge, DebugBridgeConfig};
|
||||
pub use self::hue_bridge::{HueBridge, HueBridgeConfig};
|
||||
pub use self::hue_bridge::{HueBridge, HueBridgeConfig, HueLight};
|
||||
pub use self::ikea_outlet::IkeaOutlet;
|
||||
pub use self::kasa_outlet::KasaOutlet;
|
||||
pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use impl_cast::device_trait;
|
||||
|
||||
#[async_trait]
|
||||
#[device_trait]
|
||||
pub trait Timeout {
|
||||
fn start_timeout(&mut self, _timeout: Duration) {}
|
||||
async fn start_timeout(&mut self, _timeout: Duration);
|
||||
async fn stop_timeout(&mut self);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user