Applied rust fmt
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2023-04-10 01:33:39 +02:00
parent de9203b8d5
commit 3645b53f7c
29 changed files with 842 additions and 375 deletions

View File

@@ -1,14 +1,14 @@
use async_trait::async_trait;
use google_home::traits;
use rumqttc::{AsyncClient, matches};
use tracing::{error, warn, debug};
use rumqttc::{matches, AsyncClient};
use tracing::{debug, error, warn};
use crate::config::MqttDeviceConfig;
use crate::error::DeviceError;
use crate::mqtt::{OnMqtt, RemoteMessage, RemoteAction};
use crate::mqtt::{OnMqtt, RemoteAction, RemoteMessage};
use crate::presence::OnPresence;
use super::{Device, DeviceBox, AsOnOff};
use super::{AsOnOff, Device, DeviceBox};
// TODO: Ideally we store am Arc to the childern devices,
// that way they hook into everything just like all other devices
@@ -21,19 +21,31 @@ pub struct AudioSetup {
}
impl AudioSetup {
pub async fn build(identifier: &str, mqtt: MqttDeviceConfig, mixer: DeviceBox, speakers: DeviceBox, client: AsyncClient) -> Result<Self, DeviceError> {
pub async fn build(
identifier: &str,
mqtt: MqttDeviceConfig,
mixer: DeviceBox,
speakers: DeviceBox,
client: AsyncClient,
) -> Result<Self, DeviceError> {
// We expect the children devices to implement the OnOff trait
let mixer_id = mixer.get_id().to_owned();
let mixer = AsOnOff::consume(mixer)
.ok_or_else(|| DeviceError::OnOffExpected(mixer_id))?;
let mixer = AsOnOff::consume(mixer).ok_or_else(|| DeviceError::OnOffExpected(mixer_id))?;
let speakers_id = speakers.get_id().to_owned();
let speakers = AsOnOff::consume(speakers)
.ok_or_else(|| DeviceError::OnOffExpected(speakers_id))?;
let speakers =
AsOnOff::consume(speakers).ok_or_else(|| DeviceError::OnOffExpected(speakers_id))?;
client.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce).await?;
client
.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce)
.await?;
Ok(Self { identifier: identifier.to_owned(), mqtt, mixer, speakers })
Ok(Self {
identifier: identifier.to_owned(),
mqtt,
mixer,
speakers,
})
}
}

View File

