Moved quasi-devices into the devices module and made event related device traits part of the event module
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:
@@ -8,9 +8,9 @@ use crate::{
|
||||
config::{self, CreateDevice, MqttDeviceConfig},
|
||||
error::CreateDeviceError,
|
||||
event::EventChannel,
|
||||
event::OnMqtt,
|
||||
event::OnPresence,
|
||||
messages::{RemoteAction, RemoteMessage},
|
||||
traits::OnMqtt,
|
||||
traits::OnPresence,
|
||||
};
|
||||
|
||||
use super::{As, Device};
|
||||
|
||||
@@ -8,12 +8,12 @@ use tracing::{debug, error, trace, warn};
|
||||
|
||||
use crate::{
|
||||
config::{CreateDevice, MqttDeviceConfig},
|
||||
devices::DEFAULT_PRESENCE,
|
||||
error::{CreateDeviceError, MissingWildcard},
|
||||
event::EventChannel,
|
||||
event::OnMqtt,
|
||||
event::OnPresence,
|
||||
messages::{ContactMessage, PresenceMessage},
|
||||
presence,
|
||||
traits::OnMqtt,
|
||||
traits::OnPresence,
|
||||
};
|
||||
|
||||
use super::Device;
|
||||
@@ -94,7 +94,7 @@ impl CreateDevice for ContactSensor {
|
||||
mqtt: config.mqtt,
|
||||
presence,
|
||||
client: client.clone(),
|
||||
overall_presence: presence::DEFAULT,
|
||||
overall_presence: DEFAULT_PRESENCE,
|
||||
is_closed: true,
|
||||
handle: None,
|
||||
})
|
||||
|
||||
88
src/devices/debug_bridge.rs
Normal file
88
src/devices/debug_bridge.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use async_trait::async_trait;
|
||||
use rumqttc::AsyncClient;
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::devices::Device;
|
||||
use crate::event::OnDarkness;
|
||||
use crate::event::OnPresence;
|
||||
use crate::{
|
||||
config::MqttDeviceConfig,
|
||||
messages::{DarknessMessage, PresenceMessage},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DebugBridgeConfig {
|
||||
#[serde(flatten)]
|
||||
pub mqtt: MqttDeviceConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DebugBridge {
|
||||
mqtt: MqttDeviceConfig,
|
||||
client: AsyncClient,
|
||||
}
|
||||
|
||||
impl DebugBridge {
|
||||
pub fn new(
|
||||
config: DebugBridgeConfig,
|
||||
client: &AsyncClient,
|
||||
) -> Result<Self, crate::error::CreateDeviceError> {
|
||||
Ok(Self {
|
||||
mqtt: config.mqtt,
|
||||
client: client.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for DebugBridge {
|
||||
fn get_id(&self) -> &str {
|
||||
"debug_bridge"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnPresence for DebugBridge {
|
||||
async fn on_presence(&mut self, presence: bool) {
|
||||
let message = PresenceMessage::new(presence);
|
||||
let topic = format!("{}/presence", self.mqtt.topic);
|
||||
self.client
|
||||
.publish(
|
||||
topic,
|
||||
rumqttc::QoS::AtLeastOnce,
|
||||
true,
|
||||
serde_json::to_string(&message).unwrap(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!(
|
||||
"Failed to update presence on {}/presence: {err}",
|
||||
self.mqtt.topic
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnDarkness for DebugBridge {
|
||||
async fn on_darkness(&mut self, dark: bool) {
|
||||
let message = DarknessMessage::new(dark);
|
||||
let topic = format!("{}/darkness", self.mqtt.topic);
|
||||
self.client
|
||||
.publish(
|
||||
topic,
|
||||
rumqttc::QoS::AtLeastOnce,
|
||||
true,
|
||||
serde_json::to_string(&message).unwrap(),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!(
|
||||
"Failed to update presence on {}/presence: {err}",
|
||||
self.mqtt.topic
|
||||
)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
103
src/devices/hue_bridge.rs
Normal file
103
src/devices/hue_bridge.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{error, trace, warn};
|
||||
|
||||
use crate::{devices::Device, event::OnDarkness, event::OnPresence};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Flag {
|
||||
Presence,
|
||||
Darkness,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FlagIDs {
|
||||
pub presence: isize,
|
||||
pub darkness: isize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct HueBridgeConfig {
|
||||
pub ip: Ipv4Addr,
|
||||
pub login: String,
|
||||
pub flags: FlagIDs,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HueBridge {
|
||||
addr: SocketAddr,
|
||||
login: String,
|
||||
flag_ids: FlagIDs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct FlagMessage {
|
||||
flag: bool,
|
||||
}
|
||||
|
||||
impl HueBridge {
|
||||
pub async fn set_flag(&self, flag: Flag, value: bool) {
|
||||
let flag_id = match flag {
|
||||
Flag::Presence => self.flag_ids.presence,
|
||||
Flag::Darkness => self.flag_ids.darkness,
|
||||
};
|
||||
|
||||
let url = format!(
|
||||
"http://{}/api/{}/sensors/{flag_id}/state",
|
||||
self.addr, self.login
|
||||
);
|
||||
|
||||
trace!(?flag, flag_id, value, "Sending request to change flag");
|
||||
let res = reqwest::Client::new()
|
||||
.put(url)
|
||||
.json(&FlagMessage { flag: value })
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match res {
|
||||
Ok(res) => {
|
||||
let status = res.status();
|
||||
if !status.is_success() {
|
||||
warn!(flag_id, "Status code is not success: {status}");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!(flag_id, "Error: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnPresence for HueBridge {
|
||||
async fn on_presence(&mut self, presence: bool) {
|
||||
trace!("Bridging presence to hue");
|
||||
self.set_flag(Flag::Presence, presence).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnDarkness for HueBridge {
|
||||
async fn on_darkness(&mut self, dark: bool) {
|
||||
trace!("Bridging darkness to hue");
|
||||
self.set_flag(Flag::Darkness, dark).await;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ use crate::config::{CreateDevice, InfoConfig, MqttDeviceConfig};
|
||||
use crate::devices::Device;
|
||||
use crate::error::CreateDeviceError;
|
||||
use crate::event::EventChannel;
|
||||
use crate::event::OnMqtt;
|
||||
use crate::event::OnPresence;
|
||||
use crate::messages::OnOffMessage;
|
||||
use crate::traits::OnMqtt;
|
||||
use crate::traits::OnPresence;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
||||
pub enum OutletType {
|
||||
|
||||
92
src/devices/light_sensor.rs
Normal file
92
src/devices/light_sensor.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use async_trait::async_trait;
|
||||
use rumqttc::Publish;
|
||||
use serde::Deserialize;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::{
|
||||
config::MqttDeviceConfig,
|
||||
devices::Device,
|
||||
event::OnMqtt,
|
||||
event::{self, Event, EventChannel},
|
||||
messages::BrightnessMessage,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LightSensorConfig {
|
||||
#[serde(flatten)]
|
||||
pub mqtt: MqttDeviceConfig,
|
||||
pub min: isize,
|
||||
pub max: isize,
|
||||
}
|
||||
|
||||
pub const DEFAULT: bool = false;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LightSensor {
|
||||
tx: event::Sender,
|
||||
mqtt: MqttDeviceConfig,
|
||||
min: isize,
|
||||
max: isize,
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnMqtt for LightSensor {
|
||||
fn topics(&self) -> Vec<&str> {
|
||||
vec![&self.mqtt.topic]
|
||||
}
|
||||
|
||||
async fn on_mqtt(&mut self, message: Publish) {
|
||||
let illuminance = match BrightnessMessage::try_from(message) {
|
||||
Ok(state) => state.illuminance(),
|
||||
Err(err) => {
|
||||
warn!("Failed to parse message: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Illuminance: {illuminance}");
|
||||
let is_dark = if illuminance <= self.min {
|
||||
trace!("It is dark");
|
||||
true
|
||||
} else if illuminance >= self.max {
|
||||
trace!("It is light");
|
||||
false
|
||||
} else {
|
||||
trace!(
|
||||
"In between min ({}) and max ({}) value, keeping current state: {}",
|
||||
self.min,
|
||||
self.max,
|
||||
self.is_dark
|
||||
);
|
||||
self.is_dark
|
||||
};
|
||||
|
||||
if is_dark != self.is_dark {
|
||||
debug!("Dark state has changed: {is_dark}");
|
||||
self.is_dark = is_dark;
|
||||
|
||||
if self.tx.send(Event::Darkness(is_dark)).await.is_err() {
|
||||
warn!("There are no receivers on the event channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/devices/presence.rs
Normal file
96
src/devices/presence.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use rumqttc::Publish;
|
||||
use serde::Deserialize;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::{
|
||||
config::MqttDeviceConfig,
|
||||
devices::Device,
|
||||
event::OnMqtt,
|
||||
event::{self, Event, EventChannel},
|
||||
messages::PresenceMessage,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PresenceConfig {
|
||||
#[serde(flatten)]
|
||||
pub mqtt: MqttDeviceConfig,
|
||||
}
|
||||
|
||||
pub const DEFAULT_PRESENCE: bool = false;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Presence {
|
||||
tx: event::Sender,
|
||||
mqtt: MqttDeviceConfig,
|
||||
devices: HashMap<String, bool>,
|
||||
current_overall_presence: bool,
|
||||
}
|
||||
|
||||
impl Presence {
|
||||
pub fn new(config: PresenceConfig, event_channel: &EventChannel) -> Self {
|
||||
Self {
|
||||
tx: event_channel.get_tx(),
|
||||
mqtt: config.mqtt,
|
||||
devices: HashMap::new(),
|
||||
current_overall_presence: DEFAULT_PRESENCE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for Presence {
|
||||
fn get_id(&self) -> &str {
|
||||
"presence"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OnMqtt for Presence {
|
||||
fn topics(&self) -> Vec<&str> {
|
||||
vec![&self.mqtt.topic]
|
||||
}
|
||||
|
||||
async fn on_mqtt(&mut self, message: Publish) {
|
||||
let offset = self
|
||||
.mqtt
|
||||
.topic
|
||||
.find('+')
|
||||
.or(self.mqtt.topic.find('#'))
|
||||
.expect("Presence::create fails if it does not contain wildcards");
|
||||
let device_name = message.topic[offset..].to_owned();
|
||||
|
||||
if message.payload.is_empty() {
|
||||
// Remove the device from the map
|
||||
debug!("State of device [{device_name}] has been removed");
|
||||
self.devices.remove(&device_name);
|
||||
} else {
|
||||
let present = match PresenceMessage::try_from(message) {
|
||||
Ok(state) => state.presence(),
|
||||
Err(err) => {
|
||||
warn!("Failed to parse message: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
debug!("State of device [{device_name}] has changed: {}", present);
|
||||
self.devices.insert(device_name, present);
|
||||
}
|
||||
|
||||
let overall_presence = self.devices.iter().any(|(_, v)| *v);
|
||||
if overall_presence != self.current_overall_presence {
|
||||
debug!("Overall presence updated: {overall_presence}");
|
||||
self.current_overall_presence = overall_presence;
|
||||
|
||||
if self
|
||||
.tx
|
||||
.send(Event::Presence(overall_presence))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
warn!("There are no receivers on the event channel");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ use crate::{
|
||||
config::{CreateDevice, InfoConfig, MqttDeviceConfig},
|
||||
error::CreateDeviceError,
|
||||
event::EventChannel,
|
||||
event::OnMqtt,
|
||||
messages::ActivateMessage,
|
||||
traits::OnMqtt,
|
||||
};
|
||||
|
||||
use super::Device;
|
||||
|
||||
Reference in New Issue
Block a user