Further improvements to how devices are created
This commit is contained in:
parent
b1506f8e63
commit
cff9cb4bf6
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -85,13 +85,13 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"google-home",
|
"google-home",
|
||||||
"impl_cast",
|
"impl_cast",
|
||||||
|
"indexmap 2.0.0",
|
||||||
"paste",
|
"paste",
|
||||||
"pollster",
|
"pollster",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rumqttc",
|
"rumqttc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-tuple-vec-map",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
@ -1355,15 +1355,6 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-tuple-vec-map"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a04d0ebe0de77d7d445bb729a895dcb0a288854b267ca85f030ce51cdc578c82"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.183"
|
version = "1.0.183"
|
||||||
|
|
|
@ -37,9 +37,9 @@ anyhow = "1.0.68"
|
||||||
wakey = "0.3.0"
|
wakey = "0.3.0"
|
||||||
console-subscriber = "0.1.8"
|
console-subscriber = "0.1.8"
|
||||||
tracing-subscriber = "0.3.16"
|
tracing-subscriber = "0.3.16"
|
||||||
serde-tuple-vec-map = "1.0.1"
|
|
||||||
serde_with = "3.2.0"
|
serde_with = "3.2.0"
|
||||||
enum_dispatch = "0.3.12"
|
enum_dispatch = "0.3.12"
|
||||||
|
indexmap = { version = "2.0.0", features = ["serde"] }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
wakey = { git = "https://github.com/DreadedX/wakey" }
|
wakey = { git = "https://github.com/DreadedX/wakey" }
|
||||||
|
|
|
@ -15,15 +15,18 @@ topic = "${NTFY_TOPIC}"
|
||||||
[presence]
|
[presence]
|
||||||
topic = "automation_dev/presence/+/#"
|
topic = "automation_dev/presence/+/#"
|
||||||
|
|
||||||
[debug_bridge]
|
# Devices
|
||||||
|
[device.debug_bridge]
|
||||||
|
type = "DebugBridge"
|
||||||
topic = "automation_dev/debug"
|
topic = "automation_dev/debug"
|
||||||
|
|
||||||
[light_sensor]
|
[device.light_sensor]
|
||||||
|
type = "LightSensor"
|
||||||
topic = "zigbee2mqtt_dev/living/light"
|
topic = "zigbee2mqtt_dev/living/light"
|
||||||
min = 23_000
|
min = 23_000
|
||||||
max = 25_000
|
max = 25_000
|
||||||
|
|
||||||
[devices.kitchen_kettle]
|
[device.kitchen_kettle]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
outlet_type = "Kettle"
|
outlet_type = "Kettle"
|
||||||
name = "Kettle"
|
name = "Kettle"
|
||||||
|
@ -31,7 +34,7 @@ room = "Kitchen"
|
||||||
topic = "zigbee2mqtt/kitchen/kettle"
|
topic = "zigbee2mqtt/kitchen/kettle"
|
||||||
timeout = 5
|
timeout = 5
|
||||||
|
|
||||||
[devices.workbench_charger]
|
[device.workbench_charger]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
outlet_type = "Charger"
|
outlet_type = "Charger"
|
||||||
name = "Charger"
|
name = "Charger"
|
||||||
|
@ -39,20 +42,20 @@ room = "Workbench"
|
||||||
topic = "zigbee2mqtt/workbench/charger"
|
topic = "zigbee2mqtt/workbench/charger"
|
||||||
timeout = 5
|
timeout = 5
|
||||||
|
|
||||||
[devices.workbench_outlet]
|
[device.workbench_outlet]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
name = "Outlet"
|
name = "Outlet"
|
||||||
room = "Workbench"
|
room = "Workbench"
|
||||||
topic = "zigbee2mqtt/workbench/outlet"
|
topic = "zigbee2mqtt/workbench/outlet"
|
||||||
|
|
||||||
[devices.living_zeus]
|
[device.living_zeus]
|
||||||
type = "WakeOnLAN"
|
type = "WakeOnLAN"
|
||||||
name = "Zeus"
|
name = "Zeus"
|
||||||
room = "Living Room"
|
room = "Living Room"
|
||||||
topic = "automation/appliance/living_room/zeus"
|
topic = "automation/appliance/living_room/zeus"
|
||||||
mac_address = "30:9c:23:60:9c:13"
|
mac_address = "30:9c:23:60:9c:13"
|
||||||
|
|
||||||
[devices.hallway_frontdoor]
|
[device.hallway_frontdoor]
|
||||||
type = "ContactSensor"
|
type = "ContactSensor"
|
||||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||||
presence = { timeout = 10 }
|
presence = { topic = "automation_dev/presence/contact/frontdoor", timeout = 10 }
|
||||||
|
|
|
@ -14,50 +14,25 @@ topic = "${NTFY_TOPIC}"
|
||||||
[presence]
|
[presence]
|
||||||
topic = "automation/presence/+/#"
|
topic = "automation/presence/+/#"
|
||||||
|
|
||||||
[hue_bridge]
|
# Devices
|
||||||
|
[device.debug_bridge]
|
||||||
|
type = "DebugBridge"
|
||||||
|
topic = "automation/debug"
|
||||||
|
|
||||||
|
[device.hue_bridge]
|
||||||
|
type = "HueBridge"
|
||||||
ip = "10.0.0.146"
|
ip = "10.0.0.146"
|
||||||
login = "${HUE_TOKEN}"
|
login = "${HUE_TOKEN}"
|
||||||
flags = { presence = 41, darkness = 43 }
|
flags = { presence = 41, darkness = 43 }
|
||||||
|
|
||||||
[debug_bridge]
|
|
||||||
topic = "automation/debug"
|
|
||||||
|
|
||||||
[light_sensor]
|
[device.living_light_sensor]
|
||||||
|
type = "LightSensor"
|
||||||
topic = "zigbee2mqtt/living/light"
|
topic = "zigbee2mqtt/living/light"
|
||||||
min = 22_000
|
min = 22_000
|
||||||
max = 23_500
|
max = 23_500
|
||||||
|
|
||||||
[devices.kitchen_kettle]
|
[device.living_zeus]
|
||||||
type = "IkeaOutlet"
|
|
||||||
outlet_type = "Kettle"
|
|
||||||
name = "Kettle"
|
|
||||||
room = "Kitchen"
|
|
||||||
topic = "zigbee2mqtt/kitchen/kettle"
|
|
||||||
timeout = 300
|
|
||||||
|
|
||||||
[devices.bathroom_light]
|
|
||||||
type = "IkeaOutlet"
|
|
||||||
outlet_type = "Light"
|
|
||||||
name = "Light"
|
|
||||||
room = "Bathroom"
|
|
||||||
topic = "zigbee2mqtt/bathroom/light"
|
|
||||||
timeout = 2700
|
|
||||||
|
|
||||||
[devices.workbench_charger]
|
|
||||||
type = "IkeaOutlet"
|
|
||||||
outlet_type = "Charger"
|
|
||||||
name = "Charger"
|
|
||||||
room = "Workbench"
|
|
||||||
topic = "zigbee2mqtt/workbench/charger"
|
|
||||||
timeout = 72000
|
|
||||||
|
|
||||||
[devices.workbench_outlet]
|
|
||||||
type = "IkeaOutlet"
|
|
||||||
name = "Outlet"
|
|
||||||
room = "Workbench"
|
|
||||||
topic = "zigbee2mqtt/workbench/outlet"
|
|
||||||
|
|
||||||
[devices.living_zeus]
|
|
||||||
type = "WakeOnLAN"
|
type = "WakeOnLAN"
|
||||||
name = "Zeus"
|
name = "Zeus"
|
||||||
room = "Living Room"
|
room = "Living Room"
|
||||||
|
@ -65,34 +40,68 @@ topic = "automation/appliance/living_room/zeus"
|
||||||
mac_address = "30:9c:23:60:9c:13"
|
mac_address = "30:9c:23:60:9c:13"
|
||||||
broadcast_ip = "10.0.0.255"
|
broadcast_ip = "10.0.0.255"
|
||||||
|
|
||||||
[devices.living_mixer]
|
[device.living_mixer]
|
||||||
type = "KasaOutlet"
|
type = "KasaOutlet"
|
||||||
ip = "10.0.0.49"
|
ip = "10.0.0.49"
|
||||||
|
|
||||||
[devices.living_speakers]
|
[device.living_speakers]
|
||||||
type = "KasaOutlet"
|
type = "KasaOutlet"
|
||||||
ip = "10.0.0.182"
|
ip = "10.0.0.182"
|
||||||
|
|
||||||
[devices.living_audio]
|
[device.living_audio]
|
||||||
type = "AudioSetup"
|
type = "AudioSetup"
|
||||||
topic = "zigbee2mqtt/living/remote"
|
topic = "zigbee2mqtt/living/remote"
|
||||||
mixer = "living_mixer"
|
mixer = "living_mixer"
|
||||||
speakers = "living_speakers"
|
speakers = "living_speakers"
|
||||||
|
|
||||||
[devices.hallway_light]
|
|
||||||
|
[device.kitchen_kettle]
|
||||||
|
type = "IkeaOutlet"
|
||||||
|
outlet_type = "Kettle"
|
||||||
|
name = "Kettle"
|
||||||
|
room = "Kitchen"
|
||||||
|
topic = "zigbee2mqtt/kitchen/kettle"
|
||||||
|
timeout = 300
|
||||||
|
|
||||||
|
|
||||||
|
[device.bathroom_light]
|
||||||
|
type = "IkeaOutlet"
|
||||||
|
outlet_type = "Light"
|
||||||
|
name = "Light"
|
||||||
|
room = "Bathroom"
|
||||||
|
topic = "zigbee2mqtt/bathroom/light"
|
||||||
|
timeout = 2700
|
||||||
|
|
||||||
|
[device.bathroom_washer]
|
||||||
|
type = "Washer"
|
||||||
|
topic = "zigbee2mqtt/bathroom/washer"
|
||||||
|
threshold = 1
|
||||||
|
|
||||||
|
|
||||||
|
[device.workbench_charger]
|
||||||
|
type = "IkeaOutlet"
|
||||||
|
outlet_type = "Charger"
|
||||||
|
name = "Charger"
|
||||||
|
room = "Workbench"
|
||||||
|
topic = "zigbee2mqtt/workbench/charger"
|
||||||
|
timeout = 72000
|
||||||
|
|
||||||
|
[device.workbench_outlet]
|
||||||
|
type = "IkeaOutlet"
|
||||||
|
name = "Outlet"
|
||||||
|
room = "Workbench"
|
||||||
|
topic = "zigbee2mqtt/workbench/outlet"
|
||||||
|
|
||||||
|
|
||||||
|
[device.hallway_light]
|
||||||
type = "HueLight"
|
type = "HueLight"
|
||||||
ip = "10.0.0.146"
|
ip = "10.0.0.146"
|
||||||
login = "${HUE_TOKEN}"
|
login = "${HUE_TOKEN}"
|
||||||
light_id = 16
|
light_id = 16
|
||||||
timer_id = 1
|
timer_id = 1
|
||||||
|
|
||||||
[devices.hallway_frontdoor]
|
[device.hallway_frontdoor]
|
||||||
type = "ContactSensor"
|
type = "ContactSensor"
|
||||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||||
presence = { timeout = 900 }
|
presence = { topic = "automation/presence/contact/frontdoor", timeout = 900 }
|
||||||
lights = { lights = ["hallway_light"], timeout = 60 }
|
trigger = { devices = ["hallway_light"], timeout = 60 }
|
||||||
|
|
||||||
[devices.bathroom_washer]
|
|
||||||
type = "Washer"
|
|
||||||
topic = "zigbee2mqtt/bathroom/washer"
|
|
||||||
threshold = 1
|
|
||||||
|
|
|
@ -15,20 +15,49 @@ topic = "${NTFY_TOPIC}"
|
||||||
[presence]
|
[presence]
|
||||||
topic = "automation_dev/presence/+/#"
|
topic = "automation_dev/presence/+/#"
|
||||||
|
|
||||||
[hue_bridge]
|
# Devices
|
||||||
|
[device.debug_bridge]
|
||||||
|
type = "DebugBridge"
|
||||||
|
topic = "automation_dev/debug"
|
||||||
|
|
||||||
|
[device.hue_bridge]
|
||||||
|
type = "HueBridge"
|
||||||
ip = "10.0.0.146"
|
ip = "10.0.0.146"
|
||||||
login = "${HUE_TOKEN}"
|
login = "${HUE_TOKEN}"
|
||||||
flags = { presence = 41, darkness = 43 }
|
flags = { presence = 41, darkness = 43 }
|
||||||
|
|
||||||
[debug_bridge]
|
|
||||||
topic = "automation_dev/debug"
|
|
||||||
|
|
||||||
[light_sensor]
|
[device.living_light_sensor]
|
||||||
|
type = "LightSensor"
|
||||||
topic = "zigbee2mqtt_dev/living/light"
|
topic = "zigbee2mqtt_dev/living/light"
|
||||||
min = 23_000
|
min = 23_000
|
||||||
max = 25_000
|
max = 25_000
|
||||||
|
# TODO: Implement this:
|
||||||
|
trigger = ["hue_bridge", "debug_bridge"]
|
||||||
|
|
||||||
[devices.kitchen_kettle]
|
[device.living_zeus]
|
||||||
|
type = "WakeOnLAN"
|
||||||
|
name = "Zeus"
|
||||||
|
room = "Living Room"
|
||||||
|
topic = "automation/appliance/living_room/zeus"
|
||||||
|
mac_address = "30:9c:23:60:9c:13"
|
||||||
|
|
||||||
|
[device.living_mixer]
|
||||||
|
type = "KasaOutlet"
|
||||||
|
ip = "10.0.0.49"
|
||||||
|
|
||||||
|
[device.living_speakers]
|
||||||
|
type = "KasaOutlet"
|
||||||
|
ip = "10.0.0.182"
|
||||||
|
|
||||||
|
[device.living_audio]
|
||||||
|
type = "AudioSetup"
|
||||||
|
topic = "zigbee2mqtt/living/remote"
|
||||||
|
mixer = "living_mixer"
|
||||||
|
speakers = "living_speakers"
|
||||||
|
|
||||||
|
|
||||||
|
[device.kitchen_kettle]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
outlet_type = "Kettle"
|
outlet_type = "Kettle"
|
||||||
name = "Kettle"
|
name = "Kettle"
|
||||||
|
@ -36,7 +65,8 @@ room = "Kitchen"
|
||||||
topic = "zigbee2mqtt/kitchen/kettle"
|
topic = "zigbee2mqtt/kitchen/kettle"
|
||||||
timeout = 5
|
timeout = 5
|
||||||
|
|
||||||
[devices.bathroom_light]
|
|
||||||
|
[device.bathroom_light]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
outlet_type = "Light"
|
outlet_type = "Light"
|
||||||
name = "Bathroom light"
|
name = "Bathroom light"
|
||||||
|
@ -44,7 +74,13 @@ room = "Bathroom"
|
||||||
topic = "zigbee2mqtt/bathroom/light"
|
topic = "zigbee2mqtt/bathroom/light"
|
||||||
timeout = 60
|
timeout = 60
|
||||||
|
|
||||||
[devices.workbench_charger]
|
[device.bathroom_washer]
|
||||||
|
type = "Washer"
|
||||||
|
topic = "zigbee2mqtt/bathroom/washer"
|
||||||
|
threshold = 1
|
||||||
|
|
||||||
|
|
||||||
|
[device.workbench_charger]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
outlet_type = "Charger"
|
outlet_type = "Charger"
|
||||||
name = "Charger"
|
name = "Charger"
|
||||||
|
@ -52,47 +88,22 @@ room = "Workbench"
|
||||||
topic = "zigbee2mqtt/workbench/charger"
|
topic = "zigbee2mqtt/workbench/charger"
|
||||||
timeout = 5
|
timeout = 5
|
||||||
|
|
||||||
[devices.workbench_outlet]
|
[device.workbench_outlet]
|
||||||
type = "IkeaOutlet"
|
type = "IkeaOutlet"
|
||||||
name = "Outlet"
|
name = "Outlet"
|
||||||
room = "Workbench"
|
room = "Workbench"
|
||||||
topic = "zigbee2mqtt/workbench/outlet"
|
topic = "zigbee2mqtt/workbench/outlet"
|
||||||
|
|
||||||
[devices.living_zeus]
|
|
||||||
type = "WakeOnLAN"
|
|
||||||
name = "Zeus"
|
|
||||||
room = "Living Room"
|
|
||||||
topic = "automation/appliance/living_room/zeus"
|
|
||||||
mac_address = "30:9c:23:60:9c:13"
|
|
||||||
|
|
||||||
[devices.living_mixer]
|
[device.hallway_light]
|
||||||
type = "KasaOutlet"
|
|
||||||
ip = "10.0.0.49"
|
|
||||||
|
|
||||||
[devices.living_speakers]
|
|
||||||
type = "KasaOutlet"
|
|
||||||
ip = "10.0.0.182"
|
|
||||||
|
|
||||||
[devices.living_audio]
|
|
||||||
type = "AudioSetup"
|
|
||||||
topic = "zigbee2mqtt/living/remote"
|
|
||||||
mixer = "living_mixer"
|
|
||||||
speakers = "living_speakers"
|
|
||||||
|
|
||||||
[devices.hallway_light]
|
|
||||||
type = "HueLight"
|
type = "HueLight"
|
||||||
ip = "10.0.0.146"
|
ip = "10.0.0.146"
|
||||||
login = "${HUE_TOKEN}"
|
login = "${HUE_TOKEN}"
|
||||||
light_id = 16
|
light_id = 16
|
||||||
timer_id = 1
|
timer_id = 1
|
||||||
|
|
||||||
[devices.hallway_frontdoor]
|
[device.hallway_frontdoor]
|
||||||
type = "ContactSensor"
|
type = "ContactSensor"
|
||||||
topic = "zigbee2mqtt/hallway/frontdoor"
|
topic = "zigbee2mqtt/hallway/frontdoor"
|
||||||
presence = { timeout = 10 }
|
presence = { topic = "automation_dev/presence/contact/frontdoor", timeout = 10 }
|
||||||
lights = { lights = ["hallway_light"], timeout = 10 }
|
trigger = { devices = ["hallway_light"], timeout = 10 }
|
||||||
|
|
||||||
[devices.bathroom_washer]
|
|
||||||
type = "Washer"
|
|
||||||
topic = "zigbee2mqtt/bathroom/washer"
|
|
||||||
threshold = 1
|
|
||||||
|
|
|
@ -4,23 +4,17 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use indexmap::IndexMap;
|
||||||
use enum_dispatch::enum_dispatch;
|
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
use rumqttc::{AsyncClient, MqttOptions, Transport};
|
use rumqttc::{MqttOptions, Transport};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::OpenIDConfig,
|
auth::OpenIDConfig,
|
||||||
device_manager::DeviceManager,
|
device_manager::DeviceConfigs,
|
||||||
devices::{
|
devices::{DebugBridgeConfig, PresenceConfig},
|
||||||
AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device, HueBridgeConfig,
|
error::{ConfigParseError, MissingEnv},
|
||||||
HueLightConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig, PresenceConfig,
|
|
||||||
WakeOnLANConfig, WasherConfig,
|
|
||||||
},
|
|
||||||
error::{ConfigParseError, DeviceConfigError, MissingEnv},
|
|
||||||
event::EventChannel,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -32,11 +26,9 @@ pub struct Config {
|
||||||
pub fullfillment: FullfillmentConfig,
|
pub fullfillment: FullfillmentConfig,
|
||||||
pub ntfy: Option<NtfyConfig>,
|
pub ntfy: Option<NtfyConfig>,
|
||||||
pub presence: PresenceConfig,
|
pub presence: PresenceConfig,
|
||||||
pub light_sensor: LightSensorConfig,
|
|
||||||
pub hue_bridge: Option<HueBridgeConfig>,
|
|
||||||
pub debug_bridge: Option<DebugBridgeConfig>,
|
pub debug_bridge: Option<DebugBridgeConfig>,
|
||||||
#[serde(default, with = "tuple_vec_map")]
|
#[serde(rename = "device")]
|
||||||
pub devices: Vec<(String, DeviceConfigs)>,
|
pub devices: IndexMap<String, DeviceConfigs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
@ -151,33 +143,3 @@ impl Config {
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ConfigExternal<'a> {
|
|
||||||
pub client: &'a AsyncClient,
|
|
||||||
pub device_manager: &'a DeviceManager,
|
|
||||||
pub presence_topic: &'a str,
|
|
||||||
pub event_channel: &'a EventChannel,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
#[enum_dispatch]
|
|
||||||
pub trait DeviceConfig {
|
|
||||||
async fn create(
|
|
||||||
self,
|
|
||||||
identifier: &str,
|
|
||||||
ext: &ConfigExternal,
|
|
||||||
) -> Result<Box<dyn Device>, DeviceConfigError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
#[enum_dispatch(DeviceConfig)]
|
|
||||||
pub enum DeviceConfigs {
|
|
||||||
AudioSetup(AudioSetupConfig),
|
|
||||||
ContactSensor(ContactSensorConfig),
|
|
||||||
IkeaOutlet(IkeaOutletConfig),
|
|
||||||
KasaOutlet(KasaOutletConfig),
|
|
||||||
WakeOnLAN(WakeOnLANConfig),
|
|
||||||
Washer(WasherConfig),
|
|
||||||
HueLight(HueLightConfig),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,19 +1,59 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use rumqttc::{matches, AsyncClient, QoS};
|
use rumqttc::{matches, AsyncClient, QoS};
|
||||||
|
use serde::Deserialize;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||||
use tracing::{debug, error, instrument, trace};
|
use tracing::{debug, error, instrument, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
devices::{As, Device},
|
devices::{
|
||||||
|
As, AudioSetupConfig, ContactSensorConfig, DebugBridgeConfig, Device, HueBridgeConfig,
|
||||||
|
HueLightConfig, IkeaOutletConfig, KasaOutletConfig, LightSensorConfig, WakeOnLANConfig,
|
||||||
|
WasherConfig,
|
||||||
|
},
|
||||||
|
error::DeviceConfigError,
|
||||||
event::OnDarkness,
|
event::OnDarkness,
|
||||||
event::OnNotification,
|
event::OnNotification,
|
||||||
event::OnPresence,
|
event::OnPresence,
|
||||||
event::{Event, EventChannel, OnMqtt},
|
event::{Event, EventChannel, OnMqtt},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub struct ConfigExternal<'a> {
|
||||||
|
pub client: &'a AsyncClient,
|
||||||
|
pub device_manager: &'a DeviceManager,
|
||||||
|
pub event_channel: &'a EventChannel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
#[enum_dispatch]
|
||||||
|
pub trait DeviceConfig {
|
||||||
|
async fn create(
|
||||||
|
self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
#[enum_dispatch(DeviceConfig)]
|
||||||
|
pub enum DeviceConfigs {
|
||||||
|
AudioSetup(AudioSetupConfig),
|
||||||
|
ContactSensor(ContactSensorConfig),
|
||||||
|
DebugBridge(DebugBridgeConfig),
|
||||||
|
IkeaOutlet(IkeaOutletConfig),
|
||||||
|
KasaOutlet(KasaOutletConfig),
|
||||||
|
WakeOnLAN(WakeOnLANConfig),
|
||||||
|
Washer(WasherConfig),
|
||||||
|
HueBridge(HueBridgeConfig),
|
||||||
|
HueLight(HueLightConfig),
|
||||||
|
LightSensor(LightSensorConfig),
|
||||||
|
}
|
||||||
|
|
||||||
pub type WrappedDevice = Arc<RwLock<Box<dyn Device>>>;
|
pub type WrappedDevice = Arc<RwLock<Box<dyn Device>>>;
|
||||||
pub type DeviceMap = HashMap<String, WrappedDevice>;
|
pub type DeviceMap = HashMap<String, WrappedDevice>;
|
||||||
|
|
||||||
|
@ -21,31 +61,33 @@ pub type DeviceMap = HashMap<String, WrappedDevice>;
|
||||||
pub struct DeviceManager {
|
pub struct DeviceManager {
|
||||||
devices: Arc<RwLock<DeviceMap>>,
|
devices: Arc<RwLock<DeviceMap>>,
|
||||||
client: AsyncClient,
|
client: AsyncClient,
|
||||||
|
event_channel: EventChannel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DeviceManager {
|
impl DeviceManager {
|
||||||
pub fn new(client: AsyncClient) -> Self {
|
pub fn new(client: AsyncClient) -> Self {
|
||||||
Self {
|
|
||||||
devices: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(&self) -> EventChannel {
|
|
||||||
let (event_channel, mut event_rx) = EventChannel::new();
|
let (event_channel, mut event_rx) = EventChannel::new();
|
||||||
|
|
||||||
let devices = self.clone();
|
let device_manager = Self {
|
||||||
tokio::spawn(async move {
|
devices: Arc::new(RwLock::new(HashMap::new())),
|
||||||
loop {
|
client,
|
||||||
if let Some(event) = event_rx.recv().await {
|
event_channel,
|
||||||
devices.handle_event(event).await;
|
};
|
||||||
} else {
|
|
||||||
todo!("Handle errors with the event channel properly")
|
tokio::spawn({
|
||||||
|
let device_manager = device_manager.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
if let Some(event) = event_rx.recv().await {
|
||||||
|
device_manager.handle_event(event).await;
|
||||||
|
} else {
|
||||||
|
todo!("Handle errors with the event channel properly")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
event_channel
|
device_manager
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(&self, device: Box<dyn Device>) {
|
pub async fn add(&self, device: Box<dyn Device>) {
|
||||||
|
@ -71,6 +113,28 @@ impl DeviceManager {
|
||||||
self.devices.write().await.insert(id, device);
|
self.devices.write().await.insert(id, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
device_config: DeviceConfigs,
|
||||||
|
) -> Result<(), DeviceConfigError> {
|
||||||
|
let ext = ConfigExternal {
|
||||||
|
client: &self.client,
|
||||||
|
device_manager: self,
|
||||||
|
event_channel: &self.event_channel,
|
||||||
|
};
|
||||||
|
|
||||||
|
let device = device_config.create(identifier, &ext).await?;
|
||||||
|
|
||||||
|
self.add(device).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_channel(&self) -> EventChannel {
|
||||||
|
self.event_channel.clone()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get(&self, name: &str) -> Option<WrappedDevice> {
|
pub async fn get(&self, name: &str) -> Option<WrappedDevice> {
|
||||||
self.devices.read().await.get(name).cloned()
|
self.devices.read().await.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ use serde::Deserialize;
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
|
config::MqttDeviceConfig,
|
||||||
device_manager::WrappedDevice,
|
device_manager::{ConfigExternal, DeviceConfig, WrappedDevice},
|
||||||
devices::As,
|
devices::As,
|
||||||
error::DeviceConfigError,
|
error::DeviceConfigError,
|
||||||
event::OnMqtt,
|
event::OnMqtt,
|
||||||
|
@ -64,7 +64,7 @@ impl DeviceConfig for AudioSetupConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
let device = AudioSetup {
|
let device = AudioSetup {
|
||||||
identifier: identifier.to_owned(),
|
identifier: identifier.into(),
|
||||||
mqtt: self.mqtt,
|
mqtt: self.mqtt,
|
||||||
mixer,
|
mixer,
|
||||||
speakers,
|
speakers,
|
||||||
|
|
|
@ -2,17 +2,17 @@ use std::time::Duration;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
use rumqttc::{has_wildcards, AsyncClient};
|
use rumqttc::AsyncClient;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_with::{serde_as, DurationSeconds};
|
use serde_with::{serde_as, DurationSeconds};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
|
config::MqttDeviceConfig,
|
||||||
device_manager::WrappedDevice,
|
device_manager::{ConfigExternal, DeviceConfig, WrappedDevice},
|
||||||
devices::{As, DEFAULT_PRESENCE},
|
devices::{As, DEFAULT_PRESENCE},
|
||||||
error::{DeviceConfigError, MissingWildcard},
|
error::DeviceConfigError,
|
||||||
event::OnMqtt,
|
event::OnMqtt,
|
||||||
event::OnPresence,
|
event::OnPresence,
|
||||||
messages::{ContactMessage, PresenceMessage},
|
messages::{ContactMessage, PresenceMessage},
|
||||||
|
@ -26,42 +26,15 @@ use super::Device;
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct PresenceDeviceConfig {
|
pub struct PresenceDeviceConfig {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub mqtt: Option<MqttDeviceConfig>,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[serde_as(as = "DurationSeconds")]
|
#[serde_as(as = "DurationSeconds")]
|
||||||
pub timeout: Duration,
|
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]
|
#[serde_as]
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct LightsConfig {
|
pub struct TriggerConfig {
|
||||||
lights: Vec<String>,
|
devices: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde_as(as = "DurationSeconds")]
|
#[serde_as(as = "DurationSeconds")]
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
|
@ -72,7 +45,7 @@ pub struct ContactSensorConfig {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
presence: Option<PresenceDeviceConfig>,
|
presence: Option<PresenceDeviceConfig>,
|
||||||
lights: Option<LightsConfig>,
|
trigger: Option<TriggerConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -84,43 +57,49 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(id = identifier, "Setting up ContactSensor");
|
trace!(id = identifier, "Setting up ContactSensor");
|
||||||
|
|
||||||
let presence = self
|
let trigger = if let Some(trigger_config) = &self.trigger {
|
||||||
.presence
|
let mut devices = Vec::new();
|
||||||
.map(|p| p.generate_topic("contact", identifier, ext.presence_topic))
|
for device_name in &trigger_config.devices {
|
||||||
.transpose()?;
|
let device = ext.device_manager.get(device_name).await.ok_or(
|
||||||
|
DeviceConfigError::MissingChild(device_name.into(), "OnOff".into()),
|
||||||
|
)?;
|
||||||
|
|
||||||
let lights =
|
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
||||||
if let Some(lights_config) = self.lights {
|
return Err(DeviceConfigError::MissingTrait(
|
||||||
let mut lights = Vec::new();
|
device_name.into(),
|
||||||
for name in lights_config.lights {
|
"OnOff".into(),
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Lights {
|
if !trigger_config.timeout.is_zero()
|
||||||
lights,
|
&& !As::<dyn Timeout>::is(device.read().await.as_ref())
|
||||||
timeout: lights_config.timeout,
|
{
|
||||||
})
|
return Err(DeviceConfigError::MissingTrait(
|
||||||
} else {
|
device_name.into(),
|
||||||
None
|
"Timeout".into(),
|
||||||
};
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.push((device, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Trigger {
|
||||||
|
devices,
|
||||||
|
timeout: trigger_config.timeout,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let device = ContactSensor {
|
let device = ContactSensor {
|
||||||
identifier: identifier.to_owned(),
|
identifier: identifier.into(),
|
||||||
mqtt: self.mqtt,
|
mqtt: self.mqtt,
|
||||||
presence,
|
presence: self.presence,
|
||||||
client: ext.client.clone(),
|
client: ext.client.clone(),
|
||||||
overall_presence: DEFAULT_PRESENCE,
|
overall_presence: DEFAULT_PRESENCE,
|
||||||
is_closed: true,
|
is_closed: true,
|
||||||
handle: None,
|
handle: None,
|
||||||
lights,
|
trigger,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(device))
|
Ok(Box::new(device))
|
||||||
|
@ -128,8 +107,8 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Lights {
|
struct Trigger {
|
||||||
lights: Vec<(WrappedDevice, bool)>,
|
devices: Vec<(WrappedDevice, bool)>,
|
||||||
timeout: Duration, // Timeout in seconds
|
timeout: Duration, // Timeout in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +123,7 @@ struct ContactSensor {
|
||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: Option<JoinHandle<()>>,
|
||||||
|
|
||||||
lights: Option<Lights>,
|
trigger: Option<Trigger>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for ContactSensor {
|
impl Device for ContactSensor {
|
||||||
|
@ -182,9 +161,9 @@ impl OnMqtt for ContactSensor {
|
||||||
debug!(id = self.identifier, "Updating state to {is_closed}");
|
debug!(id = self.identifier, "Updating state to {is_closed}");
|
||||||
self.is_closed = 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 {
|
if !self.is_closed {
|
||||||
for (light, previous) in &mut lights.lights {
|
for (light, previous) in &mut trigger.devices {
|
||||||
let mut light = light.write().await;
|
let mut light = light.write().await;
|
||||||
if let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
if let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut()) {
|
||||||
*previous = light.is_on().await.unwrap();
|
*previous = light.is_on().await.unwrap();
|
||||||
|
@ -192,14 +171,14 @@ impl OnMqtt for ContactSensor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (light, previous) in &lights.lights {
|
for (light, previous) in &trigger.devices {
|
||||||
let mut light = light.write().await;
|
let mut light = light.write().await;
|
||||||
if !previous {
|
if !previous {
|
||||||
// If the timeout is zero just turn the light off directly
|
// 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();
|
light.set_on(false).await.ok();
|
||||||
} else if let Some(light) = As::<dyn Timeout>::cast_mut(light.as_mut()) {
|
} 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
|
// 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,
|
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 {
|
if !is_closed {
|
||||||
// Activate presence and stop any timeout once we open the door
|
// Activate presence and stop any timeout once we open the door
|
||||||
if let Some(handle) = self.handle.take() {
|
if let Some(handle) = self.handle.take() {
|
||||||
|
@ -234,13 +205,18 @@ impl OnMqtt for ContactSensor {
|
||||||
if !self.overall_presence {
|
if !self.overall_presence {
|
||||||
self.client
|
self.client
|
||||||
.publish(
|
.publish(
|
||||||
topic.clone(),
|
presence.mqtt.topic.clone(),
|
||||||
rumqttc::QoS::AtLeastOnce,
|
rumqttc::QoS::AtLeastOnce,
|
||||||
false,
|
false,
|
||||||
serde_json::to_string(&PresenceMessage::new(true)).unwrap(),
|
serde_json::to_string(&PresenceMessage::new(true)).unwrap(),
|
||||||
)
|
)
|
||||||
.await
|
.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();
|
.ok();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -248,6 +224,7 @@ impl OnMqtt for ContactSensor {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let id = self.identifier.clone();
|
let id = self.identifier.clone();
|
||||||
let timeout = presence.timeout;
|
let timeout = presence.timeout;
|
||||||
|
let topic = presence.mqtt.topic.clone();
|
||||||
self.handle = Some(tokio::spawn(async move {
|
self.handle = Some(tokio::spawn(async move {
|
||||||
debug!(id, "Starting timeout ({timeout:?}) for contact sensor...");
|
debug!(id, "Starting timeout ({timeout:?}) for contact sensor...");
|
||||||
tokio::time::sleep(timeout).await;
|
tokio::time::sleep(timeout).await;
|
||||||
|
|
|
@ -3,7 +3,10 @@ use rumqttc::AsyncClient;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
use crate::device_manager::ConfigExternal;
|
||||||
|
use crate::device_manager::DeviceConfig;
|
||||||
use crate::devices::Device;
|
use crate::devices::Device;
|
||||||
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::OnDarkness;
|
use crate::event::OnDarkness;
|
||||||
use crate::event::OnPresence;
|
use crate::event::OnPresence;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -17,24 +20,33 @@ pub struct DebugBridgeConfig {
|
||||||
pub mqtt: MqttDeviceConfig,
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct DebugBridge {
|
pub struct DebugBridge {
|
||||||
|
identifier: String,
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
client: AsyncClient,
|
client: AsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebugBridge {
|
|
||||||
pub fn new(config: DebugBridgeConfig, client: &AsyncClient) -> Self {
|
|
||||||
Self {
|
|
||||||
mqtt: config.mqtt,
|
|
||||||
client: client.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Device for DebugBridge {
|
impl Device for DebugBridge {
|
||||||
fn get_id(&self) -> &str {
|
fn get_id(&self) -> &str {
|
||||||
"debug_bridge"
|
&self.identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
use std::{
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
net::{Ipv4Addr, SocketAddr},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use google_home::{errors::ErrorCode, traits::OnOff};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use tracing::{error, trace, warn};
|
||||||
use tracing::{debug, error, trace, warn};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ConfigExternal, DeviceConfig},
|
device_manager::{ConfigExternal, DeviceConfig},
|
||||||
devices::Device,
|
devices::Device,
|
||||||
error::DeviceConfigError,
|
error::DeviceConfigError,
|
||||||
event::OnDarkness,
|
event::OnDarkness,
|
||||||
event::OnPresence,
|
event::OnPresence,
|
||||||
traits::Timeout,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -37,8 +31,27 @@ pub struct HueBridgeConfig {
|
||||||
pub flags: FlagIDs,
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct HueBridge {
|
struct HueBridge {
|
||||||
|
identifier: String,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
login: String,
|
login: String,
|
||||||
flag_ids: FlagIDs,
|
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 {
|
impl Device for HueBridge {
|
||||||
fn get_id(&self) -> &str {
|
fn get_id(&self) -> &str {
|
||||||
"hue_bridge"
|
&self.identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,160 +116,3 @@ impl OnDarkness for HueBridge {
|
||||||
self.set_flag(Flag::Darkness, dark).await;
|
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 tokio::task::JoinHandle;
|
||||||
use tracing::{debug, error, trace, warn};
|
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::devices::Device;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::OnMqtt;
|
use crate::event::OnMqtt;
|
||||||
|
@ -62,7 +63,7 @@ impl DeviceConfig for IkeaOutletConfig {
|
||||||
);
|
);
|
||||||
|
|
||||||
let device = IkeaOutlet {
|
let device = IkeaOutlet {
|
||||||
identifier: identifier.to_owned(),
|
identifier: identifier.into(),
|
||||||
info: self.info,
|
info: self.info,
|
||||||
mqtt: self.mqtt,
|
mqtt: self.mqtt,
|
||||||
outlet_type: self.outlet_type,
|
outlet_type: self.outlet_type,
|
||||||
|
|
|
@ -18,7 +18,7 @@ use tokio::{
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ConfigExternal, DeviceConfig},
|
device_manager::{ConfigExternal, DeviceConfig},
|
||||||
error::DeviceConfigError,
|
error::DeviceConfigError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ impl DeviceConfig for KasaOutletConfig {
|
||||||
trace!(id = identifier, "Setting up KasaOutlet");
|
trace!(id = identifier, "Setting up KasaOutlet");
|
||||||
|
|
||||||
let device = KasaOutlet {
|
let device = KasaOutlet {
|
||||||
identifier: identifier.to_owned(),
|
identifier: identifier.into(),
|
||||||
addr: (self.ip, 9999).into(),
|
addr: (self.ip, 9999).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,11 @@ use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::MqttDeviceConfig,
|
config::MqttDeviceConfig,
|
||||||
|
device_manager::{ConfigExternal, DeviceConfig},
|
||||||
devices::Device,
|
devices::Device,
|
||||||
|
error::DeviceConfigError,
|
||||||
event::OnMqtt,
|
event::OnMqtt,
|
||||||
event::{self, Event, EventChannel},
|
event::{self, Event},
|
||||||
messages::BrightnessMessage,
|
messages::BrightnessMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,8 +23,31 @@ pub struct LightSensorConfig {
|
||||||
|
|
||||||
pub const DEFAULT: bool = false;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct LightSensor {
|
pub struct LightSensor {
|
||||||
|
identifier: String,
|
||||||
tx: event::Sender,
|
tx: event::Sender,
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
min: isize,
|
min: isize,
|
||||||
|
@ -30,21 +55,9 @@ pub struct LightSensor {
|
||||||
is_dark: bool,
|
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 {
|
impl Device for LightSensor {
|
||||||
fn get_id(&self) -> &str {
|
fn get_id(&self) -> &str {
|
||||||
"light_sensor"
|
&self.identifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod audio_setup;
|
||||||
mod contact_sensor;
|
mod contact_sensor;
|
||||||
mod debug_bridge;
|
mod debug_bridge;
|
||||||
mod hue_bridge;
|
mod hue_bridge;
|
||||||
|
mod hue_light;
|
||||||
mod ikea_outlet;
|
mod ikea_outlet;
|
||||||
mod kasa_outlet;
|
mod kasa_outlet;
|
||||||
mod light_sensor;
|
mod light_sensor;
|
||||||
|
@ -12,8 +13,9 @@ mod washer;
|
||||||
|
|
||||||
pub use self::audio_setup::AudioSetupConfig;
|
pub use self::audio_setup::AudioSetupConfig;
|
||||||
pub use self::contact_sensor::ContactSensorConfig;
|
pub use self::contact_sensor::ContactSensorConfig;
|
||||||
pub use self::debug_bridge::{DebugBridge, DebugBridgeConfig};
|
pub use self::debug_bridge::DebugBridgeConfig;
|
||||||
pub use self::hue_bridge::{HueBridge, HueBridgeConfig, HueLightConfig};
|
pub use self::hue_bridge::HueBridgeConfig;
|
||||||
|
pub use self::hue_light::HueLightConfig;
|
||||||
pub use self::ikea_outlet::IkeaOutletConfig;
|
pub use self::ikea_outlet::IkeaOutletConfig;
|
||||||
pub use self::kasa_outlet::KasaOutletConfig;
|
pub use self::kasa_outlet::KasaOutletConfig;
|
||||||
pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
||||||
|
|
|
@ -14,7 +14,8 @@ use serde::Deserialize;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ConfigExternal, DeviceConfig, InfoConfig, MqttDeviceConfig},
|
config::{InfoConfig, MqttDeviceConfig},
|
||||||
|
device_manager::{ConfigExternal, DeviceConfig},
|
||||||
error::DeviceConfigError,
|
error::DeviceConfigError,
|
||||||
event::OnMqtt,
|
event::OnMqtt,
|
||||||
messages::ActivateMessage,
|
messages::ActivateMessage,
|
||||||
|
@ -52,7 +53,7 @@ impl DeviceConfig for WakeOnLANConfig {
|
||||||
);
|
);
|
||||||
|
|
||||||
let device = WakeOnLAN {
|
let device = WakeOnLAN {
|
||||||
identifier: identifier.to_owned(),
|
identifier: identifier.into(),
|
||||||
info: self.info,
|
info: self.info,
|
||||||
mqtt: self.mqtt,
|
mqtt: self.mqtt,
|
||||||
mac_address: self.mac_address,
|
mac_address: self.mac_address,
|
||||||
|
|
|
@ -4,7 +4,8 @@ use serde::Deserialize;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{ConfigExternal, DeviceConfig, MqttDeviceConfig},
|
config::MqttDeviceConfig,
|
||||||
|
device_manager::{ConfigExternal, DeviceConfig},
|
||||||
error::DeviceConfigError,
|
error::DeviceConfigError,
|
||||||
event::{Event, EventChannel, OnMqtt},
|
event::{Event, EventChannel, OnMqtt},
|
||||||
messages::PowerMessage,
|
messages::PowerMessage,
|
||||||
|
@ -27,7 +28,7 @@ impl DeviceConfig for WasherConfig {
|
||||||
ext: &ConfigExternal,
|
ext: &ConfigExternal,
|
||||||
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = Washer {
|
let device = Washer {
|
||||||
identifier: identifier.to_owned(),
|
identifier: identifier.into(),
|
||||||
mqtt: self.mqtt,
|
mqtt: self.mqtt,
|
||||||
event_channel: ext.event_channel.clone(),
|
event_channel: ext.event_channel.clone(),
|
||||||
threshold: self.threshold,
|
threshold: self.threshold,
|
||||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -10,9 +10,9 @@ use tracing::{debug, error, info};
|
||||||
|
|
||||||
use automation::{
|
use automation::{
|
||||||
auth::{OpenIDConfig, User},
|
auth::{OpenIDConfig, User},
|
||||||
config::{Config, ConfigExternal, DeviceConfig},
|
config::Config,
|
||||||
device_manager::DeviceManager,
|
device_manager::DeviceManager,
|
||||||
devices::{DebugBridge, HueBridge, LightSensor, Ntfy, Presence},
|
devices::{Ntfy, Presence},
|
||||||
error::ApiError,
|
error::ApiError,
|
||||||
mqtt,
|
mqtt,
|
||||||
};
|
};
|
||||||
|
@ -58,27 +58,12 @@ async fn app() -> anyhow::Result<()> {
|
||||||
|
|
||||||
// Setup the device handler
|
// Setup the device handler
|
||||||
let device_manager = DeviceManager::new(client.clone());
|
let device_manager = DeviceManager::new(client.clone());
|
||||||
let event_channel = device_manager.start();
|
|
||||||
|
|
||||||
// Create all the devices specified in the config
|
|
||||||
let ext = ConfigExternal {
|
|
||||||
client: &client,
|
|
||||||
device_manager: &device_manager,
|
|
||||||
presence_topic: &config.presence.mqtt.topic,
|
|
||||||
event_channel: &event_channel,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (id, device_config) in config.devices {
|
for (id, device_config) in config.devices {
|
||||||
let device = device_config.create(&id, &ext).await?;
|
device_manager.create(&id, device_config).await?;
|
||||||
|
|
||||||
device_manager.add(device).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and add the light sensor
|
let event_channel = device_manager.event_channel();
|
||||||
{
|
|
||||||
let light_sensor = LightSensor::new(config.light_sensor, &event_channel);
|
|
||||||
device_manager.add(Box::new(light_sensor)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and add the presence system
|
// Create and add the presence system
|
||||||
{
|
{
|
||||||
|
@ -86,18 +71,6 @@ async fn app() -> anyhow::Result<()> {
|
||||||
device_manager.add(Box::new(presence)).await;
|
device_manager.add(Box::new(presence)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If configured, create and add the hue bridge
|
|
||||||
if let Some(config) = config.hue_bridge {
|
|
||||||
let hue_bridge = HueBridge::new(config);
|
|
||||||
device_manager.add(Box::new(hue_bridge)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the debug bridge if it is configured
|
|
||||||
if let Some(config) = config.debug_bridge {
|
|
||||||
let debug_bridge = DebugBridge::new(config, &client);
|
|
||||||
device_manager.add(Box::new(debug_bridge)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the ntfy service if it is configured
|
// Start the ntfy service if it is configured
|
||||||
if let Some(config) = config.ntfy {
|
if let Some(config) = config.ntfy {
|
||||||
let ntfy = Ntfy::new(config, &event_channel);
|
let ntfy = Ntfy::new(config, &event_channel);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user