Started actually using the google home trait macro
This commit is contained in:
parent
99808ee4b2
commit
120c1edea8
|
@ -299,7 +299,7 @@ fn get_command_enum(traits: &Punctuated<Trait, Token![,]>) -> proc_macro2::Token
|
||||||
});
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Deserialize)]
|
||||||
#[serde(tag = "command", content = "params", rename_all = "camelCase")]
|
#[serde(tag = "command", content = "params", rename_all = "camelCase")]
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
#(#items,)*
|
#(#items,)*
|
||||||
|
@ -520,7 +520,7 @@ pub fn google_home_traits(item: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
Command::#command_name {#(#parameters,)*} => {
|
Command::#command_name {#(#parameters,)*} => {
|
||||||
if let Some(t) = self.cast() as Option<&dyn #ident> {
|
if let Some(t) = self.cast_mut() as Option<&mut dyn #ident> {
|
||||||
t.#f_name(#(#parameters,)*) #asyncness #errors;
|
t.#f_name(#(#parameters,)*) #asyncness #errors;
|
||||||
serde_json::to_value(t.get_state().await?)?
|
serde_json::to_value(t.get_state().await?)?
|
||||||
} else {
|
} else {
|
||||||
|
@ -547,7 +547,7 @@ pub fn google_home_traits(item: TokenStream) -> TokenStream {
|
||||||
pub trait #fulfillment: Sync + Send {
|
pub trait #fulfillment: Sync + Send {
|
||||||
async fn sync(&self) -> Result<(Vec<Trait>, serde_json::Value), Box<dyn ::std::error::Error>>;
|
async fn sync(&self) -> Result<(Vec<Trait>, serde_json::Value), Box<dyn ::std::error::Error>>;
|
||||||
async fn query(&self) -> Result<serde_json::Value, Box<dyn ::std::error::Error>>;
|
async fn query(&self) -> Result<serde_json::Value, Box<dyn ::std::error::Error>>;
|
||||||
async fn execute(&self, command: Command) -> Result<serde_json::Value, Box<dyn std::error::Error>>;
|
async fn execute(&mut self, command: Command) -> Result<serde_json::Value, Box<dyn std::error::Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#(#structs)*
|
#(#structs)*
|
||||||
|
@ -575,7 +575,7 @@ pub fn google_home_traits(item: TokenStream) -> TokenStream {
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&self, command: Command) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
async fn execute(&mut self, command: Command) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
|
||||||
let value = match command {
|
let value = match command {
|
||||||
#(#execute)*
|
#(#execute)*
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::traits::AvailableSpeeds;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Attributes {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub command_only_on_off: Option<bool>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub query_only_on_off: Option<bool>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub scene_reversible: Option<bool>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub reversible: Option<bool>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub command_only_fan_speed: Option<bool>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub available_fan_speeds: Option<AvailableSpeeds>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub query_only_humidity_setting: Option<bool>,
|
|
||||||
}
|
|
|
@ -1,17 +1,13 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_cast::Cast;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::errors::{DeviceError, ErrorCode};
|
use crate::errors::ErrorCode;
|
||||||
use crate::request::execute::CommandType;
|
|
||||||
use crate::response;
|
use crate::response;
|
||||||
use crate::traits::{FanSpeed, HumiditySetting, OnOff, Scene, Trait};
|
use crate::traits::{Command, GoogleHomeDeviceFulfillment};
|
||||||
use crate::types::Type;
|
use crate::types::Type;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait GoogleHomeDevice:
|
pub trait GoogleHomeDevice: GoogleHomeDeviceFulfillment {
|
||||||
Sync + Send + Cast<dyn OnOff> + Cast<dyn Scene> + Cast<dyn FanSpeed> + Cast<dyn HumiditySetting>
|
|
||||||
{
|
|
||||||
fn get_device_type(&self) -> Type;
|
fn get_device_type(&self) -> Type;
|
||||||
fn get_device_name(&self) -> Name;
|
fn get_device_name(&self) -> Name;
|
||||||
fn get_id(&self) -> String;
|
fn get_id(&self) -> String;
|
||||||
|
@ -41,35 +37,10 @@ pub trait GoogleHomeDevice:
|
||||||
}
|
}
|
||||||
device.device_info = self.get_device_info();
|
device.device_info = self.get_device_info();
|
||||||
|
|
||||||
let mut traits = Vec::new();
|
let (traits, attributes) = GoogleHomeDeviceFulfillment::sync(self).await.unwrap();
|
||||||
|
|
||||||
// OnOff
|
|
||||||
if let Some(on_off) = self.cast() as Option<&dyn OnOff> {
|
|
||||||
traits.push(Trait::OnOff);
|
|
||||||
device.attributes.command_only_on_off = on_off.is_command_only();
|
|
||||||
device.attributes.query_only_on_off = on_off.is_query_only();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scene
|
|
||||||
if let Some(scene) = self.cast() as Option<&dyn Scene> {
|
|
||||||
traits.push(Trait::Scene);
|
|
||||||
device.attributes.scene_reversible = scene.is_scene_reversible();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FanSpeed
|
|
||||||
if let Some(fan_speed) = self.cast() as Option<&dyn FanSpeed> {
|
|
||||||
traits.push(Trait::FanSpeed);
|
|
||||||
device.attributes.command_only_fan_speed = fan_speed.command_only_fan_speed();
|
|
||||||
device.attributes.available_fan_speeds = Some(fan_speed.available_speeds());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(humidity_setting) = self.cast() as Option<&dyn HumiditySetting> {
|
|
||||||
traits.push(Trait::HumiditySetting);
|
|
||||||
device.attributes.query_only_humidity_setting =
|
|
||||||
humidity_setting.query_only_humidity_setting();
|
|
||||||
}
|
|
||||||
|
|
||||||
device.traits = traits;
|
device.traits = traits;
|
||||||
|
device.attributes = attributes;
|
||||||
|
|
||||||
device
|
device
|
||||||
}
|
}
|
||||||
|
@ -80,50 +51,15 @@ pub trait GoogleHomeDevice:
|
||||||
device.set_offline();
|
device.set_offline();
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnOff
|
device.state = GoogleHomeDeviceFulfillment::query(self).await.unwrap();
|
||||||
if let Some(on_off) = self.cast() as Option<&dyn OnOff> {
|
|
||||||
device.state.on = on_off
|
|
||||||
.is_on()
|
|
||||||
.await
|
|
||||||
.map_err(|err| device.set_error(err))
|
|
||||||
.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// FanSpeed
|
|
||||||
if let Some(fan_speed) = self.cast() as Option<&dyn FanSpeed> {
|
|
||||||
device.state.current_fan_speed_setting = Some(fan_speed.current_speed().await);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(humidity_setting) = self.cast() as Option<&dyn HumiditySetting> {
|
|
||||||
device.state.humidity_ambient_percent =
|
|
||||||
Some(humidity_setting.humidity_ambient_percent().await);
|
|
||||||
}
|
|
||||||
|
|
||||||
device
|
device
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(&mut self, command: &CommandType) -> Result<(), ErrorCode> {
|
async fn execute(&mut self, command: Command) -> Result<(), ErrorCode> {
|
||||||
match command {
|
GoogleHomeDeviceFulfillment::execute(self, command.clone())
|
||||||
CommandType::OnOff { on } => {
|
.await
|
||||||
if let Some(t) = self.cast_mut() as Option<&mut dyn OnOff> {
|
.unwrap();
|
||||||
t.set_on(*on).await?;
|
|
||||||
} else {
|
|
||||||
return Err(DeviceError::ActionNotAvailable.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandType::ActivateScene { deactivate } => {
|
|
||||||
if let Some(t) = self.cast_mut() as Option<&mut dyn Scene> {
|
|
||||||
t.set_active(!deactivate).await?;
|
|
||||||
} else {
|
|
||||||
return Err(DeviceError::ActionNotAvailable.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CommandType::SetFanSpeed { fan_speed } => {
|
|
||||||
if let Some(t) = self.cast_mut() as Option<&mut dyn FanSpeed> {
|
|
||||||
t.set_speed(fan_speed).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use tokio::sync::{Mutex, RwLock};
|
||||||
|
|
||||||
use crate::errors::{DeviceError, ErrorCode};
|
use crate::errors::{DeviceError, ErrorCode};
|
||||||
use crate::request::{self, Intent, Request};
|
use crate::request::{self, Intent, Request};
|
||||||
use crate::response::{self, execute, query, sync, Response, ResponsePayload, State};
|
use crate::response::{self, execute, query, sync, Response, ResponsePayload};
|
||||||
use crate::GoogleHomeDevice;
|
use crate::GoogleHomeDevice;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -66,7 +66,7 @@ impl GoogleHome {
|
||||||
let mut resp_payload = sync::Payload::new(&self.user_id);
|
let mut resp_payload = sync::Payload::new(&self.user_id);
|
||||||
let f = devices.iter().map(|(_, device)| async move {
|
let f = devices.iter().map(|(_, device)| async move {
|
||||||
if let Some(device) = device.read().await.as_ref().cast() {
|
if let Some(device) = device.read().await.as_ref().cast() {
|
||||||
Some(device.sync().await)
|
Some(GoogleHomeDevice::sync(device).await)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ impl GoogleHome {
|
||||||
let device = if let Some(device) = devices.get(id.as_str())
|
let device = if let Some(device) = devices.get(id.as_str())
|
||||||
&& let Some(device) = device.read().await.as_ref().cast()
|
&& let Some(device) = device.read().await.as_ref().cast()
|
||||||
{
|
{
|
||||||
device.query().await
|
GoogleHomeDevice::query(device).await
|
||||||
} else {
|
} else {
|
||||||
let mut device = query::Device::new();
|
let mut device = query::Device::new();
|
||||||
device.set_offline();
|
device.set_offline();
|
||||||
|
@ -121,12 +121,12 @@ impl GoogleHome {
|
||||||
let mut success = response::execute::Command::new(execute::Status::Success);
|
let mut success = response::execute::Command::new(execute::Status::Success);
|
||||||
success.states = Some(execute::States {
|
success.states = Some(execute::States {
|
||||||
online: true,
|
online: true,
|
||||||
state: State::default(),
|
state: Default::default(),
|
||||||
});
|
});
|
||||||
let mut offline = response::execute::Command::new(execute::Status::Offline);
|
let mut offline = response::execute::Command::new(execute::Status::Offline);
|
||||||
offline.states = Some(execute::States {
|
offline.states = Some(execute::States {
|
||||||
online: false,
|
online: false,
|
||||||
state: State::default(),
|
state: Default::default(),
|
||||||
});
|
});
|
||||||
let mut errors: HashMap<ErrorCode, response::execute::Command> = HashMap::new();
|
let mut errors: HashMap<ErrorCode, response::execute::Command> = HashMap::new();
|
||||||
|
|
||||||
|
@ -147,7 +147,8 @@ impl GoogleHome {
|
||||||
// NOTE: We can not use .map here because async =(
|
// NOTE: We can not use .map here because async =(
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
for cmd in &execution {
|
for cmd in &execution {
|
||||||
results.push(device.execute(cmd).await);
|
results
|
||||||
|
.push(GoogleHomeDevice::execute(device, cmd.clone()).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert vec of results to a result with a vec and the first
|
// Convert vec of results to a result with a vec and the first
|
||||||
|
|
|
@ -7,7 +7,6 @@ mod fulfillment;
|
||||||
mod request;
|
mod request;
|
||||||
mod response;
|
mod response;
|
||||||
|
|
||||||
mod attributes;
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::traits;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Payload {
|
pub struct Payload {
|
||||||
|
@ -10,7 +12,7 @@ pub struct Payload {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
pub devices: Vec<Device>,
|
pub devices: Vec<Device>,
|
||||||
pub execution: Vec<CommandType>,
|
pub execution: Vec<traits::Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -20,20 +22,6 @@ pub struct Device {
|
||||||
// customData
|
// customData
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
|
||||||
#[serde(tag = "command", content = "params")]
|
|
||||||
pub enum CommandType {
|
|
||||||
#[serde(rename = "action.devices.commands.OnOff")]
|
|
||||||
OnOff { on: bool },
|
|
||||||
#[serde(rename = "action.devices.commands.ActivateScene")]
|
|
||||||
ActivateScene { deactivate: bool },
|
|
||||||
#[serde(
|
|
||||||
rename = "action.devices.commands.SetFanSpeed",
|
|
||||||
rename_all = "camelCase"
|
|
||||||
)]
|
|
||||||
SetFanSpeed { fan_speed: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -74,7 +62,7 @@ mod tests {
|
||||||
assert_eq!(payload.commands[0].devices.len(), 0);
|
assert_eq!(payload.commands[0].devices.len(), 0);
|
||||||
assert_eq!(payload.commands[0].execution.len(), 1);
|
assert_eq!(payload.commands[0].execution.len(), 1);
|
||||||
match &payload.commands[0].execution[0] {
|
match &payload.commands[0].execution[0] {
|
||||||
CommandType::SetFanSpeed { fan_speed } => assert_eq!(fan_speed, "Test"),
|
traits::Command::SetFanSpeed { fan_speed } => assert_eq!(fan_speed, "Test"),
|
||||||
_ => panic!("Expected SetFanSpeed"),
|
_ => panic!("Expected SetFanSpeed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,3 @@ pub enum ResponsePayload {
|
||||||
Query(query::Payload),
|
Query(query::Payload),
|
||||||
Execute(execute::Payload),
|
Execute(execute::Payload),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Clone)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct State {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub on: Option<bool>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub current_fan_speed_setting: Option<String>,
|
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub humidity_ambient_percent: Option<isize>,
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::errors::ErrorCode;
|
use crate::errors::ErrorCode;
|
||||||
use crate::response::State;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -72,7 +71,7 @@ pub struct States {
|
||||||
pub online: bool,
|
pub online: bool,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub state: State,
|
pub state: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
@ -87,19 +86,19 @@ pub enum Status {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::errors::DeviceError;
|
use crate::errors::DeviceError;
|
||||||
use crate::response::{Response, ResponsePayload, State};
|
use crate::response::{Response, ResponsePayload};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize() {
|
fn serialize() {
|
||||||
let mut execute_resp = Payload::new();
|
let mut execute_resp = Payload::new();
|
||||||
|
|
||||||
let state = State {
|
let state = json!({
|
||||||
on: Some(true),
|
"on": true,
|
||||||
current_fan_speed_setting: None,
|
});
|
||||||
humidity_ambient_percent: None,
|
|
||||||
};
|
|
||||||
let mut command = Command::new(Status::Success);
|
let mut command = Command::new(Status::Success);
|
||||||
command.states = Some(States {
|
command.states = Some(States {
|
||||||
online: true,
|
online: true,
|
||||||
|
|
|
@ -3,7 +3,6 @@ use std::collections::HashMap;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::errors::ErrorCode;
|
use crate::errors::ErrorCode;
|
||||||
use crate::response::State;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -53,7 +52,7 @@ pub struct Device {
|
||||||
error_code: Option<ErrorCode>,
|
error_code: Option<ErrorCode>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub state: State,
|
pub state: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
|
@ -62,7 +61,7 @@ impl Device {
|
||||||
online: true,
|
online: true,
|
||||||
status: Status::Success,
|
status: Status::Success,
|
||||||
error_code: None,
|
error_code: None,
|
||||||
state: State::default(),
|
state: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +87,8 @@ impl Default for Device {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::response::{Response, ResponsePayload};
|
use crate::response::{Response, ResponsePayload};
|
||||||
|
|
||||||
|
@ -96,11 +97,15 @@ mod tests {
|
||||||
let mut query_resp = Payload::new();
|
let mut query_resp = Payload::new();
|
||||||
|
|
||||||
let mut device = Device::new();
|
let mut device = Device::new();
|
||||||
device.state.on = Some(true);
|
device.state = json!({
|
||||||
|
"on": true,
|
||||||
|
});
|
||||||
query_resp.add_device("123", device);
|
query_resp.add_device("123", device);
|
||||||
|
|
||||||
let mut device = Device::new();
|
let mut device = Device::new();
|
||||||
device.state.on = Some(false);
|
device.state = json!({
|
||||||
|
"on": true,
|
||||||
|
});
|
||||||
query_resp.add_device("456", device);
|
query_resp.add_device("456", device);
|
||||||
|
|
||||||
let resp = Response::new(
|
let resp = Response::new(
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::attributes::Attributes;
|
|
||||||
use crate::device;
|
use crate::device;
|
||||||
use crate::errors::ErrorCode;
|
use crate::errors::ErrorCode;
|
||||||
use crate::traits::Trait;
|
use crate::traits::Trait;
|
||||||
|
@ -47,7 +46,7 @@ pub struct Device {
|
||||||
pub room_hint: Option<String>,
|
pub room_hint: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub device_info: Option<device::Info>,
|
pub device_info: Option<device::Info>,
|
||||||
pub attributes: Attributes,
|
pub attributes: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device {
|
impl Device {
|
||||||
|
@ -61,7 +60,7 @@ impl Device {
|
||||||
notification_supported_by_agent: None,
|
notification_supported_by_agent: None,
|
||||||
room_hint: None,
|
room_hint: None,
|
||||||
device_info: None,
|
device_info: None,
|
||||||
attributes: Attributes::default(),
|
attributes: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,39 @@
|
||||||
use async_trait::async_trait;
|
use automation_cast::Cast;
|
||||||
|
use automation_macro::google_home_traits;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::errors::ErrorCode;
|
use crate::errors::ErrorCode;
|
||||||
|
use crate::GoogleHomeDevice;
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
google_home_traits! {
|
||||||
pub enum Trait {
|
GoogleHomeDevice,
|
||||||
#[serde(rename = "action.devices.traits.OnOff")]
|
"action.devices.traits.OnOff" => trait OnOff {
|
||||||
OnOff,
|
command_only_on_off: Option<bool>,
|
||||||
#[serde(rename = "action.devices.traits.Scene")]
|
query_only_on_off: Option<bool>,
|
||||||
Scene,
|
async fn on(&self) -> Result<bool, ErrorCode>,
|
||||||
#[serde(rename = "action.devices.traits.FanSpeed")]
|
"action.devices.commands.OnOff" => async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode>,
|
||||||
FanSpeed,
|
},
|
||||||
#[serde(rename = "action.devices.traits.HumiditySetting")]
|
"action.devices.traits.Scene" => trait Scene {
|
||||||
HumiditySetting,
|
scene_reversible: Option<bool>,
|
||||||
|
|
||||||
|
"action.devices.commands.ActivateScene" => async fn set_active(&mut self, activate: bool) -> Result<(), ErrorCode>,
|
||||||
|
},
|
||||||
|
"action.devices.traits.FanSpeed" => trait FanSpeed {
|
||||||
|
reversible: Option<bool>,
|
||||||
|
command_only_fan_speed: Option<bool>,
|
||||||
|
available_fan_speeds: AvailableSpeeds,
|
||||||
|
|
||||||
|
fn current_fan_speed_setting(&self) -> Result<String, ErrorCode>,
|
||||||
|
|
||||||
|
// TODO: Figure out some syntax for optional command?
|
||||||
|
// Probably better to just force the user to always implement commands?
|
||||||
|
"action.devices.commands.SetFanSpeed" => async fn set_fan_speed(&mut self, fan_speed: String) -> Result<(), ErrorCode>,
|
||||||
|
},
|
||||||
|
"action.devices.traits.HumiditySetting" => trait HumiditySetting {
|
||||||
|
query_only_humidity_setting: Option<bool>,
|
||||||
|
|
||||||
|
fn humidity_ambient_percent(&self) -> Result<isize, ErrorCode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait OnOff: Sync + Send {
|
|
||||||
fn is_command_only(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_query_only(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement correct error so we can handle them properly
|
|
||||||
async fn is_on(&self) -> Result<bool, ErrorCode>;
|
|
||||||
async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Scene: Sync + Send {
|
|
||||||
fn is_scene_reversible(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_active(&self, activate: bool) -> Result<(), ErrorCode>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
@ -56,28 +53,3 @@ pub struct AvailableSpeeds {
|
||||||
pub speeds: Vec<Speed>,
|
pub speeds: Vec<Speed>,
|
||||||
pub ordered: bool,
|
pub ordered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait FanSpeed: Sync + Send {
|
|
||||||
fn reversible(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn command_only_fan_speed(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn available_speeds(&self) -> AvailableSpeeds;
|
|
||||||
async fn current_speed(&self) -> String;
|
|
||||||
async fn set_speed(&self, speed: &str) -> Result<(), ErrorCode>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait HumiditySetting: Sync + Send {
|
|
||||||
// TODO: This implementation is not complete, I have only implemented what I need right now
|
|
||||||
fn query_only_humidity_setting(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn humidity_ambient_percent(&self) -> isize;
|
|
||||||
}
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ impl GoogleHomeDevice for AirFilter {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnOff for AirFilter {
|
impl OnOff for AirFilter {
|
||||||
async fn is_on(&self) -> Result<bool, ErrorCode> {
|
async fn on(&self) -> Result<bool, ErrorCode> {
|
||||||
Ok(self.last_known_state.state != AirFilterFanState::Off)
|
Ok(self.last_known_state.state != AirFilterFanState::Off)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ impl OnOff for AirFilter {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl FanSpeed for AirFilter {
|
impl FanSpeed for AirFilter {
|
||||||
fn available_speeds(&self) -> AvailableSpeeds {
|
fn available_fan_speeds(&self) -> AvailableSpeeds {
|
||||||
AvailableSpeeds {
|
AvailableSpeeds {
|
||||||
speeds: vec![
|
speeds: vec![
|
||||||
Speed {
|
Speed {
|
||||||
|
@ -189,7 +189,7 @@ impl FanSpeed for AirFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn current_speed(&self) -> String {
|
fn current_fan_speed_setting(&self) -> Result<String, ErrorCode> {
|
||||||
let speed = match self.last_known_state.state {
|
let speed = match self.last_known_state.state {
|
||||||
AirFilterFanState::Off => "off",
|
AirFilterFanState::Off => "off",
|
||||||
AirFilterFanState::Low => "low",
|
AirFilterFanState::Low => "low",
|
||||||
|
@ -197,17 +197,18 @@ impl FanSpeed for AirFilter {
|
||||||
AirFilterFanState::High => "high",
|
AirFilterFanState::High => "high",
|
||||||
};
|
};
|
||||||
|
|
||||||
speed.into()
|
Ok(speed.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_speed(&self, speed: &str) -> Result<(), ErrorCode> {
|
async fn set_fan_speed(&mut self, fan_speed: String) -> Result<(), ErrorCode> {
|
||||||
let state = if speed == "off" {
|
let fan_speed = fan_speed.as_str();
|
||||||
|
let state = if fan_speed == "off" {
|
||||||
AirFilterFanState::Off
|
AirFilterFanState::Off
|
||||||
} else if speed == "low" {
|
} else if fan_speed == "low" {
|
||||||
AirFilterFanState::Low
|
AirFilterFanState::Low
|
||||||
} else if speed == "medium" {
|
} else if fan_speed == "medium" {
|
||||||
AirFilterFanState::Medium
|
AirFilterFanState::Medium
|
||||||
} else if speed == "high" {
|
} else if fan_speed == "high" {
|
||||||
AirFilterFanState::High
|
AirFilterFanState::High
|
||||||
} else {
|
} else {
|
||||||
return Err(google_home::errors::DeviceError::TransientError.into());
|
return Err(google_home::errors::DeviceError::TransientError.into());
|
||||||
|
@ -225,7 +226,7 @@ impl HumiditySetting for AirFilter {
|
||||||
Some(true)
|
Some(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn humidity_ambient_percent(&self) -> isize {
|
fn humidity_ambient_percent(&self) -> Result<isize, ErrorCode> {
|
||||||
self.last_known_state.humidity.round() as isize
|
Ok(self.last_known_state.humidity.round() as isize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl OnMqtt for AudioSetup {
|
||||||
) {
|
) {
|
||||||
match action {
|
match action {
|
||||||
RemoteAction::On => {
|
RemoteAction::On => {
|
||||||
if mixer.is_on().await.unwrap() {
|
if mixer.on().await.unwrap() {
|
||||||
speakers.set_on(false).await.unwrap();
|
speakers.set_on(false).await.unwrap();
|
||||||
mixer.set_on(false).await.unwrap();
|
mixer.set_on(false).await.unwrap();
|
||||||
} else {
|
} else {
|
||||||
|
@ -101,9 +101,9 @@ impl OnMqtt for AudioSetup {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RemoteAction::BrightnessMoveUp => {
|
RemoteAction::BrightnessMoveUp => {
|
||||||
if !mixer.is_on().await.unwrap() {
|
if !mixer.on().await.unwrap() {
|
||||||
mixer.set_on(true).await.unwrap();
|
mixer.set_on(true).await.unwrap();
|
||||||
} else if speakers.is_on().await.unwrap() {
|
} else if speakers.on().await.unwrap() {
|
||||||
speakers.set_on(false).await.unwrap();
|
speakers.set_on(false).await.unwrap();
|
||||||
} else {
|
} else {
|
||||||
speakers.set_on(true).await.unwrap();
|
speakers.set_on(true).await.unwrap();
|
||||||
|
|
|
@ -155,7 +155,7 @@ impl OnMqtt for ContactSensor {
|
||||||
for (light, previous) in &mut trigger.devices {
|
for (light, previous) in &mut trigger.devices {
|
||||||
let mut light = light.write().await;
|
let mut light = light.write().await;
|
||||||
if let Some(light) = light.as_mut().cast_mut() as Option<&mut dyn OnOff> {
|
if let Some(light) = light.as_mut().cast_mut() as Option<&mut dyn OnOff> {
|
||||||
*previous = light.is_on().await.unwrap();
|
*previous = light.on().await.unwrap();
|
||||||
light.set_on(true).await.ok();
|
light.set_on(true).await.ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,7 @@ impl OnOff for HueGroup {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn is_on(&self) -> Result<bool, ErrorCode> {
|
async fn on(&self) -> Result<bool, ErrorCode> {
|
||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.get(self.url_get_state())
|
.get(self.url_get_state())
|
||||||
.send()
|
.send()
|
||||||
|
|
|
@ -205,7 +205,7 @@ impl GoogleHomeDevice for IkeaOutlet {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl traits::OnOff for IkeaOutlet {
|
impl traits::OnOff for IkeaOutlet {
|
||||||
async fn is_on(&self) -> Result<bool, ErrorCode> {
|
async fn on(&self) -> Result<bool, ErrorCode> {
|
||||||
Ok(self.last_known_state)
|
Ok(self.last_known_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ impl Response {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl traits::OnOff for KasaOutlet {
|
impl traits::OnOff for KasaOutlet {
|
||||||
async fn is_on(&self) -> Result<bool, errors::ErrorCode> {
|
async fn on(&self) -> Result<bool, errors::ErrorCode> {
|
||||||
let mut stream = TcpStream::connect(self.config.addr)
|
let mut stream = TcpStream::connect(self.config.addr)
|
||||||
.await
|
.await
|
||||||
.or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
|
.or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
|
||||||
|
|
|
@ -103,7 +103,7 @@ impl GoogleHomeDevice for WakeOnLAN {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl traits::Scene for WakeOnLAN {
|
impl traits::Scene for WakeOnLAN {
|
||||||
async fn set_active(&self, activate: bool) -> Result<(), ErrorCode> {
|
async fn set_active(&mut self, activate: bool) -> Result<(), ErrorCode> {
|
||||||
if activate {
|
if activate {
|
||||||
debug!(
|
debug!(
|
||||||
id = Device::get_id(self),
|
id = Device::get_id(self),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user