Further improvements to how devices are created
This commit is contained in:
@@ -4,8 +4,8 @@ use serde::Deserialize;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
|
||||
device_manager::WrappedDevice,
|
||||
config::MqttDeviceConfig,
|
||||
device_manager::{ConfigExternal, DeviceConfig, WrappedDevice},
|
||||
devices::As,
|
||||
error::DeviceConfigError,
|
||||
event::OnMqtt,
|
||||
@@ -64,7 +64,7 @@ impl DeviceConfig for AudioSetupConfig {
|
||||
}
|
||||
|
||||
let device = AudioSetup {
|
||||
identifier: identifier.to_owned(),
|
||||
identifier: identifier.into(),
|
||||
mqtt: self.mqtt,
|
||||
mixer,
|
||||
speakers,
|
||||
|
||||
@@ -2,17 +2,17 @@ use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use google_home::traits::OnOff;
|
||||
use rumqttc::{has_wildcards, AsyncClient};
|
||||
use rumqttc::AsyncClient;
|
||||
use serde::Deserialize;
|
||||
use serde_with::{serde_as, DurationSeconds};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
|
||||
device_manager::WrappedDevice,
|
||||
config::MqttDeviceConfig,
|
||||
device_manager::{ConfigExternal, DeviceConfig, WrappedDevice},
|
||||
devices::{As, DEFAULT_PRESENCE},
|
||||
error::{DeviceConfigError, MissingWildcard},
|
||||
error::DeviceConfigError,
|
||||
event::OnMqtt,
|
||||
event::OnPresence,
|
||||
messages::{ContactMessage, PresenceMessage},
|
||||
@@ -26,42 +26,15 @@ use super::Device;
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PresenceDeviceConfig {
|
||||
#[serde(flatten)]
|
||||
pub mqtt: Option<MqttDeviceConfig>,
|
||||
pub mqtt: MqttDeviceConfig,
|
||||
#[serde_as(as = "DurationSeconds")]
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
impl PresenceDeviceConfig {
|
||||
/// Set the mqtt topic to an appropriate value if it is not already set
|
||||
fn generate_topic(
|
||||
mut self,
|
||||
class: &str,
|
||||
identifier: &str,
|
||||
presence_topic: &str,
|
||||
) -> Result<PresenceDeviceConfig, MissingWildcard> {
|
||||
if self.mqtt.is_none() {
|
||||
if !has_wildcards(presence_topic) {
|
||||
return Err(MissingWildcard::new(presence_topic));
|
||||
}
|
||||
|
||||
// TODO: This is not perfect, if the topic is some/+/thing/# this will fail
|
||||
let offset = presence_topic
|
||||
.find('+')
|
||||
.or(presence_topic.find('#'))
|
||||
.unwrap();
|
||||
let topic = format!("{}/{class}/{identifier}", &presence_topic[..offset - 1]);
|
||||
trace!("Setting presence mqtt topic: {topic}");
|
||||
self.mqtt = Some(MqttDeviceConfig { topic });
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LightsConfig {
|
||||
lights: Vec<String>,
|
||||
pub struct TriggerConfig {
|
||||
devices: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "DurationSeconds")]
|
||||
pub timeout: Duration,
|
||||
@@ -72,7 +45,7 @@ pub struct ContactSensorConfig {
|
||||
#[serde(flatten)]
|
||||
mqtt: MqttDeviceConfig,
|
||||
presence: Option<PresenceDeviceConfig>,
|
||||
lights: Option<LightsConfig>,
|
||||
trigger: Option<TriggerConfig>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -84,43 +57,49 @@ impl DeviceConfig for ContactSensorConfig {
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
trace!(id = identifier, "Setting up ContactSensor");
|
||||
|
||||
let presence = self
|
||||
.presence
|
||||
.map(|p| p.generate_topic("contact", identifier, ext.presence_topic))
|
||||
.transpose()?;
|
||||
let trigger = if let Some(trigger_config) = &self.trigger {
|
||||
let mut devices = Vec::new();
|
||||
for device_name in &trigger_config.devices {
|
||||
let device = ext.device_manager.get(device_name).await.ok_or(
|
||||
DeviceConfigError::MissingChild(device_name.into(), "OnOff".into()),
|
||||
)?;
|
||||
|
||||
let lights =
|
||||
if let Some(lights_config) = self.lights {
|
||||
let mut lights = Vec::new();
|
||||
for name in lights_config.lights {
|
||||
let light = ext.device_manager.get(&name).await.ok_or(
|
||||
DeviceConfigError::MissingChild(name.clone(), "OnOff".into()),
|
||||
)?;
|
||||
|
||||
if !As::<dyn OnOff>::is(light.read().await.as_ref()) {
|
||||
return Err(DeviceConfigError::MissingTrait(name, "OnOff".into()));
|
||||
}
|
||||
|
||||
lights.push((light, false));
|
||||
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
||||
return Err(DeviceConfigError::MissingTrait(
|
||||
device_name.into(),
|
||||
"OnOff".into(),
|
||||
));
|
||||
}
|
||||
|
||||
Some(Lights {
|
||||
lights,
|
||||
timeout: lights_config.timeout,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if !trigger_config.timeout.is_zero()
|
||||
&& !As::<dyn Timeout>::is(device.read().await.as_ref())
|
||||
{
|
||||
return Err(DeviceConfigError::MissingTrait(
|
||||
device_name.into(),
|
||||
"Timeout".into(),
|
||||
));
|
||||
}
|
||||
|
||||
devices.push((device, false));
|
||||
}
|
||||
|
||||
Some(Trigger {
|
||||
devices,
|
||||
timeout: trigger_config.timeout,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let device = ContactSensor {
|
||||
identifier: identifier.to_owned(),
|
||||
identifier: identifier.into(),
|
||||
mqtt: self.mqtt,
|
||||
presence,
|
||||
presence: self.presence,
|
||||
client: ext.client.clone(),
|
||||
overall_presence: DEFAULT_PRESENCE,
|
||||
is_closed: true,
|
||||
handle: None,
|
||||
lights,
|
||||
trigger,
|
||||
};
|
||||
|
||||
Ok(Box::new(device))
|
||||
@@ -128,8 +107,8 @@ impl DeviceConfig for ContactSensorConfig {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Lights {
|
||||
lights: Vec<(WrappedDevice, bool)>,
|
||||
struct Trigger {
|
||||
devices: Vec<(WrappedDevice, bool)>,
|
||||
timeout: Duration, // Timeout in seconds
|
||||
}
|
||||
|
||||
@@ -144,7 +123,7 @@ struct ContactSensor {
|
||||
is_closed: bool,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
|
||||
lights: Option<Lights>,
|
||||
trigger: Option<Trigger>,
|
||||
}
|
||||
|
||||
impl Device for ContactSensor {
|
||||
@@ -182,9 +161,9 @@ impl OnMqtt for ContactSensor {
|
||||
debug!(id = self.identifier, "Updating state to {is_closed}");
|
||||
self.is_closed = is_closed;
|
||||
|
||||
if let Some(lights) = &mut self.lights {
|
||||
if let Some(trigger) = &mut self.trigger {
|
||||
if !self.is_closed {
|
||||
for (light, previous) in &mut lights.lights {
|
||||
for (light, previous) in &mut trigger.devices {
|
||||
let mut light = light.write().await;
|
||||
if let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
||||
*previous = light.is_on().await.unwrap();
|
||||
@@ -192,14 +171,14 @@ impl OnMqtt for ContactSensor {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (light, previous) in &lights.lights {
|
||||
for (light, previous) in &trigger.devices {
|
||||
let mut light = light.write().await;
|
||||
if !previous {
|
||||
// If the timeout is zero just turn the light off directly
|
||||
if lights.timeout.is_zero() && let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
||||
if trigger.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).await;
|
||||
light.start_timeout(trigger.timeout).await;
|
||||
}
|
||||
// TODO: Put a warning/error on creation if either of this has to option to fail
|
||||
}
|
||||
@@ -214,14 +193,6 @@ impl OnMqtt for ContactSensor {
|
||||
None => return,
|
||||
};
|
||||
|
||||
let topic = match &presence.mqtt {
|
||||
Some(mqtt) => mqtt.topic.clone(),
|
||||
None => {
|
||||
warn!("Contact sensors is configured as a presence sensor, but no mqtt topic is specified");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !is_closed {
|
||||
// Activate presence and stop any timeout once we open the door
|
||||
if let Some(handle) = self.handle.take() {
|
||||
@@ -234,13 +205,18 @@ impl OnMqtt for ContactSensor {
|
||||
if !self.overall_presence {
|
||||
self.client
|
||||
.publish(
|
||||
topic.clone(),
|
||||
presence.mqtt.topic.clone(),
|
||||
rumqttc::QoS::AtLeastOnce,
|
||||
false,
|
||||
serde_json::to_string(&PresenceMessage::new(true)).unwrap(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| warn!("Failed to publish presence on {topic}: {err}"))
|
||||
.map_err(|err| {
|
||||
warn!(
|
||||
"Failed to publish presence on {}: {err}",
|
||||
presence.mqtt.topic
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
@@ -248,6 +224,7 @@ impl OnMqtt for ContactSensor {
|
||||
let client = self.client.clone();
|
||||
let id = self.identifier.clone();
|
||||
let timeout = presence.timeout;
|
||||
let topic = presence.mqtt.topic.clone();
|
||||
self.handle = Some(tokio::spawn(async move {
|
||||
debug!(id, "Starting timeout ({timeout:?}) for contact sensor...");
|
||||
tokio::time::sleep(timeout).await;
|
||||
|
||||
@@ -3,7 +3,10 @@ use rumqttc::AsyncClient;
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::device_manager::ConfigExternal;
|
||||
use crate::device_manager::DeviceConfig;
|
||||
use crate::devices::Device;
|
||||
use crate::error::DeviceConfigError;
|
||||
use crate::event::OnDarkness;
|
||||
use crate::event::OnPresence;
|
||||
use crate::{
|
||||
@@ -17,24 +20,33 @@ pub struct DebugBridgeConfig {
|
||||
pub mqtt: MqttDeviceConfig,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DeviceConfig for DebugBridgeConfig {
|
||||
async fn create(
|
||||
self,
|
||||
identifier: &str,
|
||||
ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = DebugBridge {
|
||||
identifier: identifier.into(),
|
||||
mqtt: self.mqtt,
|
||||
client: ext.client.clone(),
|
||||
};
|
||||
|
||||
Ok(Box::new(device))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugBridge {
|
||||
identifier: String,
|
||||
mqtt: MqttDeviceConfig,
|
||||
client: AsyncClient,
|
||||
}
|
||||
|
||||
impl DebugBridge {
|
||||
pub fn new(config: DebugBridgeConfig, client: &AsyncClient) -> Self {
|
||||
Self {
|
||||
mqtt: config.mqtt,
|
||||
client: client.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for DebugBridge {
|
||||
fn get_id(&self) -> &str {
|
||||
"debug_bridge"
|
||||
&self.identifier
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use google_home::{errors::ErrorCode, traits::OnOff};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
config::{ConfigExternal, DeviceConfig},
|
||||
device_manager::{ConfigExternal, DeviceConfig},
|
||||
devices::Device,
|
||||
error::DeviceConfigError,
|
||||
event::OnDarkness,
|
||||
event::OnPresence,
|
||||
traits::Timeout,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -37,8 +31,27 @@ pub struct HueBridgeConfig {
|
||||
pub flags: FlagIDs,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DeviceConfig for HueBridgeConfig {
|
||||
async fn create(
|
||||
self,
|
||||
identifier: &str,
|
||||
_ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = HueBridge {
|
||||
identifier: identifier.into(),
|
||||
addr: (self.ip, 80).into(),
|
||||
login: self.login,
|
||||
flag_ids: self.flags,
|
||||
};
|
||||
|
||||
Ok(Box::new(device))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HueBridge {
|
||||
struct HueBridge {
|
||||
identifier: String,
|
||||
addr: SocketAddr,
|
||||
login: String,
|
||||
flag_ids: FlagIDs,
|
||||
@@ -80,19 +93,11 @@ impl HueBridge {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(config: HueBridgeConfig) -> Self {
|
||||
Self {
|
||||
addr: (config.ip, 80).into(),
|
||||
login: config.login,
|
||||
flag_ids: config.flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for HueBridge {
|
||||
fn get_id(&self) -> &str {
|
||||
"hue_bridge"
|
||||
&self.identifier
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,160 +116,3 @@ 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,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DeviceConfig for HueLightConfig {
|
||||
async fn create(
|
||||
self,
|
||||
identifier: &str,
|
||||
_ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = HueLight {
|
||||
identifier: identifier.to_owned(),
|
||||
addr: (self.ip, 80).into(),
|
||||
login: self.login,
|
||||
light_id: self.light_id,
|
||||
timer_id: self.timer_id,
|
||||
};
|
||||
|
||||
Ok(Box::new(device))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HueLight {
|
||||
pub identifier: String,
|
||||
pub addr: SocketAddr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub timer_id: isize,
|
||||
}
|
||||
|
||||
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!(id = self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(id = 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!(id = 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!(id = 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!(id = 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!(id = self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(id = 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!(id = self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(id = self.identifier, "Error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
175
src/devices/hue_light.rs
Normal file
175
src/devices/hue_light.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use std::{
|
||||
net::{Ipv4Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use google_home::{errors::ErrorCode, traits::OnOff};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::{
|
||||
device_manager::{ConfigExternal, DeviceConfig},
|
||||
error::DeviceConfigError,
|
||||
traits::Timeout,
|
||||
};
|
||||
|
||||
use super::Device;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct HueLightConfig {
|
||||
pub ip: Ipv4Addr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub timer_id: isize,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl DeviceConfig for HueLightConfig {
|
||||
async fn create(
|
||||
self,
|
||||
identifier: &str,
|
||||
_ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = HueLight {
|
||||
identifier: identifier.into(),
|
||||
addr: (self.ip, 80).into(),
|
||||
login: self.login,
|
||||
light_id: self.light_id,
|
||||
timer_id: self.timer_id,
|
||||
};
|
||||
|
||||
Ok(Box::new(device))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HueLight {
|
||||
pub identifier: String,
|
||||
pub addr: SocketAddr,
|
||||
pub login: String,
|
||||
pub light_id: isize,
|
||||
pub timer_id: isize,
|
||||
}
|
||||
|
||||
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!(id = self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(id = 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!(id = 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!(id = 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!(id = 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!(id = self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(id = 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!(id = self.identifier, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!(id = self.identifier, "Error: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ use std::time::Duration;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::config::{ConfigExternal, DeviceConfig, InfoConfig, MqttDeviceConfig};
|
||||
use crate::config::{InfoConfig, MqttDeviceConfig};
|
||||
use crate::device_manager::{ConfigExternal, DeviceConfig};
|
||||
use crate::devices::Device;
|
||||
use crate::error::DeviceConfigError;
|
||||
use crate::event::OnMqtt;
|
||||
@@ -62,7 +63,7 @@ impl DeviceConfig for IkeaOutletConfig {
|
||||
);
|
||||
|
||||
let device = IkeaOutlet {
|
||||
identifier: identifier.to_owned(),
|
||||
identifier: identifier.into(),
|
||||
info: self.info,
|
||||
mqtt: self.mqtt,
|
||||
outlet_type: self.outlet_type,
|
||||
|
||||
@@ -18,7 +18,7 @@ use tokio::{
|
||||
use tracing::trace;
|
||||
|
||||
use crate::{
|
||||
config::{ConfigExternal, DeviceConfig},
|
||||
device_manager::{ConfigExternal, DeviceConfig},
|
||||
error::DeviceConfigError,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ impl DeviceConfig for KasaOutletConfig {
|
||||
trace!(id = identifier, "Setting up KasaOutlet");
|
||||
|
||||
let device = KasaOutlet {
|
||||
identifier: identifier.to_owned(),
|
||||
identifier: identifier.into(),
|
||||
addr: (self.ip, 9999).into(),
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@ use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::{
|
||||
config::MqttDeviceConfig,
|
||||
device_manager::{ConfigExternal, DeviceConfig},
|
||||
devices::Device,
|
||||
error::DeviceConfigError,
|
||||
event::OnMqtt,
|
||||
event::{self, Event, EventChannel},
|
||||
event::{self, Event},
|
||||
messages::BrightnessMessage,
|
||||
};
|
||||
|
||||
@@ -21,8 +23,31 @@ pub struct LightSensorConfig {
|
||||
|
||||
pub const DEFAULT: bool = false;
|
||||
|
||||
// TODO: The light sensor should get a list of devices that it should inform
|
||||
|
||||
#[async_trait]
|
||||
impl DeviceConfig for LightSensorConfig {
|
||||
async fn create(
|
||||
self,
|
||||
identifier: &str,
|
||||
ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = LightSensor {
|
||||
identifier: identifier.into(),
|
||||
tx: ext.event_channel.get_tx(),
|
||||
mqtt: self.mqtt,
|
||||
min: self.min,
|
||||
max: self.max,
|
||||
is_dark: DEFAULT,
|
||||
};
|
||||
|
||||
Ok(Box::new(device))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LightSensor {
|
||||
identifier: String,
|
||||
tx: event::Sender,
|
||||
mqtt: MqttDeviceConfig,
|
||||
min: isize,
|
||||
@@ -30,21 +55,9 @@ pub struct LightSensor {
|
||||
is_dark: bool,
|
||||
}
|
||||
|
||||
impl LightSensor {
|
||||
pub fn new(config: LightSensorConfig, event_channel: &EventChannel) -> Self {
|
||||
Self {
|
||||
tx: event_channel.get_tx(),
|
||||
mqtt: config.mqtt,
|
||||
min: config.min,
|
||||
max: config.max,
|
||||
is_dark: DEFAULT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for LightSensor {
|
||||
fn get_id(&self) -> &str {
|
||||
"light_sensor"
|
||||
&self.identifier
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ mod audio_setup;
|
||||
mod contact_sensor;
|
||||
mod debug_bridge;
|
||||
mod hue_bridge;
|
||||
mod hue_light;
|
||||
mod ikea_outlet;
|
||||
mod kasa_outlet;
|
||||
mod light_sensor;
|
||||
@@ -12,8 +13,9 @@ mod washer;
|
||||
|
||||
pub use self::audio_setup::AudioSetupConfig;
|
||||
pub use self::contact_sensor::ContactSensorConfig;
|
||||
pub use self::debug_bridge::{DebugBridge, DebugBridgeConfig};
|
||||
pub use self::hue_bridge::{HueBridge, HueBridgeConfig, HueLightConfig};
|
||||
pub use self::debug_bridge::DebugBridgeConfig;
|
||||
pub use self::hue_bridge::HueBridgeConfig;
|
||||
pub use self::hue_light::HueLightConfig;
|
||||
pub use self::ikea_outlet::IkeaOutletConfig;
|
||||
pub use self::kasa_outlet::KasaOutletConfig;
|
||||
pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
||||
|
||||
@@ -14,7 +14,8 @@ use serde::Deserialize;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use crate::{
|
||||
config::{ConfigExternal, DeviceConfig, InfoConfig, MqttDeviceConfig},
|
||||
config::{InfoConfig, MqttDeviceConfig},
|
||||
device_manager::{ConfigExternal, DeviceConfig},
|
||||
error::DeviceConfigError,
|
||||
event::OnMqtt,
|
||||
messages::ActivateMessage,
|
||||
@@ -52,7 +53,7 @@ impl DeviceConfig for WakeOnLANConfig {
|
||||
);
|
||||
|
||||
let device = WakeOnLAN {
|
||||
identifier: identifier.to_owned(),
|
||||
identifier: identifier.into(),
|
||||
info: self.info,
|
||||
mqtt: self.mqtt,
|
||||
mac_address: self.mac_address,
|
||||
|
||||
@@ -4,7 +4,8 @@ use serde::Deserialize;
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::{
|
||||
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
|
||||
config::MqttDeviceConfig,
|
||||
device_manager::{ConfigExternal, DeviceConfig},
|
||||
error::DeviceConfigError,
|
||||
event::{Event, EventChannel, OnMqtt},
|
||||
messages::PowerMessage,
|
||||
@@ -27,7 +28,7 @@ impl DeviceConfig for WasherConfig {
|
||||
ext: &ConfigExternal,
|
||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||
let device = Washer {
|
||||
identifier: identifier.to_owned(),
|
||||
identifier: identifier.into(),
|
||||
mqtt: self.mqtt,
|
||||
event_channel: ext.event_channel.clone(),
|
||||
threshold: self.threshold,
|
||||
|
||||
Reference in New Issue
Block a user