@@ -1,11 +1,16 @@
use std::time::Duration;
use async_trait::async_trait;
use rumqttc::{AsyncClient, matches};
use rumqttc::{matches, AsyncClient};
use tokio::task::JoinHandle;
use tracing::{error, debug, warn};
use tracing::{debug, error, warn};
use crate::{config::{MqttDeviceConfig, PresenceDeviceConfig}, mqtt::{OnMqtt, ContactMessage, PresenceMessage}, presence::OnPresence, error::DeviceError};
use crate::{
config::{MqttDeviceConfig, PresenceDeviceConfig},
error::DeviceError,
mqtt::{ContactMessage, OnMqtt, PresenceMessage},
presence::OnPresence,
};
use super::Device;
@@ -22,8 +27,15 @@ pub struct ContactSensor {
}
impl ContactSensor {
pub async fn build(identifier: &str, mqtt: MqttDeviceConfig, presence: Option<PresenceDeviceConfig>, client: AsyncClient) -> Result<Self, DeviceError> {
client.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce).await?;
pub async fn build(
identifier: &str,
mqtt: MqttDeviceConfig,
presence: Option<PresenceDeviceConfig>,
client: AsyncClient,
) -> Result<Self, DeviceError> {
client
.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce)
.await?;
Ok(Self {
identifier: identifier.to_owned(),
@@ -62,7 +74,7 @@ impl OnMqtt for ContactSensor {
Err(err) => {
error!(id = self.identifier, "Failed to parse message: {err}");
return;
},
}
};
if is_closed == self.is_closed {
@@ -97,7 +109,13 @@ impl OnMqtt for ContactSensor {
// This is to prevent the house from being marked as present for however long the
// timeout is set when leaving the house
if !self.overall_presence {
self.client.publish(topic.clone(), rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&PresenceMessage::new(true)).unwrap())
self.client
.publish(
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}"))
.ok();
@@ -107,17 +125,16 @@ impl OnMqtt for ContactSensor {
let client = self.client.clone();
let id = self.identifier.clone();
let timeout = Duration::from_secs(presence.timeout);
self.handle = Some(
tokio::spawn(async move {
debug!(id, "Starting timeout ({timeout:?}) for contact sensor...");
tokio::time::sleep(timeout).await;
debug!(id, "Removing door device!");
client.publish(topic.clone(), rumqttc::QoS::AtLeastOnce, false, "")
.await
.map_err(|err| warn!("Failed to publish presence on {topic}: {err}"))
.ok();
})
);
self.handle = Some(tokio::spawn(async move {
debug!(id, "Starting timeout ({timeout:?}) for contact sensor...");
tokio::time::sleep(timeout).await;
debug!(id, "Removing door device!");
client
.publish(topic.clone(), rumqttc::QoS::AtLeastOnce, false, "")
.await
.map_err(|err| warn!("Failed to publish presence on {topic}: {err}"))
.ok();
}));
}
}
}

View File

@@ -1,11 +1,16 @@
use std::time::Duration;
use async_trait::async_trait;
use google_home::errors::ErrorCode;
use google_home::{GoogleHomeDevice, device, types::Type, traits::{self, OnOff}};
use rumqttc::{AsyncClient, Publish, matches};
use tracing::{debug, error, warn};
use tokio::task::JoinHandle;
use google_home::{
device,
traits::{self, OnOff},
types::Type,
GoogleHomeDevice,
};
use pollster::FutureExt as _;
use rumqttc::{matches, AsyncClient, Publish};
use std::time::Duration;
use tokio::task::JoinHandle;
use tracing::{debug, error, warn};
use crate::config::{InfoConfig, MqttDeviceConfig, OutletType};
use crate::devices::Device;
@@ -27,11 +32,29 @@ pub struct IkeaOutlet {
}
impl IkeaOutlet {
pub async fn build(identifier: &str, info: InfoConfig, mqtt: MqttDeviceConfig, outlet_type: OutletType, timeout: Option<u64>, client: AsyncClient) -> Result<Self, DeviceError> {
pub async fn build(
identifier: &str,
info: InfoConfig,
mqtt: MqttDeviceConfig,
outlet_type: OutletType,
timeout: Option<u64>,
client: AsyncClient,
) -> Result<Self, DeviceError> {
// TODO: Handle potential errors here
client.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce).await?;
client
.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce)
.await?;
Ok(Self{ identifier: identifier.to_owned(), info, mqtt, outlet_type, timeout, client, last_known_state: false, handle: None })
Ok(Self {
identifier: identifier.to_owned(),
info,
mqtt,
outlet_type,
timeout,
client,
last_known_state: false,
handle: None,
})
}
}
@@ -40,7 +63,13 @@ async fn set_on(client: AsyncClient, topic: &str, on: bool) {
let topic = format!("{}/set", topic);
// TODO: Handle potential errors here
client.publish(topic.clone(), rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&message).unwrap())
client
.publish(
topic.clone(),
rumqttc::QoS::AtLeastOnce,
false,
serde_json::to_string(&message).unwrap(),
)
.await
.map_err(|err| warn!("Failed to update state on {topic}: {err}"))
.ok();
@@ -94,17 +123,15 @@ impl OnMqtt for IkeaOutlet {
let client = self.client.clone();
let topic = self.mqtt.topic.clone();
let id = self.identifier.clone();
self.handle = Some(
tokio::spawn(async move {
debug!(id, "Starting timeout ({timeout:?}) for kettle...");
tokio::time::sleep(timeout).await;
debug!(id, "Turning kettle off!");
// TODO: Idealy we would call self.set_on(false), however since we want to do
// it after a timeout we have to put it in a seperate task.
// I don't think we can really get around calling outside function
set_on(client, &topic, false).await;
})
);
self.handle = Some(tokio::spawn(async move {
debug!(id, "Starting timeout ({timeout:?}) for kettle...");
tokio::time::sleep(timeout).await;
debug!(id, "Turning kettle off!");
// TODO: Idealy we would call self.set_on(false), however since we want to do
// it after a timeout we have to put it in a seperate task.
// I don't think we can really get around calling outside function
set_on(client, &topic, false).await;
}));
}
}
}

