Compare commits
No commits in common. "54c4b70d1962c210269eceefdfe7496fba4c1363" and "0d1e15c676bf56dd407b4e2a04a65a476b4c3d02" have entirely different histories.
54c4b70d19
...
0d1e15c676
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -110,7 +110,6 @@ dependencies = [
|
||||||
name = "automation_macro"
|
name = "automation_macro"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.60",
|
"syn 2.0.60",
|
||||||
]
|
]
|
||||||
|
|
|
@ -7,6 +7,5 @@ edition = "2021"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0.81"
|
|
||||||
quote = "1.0.36"
|
quote = "1.0.36"
|
||||||
syn = { version = "2.0.60", features = ["extra-traits"] }
|
syn = "2.0.60"
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
use syn::{parse_macro_input, DeriveInput, Token};
|
|
||||||
|
|
||||||
#[proc_macro_derive(LuaDevice, attributes(config))]
|
#[proc_macro_derive(LuaDevice, attributes(config))]
|
||||||
pub fn lua_device_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn lua_device_derive(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
impl_lua_device_macro(&ast).into()
|
impl_lua_device_macro(&ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_lua_device_macro(ast: &syn::DeriveInput) -> TokenStream {
|
fn impl_lua_device_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
let name = &ast.ident;
|
let name = &ast.ident;
|
||||||
|
let name_string = name.to_string();
|
||||||
// TODO: Handle errors properly
|
// TODO: Handle errors properly
|
||||||
// This includes making sure one, and only one config is specified
|
// This includes making sure one, and only one config is specified
|
||||||
let config = if let syn::Data::Struct(syn::DataStruct {
|
let config = if let syn::Data::Struct(syn::DataStruct {
|
||||||
|
@ -36,13 +36,13 @@ fn impl_lua_device_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
let gen = quote! {
|
let gen = quote! {
|
||||||
impl #name {
|
impl #name {
|
||||||
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||||
lua.globals().set(stringify!(#name), lua.create_proxy::<#name>()?)
|
lua.globals().set(#name_string, lua.create_proxy::<#name>()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl mlua::UserData for #name {
|
impl mlua::UserData for #name {
|
||||||
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_function("new", |lua, config: mlua::Value| {
|
methods.add_function("new", |lua, config: mlua::Value| {
|
||||||
let config: #config = mlua::FromLua::from_lua(config, lua)?;
|
let config: #config = mlua::LuaSerdeExt::from_value(lua, config)?;
|
||||||
let config: Box<dyn crate::device_manager::DeviceConfig> = Box::new(config);
|
let config: Box<dyn crate::device_manager::DeviceConfig> = Box::new(config);
|
||||||
Ok(config)
|
Ok(config)
|
||||||
});
|
});
|
||||||
|
@ -50,273 +50,5 @@ fn impl_lua_device_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
gen
|
gen.into()
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Arg {
|
|
||||||
Flatten,
|
|
||||||
UserData,
|
|
||||||
Rename(String),
|
|
||||||
With(TokenStream),
|
|
||||||
Default(Option<syn::Ident>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl syn::parse::Parse for Arg {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let arg = match input.parse::<syn::Ident>()?.to_string().as_str() {
|
|
||||||
"flatten" => Arg::Flatten,
|
|
||||||
"user_data" => Arg::UserData,
|
|
||||||
"rename" => {
|
|
||||||
input.parse::<Token![=]>()?;
|
|
||||||
let lit = input.parse::<syn::Lit>()?;
|
|
||||||
if let syn::Lit::Str(lit_str) = lit {
|
|
||||||
Arg::Rename(lit_str.value())
|
|
||||||
} else {
|
|
||||||
panic!("Expected literal string");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"with" => {
|
|
||||||
input.parse::<Token![=]>()?;
|
|
||||||
let lit = input.parse::<syn::Lit>()?;
|
|
||||||
if let syn::Lit::Str(lit_str) = lit {
|
|
||||||
let token_stream: TokenStream = lit_str.parse()?;
|
|
||||||
Arg::With(token_stream)
|
|
||||||
} else {
|
|
||||||
panic!("Expected literal string");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"default" => {
|
|
||||||
if input.parse::<Token![=]>().is_ok() {
|
|
||||||
let func = input.parse::<syn::Ident>()?;
|
|
||||||
Arg::Default(Some(func))
|
|
||||||
} else {
|
|
||||||
Arg::Default(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name => todo!("Handle unknown arg: {name}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ArgsParser {
|
|
||||||
args: Punctuated<Arg, Token![,]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl syn::parse::Parse for ArgsParser {
|
|
||||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
||||||
let args = input.parse_terminated(Arg::parse, Token![,])?;
|
|
||||||
|
|
||||||
Ok(Self { args })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Args {
|
|
||||||
flatten: bool,
|
|
||||||
user_data: bool,
|
|
||||||
rename: Option<String>,
|
|
||||||
with: Option<TokenStream>,
|
|
||||||
default: Option<Option<syn::Ident>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args {
|
|
||||||
fn new(args: Vec<Arg>) -> Self {
|
|
||||||
let mut result = Args {
|
|
||||||
flatten: false,
|
|
||||||
user_data: false,
|
|
||||||
rename: None,
|
|
||||||
with: None,
|
|
||||||
default: None,
|
|
||||||
};
|
|
||||||
for arg in args {
|
|
||||||
match arg {
|
|
||||||
Arg::Flatten => {
|
|
||||||
if result.flatten {
|
|
||||||
panic!("Option 'flatten' is already set")
|
|
||||||
}
|
|
||||||
result.flatten = true
|
|
||||||
}
|
|
||||||
Arg::UserData => {
|
|
||||||
if result.flatten {
|
|
||||||
panic!("Option 'user_data' is already set")
|
|
||||||
}
|
|
||||||
result.user_data = true
|
|
||||||
}
|
|
||||||
Arg::Rename(name) => {
|
|
||||||
if result.rename.is_some() {
|
|
||||||
panic!("Option 'rename' is already set")
|
|
||||||
}
|
|
||||||
result.rename = Some(name)
|
|
||||||
}
|
|
||||||
Arg::With(ty) => {
|
|
||||||
if result.with.is_some() {
|
|
||||||
panic!("Option 'with' is already set")
|
|
||||||
}
|
|
||||||
result.with = Some(ty)
|
|
||||||
}
|
|
||||||
Arg::Default(func) => {
|
|
||||||
if result.default.is_some() {
|
|
||||||
panic!("Option 'default' is already set")
|
|
||||||
}
|
|
||||||
result.default = Some(func)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.flatten && result.user_data {
|
|
||||||
panic!("The options 'flatten' and 'user_data' conflict with each other")
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.flatten && result.default.is_some() {
|
|
||||||
panic!("The options 'flatten' and 'default' conflict with each other")
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(LuaDeviceConfig, attributes(device_config))]
|
|
||||||
pub fn lua_device_config_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
|
||||||
|
|
||||||
impl_lua_device_config_macro(&ast).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
// struct Args
|
|
||||||
|
|
||||||
fn impl_lua_device_config_macro(ast: &syn::DeriveInput) -> TokenStream {
|
|
||||||
let name = &ast.ident;
|
|
||||||
// TODO: Handle errors properly
|
|
||||||
// This includes making sure one, and only one config is specified
|
|
||||||
let fields = if let syn::Data::Struct(syn::DataStruct {
|
|
||||||
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
|
|
||||||
..
|
|
||||||
}) = ast.data
|
|
||||||
{
|
|
||||||
named
|
|
||||||
} else {
|
|
||||||
unimplemented!("Macro can only handle named structs");
|
|
||||||
};
|
|
||||||
|
|
||||||
let fields: Vec<_> = fields
|
|
||||||
.iter()
|
|
||||||
.map(|field| {
|
|
||||||
let field_name = field.ident.clone().unwrap();
|
|
||||||
let args: Vec<_> = field
|
|
||||||
.attrs
|
|
||||||
.iter()
|
|
||||||
.filter_map(|attr| {
|
|
||||||
if attr.path().is_ident("device_config") {
|
|
||||||
let args: ArgsParser = attr.parse_args().unwrap();
|
|
||||||
Some(args.args)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let args = Args::new(args);
|
|
||||||
|
|
||||||
let table_name = if let Some(name) = args.rename {
|
|
||||||
name
|
|
||||||
} else {
|
|
||||||
field_name.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Improve how optional fields are detected
|
|
||||||
let optional = if let syn::Type::Path(path) = field.ty.clone() {
|
|
||||||
path.path.segments.first().unwrap().ident == "Option"
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let default = if optional {
|
|
||||||
quote! { None }
|
|
||||||
} else if let Some(func) = args.default {
|
|
||||||
if func.is_some() {
|
|
||||||
quote! { #func() }
|
|
||||||
} else {
|
|
||||||
quote! { Default::default() }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let missing = format!("Missing field '{table_name}'");
|
|
||||||
quote! { panic!(#missing) }
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = if args.flatten {
|
|
||||||
// println!("ValueFlatten: {}", field_name);
|
|
||||||
quote! {
|
|
||||||
mlua::LuaSerdeExt::from_value_with(lua, value.clone(), mlua::DeserializeOptions::new().deny_unsupported_types(false))?
|
|
||||||
}
|
|
||||||
} else if args.user_data {
|
|
||||||
// println!("UserData: {}", field_name);
|
|
||||||
quote! {
|
|
||||||
if table.contains_key(#table_name)? {
|
|
||||||
table.get(#table_name)?
|
|
||||||
} else {
|
|
||||||
#default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// println!("Value: {}", field_name);
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let #field_name: mlua::Value = table.get(#table_name)?;
|
|
||||||
if !#field_name.is_nil() {
|
|
||||||
mlua::LuaSerdeExt::from_value(lua, #field_name)?
|
|
||||||
} else {
|
|
||||||
#default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = if let Some(temp_type) = args.with {
|
|
||||||
if optional {
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let temp: #temp_type = #value;
|
|
||||||
temp.map(|v| v.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
{
|
|
||||||
let temp: #temp_type = #value;
|
|
||||||
temp.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
value
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#field_name: #value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let gen = quote! {
|
|
||||||
impl<'lua> mlua::FromLua<'lua> for #name {
|
|
||||||
fn from_lua(value: mlua::Value<'lua>, lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
|
||||||
if !value.is_table() {
|
|
||||||
panic!("Expected table");
|
|
||||||
}
|
|
||||||
let table = value.as_table().unwrap();
|
|
||||||
|
|
||||||
Ok(#name {
|
|
||||||
#(#fields,)*
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
gen
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ automation.device_manager:create(
|
||||||
"debug_bridge",
|
"debug_bridge",
|
||||||
DebugBridge.new({
|
DebugBridge.new({
|
||||||
topic = mqtt_automation("debug"),
|
topic = mqtt_automation("debug"),
|
||||||
client = automation.mqtt_client,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,7 +41,6 @@ automation.device_manager:create(
|
||||||
topic = mqtt_z2m("living/light"),
|
topic = mqtt_z2m("living/light"),
|
||||||
min = 22000,
|
min = 22000,
|
||||||
max = 23500,
|
max = 23500,
|
||||||
event_channel = automation.event_channel,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,7 +74,6 @@ automation.device_manager:create(
|
||||||
name = "Kettle",
|
name = "Kettle",
|
||||||
room = "Kitchen",
|
room = "Kitchen",
|
||||||
topic = mqtt_z2m("kitchen/kettle"),
|
topic = mqtt_z2m("kitchen/kettle"),
|
||||||
client = automation.mqtt_client,
|
|
||||||
timeout = debug and 5 or 300,
|
timeout = debug and 5 or 300,
|
||||||
remotes = {
|
remotes = {
|
||||||
{ topic = mqtt_z2m("bedroom/remote") },
|
{ topic = mqtt_z2m("bedroom/remote") },
|
||||||
|
@ -92,7 +89,6 @@ automation.device_manager:create(
|
||||||
name = "Light",
|
name = "Light",
|
||||||
room = "Bathroom",
|
room = "Bathroom",
|
||||||
topic = mqtt_z2m("batchroom/light"),
|
topic = mqtt_z2m("batchroom/light"),
|
||||||
client = automation.mqtt_client,
|
|
||||||
timeout = debug and 60 or 45 * 60,
|
timeout = debug and 60 or 45 * 60,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -102,7 +98,6 @@ automation.device_manager:create(
|
||||||
Washer.new({
|
Washer.new({
|
||||||
topic = mqtt_z2m("batchroom/washer"),
|
topic = mqtt_z2m("batchroom/washer"),
|
||||||
threshold = 1,
|
threshold = 1,
|
||||||
event_channel = automation.event_channel,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -113,7 +108,6 @@ automation.device_manager:create(
|
||||||
name = "Charger",
|
name = "Charger",
|
||||||
room = "Workbench",
|
room = "Workbench",
|
||||||
topic = mqtt_z2m("workbench/charger"),
|
topic = mqtt_z2m("workbench/charger"),
|
||||||
client = automation.mqtt_client,
|
|
||||||
timeout = debug and 5 or 20 * 3600,
|
timeout = debug and 5 or 20 * 3600,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -124,7 +118,6 @@ automation.device_manager:create(
|
||||||
name = "Outlet",
|
name = "Outlet",
|
||||||
room = "Workbench",
|
room = "Workbench",
|
||||||
topic = mqtt_z2m("workbench/outlet"),
|
topic = mqtt_z2m("workbench/outlet"),
|
||||||
client = automation.mqtt_client,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -146,7 +139,6 @@ automation.device_manager:create(
|
||||||
"hallway_frontdoor",
|
"hallway_frontdoor",
|
||||||
ContactSensor.new({
|
ContactSensor.new({
|
||||||
topic = mqtt_z2m("hallway/frontdoor"),
|
topic = mqtt_z2m("hallway/frontdoor"),
|
||||||
client = automation.mqtt_client,
|
|
||||||
presence = {
|
presence = {
|
||||||
topic = mqtt_automation("presence/contact/frontdoor"),
|
topic = mqtt_automation("presence/contact/frontdoor"),
|
||||||
timeout = debug and 10 or 15 * 60,
|
timeout = debug and 10 or 15 * 60,
|
||||||
|
@ -164,6 +156,5 @@ automation.device_manager:create(
|
||||||
name = "Air Filter",
|
name = "Air Filter",
|
||||||
room = "Bedroom",
|
room = "Bedroom",
|
||||||
topic = "pico/filter/bedroom",
|
topic = "pico/filter/bedroom",
|
||||||
client = automation.mqtt_client,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use enum_dispatch::enum_dispatch;
|
use enum_dispatch::enum_dispatch;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
use mlua::FromLua;
|
|
||||||
use rumqttc::{matches, AsyncClient, QoS};
|
use rumqttc::{matches, AsyncClient, QoS};
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||||
use tokio_cron_scheduler::{Job, JobScheduler};
|
use tokio_cron_scheduler::{Job, JobScheduler};
|
||||||
|
@ -17,38 +15,25 @@ use crate::error::DeviceConfigError;
|
||||||
use crate::event::{Event, EventChannel, OnDarkness, OnMqtt, OnNotification, OnPresence};
|
use crate::event::{Event, EventChannel, OnDarkness, OnMqtt, OnNotification, OnPresence};
|
||||||
use crate::schedule::{Action, Schedule};
|
use crate::schedule::{Action, Schedule};
|
||||||
|
|
||||||
|
pub struct ConfigExternal<'a> {
|
||||||
|
pub client: &'a AsyncClient,
|
||||||
|
pub device_manager: &'a DeviceManager,
|
||||||
|
pub event_channel: &'a EventChannel,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait DeviceConfig {
|
pub trait DeviceConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError>;
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError>;
|
||||||
}
|
}
|
||||||
impl mlua::UserData for Box<dyn DeviceConfig> {}
|
impl mlua::UserData for Box<dyn DeviceConfig> {}
|
||||||
|
|
||||||
#[derive(Debug, FromLua, Clone)]
|
pub type WrappedDevice = Arc<RwLock<Box<dyn Device>>>;
|
||||||
pub struct WrappedDevice(Arc<RwLock<Box<dyn Device>>>);
|
pub type DeviceMap = HashMap<String, WrappedDevice>;
|
||||||
|
|
||||||
impl WrappedDevice {
|
|
||||||
fn new(device: Box<dyn Device>) -> Self {
|
|
||||||
Self(Arc::new(RwLock::new(device)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for WrappedDevice {
|
|
||||||
type Target = Arc<RwLock<Box<dyn Device>>>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for WrappedDevice {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl mlua::UserData for WrappedDevice {}
|
|
||||||
|
|
||||||
pub type DeviceMap = HashMap<String, Arc<RwLock<Box<dyn Device>>>>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DeviceManager {
|
pub struct DeviceManager {
|
||||||
|
@ -132,7 +117,7 @@ impl DeviceManager {
|
||||||
sched.start().await.unwrap();
|
sched.start().await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add(&self, device: Box<dyn Device>) -> WrappedDevice {
|
pub async fn add(&self, device: Box<dyn Device>) {
|
||||||
let id = device.get_id().into();
|
let id = device.get_id().into();
|
||||||
|
|
||||||
debug!(id, "Adding device");
|
debug!(id, "Adding device");
|
||||||
|
@ -150,11 +135,9 @@ impl DeviceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the device
|
// Wrap the device
|
||||||
let device = WrappedDevice::new(device);
|
let device = Arc::new(RwLock::new(device));
|
||||||
|
|
||||||
self.devices.write().await.insert(id, device.0.clone());
|
self.devices.write().await.insert(id, device);
|
||||||
|
|
||||||
device
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event_channel(&self) -> EventChannel {
|
pub fn event_channel(&self) -> EventChannel {
|
||||||
|
@ -162,12 +145,7 @@ impl DeviceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(&self, name: &str) -> Option<WrappedDevice> {
|
pub async fn get(&self, name: &str) -> Option<WrappedDevice> {
|
||||||
self.devices
|
self.devices.read().await.get(name).cloned()
|
||||||
.read()
|
|
||||||
.await
|
|
||||||
.get(name)
|
|
||||||
.cloned()
|
|
||||||
.map(WrappedDevice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn devices(&self) -> RwLockReadGuard<DeviceMap> {
|
pub async fn devices(&self) -> RwLockReadGuard<DeviceMap> {
|
||||||
|
@ -254,12 +232,22 @@ impl mlua::UserData for DeviceManager {
|
||||||
// TODO: Handle the error here properly
|
// TODO: Handle the error here properly
|
||||||
let config: Box<dyn DeviceConfig> = config.as_userdata().unwrap().take()?;
|
let config: Box<dyn DeviceConfig> = config.as_userdata().unwrap().take()?;
|
||||||
|
|
||||||
|
let ext = ConfigExternal {
|
||||||
|
client: &this.client,
|
||||||
|
device_manager: this,
|
||||||
|
event_channel: &this.event_channel,
|
||||||
|
};
|
||||||
|
|
||||||
let device = config
|
let device = config
|
||||||
.create(&identifier)
|
.create(&identifier, &ext)
|
||||||
.await
|
.await
|
||||||
.map_err(mlua::ExternalError::into_lua_err)?;
|
.map_err(mlua::ExternalError::into_lua_err)?;
|
||||||
|
|
||||||
Ok(this.add(device).await)
|
let id = device.get_id().to_owned();
|
||||||
|
|
||||||
|
this.add(device).await;
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use google_home::device::Name;
|
use google_home::device::Name;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::{AvailableSpeeds, FanSpeed, HumiditySetting, OnOff, Speed, SpeedValues};
|
use google_home::traits::{AvailableSpeeds, FanSpeed, HumiditySetting, OnOff, Speed, SpeedValues};
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
use google_home::GoogleHomeDevice;
|
use google_home::GoogleHomeDevice;
|
||||||
use rumqttc::Publish;
|
use rumqttc::{AsyncClient, Publish};
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use crate::config::{InfoConfig, MqttDeviceConfig};
|
use crate::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use crate::device_manager::DeviceConfig;
|
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;
|
||||||
use crate::messages::{AirFilterFanState, AirFilterState, SetAirFilterFanState};
|
use crate::messages::{AirFilterFanState, AirFilterState, SetAirFilterFanState};
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct AirFilterConfig {
|
pub struct AirFilterConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
info: InfoConfig,
|
info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
|
||||||
client: WrappedAsyncClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for AirFilterConfig {
|
impl DeviceConfig for AirFilterConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = AirFilter {
|
let device = AirFilter {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
client: ext.client.clone(),
|
||||||
last_known_state: AirFilterState {
|
last_known_state: AirFilterState {
|
||||||
state: AirFilterFanState::Off,
|
state: AirFilterFanState::Off,
|
||||||
humidity: 0.0,
|
humidity: 0.0,
|
||||||
|
@ -48,6 +51,7 @@ pub struct AirFilter {
|
||||||
#[config]
|
#[config]
|
||||||
config: AirFilterConfig,
|
config: AirFilterConfig,
|
||||||
|
|
||||||
|
client: AsyncClient,
|
||||||
last_known_state: AirFilterState,
|
last_known_state: AirFilterState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +61,7 @@ impl AirFilter {
|
||||||
|
|
||||||
let topic = format!("{}/set", self.config.mqtt.topic);
|
let topic = format!("{}/set", self.config.mqtt.topic);
|
||||||
// TODO: Handle potential errors here
|
// TODO: Handle potential errors here
|
||||||
self.config
|
self.client
|
||||||
.client
|
|
||||||
.publish(
|
.publish(
|
||||||
topic.clone(),
|
topic.clone(),
|
||||||
rumqttc::QoS::AtLeastOnce,
|
rumqttc::QoS::AtLeastOnce,
|
||||||
|
|
|
@ -1,44 +1,73 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, error, trace, warn};
|
use tracing::{debug, error, trace, warn};
|
||||||
|
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::{DeviceConfig, WrappedDevice};
|
use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice};
|
||||||
use crate::devices::As;
|
use crate::devices::As;
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::messages::{RemoteAction, RemoteMessage};
|
use crate::messages::{RemoteAction, RemoteMessage};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct AudioSetupConfig {
|
pub struct AudioSetupConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
mixer: String,
|
||||||
mixer: WrappedDevice,
|
speakers: String,
|
||||||
#[device_config(user_data)]
|
|
||||||
speakers: WrappedDevice,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for AudioSetupConfig {
|
impl DeviceConfig for AudioSetupConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(id = identifier, "Setting up AudioSetup");
|
trace!(id = identifier, "Setting up AudioSetup");
|
||||||
|
|
||||||
let mixer_id = self.mixer.read().await.get_id().to_owned();
|
// TODO: Make sure they implement OnOff?
|
||||||
if !As::<dyn OnOff>::is(self.mixer.read().await.as_ref()) {
|
let mixer = ext
|
||||||
return Err(DeviceConfigError::MissingTrait(mixer_id, "OnOff".into()));
|
.device_manager
|
||||||
|
.get(&self.mixer)
|
||||||
|
.await
|
||||||
|
// NOTE: We need to clone to make the compiler happy, how ever if this clone happens the next one can never happen...
|
||||||
|
.ok_or(DeviceConfigError::MissingChild(
|
||||||
|
identifier.into(),
|
||||||
|
self.mixer.clone(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
if !As::<dyn OnOff>::is(mixer.read().await.as_ref()) {
|
||||||
|
return Err(DeviceConfigError::MissingTrait(
|
||||||
|
self.mixer.clone(),
|
||||||
|
"OnOff".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let speakers_id = self.speakers.read().await.get_id().to_owned();
|
let speakers =
|
||||||
if !As::<dyn OnOff>::is(self.speakers.read().await.as_ref()) {
|
ext.device_manager
|
||||||
return Err(DeviceConfigError::MissingTrait(speakers_id, "OnOff".into()));
|
.get(&self.speakers)
|
||||||
|
.await
|
||||||
|
.ok_or(DeviceConfigError::MissingChild(
|
||||||
|
identifier.into(),
|
||||||
|
self.speakers.clone(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
if !As::<dyn OnOff>::is(speakers.read().await.as_ref()) {
|
||||||
|
return Err(DeviceConfigError::MissingTrait(
|
||||||
|
self.speakers.clone(),
|
||||||
|
"OnOff".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let device = AudioSetup {
|
let device = AudioSetup {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
mixer,
|
||||||
|
speakers,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(device))
|
Ok(Box::new(device))
|
||||||
|
@ -51,6 +80,8 @@ pub struct AudioSetup {
|
||||||
identifier: String,
|
identifier: String,
|
||||||
#[config]
|
#[config]
|
||||||
config: AudioSetupConfig,
|
config: AudioSetupConfig,
|
||||||
|
mixer: WrappedDevice,
|
||||||
|
speakers: WrappedDevice,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for AudioSetup {
|
impl Device for AudioSetup {
|
||||||
|
@ -74,8 +105,8 @@ impl OnMqtt for AudioSetup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mixer = self.config.mixer.write().await;
|
let mut mixer = self.mixer.write().await;
|
||||||
let mut speakers = self.config.speakers.write().await;
|
let mut speakers = self.speakers.write().await;
|
||||||
if let (Some(mixer), Some(speakers)) = (
|
if let (Some(mixer), Some(speakers)) = (
|
||||||
As::<dyn OnOff>::cast_mut(mixer.as_mut()),
|
As::<dyn OnOff>::cast_mut(mixer.as_mut()),
|
||||||
As::<dyn OnOff>::cast_mut(speakers.as_mut()),
|
As::<dyn OnOff>::cast_mut(speakers.as_mut()),
|
||||||
|
@ -109,8 +140,8 @@ impl OnMqtt for AudioSetup {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OnPresence for AudioSetup {
|
impl OnPresence for AudioSetup {
|
||||||
async fn on_presence(&mut self, presence: bool) {
|
async fn on_presence(&mut self, presence: bool) {
|
||||||
let mut mixer = self.config.mixer.write().await;
|
let mut mixer = self.mixer.write().await;
|
||||||
let mut speakers = self.config.speakers.write().await;
|
let mut speakers = self.speakers.write().await;
|
||||||
|
|
||||||
if let (Some(mixer), Some(speakers)) = (
|
if let (Some(mixer), Some(speakers)) = (
|
||||||
As::<dyn OnOff>::cast_mut(mixer.as_mut()),
|
As::<dyn OnOff>::cast_mut(mixer.as_mut()),
|
||||||
|
|
|
@ -1,71 +1,83 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
|
use rumqttc::AsyncClient;
|
||||||
|
use serde::Deserialize;
|
||||||
|
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 super::Device;
|
use super::Device;
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::{DeviceConfig, WrappedDevice};
|
use crate::device_manager::{ConfigExternal, DeviceConfig, WrappedDevice};
|
||||||
use crate::devices::{As, DEFAULT_PRESENCE};
|
use crate::devices::{As, DEFAULT_PRESENCE};
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{OnMqtt, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::helper::DurationSeconds;
|
|
||||||
use crate::messages::{ContactMessage, PresenceMessage};
|
use crate::messages::{ContactMessage, PresenceMessage};
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
|
||||||
// NOTE: If we add more presence devices we might need to move this out of here
|
// NOTE: If we add more presence devices we might need to move this out of here
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[serde_as]
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct PresenceDeviceConfig {
|
pub struct PresenceDeviceConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(with = "DurationSeconds")]
|
#[serde_as(as = "DurationSeconds")]
|
||||||
pub timeout: Duration,
|
pub timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[serde_as]
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct TriggerConfig {
|
pub struct TriggerConfig {
|
||||||
#[device_config(user_data)]
|
devices: Vec<String>,
|
||||||
devices: Vec<WrappedDevice>,
|
#[serde(default)]
|
||||||
#[device_config(with = "Option<DurationSeconds>")]
|
#[serde_as(as = "DurationSeconds")]
|
||||||
pub timeout: Option<Duration>,
|
pub timeout: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct ContactSensorConfig {
|
pub struct ContactSensorConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
|
||||||
presence: Option<PresenceDeviceConfig>,
|
presence: Option<PresenceDeviceConfig>,
|
||||||
#[device_config(user_data)]
|
|
||||||
trigger: Option<TriggerConfig>,
|
trigger: Option<TriggerConfig>,
|
||||||
#[device_config(user_data)]
|
|
||||||
client: WrappedAsyncClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for ContactSensorConfig {
|
impl DeviceConfig for ContactSensorConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(id = identifier, "Setting up ContactSensor");
|
trace!(id = identifier, "Setting up ContactSensor");
|
||||||
|
|
||||||
let trigger = if let Some(trigger_config) = &self.trigger {
|
let trigger = if let Some(trigger_config) = &self.trigger {
|
||||||
let mut devices = Vec::new();
|
let mut devices = Vec::new();
|
||||||
for device in &trigger_config.devices {
|
for device_name in &trigger_config.devices {
|
||||||
let id = device.read().await.get_id().to_owned();
|
let device = ext.device_manager.get(device_name).await.ok_or(
|
||||||
|
DeviceConfigError::MissingChild(device_name.into(), "OnOff".into()),
|
||||||
|
)?;
|
||||||
|
|
||||||
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
if !As::<dyn OnOff>::is(device.read().await.as_ref()) {
|
||||||
return Err(DeviceConfigError::MissingTrait(id, "OnOff".into()));
|
return Err(DeviceConfigError::MissingTrait(
|
||||||
|
device_name.into(),
|
||||||
|
"OnOff".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if trigger_config.timeout.is_none()
|
if !trigger_config.timeout.is_zero()
|
||||||
&& !As::<dyn Timeout>::is(device.read().await.as_ref())
|
&& !As::<dyn Timeout>::is(device.read().await.as_ref())
|
||||||
{
|
{
|
||||||
return Err(DeviceConfigError::MissingTrait(id, "Timeout".into()));
|
return Err(DeviceConfigError::MissingTrait(
|
||||||
|
device_name.into(),
|
||||||
|
"Timeout".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
devices.push((device.clone(), false));
|
devices.push((device, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Trigger {
|
Some(Trigger {
|
||||||
|
@ -79,6 +91,7 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
let device = ContactSensor {
|
let device = ContactSensor {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
client: ext.client.clone(),
|
||||||
overall_presence: DEFAULT_PRESENCE,
|
overall_presence: DEFAULT_PRESENCE,
|
||||||
is_closed: true,
|
is_closed: true,
|
||||||
handle: None,
|
handle: None,
|
||||||
|
@ -92,7 +105,7 @@ impl DeviceConfig for ContactSensorConfig {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Trigger {
|
struct Trigger {
|
||||||
devices: Vec<(WrappedDevice, bool)>,
|
devices: Vec<(WrappedDevice, bool)>,
|
||||||
timeout: Option<Duration>,
|
timeout: Duration, // Timeout in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, LuaDevice)]
|
#[derive(Debug, LuaDevice)]
|
||||||
|
@ -101,6 +114,7 @@ pub struct ContactSensor {
|
||||||
#[config]
|
#[config]
|
||||||
config: ContactSensorConfig,
|
config: ContactSensorConfig,
|
||||||
|
|
||||||
|
client: AsyncClient,
|
||||||
overall_presence: bool,
|
overall_presence: bool,
|
||||||
is_closed: bool,
|
is_closed: bool,
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: Option<JoinHandle<()>>,
|
||||||
|
@ -156,14 +170,12 @@ impl OnMqtt for ContactSensor {
|
||||||
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 trigger.timeout.is_none()
|
if trigger.timeout.is_zero()
|
||||||
&& let Some(light) = As::<dyn OnOff>::cast_mut(light.as_mut())
|
&& 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(timeout) = trigger.timeout
|
} else if let Some(light) = As::<dyn Timeout>::cast_mut(light.as_mut()) {
|
||||||
&& let Some(light) = As::<dyn Timeout>::cast_mut(light.as_mut())
|
light.start_timeout(trigger.timeout).await.unwrap();
|
||||||
{
|
|
||||||
light.start_timeout(timeout).await.unwrap();
|
|
||||||
}
|
}
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
@ -188,8 +200,7 @@ impl OnMqtt for ContactSensor {
|
||||||
// This is to prevent the house from being marked as present for however long the
|
// This is to prevent the house from being marked as present for however long the
|
||||||
// timeout is set when leaving the house
|
// timeout is set when leaving the house
|
||||||
if !self.overall_presence {
|
if !self.overall_presence {
|
||||||
self.config
|
self.client
|
||||||
.client
|
|
||||||
.publish(
|
.publish(
|
||||||
presence.mqtt.topic.clone(),
|
presence.mqtt.topic.clone(),
|
||||||
rumqttc::QoS::AtLeastOnce,
|
rumqttc::QoS::AtLeastOnce,
|
||||||
|
@ -207,7 +218,7 @@ impl OnMqtt for ContactSensor {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Once the door is closed again we start a timeout for removing the presence
|
// Once the door is closed again we start a timeout for removing the presence
|
||||||
let client = self.config.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();
|
let topic = presence.mqtt.topic.clone();
|
||||||
|
|
|
@ -1,29 +1,33 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
|
use rumqttc::AsyncClient;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::DeviceConfig;
|
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::{OnDarkness, OnPresence};
|
use crate::event::{OnDarkness, OnPresence};
|
||||||
use crate::messages::{DarknessMessage, PresenceMessage};
|
use crate::messages::{DarknessMessage, PresenceMessage};
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
|
||||||
|
|
||||||
#[derive(Debug, LuaDeviceConfig, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct DebugBridgeConfig {
|
pub struct DebugBridgeConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(user_data)]
|
|
||||||
client: WrappedAsyncClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for DebugBridgeConfig {
|
impl DeviceConfig for DebugBridgeConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = DebugBridge {
|
let device = DebugBridge {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
client: ext.client.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Box::new(device))
|
Ok(Box::new(device))
|
||||||
|
@ -35,6 +39,7 @@ pub struct DebugBridge {
|
||||||
identifier: String,
|
identifier: String,
|
||||||
#[config]
|
#[config]
|
||||||
config: DebugBridgeConfig,
|
config: DebugBridgeConfig,
|
||||||
|
client: AsyncClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for DebugBridge {
|
impl Device for DebugBridge {
|
||||||
|
@ -48,8 +53,7 @@ impl OnPresence for DebugBridge {
|
||||||
async fn on_presence(&mut self, presence: bool) {
|
async fn on_presence(&mut self, presence: bool) {
|
||||||
let message = PresenceMessage::new(presence);
|
let message = PresenceMessage::new(presence);
|
||||||
let topic = format!("{}/presence", self.config.mqtt.topic);
|
let topic = format!("{}/presence", self.config.mqtt.topic);
|
||||||
self.config
|
self.client
|
||||||
.client
|
|
||||||
.publish(
|
.publish(
|
||||||
topic,
|
topic,
|
||||||
rumqttc::QoS::AtLeastOnce,
|
rumqttc::QoS::AtLeastOnce,
|
||||||
|
@ -72,8 +76,7 @@ impl OnDarkness for DebugBridge {
|
||||||
async fn on_darkness(&mut self, dark: bool) {
|
async fn on_darkness(&mut self, dark: bool) {
|
||||||
let message = DarknessMessage::new(dark);
|
let message = DarknessMessage::new(dark);
|
||||||
let topic = format!("{}/darkness", self.config.mqtt.topic);
|
let topic = format!("{}/darkness", self.config.mqtt.topic);
|
||||||
self.config
|
self.client
|
||||||
.client
|
|
||||||
.publish(
|
.publish(
|
||||||
topic,
|
topic,
|
||||||
rumqttc::QoS::AtLeastOnce,
|
rumqttc::QoS::AtLeastOnce,
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
use std::net::SocketAddr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
|
|
||||||
use crate::device_manager::DeviceConfig;
|
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::{OnDarkness, OnPresence};
|
use crate::event::{OnDarkness, OnPresence};
|
||||||
use crate::helper::Ipv4SocketAddr;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Flag {
|
pub enum Flag {
|
||||||
|
@ -23,17 +22,20 @@ pub struct FlagIDs {
|
||||||
pub darkness: isize,
|
pub darkness: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, LuaDeviceConfig, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct HueBridgeConfig {
|
pub struct HueBridgeConfig {
|
||||||
#[device_config(rename = "ip", with = "Ipv4SocketAddr<80>")]
|
pub ip: Ipv4Addr,
|
||||||
pub addr: SocketAddr,
|
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub flags: FlagIDs,
|
pub flags: FlagIDs,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for HueBridgeConfig {
|
impl DeviceConfig for HueBridgeConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
_ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = HueBridge {
|
let device = HueBridge {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
@ -63,8 +65,8 @@ impl HueBridge {
|
||||||
};
|
};
|
||||||
|
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"http://{}/api/{}/sensors/{flag_id}/state",
|
"http://{}:80/api/{}/sensors/{flag_id}/state",
|
||||||
self.config.addr, self.config.login
|
self.config.ip, self.config.login
|
||||||
);
|
);
|
||||||
|
|
||||||
trace!(?flag, flag_id, value, "Sending request to change flag");
|
trace!(?flag, flag_id, value, "Sending request to change flag");
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
use std::net::SocketAddr;
|
use std::net::Ipv4Addr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::{ConfigExternal, DeviceConfig};
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::OnMqtt;
|
use crate::event::OnMqtt;
|
||||||
use crate::helper::Ipv4SocketAddr;
|
|
||||||
use crate::messages::{RemoteAction, RemoteMessage};
|
use crate::messages::{RemoteAction, RemoteMessage};
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct HueGroupConfig {
|
pub struct HueGroupConfig {
|
||||||
#[device_config(rename = "ip", with = "Ipv4SocketAddr<80>")]
|
pub ip: Ipv4Addr,
|
||||||
pub addr: SocketAddr,
|
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub group_id: isize,
|
pub group_id: isize,
|
||||||
pub timer_id: isize,
|
pub timer_id: isize,
|
||||||
pub scene_id: String,
|
pub scene_id: String,
|
||||||
#[device_config(default)]
|
#[serde(default)]
|
||||||
pub remotes: Vec<MqttDeviceConfig>,
|
pub remotes: Vec<MqttDeviceConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for HueGroupConfig {
|
impl DeviceConfig for HueGroupConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
_ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = HueGroup {
|
let device = HueGroup {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
@ -52,7 +55,7 @@ pub struct HueGroup {
|
||||||
// Couple of helper function to get the correct urls
|
// Couple of helper function to get the correct urls
|
||||||
impl HueGroup {
|
impl HueGroup {
|
||||||
fn url_base(&self) -> String {
|
fn url_base(&self) -> String {
|
||||||
format!("http://{}/api/{}", self.config.addr, self.config.login)
|
format!("http://{}:80/api/{}", self.config.ip, self.config.login)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url_set_schedule(&self) -> String {
|
fn url_set_schedule(&self) -> String {
|
||||||
|
|
|
@ -2,24 +2,23 @@ use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::{self, OnOff};
|
use google_home::traits::{self, OnOff};
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
use google_home::{device, GoogleHomeDevice};
|
use google_home::{device, GoogleHomeDevice};
|
||||||
use rumqttc::{matches, Publish};
|
use rumqttc::{matches, AsyncClient, Publish};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
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::config::{InfoConfig, MqttDeviceConfig};
|
use crate::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use crate::device_manager::DeviceConfig;
|
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, OnPresence};
|
use crate::event::{OnMqtt, OnPresence};
|
||||||
use crate::helper::DurationSeconds;
|
|
||||||
use crate::messages::{OnOffMessage, RemoteAction, RemoteMessage};
|
use crate::messages::{OnOffMessage, RemoteAction, RemoteMessage};
|
||||||
use crate::mqtt::WrappedAsyncClient;
|
|
||||||
use crate::traits::Timeout;
|
use crate::traits::Timeout;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
||||||
|
@ -30,21 +29,19 @@ pub enum OutletType {
|
||||||
Light,
|
Light,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[serde_as]
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct IkeaOutletConfig {
|
pub struct IkeaOutletConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
info: InfoConfig,
|
info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
#[device_config(default = default_outlet_type)]
|
#[serde(default = "default_outlet_type")]
|
||||||
outlet_type: OutletType,
|
outlet_type: OutletType,
|
||||||
#[device_config(with = "Option<DurationSeconds>")]
|
#[serde_as(as = "Option<DurationSeconds>")]
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>, // Timeout in seconds
|
||||||
#[device_config(default)]
|
#[serde(default)]
|
||||||
pub remotes: Vec<MqttDeviceConfig>,
|
pub remotes: Vec<MqttDeviceConfig>,
|
||||||
|
|
||||||
#[device_config(user_data)]
|
|
||||||
client: WrappedAsyncClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_outlet_type() -> OutletType {
|
fn default_outlet_type() -> OutletType {
|
||||||
|
@ -53,7 +50,11 @@ fn default_outlet_type() -> OutletType {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for IkeaOutletConfig {
|
impl DeviceConfig for IkeaOutletConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(
|
trace!(
|
||||||
id = identifier,
|
id = identifier,
|
||||||
name = self.info.name,
|
name = self.info.name,
|
||||||
|
@ -64,6 +65,7 @@ impl DeviceConfig for IkeaOutletConfig {
|
||||||
let device = IkeaOutlet {
|
let device = IkeaOutlet {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
client: ext.client.clone(),
|
||||||
last_known_state: false,
|
last_known_state: false,
|
||||||
handle: None,
|
handle: None,
|
||||||
};
|
};
|
||||||
|
@ -78,11 +80,12 @@ pub struct IkeaOutlet {
|
||||||
#[config]
|
#[config]
|
||||||
config: IkeaOutletConfig,
|
config: IkeaOutletConfig,
|
||||||
|
|
||||||
|
client: AsyncClient,
|
||||||
last_known_state: bool,
|
last_known_state: bool,
|
||||||
handle: Option<JoinHandle<()>>,
|
handle: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_on(client: WrappedAsyncClient, topic: &str, on: bool) {
|
async fn set_on(client: AsyncClient, topic: &str, on: bool) {
|
||||||
let message = OnOffMessage::new(on);
|
let message = OnOffMessage::new(on);
|
||||||
|
|
||||||
let topic = format!("{}/set", topic);
|
let topic = format!("{}/set", topic);
|
||||||
|
@ -216,7 +219,7 @@ impl traits::OnOff for IkeaOutlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> {
|
async fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> {
|
||||||
set_on(self.config.client.clone(), &self.config.mqtt.topic, on).await;
|
set_on(self.client.clone(), &self.config.mqtt.topic, on).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -231,7 +234,7 @@ impl crate::traits::Timeout for IkeaOutlet {
|
||||||
// Turn the kettle of after the specified timeout
|
// Turn the kettle of after the specified timeout
|
||||||
// TODO: Impl Drop for IkeaOutlet that will abort the handle if the IkeaOutlet
|
// TODO: Impl Drop for IkeaOutlet that will abort the handle if the IkeaOutlet
|
||||||
// get dropped
|
// get dropped
|
||||||
let client = self.config.client.clone();
|
let client = self.client.clone();
|
||||||
let topic = self.config.mqtt.topic.clone();
|
let topic = self.config.mqtt.topic.clone();
|
||||||
let id = self.identifier.clone();
|
let id = self.identifier.clone();
|
||||||
self.handle = Some(tokio::spawn(async move {
|
self.handle = Some(tokio::spawn(async move {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::net::SocketAddr;
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use bytes::{Buf, BufMut};
|
use bytes::{Buf, BufMut};
|
||||||
use google_home::errors::{self, DeviceError};
|
use google_home::errors::{self, DeviceError};
|
||||||
use google_home::traits;
|
use google_home::traits;
|
||||||
|
@ -13,19 +13,21 @@ use tokio::net::TcpStream;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::{ConfigExternal, DeviceConfig};
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::helper::Ipv4SocketAddr;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct KasaOutletConfig {
|
pub struct KasaOutletConfig {
|
||||||
#[device_config(rename = "ip", with = "Ipv4SocketAddr<9999>")]
|
ip: Ipv4Addr,
|
||||||
addr: SocketAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for KasaOutletConfig {
|
impl DeviceConfig for KasaOutletConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
_ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(id = identifier, "Setting up KasaOutlet");
|
trace!(id = identifier, "Setting up KasaOutlet");
|
||||||
|
|
||||||
let device = KasaOutlet {
|
let device = KasaOutlet {
|
||||||
|
@ -214,7 +216,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 is_on(&self) -> Result<bool, errors::ErrorCode> {
|
||||||
let mut stream = TcpStream::connect(self.config.addr)
|
let mut stream = TcpStream::connect::<SocketAddr>((self.config.ip, 9999).into())
|
||||||
.await
|
.await
|
||||||
.or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
|
.or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
|
||||||
|
|
||||||
|
@ -248,7 +250,7 @@ impl traits::OnOff for KasaOutlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_on(&mut self, on: bool) -> Result<(), errors::ErrorCode> {
|
async fn set_on(&mut self, on: bool) -> Result<(), errors::ErrorCode> {
|
||||||
let mut stream = TcpStream::connect(self.config.addr)
|
let mut stream = TcpStream::connect::<SocketAddr>((self.config.ip, 9999).into())
|
||||||
.await
|
.await
|
||||||
.or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
|
.or::<DeviceError>(Err(DeviceError::DeviceOffline))?;
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,22 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::DeviceConfig;
|
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::{self, Event, EventChannel, OnMqtt};
|
use crate::event::{self, Event, OnMqtt};
|
||||||
use crate::messages::BrightnessMessage;
|
use crate::messages::BrightnessMessage;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct LightSensorConfig {
|
pub struct LightSensorConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
pub min: isize,
|
pub min: isize,
|
||||||
pub max: isize,
|
pub max: isize,
|
||||||
#[device_config(user_data)]
|
|
||||||
pub event_channel: EventChannel,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT: bool = false;
|
pub const DEFAULT: bool = false;
|
||||||
|
@ -26,11 +25,14 @@ pub const DEFAULT: bool = false;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for LightSensorConfig {
|
impl DeviceConfig for LightSensorConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = LightSensor {
|
let device = LightSensor {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
// Add helper type that does this conversion for us
|
tx: ext.event_channel.get_tx(),
|
||||||
tx: self.event_channel.get_tx(),
|
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
is_dark: DEFAULT,
|
is_dark: DEFAULT,
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub use self::hue_bridge::*;
|
||||||
pub use self::hue_light::*;
|
pub use self::hue_light::*;
|
||||||
pub use self::ikea_outlet::*;
|
pub use self::ikea_outlet::*;
|
||||||
pub use self::kasa_outlet::*;
|
pub use self::kasa_outlet::*;
|
||||||
pub use self::light_sensor::*;
|
pub use self::light_sensor::{LightSensor, LightSensorConfig};
|
||||||
pub use self::ntfy::{Notification, Ntfy};
|
pub use self::ntfy::{Notification, Ntfy};
|
||||||
pub use self::presence::{Presence, PresenceConfig, DEFAULT_PRESENCE};
|
pub use self::presence::{Presence, PresenceConfig, DEFAULT_PRESENCE};
|
||||||
pub use self::wake_on_lan::*;
|
pub use self::wake_on_lan::*;
|
||||||
|
|
|
@ -1,30 +1,31 @@
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use eui48::MacAddress;
|
use eui48::MacAddress;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::{self, Scene};
|
use google_home::traits::{self, Scene};
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
use google_home::{device, GoogleHomeDevice};
|
use google_home::{device, GoogleHomeDevice};
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
use super::Device;
|
use super::Device;
|
||||||
use crate::config::{InfoConfig, MqttDeviceConfig};
|
use crate::config::{InfoConfig, MqttDeviceConfig};
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::{ConfigExternal, DeviceConfig};
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::OnMqtt;
|
use crate::event::OnMqtt;
|
||||||
use crate::messages::ActivateMessage;
|
use crate::messages::ActivateMessage;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct WakeOnLANConfig {
|
pub struct WakeOnLANConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
info: InfoConfig,
|
info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
mac_address: MacAddress,
|
mac_address: MacAddress,
|
||||||
#[device_config(default = default_broadcast_ip)]
|
#[serde(default = "default_broadcast_ip")]
|
||||||
broadcast_ip: Ipv4Addr,
|
broadcast_ip: Ipv4Addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +35,11 @@ fn default_broadcast_ip() -> Ipv4Addr {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for WakeOnLANConfig {
|
impl DeviceConfig for WakeOnLANConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
_ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
trace!(
|
trace!(
|
||||||
id = identifier,
|
id = identifier,
|
||||||
name = self.info.name,
|
name = self.info.name,
|
||||||
|
@ -42,8 +47,6 @@ impl DeviceConfig for WakeOnLANConfig {
|
||||||
"Setting up WakeOnLAN"
|
"Setting up WakeOnLAN"
|
||||||
);
|
);
|
||||||
|
|
||||||
debug!("broadcast_ip = {}", self.broadcast_ip);
|
|
||||||
|
|
||||||
let device = WakeOnLAN {
|
let device = WakeOnLAN {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_macro::{LuaDevice, LuaDeviceConfig};
|
use automation_macro::LuaDevice;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
|
use serde::Deserialize;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
use super::ntfy::Priority;
|
use super::ntfy::Priority;
|
||||||
use super::{Device, Notification};
|
use super::{Device, Notification};
|
||||||
use crate::config::MqttDeviceConfig;
|
use crate::config::MqttDeviceConfig;
|
||||||
use crate::device_manager::DeviceConfig;
|
use crate::device_manager::{ConfigExternal, DeviceConfig};
|
||||||
use crate::error::DeviceConfigError;
|
use crate::error::DeviceConfigError;
|
||||||
use crate::event::{Event, EventChannel, OnMqtt};
|
use crate::event::{Event, EventChannel, OnMqtt};
|
||||||
use crate::messages::PowerMessage;
|
use crate::messages::PowerMessage;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct WasherConfig {
|
pub struct WasherConfig {
|
||||||
#[device_config(flatten)]
|
#[serde(flatten)]
|
||||||
mqtt: MqttDeviceConfig,
|
mqtt: MqttDeviceConfig,
|
||||||
// Power in Watt
|
threshold: f32, // Power in Watt
|
||||||
threshold: f32,
|
|
||||||
#[device_config(user_data)]
|
|
||||||
event_channel: EventChannel,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DeviceConfig for WasherConfig {
|
impl DeviceConfig for WasherConfig {
|
||||||
async fn create(&self, identifier: &str) -> Result<Box<dyn Device>, DeviceConfigError> {
|
async fn create(
|
||||||
|
&self,
|
||||||
|
identifier: &str,
|
||||||
|
ext: &ConfigExternal,
|
||||||
|
) -> Result<Box<dyn Device>, DeviceConfigError> {
|
||||||
let device = Washer {
|
let device = Washer {
|
||||||
identifier: identifier.into(),
|
identifier: identifier.into(),
|
||||||
config: self.clone(),
|
config: self.clone(),
|
||||||
|
event_channel: ext.event_channel.clone(),
|
||||||
running: 0,
|
running: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -42,6 +45,7 @@ pub struct Washer {
|
||||||
#[config]
|
#[config]
|
||||||
config: WasherConfig,
|
config: WasherConfig,
|
||||||
|
|
||||||
|
event_channel: EventChannel,
|
||||||
running: isize,
|
running: isize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +94,6 @@ impl OnMqtt for Washer {
|
||||||
.set_priority(Priority::High);
|
.set_priority(Priority::High);
|
||||||
|
|
||||||
if self
|
if self
|
||||||
.config
|
|
||||||
.event_channel
|
.event_channel
|
||||||
.get_tx()
|
.get_tx()
|
||||||
.send(Event::Ntfy(notification))
|
.send(Event::Ntfy(notification))
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use impl_cast::device_trait;
|
use impl_cast::device_trait;
|
||||||
use mlua::FromLua;
|
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
@ -17,7 +16,7 @@ pub enum Event {
|
||||||
pub type Sender = mpsc::Sender<Event>;
|
pub type Sender = mpsc::Sender<Event>;
|
||||||
pub type Receiver = mpsc::Receiver<Event>;
|
pub type Receiver = mpsc::Receiver<Event>;
|
||||||
|
|
||||||
#[derive(Clone, Debug, FromLua)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EventChannel(Sender);
|
pub struct EventChannel(Sender);
|
||||||
|
|
||||||
impl EventChannel {
|
impl EventChannel {
|
||||||
|
@ -32,8 +31,6 @@ impl EventChannel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl mlua::UserData for EventChannel {}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[device_trait]
|
#[device_trait]
|
||||||
pub trait OnMqtt {
|
pub trait OnMqtt {
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
use std::net::{Ipv4Addr, SocketAddr};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct DurationSeconds(u64);
|
|
||||||
|
|
||||||
impl From<DurationSeconds> for Duration {
|
|
||||||
fn from(value: DurationSeconds) -> Self {
|
|
||||||
Self::from_secs(value.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Ipv4SocketAddr<const PORT: u16>(Ipv4Addr);
|
|
||||||
|
|
||||||
impl<const PORT: u16> From<Ipv4SocketAddr<PORT>> for SocketAddr {
|
|
||||||
fn from(ip: Ipv4SocketAddr<PORT>) -> Self {
|
|
||||||
Self::from((ip.0, PORT))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ pub mod device_manager;
|
||||||
pub mod devices;
|
pub mod devices;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod helper;
|
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod mqtt;
|
pub mod mqtt;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use automation::devices::{
|
||||||
LightSensor, Ntfy, Presence, WakeOnLAN, Washer,
|
LightSensor, Ntfy, Presence, WakeOnLAN, Washer,
|
||||||
};
|
};
|
||||||
use automation::error::ApiError;
|
use automation::error::ApiError;
|
||||||
use automation::mqtt::{self, WrappedAsyncClient};
|
use automation::mqtt;
|
||||||
use axum::extract::FromRef;
|
use axum::extract::FromRef;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
|
@ -84,8 +84,6 @@ async fn app() -> anyhow::Result<()> {
|
||||||
let automation = lua.create_table()?;
|
let automation = lua.create_table()?;
|
||||||
|
|
||||||
automation.set("device_manager", device_manager.clone())?;
|
automation.set("device_manager", device_manager.clone())?;
|
||||||
automation.set("mqtt_client", WrappedAsyncClient(client.clone()))?;
|
|
||||||
automation.set("event_channel", device_manager.event_channel())?;
|
|
||||||
|
|
||||||
let util = lua.create_table()?;
|
let util = lua.create_table()?;
|
||||||
let get_env = lua.create_function(|_lua, name: String| {
|
let get_env = lua.create_function(|_lua, name: String| {
|
||||||
|
|
24
src/mqtt.rs
24
src/mqtt.rs
|
@ -1,30 +1,8 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use rumqttc::{Event, EventLoop, Incoming};
|
||||||
|
|
||||||
use mlua::FromLua;
|
|
||||||
use rumqttc::{AsyncClient, Event, EventLoop, Incoming};
|
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::event::{self, EventChannel};
|
use crate::event::{self, EventChannel};
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromLua)]
|
|
||||||
pub struct WrappedAsyncClient(pub AsyncClient);
|
|
||||||
|
|
||||||
impl Deref for WrappedAsyncClient {
|
|
||||||
type Target = AsyncClient;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for WrappedAsyncClient {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl mlua::UserData for WrappedAsyncClient {}
|
|
||||||
|
|
||||||
pub fn start(mut eventloop: EventLoop, event_channel: &EventChannel) {
|
pub fn start(mut eventloop: EventLoop, event_channel: &EventChannel) {
|
||||||
let tx = event_channel.get_tx();
|
let tx = event_channel.get_tx();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user