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

This commit is contained in:
2023-04-24 04:28:17 +02:00
parent 28ce9c9d82
commit 40c0ac5144
15 changed files with 64 additions and 66 deletions

View File

@@ -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};

View File

@@ -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,
})

View 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
View 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;
}
}

View File

@@ -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 {

View 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
View 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");
}
}
}
}

View File

@@ -17,8 +17,8 @@ use crate::{
config::{CreateDevice, InfoConfig, MqttDeviceConfig},
error::CreateDeviceError,
event::EventChannel,
event::OnMqtt,
messages::ActivateMessage,
traits::OnMqtt,
};
use super::Device;