View File

@@ -1,9 +1,16 @@
use std::{net::{SocketAddr, Ipv4Addr, TcpStream}, io::{Write, Read}, str::Utf8Error};
use std::{
io::{Read, Write},
net::{Ipv4Addr, SocketAddr, TcpStream},
str::Utf8Error,
};
use thiserror::Error;
use bytes::{Buf, BufMut};
use google_home::{traits, errors::{self, DeviceError}};
use serde::{Serialize, Deserialize};
use google_home::{
errors::{self, DeviceError},
traits,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::Device;
@@ -15,7 +22,10 @@ pub struct KasaOutlet {
impl KasaOutlet {
pub fn new(identifier: &str, ip: Ipv4Addr) -> Self {
Self { identifier: identifier.to_owned(), addr: (ip, 9999).into() }
Self {
identifier: identifier.to_owned(),
addr: (ip, 9999).into(),
}
}
}
@@ -50,9 +60,9 @@ impl Request {
fn get_sysinfo() -> Self {
Self {
system: RequestSystem {
get_sysinfo: Some(RequestSysinfo{}),
set_relay_state: None
}
get_sysinfo: Some(RequestSysinfo {}),
set_relay_state: None,
},
}
}
@@ -61,9 +71,9 @@ impl Request {
system: RequestSystem {
get_sysinfo: None,
set_relay_state: Some(RequestRelayState {
state: if on { 1 } else { 0 }
})
}
state: if on { 1 } else { 0 },
}),
},
}
}
@@ -153,8 +163,7 @@ impl From<serde_json::Error> for ResponseError {
impl Response {
fn get_current_relay_state(&self) -> Result<bool, ResponseError> {
if let Some(sysinfo) = &self.system.get_sysinfo {
return sysinfo.err_code.ok()
.map(|_| sysinfo.relay_state == 1);
return sysinfo.err_code.ok().map(|_| sysinfo.relay_state == 1);
}
Err(ResponseError::SysinfoNotFound)
@@ -189,15 +198,21 @@ impl Response {
impl traits::OnOff for KasaOutlet {
fn is_on(&self) -> Result<bool, errors::ErrorCode> {
let mut stream = TcpStream::connect(self.addr).or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
let mut stream =
TcpStream::connect(self.addr).or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
let body = Request::get_sysinfo().encrypt();
stream.write_all(&body).and(stream.flush()).or::<DeviceError>(Err(DeviceError::TransientError))?;
stream
.write_all(&body)
.and(stream.flush())
.or::<DeviceError>(Err(DeviceError::TransientError))?;
let mut received = Vec::new();
let mut rx_bytes = [0; 1024];
loop {
let read = stream.read(&mut rx_bytes).or::<errors::ErrorCode>(Err(DeviceError::TransientError.into()))?;
let read = stream
.read(&mut rx_bytes)
.or::<errors::ErrorCode>(Err(DeviceError::TransientError.into()))?;
received.extend_from_slice(&rx_bytes[..read]);
@@ -206,16 +221,22 @@ impl traits::OnOff for KasaOutlet {
}
}
let resp = Response::decrypt(received.into()).or::<errors::ErrorCode>(Err(DeviceError::TransientError.into()))?;
let resp = Response::decrypt(received.into())
.or::<errors::ErrorCode>(Err(DeviceError::TransientError.into()))?;
resp.get_current_relay_state().or(Err(DeviceError::TransientError.into()))
resp.get_current_relay_state()
.or(Err(DeviceError::TransientError.into()))
}
fn set_on(&mut self, on: bool) -> Result<(), errors::ErrorCode> {
let mut stream = TcpStream::connect(self.addr).or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
let mut stream =
TcpStream::connect(self.addr).or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
let body = Request::set_relay_state(on).encrypt();
stream.write_all(&body).and(stream.flush()).or::<DeviceError>(Err(DeviceError::TransientError))?;
stream
.write_all(&body)
.and(stream.flush())
.or::<DeviceError>(Err(DeviceError::TransientError))?;
let mut received = Vec::new();
let mut rx_bytes = [0; 1024];
@@ -232,9 +253,10 @@ impl traits::OnOff for KasaOutlet {
}
}
let resp = Response::decrypt(received.into()).or::<errors::ErrorCode>(Err(DeviceError::TransientError.into()))?;
let resp = Response::decrypt(received.into())
.or::<errors::ErrorCode>(Err(DeviceError::TransientError.into()))?;
resp.check_set_relay_success().or(Err(DeviceError::TransientError.into()))
resp.check_set_relay_success()
.or(Err(DeviceError::TransientError.into()))
}
}

View File

@@ -1,12 +1,22 @@
use std::net::Ipv4Addr;
use async_trait::async_trait;
use google_home::{GoogleHomeDevice, types::Type, device, traits::{self, Scene}, errors::ErrorCode};
use tracing::{debug, error};
use rumqttc::{AsyncClient, Publish, matches};
use eui48::MacAddress;
use google_home::{
device,
errors::ErrorCode,
traits::{self, Scene},
types::Type,
GoogleHomeDevice,
};
use rumqttc::{matches, AsyncClient, Publish};
use tracing::{debug, error};
use crate::{config::{InfoConfig, MqttDeviceConfig}, mqtt::{OnMqtt, ActivateMessage}, error::DeviceError};
use crate::{
config::{InfoConfig, MqttDeviceConfig},
error::DeviceError,
mqtt::{ActivateMessage, OnMqtt},
};
use super::Device;
@@ -20,11 +30,26 @@ pub struct WakeOnLAN {
}
impl WakeOnLAN {
pub async fn build(identifier: &str, info: InfoConfig, mqtt: MqttDeviceConfig, mac_address: MacAddress, broadcast_ip: Ipv4Addr, client: AsyncClient) -> Result<Self, DeviceError> {
pub async fn build(
identifier: &str,
info: InfoConfig,
mqtt: MqttDeviceConfig,
mac_address: MacAddress,
broadcast_ip: Ipv4Addr,
client: AsyncClient,
) -> Result<Self, DeviceError> {
// TODO: Handle potential errors here
client.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce).await?;
client
.subscribe(mqtt.topic.clone(), rumqttc::QoS::AtLeastOnce)
.await?;
Ok(Self { identifier: identifier.to_owned(), info, mqtt, mac_address, broadcast_ip })
Ok(Self {
identifier: identifier.to_owned(),
info,
mqtt,
mac_address,
broadcast_ip,
})
}
}
@@ -81,20 +106,31 @@ impl GoogleHomeDevice for WakeOnLAN {
impl traits::Scene for WakeOnLAN {
fn set_active(&self, activate: bool) -> Result<(), ErrorCode> {
if activate {
debug!(id = self.identifier, "Activating Computer: {} (Sending to {})", self.mac_address, self.broadcast_ip);
let wol = wakey::WolPacket::from_bytes(&self.mac_address.to_array()).map_err(|err| {
error!(id = self.identifier, "invalid mac address: {err}");
google_home::errors::DeviceError::TransientError
})?;
debug!(
id = self.identifier,
"Activating Computer: {} (Sending to {})", self.mac_address, self.broadcast_ip
);
let wol =
wakey::WolPacket::from_bytes(&self.mac_address.to_array()).map_err(|err| {
error!(id = self.identifier, "invalid mac address: {err}");
google_home::errors::DeviceError::TransientError
})?;
wol.send_magic_to((Ipv4Addr::new(0, 0, 0, 0), 0), (self.broadcast_ip, 9)).map_err(|err| {
error!(id = self.identifier, "Failed to activate computer: {err}");
google_home::errors::DeviceError::TransientError.into()
}).map(|_| debug!(id = self.identifier, "Success!"))
wol.send_magic_to((Ipv4Addr::new(0, 0, 0, 0), 0), (self.broadcast_ip, 9))
.map_err(|err| {
error!(id = self.identifier, "Failed to activate computer: {err}");
google_home::errors::DeviceError::TransientError.into()
})
.map(|_| debug!(id = self.identifier, "Success!"))
} else {
debug!(id = self.identifier, "Trying to deactive computer, this is not currently supported");
debug!(
id = self.identifier,
"Trying to deactive computer, this is not currently supported"
);
// We do not support deactivating this scene
Err(ErrorCode::DeviceError(google_home::errors::DeviceError::ActionNotAvailable))
Err(ErrorCode::DeviceError(
google_home::errors::DeviceError::ActionNotAvailable,
))
}
}
}