Compare commits
6 Commits
feature/im
...
686801bd36
| Author | SHA1 | Date | |
|---|---|---|---|
|
686801bd36
|
|||
|
8ce9c01228
|
|||
|
56d37a3570
|
|||
|
5731300153
|
|||
|
47816504e3
|
|||
|
9fa2db8fe6
|
38
Cargo.lock
generated
38
Cargo.lock
generated
@@ -128,6 +128,7 @@ dependencies = [
|
|||||||
"eui48",
|
"eui48",
|
||||||
"google_home",
|
"google_home",
|
||||||
"inventory",
|
"inventory",
|
||||||
|
"lua_typed",
|
||||||
"mlua",
|
"mlua",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rumqttc",
|
"rumqttc",
|
||||||
@@ -153,6 +154,7 @@ dependencies = [
|
|||||||
"hostname",
|
"hostname",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"inventory",
|
"inventory",
|
||||||
|
"lua_typed",
|
||||||
"mlua",
|
"mlua",
|
||||||
"rumqttc",
|
"rumqttc",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -345,6 +347,15 @@ dependencies = [
|
|||||||
"winnow",
|
"winnow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -1094,6 +1105,27 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lua_typed"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b"
|
||||||
|
dependencies = [
|
||||||
|
"eui48",
|
||||||
|
"lua_typed_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lua_typed_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"itertools",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "luajit-src"
|
name = "luajit-src"
|
||||||
version = "210.6.1+f9140a6"
|
version = "210.6.1+f9140a6"
|
||||||
@@ -2256,6 +2288,12 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ indexmap = { version = "2.11.0", features = ["serde"] }
|
|||||||
inventory = "0.3.21"
|
inventory = "0.3.21"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
json_value_merge = "2.0.1"
|
json_value_merge = "2.0.1"
|
||||||
|
lua_typed = { git = "https://git.huizinga.dev/Dreaded_X/lua_typed" }
|
||||||
mlua = { version = "0.11.3", features = [
|
mlua = { version = "0.11.3", features = [
|
||||||
"lua54",
|
"lua54",
|
||||||
"vendored",
|
"vendored",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ dyn-clone = { workspace = true }
|
|||||||
eui48 = { workspace = true }
|
eui48 = { workspace = true }
|
||||||
google_home = { workspace = true }
|
google_home = { workspace = true }
|
||||||
inventory = { workspace = true }
|
inventory = { workspace = true }
|
||||||
|
lua_typed = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
rumqttc = { workspace = true }
|
rumqttc = { workspace = true }
|
||||||
|
|||||||
@@ -9,15 +9,19 @@ use google_home::traits::{
|
|||||||
TemperatureUnit,
|
TemperatureUnit,
|
||||||
};
|
};
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
|
use lua_typed::Typed;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "AirFilterConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(traits(OnOff))]
|
#[device(traits(OnOff))]
|
||||||
|
|||||||
@@ -13,35 +13,45 @@ use google_home::device;
|
|||||||
use google_home::errors::{DeviceError, ErrorCode};
|
use google_home::errors::{DeviceError, ErrorCode};
|
||||||
use google_home::traits::OpenClose;
|
use google_home::traits::OpenClose;
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
|
use lua_typed::Typed;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy, Typed)]
|
||||||
pub enum SensorType {
|
pub enum SensorType {
|
||||||
Door,
|
Door,
|
||||||
Drawer,
|
Drawer,
|
||||||
Window,
|
Window,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(SensorType);
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "ContactSensorConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
|
||||||
#[device_config(default(SensorType::Window))]
|
#[device_config(default(SensorType::Window))]
|
||||||
|
#[typed(default)]
|
||||||
pub sensor_type: SensorType,
|
pub sensor_type: SensorType,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub callback: ActionCallback<(ContactSensor, bool)>,
|
pub callback: ActionCallback<(ContactSensor, bool)>,
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub battery_callback: ActionCallback<(ContactSensor, f32)>,
|
pub battery_callback: ActionCallback<(ContactSensor, f32)>,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
|
#[typed(default)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct State {
|
struct State {
|
||||||
|
|||||||
@@ -3,40 +3,72 @@ use std::net::SocketAddr;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
|
use automation_lib::lua::traits::PartialUserData;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Typed)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[typed(rename_all = "snake_case")]
|
||||||
pub enum Flag {
|
pub enum Flag {
|
||||||
Presence,
|
Presence,
|
||||||
Darkness,
|
Darkness,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Flag);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, Typed)]
|
||||||
pub struct FlagIDs {
|
pub struct FlagIDs {
|
||||||
presence: isize,
|
presence: isize,
|
||||||
darkness: isize,
|
darkness: isize,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(FlagIDs);
|
||||||
|
|
||||||
#[derive(Debug, LuaDeviceConfig, Clone)]
|
#[derive(Debug, LuaDeviceConfig, Clone, Typed)]
|
||||||
|
#[typed(as = "HueBridgeConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
|
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
|
||||||
|
#[typed(as = "ip")]
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub flags: FlagIDs,
|
pub flags: FlagIDs,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(add_methods(Self::add_methods))]
|
#[device(extra_user_data = SetFlag)]
|
||||||
pub struct HueBridge {
|
pub struct HueBridge {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
crate::register_device!(HueBridge);
|
crate::register_device!(HueBridge);
|
||||||
|
|
||||||
|
struct SetFlag;
|
||||||
|
impl PartialUserData<HueBridge> for SetFlag {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<HueBridge>>(methods: &mut M) {
|
||||||
|
methods.add_async_method(
|
||||||
|
"set_flag",
|
||||||
|
async |lua, this, (flag, value): (mlua::Value, bool)| {
|
||||||
|
let flag: Flag = lua.from_value(flag)?;
|
||||||
|
|
||||||
|
this.set_flag(flag, value).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definitions() -> Option<String> {
|
||||||
|
Some(format!(
|
||||||
|
"---@async\n---@param flag {}\n---@param value boolean\nfunction {}:set_flag(flag, value) end\n",
|
||||||
|
<Flag as Typed>::type_name(),
|
||||||
|
<HueBridge as Typed>::type_name(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct FlagMessage {
|
struct FlagMessage {
|
||||||
flag: bool,
|
flag: bool,
|
||||||
@@ -84,19 +116,6 @@ impl HueBridge {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_async_method(
|
|
||||||
"set_flag",
|
|
||||||
async |lua, this, (flag, value): (mlua::Value, bool)| {
|
|
||||||
let flag: Flag = lua.from_value(flag)?;
|
|
||||||
|
|
||||||
this.set_flag(flag, value).await;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for HueBridge {
|
impl Device for HueBridge {
|
||||||
|
|||||||
@@ -5,19 +5,23 @@ use async_trait::async_trait;
|
|||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
|
use lua_typed::Typed;
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
|
|
||||||
use super::{Device, LuaDeviceCreate};
|
use super::{Device, LuaDeviceCreate};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "HueGroupConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
|
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 80)))]
|
||||||
|
#[typed(as = "ip")]
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub group_id: isize,
|
pub group_id: isize,
|
||||||
pub scene_id: String,
|
pub scene_id: String,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(traits(OnOff))]
|
#[device(traits(OnOff))]
|
||||||
|
|||||||
@@ -5,36 +5,46 @@ use automation_lib::device::{Device, LuaDeviceCreate};
|
|||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::{Publish, matches};
|
use rumqttc::{Publish, matches};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "HueSwitchConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
|
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub left_callback: ActionCallback<HueSwitch>,
|
pub left_callback: ActionCallback<HueSwitch>,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub right_callback: ActionCallback<HueSwitch>,
|
pub right_callback: ActionCallback<HueSwitch>,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub left_hold_callback: ActionCallback<HueSwitch>,
|
pub left_hold_callback: ActionCallback<HueSwitch>,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub right_hold_callback: ActionCallback<HueSwitch>,
|
pub right_hold_callback: ActionCallback<HueSwitch>,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub battery_callback: ActionCallback<(HueSwitch, f32)>,
|
pub battery_callback: ActionCallback<(HueSwitch, f32)>,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Deserialize)]
|
#[derive(Debug, Copy, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
|||||||
@@ -6,28 +6,36 @@ use automation_lib::event::OnMqtt;
|
|||||||
use automation_lib::messages::{RemoteAction, RemoteMessage};
|
use automation_lib::messages::{RemoteAction, RemoteMessage};
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::{Publish, matches};
|
use rumqttc::{Publish, matches};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "IkeaRemoteConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
|
|
||||||
#[device_config(default)]
|
#[device_config(default)]
|
||||||
|
#[typed(default)]
|
||||||
pub single_button: bool,
|
pub single_button: bool,
|
||||||
|
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub callback: ActionCallback<(IkeaRemote, bool)>,
|
pub callback: ActionCallback<(IkeaRemote, bool)>,
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub battery_callback: ActionCallback<(IkeaRemote, f32)>,
|
pub battery_callback: ActionCallback<(IkeaRemote, f32)>,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct IkeaRemote {
|
pub struct IkeaRemote {
|
||||||
|
|||||||
@@ -8,18 +8,22 @@ use automation_macro::{Device, LuaDeviceConfig};
|
|||||||
use bytes::{Buf, BufMut};
|
use bytes::{Buf, BufMut};
|
||||||
use google_home::errors::{self, DeviceError};
|
use google_home::errors::{self, DeviceError};
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
|
use lua_typed::Typed;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "KasaOutletConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))]
|
#[device_config(rename("ip"), with(|ip| SocketAddr::new(ip, 9999)))]
|
||||||
|
#[typed(as = "ip")]
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(traits(OnOff))]
|
#[device(traits(OnOff))]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![feature(iter_intersperse)]
|
||||||
mod air_filter;
|
mod air_filter;
|
||||||
mod contact_sensor;
|
mod contact_sensor;
|
||||||
mod hue_bridge;
|
mod hue_bridge;
|
||||||
@@ -22,20 +23,22 @@ macro_rules! register_device {
|
|||||||
stringify!($device),
|
stringify!($device),
|
||||||
::mlua::Lua::create_proxy::<$device>
|
::mlua::Lua::create_proxy::<$device>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
crate::register_type!($device);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use register_device;
|
pub(crate) use register_device;
|
||||||
|
|
||||||
type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
|
type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
|
||||||
|
|
||||||
pub struct RegisteredDevice {
|
pub struct RegisteredDevice {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
register_fn: RegisterFn,
|
register_fn: RegisterDeviceFn,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegisteredDevice {
|
impl RegisteredDevice {
|
||||||
pub const fn new(name: &'static str, register_fn: RegisterFn) -> Self {
|
pub const fn new(name: &'static str, register_fn: RegisterDeviceFn) -> Self {
|
||||||
Self { name, register_fn }
|
Self { name, register_fn }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,3 +67,28 @@ pub fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inventory::submit! {Module::new("devices", create_module)}
|
inventory::submit! {Module::new("devices", create_module)}
|
||||||
|
|
||||||
|
macro_rules! register_type {
|
||||||
|
($ty:ty) => {
|
||||||
|
::inventory::submit!(crate::RegisteredType(
|
||||||
|
<$ty as ::lua_typed::Typed>::generate_full
|
||||||
|
));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use register_type;
|
||||||
|
|
||||||
|
type RegisterTypeFn = fn() -> Option<String>;
|
||||||
|
|
||||||
|
pub struct RegisteredType(RegisterTypeFn);
|
||||||
|
|
||||||
|
inventory::collect!(RegisteredType);
|
||||||
|
|
||||||
|
pub fn generate_definitions() {
|
||||||
|
println!("---@meta\n\nlocal devices\n");
|
||||||
|
for ty in inventory::iter::<RegisteredType> {
|
||||||
|
let def = ty.0().unwrap();
|
||||||
|
println!("{def}");
|
||||||
|
}
|
||||||
|
println!("return devices")
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,24 +8,29 @@ use automation_lib::event::OnMqtt;
|
|||||||
use automation_lib::messages::BrightnessMessage;
|
use automation_lib::messages::BrightnessMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "LightSensorConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
pub min: isize,
|
pub min: isize,
|
||||||
pub max: isize,
|
pub max: isize,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub callback: ActionCallback<(LightSensor, bool)>,
|
pub callback: ActionCallback<(LightSensor, bool)>,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
const DEFAULT: bool = false;
|
const DEFAULT: bool = false;
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ use std::convert::Infallible;
|
|||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
|
use automation_lib::lua::traits::PartialUserData;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_repr::*;
|
use serde_repr::*;
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
|
|
||||||
#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy)]
|
#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[typed(rename_all = "snake_case")]
|
||||||
pub enum Priority {
|
pub enum Priority {
|
||||||
Min = 1,
|
Min = 1,
|
||||||
Low,
|
Low,
|
||||||
@@ -19,44 +22,54 @@ pub enum Priority {
|
|||||||
High,
|
High,
|
||||||
Max,
|
Max,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Priority);
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Typed)]
|
||||||
#[serde(rename_all = "snake_case", tag = "action")]
|
#[serde(rename_all = "snake_case", tag = "action")]
|
||||||
|
#[typed(rename_all = "snake_case", tag = "action")]
|
||||||
pub enum ActionType {
|
pub enum ActionType {
|
||||||
Broadcast {
|
Broadcast {
|
||||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
||||||
|
#[serde(default)]
|
||||||
|
#[typed(default)]
|
||||||
extras: HashMap<String, String>,
|
extras: HashMap<String, String>,
|
||||||
},
|
},
|
||||||
// View,
|
// View,
|
||||||
// Http
|
// Http
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Typed)]
|
||||||
pub struct Action {
|
pub struct Action {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub action: ActionType,
|
pub action: ActionType,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub clear: Option<bool>,
|
pub clear: Option<bool>,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Action);
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Typed)]
|
||||||
struct NotificationFinal {
|
struct NotificationFinal {
|
||||||
topic: String,
|
topic: String,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
inner: Notification,
|
inner: Notification,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone, Deserialize)]
|
#[derive(Debug, Serialize, Clone, Deserialize, Typed)]
|
||||||
pub struct Notification {
|
pub struct Notification {
|
||||||
title: String,
|
title: String,
|
||||||
message: Option<String>,
|
message: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
|
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
|
||||||
|
#[typed(default)]
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
priority: Option<Priority>,
|
priority: Option<Priority>,
|
||||||
|
#[typed(default)]
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
|
#[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")]
|
||||||
actions: Vec<Action>,
|
actions: Vec<Action>,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Notification);
|
||||||
|
|
||||||
impl Notification {
|
impl Notification {
|
||||||
fn finalize(self, topic: &str) -> NotificationFinal {
|
fn finalize(self, topic: &str) -> NotificationFinal {
|
||||||
@@ -67,22 +80,26 @@ impl Notification {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "NtfyConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(default("https://ntfy.sh".into()))]
|
#[device_config(default("https://ntfy.sh".into()))]
|
||||||
|
#[typed(default)]
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub topic: String,
|
pub topic: String,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(add_methods(Self::add_methods))]
|
#[device(extra_user_data = SendNotification)]
|
||||||
pub struct Ntfy {
|
pub struct Ntfy {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
crate::register_device!(Ntfy);
|
crate::register_device!(Ntfy);
|
||||||
|
|
||||||
impl Ntfy {
|
struct SendNotification;
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
impl PartialUserData<Ntfy> for SendNotification {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Ntfy>>(methods: &mut M) {
|
||||||
methods.add_async_method(
|
methods.add_async_method(
|
||||||
"send_notification",
|
"send_notification",
|
||||||
async |lua, this, notification: mlua::Value| {
|
async |lua, this, notification: mlua::Value| {
|
||||||
@@ -94,6 +111,14 @@ impl Ntfy {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn definitions() -> Option<String> {
|
||||||
|
Some(format!(
|
||||||
|
"---@async\n---@param notification {}\nfunction {}:send_notification(notification) end\n",
|
||||||
|
<Notification as Typed>::type_name(),
|
||||||
|
<Ntfy as Typed>::type_name(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -6,24 +6,30 @@ use automation_lib::action_callback::ActionCallback;
|
|||||||
use automation_lib::config::MqttDeviceConfig;
|
use automation_lib::config::MqttDeviceConfig;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::event::OnMqtt;
|
use automation_lib::event::OnMqtt;
|
||||||
|
use automation_lib::lua::traits::PartialUserData;
|
||||||
use automation_lib::messages::PresenceMessage;
|
use automation_lib::messages::PresenceMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "PresenceConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub callback: ActionCallback<(Presence, bool)>,
|
pub callback: ActionCallback<(Presence, bool)>,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
pub const DEFAULT_PRESENCE: bool = false;
|
pub const DEFAULT_PRESENCE: bool = false;
|
||||||
|
|
||||||
@@ -34,13 +40,29 @@ pub struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(add_methods(Self::add_methods))]
|
#[device(extra_user_data = OverallPresence)]
|
||||||
pub struct Presence {
|
pub struct Presence {
|
||||||
config: Config,
|
config: Config,
|
||||||
state: Arc<RwLock<State>>,
|
state: Arc<RwLock<State>>,
|
||||||
}
|
}
|
||||||
crate::register_device!(Presence);
|
crate::register_device!(Presence);
|
||||||
|
|
||||||
|
struct OverallPresence;
|
||||||
|
impl PartialUserData<Presence> for OverallPresence {
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<Presence>>(methods: &mut M) {
|
||||||
|
methods.add_async_method("overall_presence", async |_lua, this, ()| {
|
||||||
|
Ok(this.state().await.current_overall_presence)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn definitions() -> Option<String> {
|
||||||
|
Some(format!(
|
||||||
|
"---@async\n---@return boolean\nfunction {}:overall_presence() end\n",
|
||||||
|
<Presence as Typed>::type_name(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Presence {
|
impl Presence {
|
||||||
async fn state(&self) -> RwLockReadGuard<'_, State> {
|
async fn state(&self) -> RwLockReadGuard<'_, State> {
|
||||||
self.state.read().await
|
self.state.read().await
|
||||||
@@ -49,12 +71,6 @@ impl Presence {
|
|||||||
async fn state_mut(&self) -> RwLockWriteGuard<'_, State> {
|
async fn state_mut(&self) -> RwLockWriteGuard<'_, State> {
|
||||||
self.state.write().await
|
self.state.write().await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
|
||||||
methods.add_async_method("overall_presence", async |_lua, this, ()| {
|
|
||||||
Ok(this.state().await.current_overall_presence)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -12,21 +12,27 @@ use google_home::device;
|
|||||||
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 lua_typed::Typed;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "WolConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
pub mac_address: MacAddress,
|
pub mac_address: MacAddress,
|
||||||
#[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))]
|
#[device_config(default(Ipv4Addr::new(255, 255, 255, 255)))]
|
||||||
|
#[typed(default)]
|
||||||
pub broadcast_ip: Ipv4Addr,
|
pub broadcast_ip: Ipv4Addr,
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
pub struct WakeOnLAN {
|
pub struct WakeOnLAN {
|
||||||
|
|||||||
@@ -8,24 +8,29 @@ use automation_lib::event::OnMqtt;
|
|||||||
use automation_lib::messages::PowerMessage;
|
use automation_lib::messages::PowerMessage;
|
||||||
use automation_lib::mqtt::WrappedAsyncClient;
|
use automation_lib::mqtt::WrappedAsyncClient;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::Publish;
|
use rumqttc::Publish;
|
||||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "WasherConfig")]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
// Power in Watt
|
// Power in Watt
|
||||||
pub threshold: f32,
|
pub threshold: f32,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub done_callback: ActionCallback<Washer>,
|
pub done_callback: ActionCallback<Washer>,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use google_home::device;
|
|||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff};
|
use google_home::traits::{Brightness, Color, ColorSetting, ColorTemperatureRange, OnOff};
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::{Publish, matches};
|
use rumqttc::{Publish, matches};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -22,33 +23,47 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
pub trait LightState:
|
pub trait LightState:
|
||||||
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + 'static
|
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + Typed + 'static
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
pub struct Config<T: LightState> {
|
#[typed(as = "ConfigLight")]
|
||||||
|
pub struct Config<T: LightState>
|
||||||
|
where
|
||||||
|
Light<T>: Typed,
|
||||||
|
{
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub callback: ActionCallback<(Light<T>, T)>,
|
pub callback: ActionCallback<(Light<T>, T)>,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
|
#[typed(default)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config<StateOnOff>);
|
||||||
|
crate::register_type!(Config<StateBrightness>);
|
||||||
|
crate::register_type!(Config<StateColorTemperature>);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
|
||||||
|
#[typed(as = "LightStateOnOff")]
|
||||||
pub struct StateOnOff {
|
pub struct StateOnOff {
|
||||||
#[serde(deserialize_with = "state_deserializer")]
|
#[serde(deserialize_with = "state_deserializer")]
|
||||||
state: bool,
|
state: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LightState for StateOnOff {}
|
impl LightState for StateOnOff {}
|
||||||
|
crate::register_type!(StateOnOff);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
|
||||||
|
#[typed(as = "LightStateBrightness")]
|
||||||
pub struct StateBrightness {
|
pub struct StateBrightness {
|
||||||
#[serde(deserialize_with = "state_deserializer")]
|
#[serde(deserialize_with = "state_deserializer")]
|
||||||
state: bool,
|
state: bool,
|
||||||
@@ -56,6 +71,7 @@ pub struct StateBrightness {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LightState for StateBrightness {}
|
impl LightState for StateBrightness {}
|
||||||
|
crate::register_type!(StateBrightness);
|
||||||
|
|
||||||
impl From<StateBrightness> for StateOnOff {
|
impl From<StateBrightness> for StateOnOff {
|
||||||
fn from(state: StateBrightness) -> Self {
|
fn from(state: StateBrightness) -> Self {
|
||||||
@@ -63,13 +79,15 @@ impl From<StateBrightness> for StateOnOff {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
|
||||||
|
#[typed(as = "LightStateColorTemperature")]
|
||||||
pub struct StateColorTemperature {
|
pub struct StateColorTemperature {
|
||||||
#[serde(deserialize_with = "state_deserializer")]
|
#[serde(deserialize_with = "state_deserializer")]
|
||||||
state: bool,
|
state: bool,
|
||||||
brightness: f32,
|
brightness: f32,
|
||||||
color_temp: u32,
|
color_temp: u32,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(StateColorTemperature);
|
||||||
|
|
||||||
impl LightState for StateColorTemperature {}
|
impl LightState for StateColorTemperature {}
|
||||||
|
|
||||||
@@ -92,7 +110,10 @@ impl From<StateColorTemperature> for StateBrightness {
|
|||||||
#[device(traits(OnOff for LightOnOff, LightBrightness, LightColorTemperature))]
|
#[device(traits(OnOff for LightOnOff, LightBrightness, LightColorTemperature))]
|
||||||
#[device(traits(Brightness for LightBrightness, LightColorTemperature))]
|
#[device(traits(Brightness for LightBrightness, LightColorTemperature))]
|
||||||
#[device(traits(ColorSetting for LightColorTemperature))]
|
#[device(traits(ColorSetting for LightColorTemperature))]
|
||||||
pub struct Light<T: LightState> {
|
pub struct Light<T: LightState>
|
||||||
|
where
|
||||||
|
Light<T>: Typed,
|
||||||
|
{
|
||||||
config: Config<T>,
|
config: Config<T>,
|
||||||
|
|
||||||
state: Arc<RwLock<T>>,
|
state: Arc<RwLock<T>>,
|
||||||
@@ -107,7 +128,10 @@ crate::register_device!(LightBrightness);
|
|||||||
pub type LightColorTemperature = Light<StateColorTemperature>;
|
pub type LightColorTemperature = Light<StateColorTemperature>;
|
||||||
crate::register_device!(LightColorTemperature);
|
crate::register_device!(LightColorTemperature);
|
||||||
|
|
||||||
impl<T: LightState> Light<T> {
|
impl<T: LightState> Light<T>
|
||||||
|
where
|
||||||
|
Light<T>: Typed,
|
||||||
|
{
|
||||||
async fn state(&self) -> RwLockReadGuard<'_, T> {
|
async fn state(&self) -> RwLockReadGuard<'_, T> {
|
||||||
self.state.read().await
|
self.state.read().await
|
||||||
}
|
}
|
||||||
@@ -118,7 +142,10 @@ impl<T: LightState> Light<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: LightState> LuaDeviceCreate for Light<T> {
|
impl<T: LightState> LuaDeviceCreate for Light<T>
|
||||||
|
where
|
||||||
|
Light<T>: Typed,
|
||||||
|
{
|
||||||
type Config = Config<T>;
|
type Config = Config<T>;
|
||||||
type Error = rumqttc::ClientError;
|
type Error = rumqttc::ClientError;
|
||||||
|
|
||||||
@@ -137,7 +164,10 @@ impl<T: LightState> LuaDeviceCreate for Light<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: LightState> Device for Light<T> {
|
impl<T: LightState> Device for Light<T>
|
||||||
|
where
|
||||||
|
Light<T>: Typed,
|
||||||
|
{
|
||||||
fn get_id(&self) -> String {
|
fn get_id(&self) -> String {
|
||||||
self.config.info.identifier()
|
self.config.info.identifier()
|
||||||
}
|
}
|
||||||
@@ -257,7 +287,10 @@ impl OnMqtt for LightColorTemperature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: LightState> google_home::Device for Light<T> {
|
impl<T: LightState> google_home::Device for Light<T>
|
||||||
|
where
|
||||||
|
Light<T>: Typed,
|
||||||
|
{
|
||||||
fn get_device_type(&self) -> Type {
|
fn get_device_type(&self) -> Type {
|
||||||
Type::Light
|
Type::Light
|
||||||
}
|
}
|
||||||
@@ -288,6 +321,7 @@ impl<T: LightState> google_home::Device for Light<T> {
|
|||||||
impl<T> OnOff for Light<T>
|
impl<T> OnOff for Light<T>
|
||||||
where
|
where
|
||||||
T: LightState,
|
T: LightState,
|
||||||
|
Light<T>: Typed,
|
||||||
{
|
{
|
||||||
async fn on(&self) -> Result<bool, ErrorCode> {
|
async fn on(&self) -> Result<bool, ErrorCode> {
|
||||||
let state = self.state().await;
|
let state = self.state().await;
|
||||||
@@ -327,6 +361,7 @@ impl<T> Brightness for Light<T>
|
|||||||
where
|
where
|
||||||
T: LightState,
|
T: LightState,
|
||||||
T: Into<StateBrightness>,
|
T: Into<StateBrightness>,
|
||||||
|
Light<T>: Typed,
|
||||||
{
|
{
|
||||||
async fn brightness(&self) -> Result<u8, ErrorCode> {
|
async fn brightness(&self) -> Result<u8, ErrorCode> {
|
||||||
let state = self.state().await;
|
let state = self.state().await;
|
||||||
@@ -368,6 +403,7 @@ impl<T> ColorSetting for Light<T>
|
|||||||
where
|
where
|
||||||
T: LightState,
|
T: LightState,
|
||||||
T: Into<StateColorTemperature>,
|
T: Into<StateColorTemperature>,
|
||||||
|
Light<T>: Typed,
|
||||||
{
|
{
|
||||||
fn color_temperature_range(&self) -> ColorTemperatureRange {
|
fn color_temperature_range(&self) -> ColorTemperatureRange {
|
||||||
ColorTemperatureRange {
|
ColorTemperatureRange {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use google_home::device;
|
|||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
use google_home::types::Type;
|
use google_home::types::Type;
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::{Publish, matches};
|
use rumqttc::{Publish, matches};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -22,15 +23,16 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|||||||
use tracing::{debug, trace, warn};
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
pub trait OutletState:
|
pub trait OutletState:
|
||||||
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + 'static
|
Debug + Clone + Default + Sync + Send + Serialize + Into<StateOnOff> + Typed + 'static
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Copy, Typed)]
|
||||||
pub enum OutletType {
|
pub enum OutletType {
|
||||||
Outlet,
|
Outlet,
|
||||||
Kettle,
|
Kettle,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(OutletType);
|
||||||
|
|
||||||
impl From<OutletType> for Type {
|
impl From<OutletType> for Type {
|
||||||
fn from(outlet: OutletType) -> Self {
|
fn from(outlet: OutletType) -> Self {
|
||||||
@@ -41,36 +43,50 @@ impl From<OutletType> for Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, LuaDeviceConfig)]
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
pub struct Config<T: OutletState> {
|
#[typed(as = "ConfigOutlet")]
|
||||||
|
pub struct Config<T: OutletState>
|
||||||
|
where
|
||||||
|
Outlet<T>: Typed,
|
||||||
|
{
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub info: InfoConfig,
|
pub info: InfoConfig,
|
||||||
#[device_config(flatten)]
|
#[device_config(flatten)]
|
||||||
|
#[typed(flatten)]
|
||||||
pub mqtt: MqttDeviceConfig,
|
pub mqtt: MqttDeviceConfig,
|
||||||
#[device_config(default(OutletType::Outlet))]
|
#[device_config(default(OutletType::Outlet))]
|
||||||
|
#[typed(default)]
|
||||||
pub outlet_type: OutletType,
|
pub outlet_type: OutletType,
|
||||||
|
|
||||||
#[device_config(from_lua, default)]
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
pub callback: ActionCallback<(Outlet<T>, T)>,
|
pub callback: ActionCallback<(Outlet<T>, T)>,
|
||||||
|
|
||||||
#[device_config(from_lua)]
|
#[device_config(from_lua)]
|
||||||
pub client: WrappedAsyncClient,
|
pub client: WrappedAsyncClient,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(Config<StateOnOff>);
|
||||||
|
crate::register_type!(Config<StatePower>);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
|
||||||
|
#[typed(as = "OutletStateOnOff")]
|
||||||
pub struct StateOnOff {
|
pub struct StateOnOff {
|
||||||
#[serde(deserialize_with = "state_deserializer")]
|
#[serde(deserialize_with = "state_deserializer")]
|
||||||
state: bool,
|
state: bool,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(StateOnOff);
|
||||||
|
|
||||||
impl OutletState for StateOnOff {}
|
impl OutletState for StateOnOff {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, LuaSerialize, Typed)]
|
||||||
|
#[typed(as = "OutletStatePower")]
|
||||||
pub struct StatePower {
|
pub struct StatePower {
|
||||||
#[serde(deserialize_with = "state_deserializer")]
|
#[serde(deserialize_with = "state_deserializer")]
|
||||||
state: bool,
|
state: bool,
|
||||||
power: f64,
|
power: f64,
|
||||||
}
|
}
|
||||||
|
crate::register_type!(StatePower);
|
||||||
|
|
||||||
impl OutletState for StatePower {}
|
impl OutletState for StatePower {}
|
||||||
|
|
||||||
@@ -82,7 +98,10 @@ impl From<StatePower> for StateOnOff {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(traits(OnOff for OutletOnOff, OutletPower))]
|
#[device(traits(OnOff for OutletOnOff, OutletPower))]
|
||||||
pub struct Outlet<T: OutletState> {
|
pub struct Outlet<T: OutletState>
|
||||||
|
where
|
||||||
|
Outlet<T>: Typed,
|
||||||
|
{
|
||||||
config: Config<T>,
|
config: Config<T>,
|
||||||
|
|
||||||
state: Arc<RwLock<T>>,
|
state: Arc<RwLock<T>>,
|
||||||
@@ -94,7 +113,10 @@ crate::register_device!(OutletOnOff);
|
|||||||
pub type OutletPower = Outlet<StatePower>;
|
pub type OutletPower = Outlet<StatePower>;
|
||||||
crate::register_device!(OutletPower);
|
crate::register_device!(OutletPower);
|
||||||
|
|
||||||
impl<T: OutletState> Outlet<T> {
|
impl<T: OutletState> Outlet<T>
|
||||||
|
where
|
||||||
|
Outlet<T>: Typed,
|
||||||
|
{
|
||||||
async fn state(&self) -> RwLockReadGuard<'_, T> {
|
async fn state(&self) -> RwLockReadGuard<'_, T> {
|
||||||
self.state.read().await
|
self.state.read().await
|
||||||
}
|
}
|
||||||
@@ -105,7 +127,10 @@ impl<T: OutletState> Outlet<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: OutletState> LuaDeviceCreate for Outlet<T> {
|
impl<T: OutletState> LuaDeviceCreate for Outlet<T>
|
||||||
|
where
|
||||||
|
Outlet<T>: Typed,
|
||||||
|
{
|
||||||
type Config = Config<T>;
|
type Config = Config<T>;
|
||||||
type Error = rumqttc::ClientError;
|
type Error = rumqttc::ClientError;
|
||||||
|
|
||||||
@@ -124,7 +149,10 @@ impl<T: OutletState> LuaDeviceCreate for Outlet<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: OutletState> Device for Outlet<T> {
|
impl<T: OutletState> Device for Outlet<T>
|
||||||
|
where
|
||||||
|
Outlet<T>: Typed,
|
||||||
|
{
|
||||||
fn get_id(&self) -> String {
|
fn get_id(&self) -> String {
|
||||||
self.config.info.identifier()
|
self.config.info.identifier()
|
||||||
}
|
}
|
||||||
@@ -201,7 +229,10 @@ impl OnMqtt for OutletPower {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: OutletState> google_home::Device for Outlet<T> {
|
impl<T: OutletState> google_home::Device for Outlet<T>
|
||||||
|
where
|
||||||
|
Outlet<T>: Typed,
|
||||||
|
{
|
||||||
fn get_device_type(&self) -> Type {
|
fn get_device_type(&self) -> Type {
|
||||||
self.config.outlet_type.into()
|
self.config.outlet_type.into()
|
||||||
}
|
}
|
||||||
@@ -232,6 +263,7 @@ impl<T: OutletState> google_home::Device for Outlet<T> {
|
|||||||
impl<T> OnOff for Outlet<T>
|
impl<T> OnOff for Outlet<T>
|
||||||
where
|
where
|
||||||
T: OutletState,
|
T: OutletState,
|
||||||
|
Outlet<T>: Typed,
|
||||||
{
|
{
|
||||||
async fn on(&self) -> Result<bool, ErrorCode> {
|
async fn on(&self) -> Result<bool, ErrorCode> {
|
||||||
let state = self.state().await;
|
let state = self.state().await;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ google_home = { workspace = true }
|
|||||||
hostname = { workspace = true }
|
hostname = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
inventory = { workspace = true }
|
inventory = { workspace = true }
|
||||||
|
lua_typed = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
rumqttc = { workspace = true }
|
rumqttc = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
|
use lua_typed::Typed;
|
||||||
use mlua::{FromLua, IntoLuaMulti};
|
use mlua::{FromLua, IntoLuaMulti};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -9,6 +10,23 @@ pub struct ActionCallback<P> {
|
|||||||
_parameters: PhantomData<P>,
|
_parameters: PhantomData<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A: Typed> Typed for ActionCallback<A> {
|
||||||
|
fn type_name() -> String {
|
||||||
|
let type_name = A::type_name();
|
||||||
|
format!("fun(_: {type_name}) | fun(_: {type_name})[]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Typed, B: Typed> Typed for ActionCallback<(A, B)> {
|
||||||
|
fn type_name() -> String {
|
||||||
|
let type_name_a = A::type_name();
|
||||||
|
let type_name_b = B::type_name();
|
||||||
|
format!(
|
||||||
|
"fun(_: {type_name_a}, _: {type_name_b}) | fun(_: {type_name_a}, _: {type_name_b})[]"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: For some reason the derive macro combined with PhantomData leads to issues where it
|
// NOTE: For some reason the derive macro combined with PhantomData leads to issues where it
|
||||||
// requires all types part of P to implement default, even if they never actually get constructed.
|
// requires all types part of P to implement default, even if they never actually get constructed.
|
||||||
// By manually implemented Default it works fine.
|
// By manually implemented Default it works fine.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::net::{Ipv4Addr, SocketAddr};
|
use std::net::{Ipv4Addr, SocketAddr};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use lua_typed::Typed;
|
||||||
use rumqttc::{MqttOptions, Transport};
|
use rumqttc::{MqttOptions, Transport};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ fn default_fulfillment_port() -> u16 {
|
|||||||
7878
|
7878
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, Typed)]
|
||||||
pub struct InfoConfig {
|
pub struct InfoConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub room: Option<String>,
|
pub room: Option<String>,
|
||||||
@@ -68,7 +69,7 @@ impl InfoConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, Typed)]
|
||||||
pub struct MqttDeviceConfig {
|
pub struct MqttDeviceConfig {
|
||||||
pub topic: String,
|
pub topic: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
#![feature(iterator_try_collect)]
|
#![feature(iterator_try_collect)]
|
||||||
|
#![feature(with_negative_coherence)]
|
||||||
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
|||||||
@@ -2,21 +2,40 @@ use std::ops::Deref;
|
|||||||
|
|
||||||
// TODO: Enable and disable functions based on query_only and command_only
|
// TODO: Enable and disable functions based on query_only and command_only
|
||||||
|
|
||||||
pub trait Device {
|
pub trait PartialUserData<T> {
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
|
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M);
|
||||||
where
|
|
||||||
Self: Sized + crate::device::Device + 'static,
|
fn interface_name() -> Option<&'static str> {
|
||||||
{
|
None
|
||||||
methods.add_async_method("get_id", async |_lua, this, _: ()| Ok(this.get_id()));
|
}
|
||||||
|
|
||||||
|
fn definitions() -> Option<String> {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> Device for T where T: crate::device::Device {}
|
|
||||||
|
|
||||||
pub trait OnOff {
|
pub struct Device;
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
|
|
||||||
where
|
impl<T> PartialUserData<T> for Device
|
||||||
Self: Sized + google_home::traits::OnOff + 'static,
|
where
|
||||||
{
|
T: crate::device::Device + 'static,
|
||||||
|
{
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
|
||||||
|
methods.add_async_method("get_id", async |_lua, this, _: ()| Ok(this.get_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interface_name() -> Option<&'static str> {
|
||||||
|
Some("DeviceInterface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OnOff;
|
||||||
|
|
||||||
|
impl<T> PartialUserData<T> for OnOff
|
||||||
|
where
|
||||||
|
T: google_home::traits::OnOff + 'static,
|
||||||
|
{
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
|
||||||
methods.add_async_method("set_on", async |_lua, this, on: bool| {
|
methods.add_async_method("set_on", async |_lua, this, on: bool| {
|
||||||
this.deref().set_on(on).await.unwrap();
|
this.deref().set_on(on).await.unwrap();
|
||||||
|
|
||||||
@@ -27,14 +46,19 @@ pub trait OnOff {
|
|||||||
Ok(this.deref().on().await.unwrap())
|
Ok(this.deref().on().await.unwrap())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl<T> OnOff for T where T: google_home::traits::OnOff {}
|
|
||||||
|
|
||||||
pub trait Brightness {
|
fn interface_name() -> Option<&'static str> {
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
|
Some("OnOffInterface")
|
||||||
where
|
}
|
||||||
Self: Sized + google_home::traits::Brightness + 'static,
|
}
|
||||||
{
|
|
||||||
|
pub struct Brightness;
|
||||||
|
|
||||||
|
impl<T> PartialUserData<T> for Brightness
|
||||||
|
where
|
||||||
|
T: google_home::traits::Brightness + 'static,
|
||||||
|
{
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
|
||||||
methods.add_async_method("set_brightness", async |_lua, this, brightness: u8| {
|
methods.add_async_method("set_brightness", async |_lua, this, brightness: u8| {
|
||||||
this.set_brightness(brightness).await.unwrap();
|
this.set_brightness(brightness).await.unwrap();
|
||||||
|
|
||||||
@@ -45,14 +69,19 @@ pub trait Brightness {
|
|||||||
Ok(this.brightness().await.unwrap())
|
Ok(this.brightness().await.unwrap())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl<T> Brightness for T where T: google_home::traits::Brightness {}
|
|
||||||
|
|
||||||
pub trait ColorSetting {
|
fn interface_name() -> Option<&'static str> {
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
|
Some("BrightnessInterface")
|
||||||
where
|
}
|
||||||
Self: Sized + google_home::traits::ColorSetting + 'static,
|
}
|
||||||
{
|
|
||||||
|
pub struct ColorSetting;
|
||||||
|
|
||||||
|
impl<T> PartialUserData<T> for ColorSetting
|
||||||
|
where
|
||||||
|
T: google_home::traits::ColorSetting + 'static,
|
||||||
|
{
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
|
||||||
methods.add_async_method(
|
methods.add_async_method(
|
||||||
"set_color_temperature",
|
"set_color_temperature",
|
||||||
async |_lua, this, temperature: u32| {
|
async |_lua, this, temperature: u32| {
|
||||||
@@ -68,14 +97,19 @@ pub trait ColorSetting {
|
|||||||
Ok(this.color().await.temperature)
|
Ok(this.color().await.temperature)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl<T> ColorSetting for T where T: google_home::traits::ColorSetting {}
|
|
||||||
|
|
||||||
pub trait OpenClose {
|
fn interface_name() -> Option<&'static str> {
|
||||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M)
|
Some("ColorSettingInterface")
|
||||||
where
|
}
|
||||||
Self: Sized + google_home::traits::OpenClose + 'static,
|
}
|
||||||
{
|
|
||||||
|
pub struct OpenClose;
|
||||||
|
|
||||||
|
impl<T> PartialUserData<T> for OpenClose
|
||||||
|
where
|
||||||
|
T: google_home::traits::OpenClose + 'static,
|
||||||
|
{
|
||||||
|
fn add_methods<M: mlua::UserDataMethods<T>>(methods: &mut M) {
|
||||||
methods.add_async_method("set_open_percent", async |_lua, this, open_percent: u8| {
|
methods.add_async_method("set_open_percent", async |_lua, this, open_percent: u8| {
|
||||||
this.set_open_percent(open_percent).await.unwrap();
|
this.set_open_percent(open_percent).await.unwrap();
|
||||||
|
|
||||||
@@ -86,5 +120,8 @@ pub trait OpenClose {
|
|||||||
Ok(this.open_percent().await.unwrap())
|
Ok(this.open_percent().await.unwrap())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interface_name() -> Option<&'static str> {
|
||||||
|
Some("OpenCloseInterface")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<T> OpenClose for T where T: google_home::traits::OpenClose {}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use lua_typed::Typed;
|
||||||
use mlua::FromLua;
|
use mlua::FromLua;
|
||||||
use rumqttc::{AsyncClient, Event, EventLoop, Incoming};
|
use rumqttc::{AsyncClient, Event, EventLoop, Incoming};
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
@@ -9,6 +10,12 @@ use crate::event::{self, EventChannel};
|
|||||||
#[derive(Debug, Clone, FromLua)]
|
#[derive(Debug, Clone, FromLua)]
|
||||||
pub struct WrappedAsyncClient(pub AsyncClient);
|
pub struct WrappedAsyncClient(pub AsyncClient);
|
||||||
|
|
||||||
|
impl Typed for WrappedAsyncClient {
|
||||||
|
fn type_name() -> String {
|
||||||
|
"AsyncClient".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Deref for WrappedAsyncClient {
|
impl Deref for WrappedAsyncClient {
|
||||||
type Target = AsyncClient;
|
type Target = AsyncClient;
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{ToTokens, quote};
|
use quote::quote;
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
use syn::{Attribute, DeriveInput, Token, parenthesized};
|
use syn::{Attribute, DeriveInput, Token, parenthesized};
|
||||||
|
|
||||||
enum Attr {
|
enum Attr {
|
||||||
Trait(TraitAttr),
|
Trait(TraitAttr),
|
||||||
AddMethods(AddMethodsAttr),
|
ExtraUserData(ExtraUserDataAttr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Attr {
|
impl Attr {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(attr: &Attribute) -> syn::Result<Self> {
|
||||||
let ident: syn::Ident = input.parse()?;
|
let mut parsed = None;
|
||||||
|
attr.parse_nested_meta(|meta| {
|
||||||
let attr;
|
if meta.path.is_ident("traits") {
|
||||||
_ = parenthesized!(attr in input);
|
let input;
|
||||||
|
_ = parenthesized!(input in meta.input);
|
||||||
let attr = match ident.to_string().as_str() {
|
parsed = Some(Attr::Trait(input.parse()?));
|
||||||
"traits" => Attr::Trait(attr.parse()?),
|
} else if meta.path.is_ident("extra_user_data") {
|
||||||
"add_methods" => Attr::AddMethods(attr.parse()?),
|
let value = meta.value()?;
|
||||||
_ => {
|
parsed = Some(Attr::ExtraUserData(value.parse()?));
|
||||||
return Err(syn::Error::new(
|
} else {
|
||||||
ident.span(),
|
return Err(syn::Error::new(meta.path.span(), "Unknown attribute"));
|
||||||
"Expected 'traits' or 'add_methods'",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Ok(attr)
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(parsed.expect("Parsed should be set"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,18 +66,6 @@ impl Parse for Traits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for Traits {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
||||||
let Self(traits) = &self;
|
|
||||||
|
|
||||||
tokens.extend(quote! {
|
|
||||||
#(
|
|
||||||
::automation_lib::lua::traits::#traits::add_methods(methods);
|
|
||||||
)*
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Aliases(Vec<syn::Ident>);
|
struct Aliases(Vec<syn::Ident>);
|
||||||
|
|
||||||
@@ -106,28 +95,18 @@ impl Parse for Aliases {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AddMethodsAttr(syn::Path);
|
struct ExtraUserDataAttr(syn::Ident);
|
||||||
|
|
||||||
impl Parse for AddMethodsAttr {
|
impl Parse for ExtraUserDataAttr {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
Ok(Self(input.parse()?))
|
Ok(Self(input.parse()?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for AddMethodsAttr {
|
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
||||||
let Self(path) = self;
|
|
||||||
|
|
||||||
tokens.extend(quote! {
|
|
||||||
#path
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Implementation {
|
struct Implementation {
|
||||||
name: syn::Ident,
|
name: syn::Ident,
|
||||||
traits: Traits,
|
traits: Traits,
|
||||||
add_methods: Vec<AddMethodsAttr>,
|
extra_user_data: Vec<ExtraUserDataAttr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl quote::ToTokens for Implementation {
|
impl quote::ToTokens for Implementation {
|
||||||
@@ -135,8 +114,10 @@ impl quote::ToTokens for Implementation {
|
|||||||
let Self {
|
let Self {
|
||||||
name,
|
name,
|
||||||
traits,
|
traits,
|
||||||
add_methods,
|
extra_user_data,
|
||||||
} = &self;
|
} = &self;
|
||||||
|
let Traits(traits) = traits;
|
||||||
|
let extra_user_data: Vec<_> = extra_user_data.iter().map(|tr| tr.0.clone()).collect();
|
||||||
|
|
||||||
tokens.extend(quote! {
|
tokens.extend(quote! {
|
||||||
impl mlua::UserData for #name {
|
impl mlua::UserData for #name {
|
||||||
@@ -154,13 +135,64 @@ impl quote::ToTokens for Implementation {
|
|||||||
Ok(b)
|
Ok(b)
|
||||||
});
|
});
|
||||||
|
|
||||||
::automation_lib::lua::traits::Device::add_methods(methods);
|
<::automation_lib::lua::traits::Device as ::automation_lib::lua::traits::PartialUserData<#name>>::add_methods(methods);
|
||||||
|
|
||||||
#traits
|
|
||||||
|
|
||||||
#(
|
#(
|
||||||
#add_methods(methods);
|
<::automation_lib::lua::traits::#traits as ::automation_lib::lua::traits::PartialUserData<#name>>::add_methods(methods);
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
#(
|
||||||
|
<#extra_user_data as ::automation_lib::lua::traits::PartialUserData<#name>>::add_methods(methods);
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::lua_typed::Typed for #name {
|
||||||
|
fn type_name() -> String {
|
||||||
|
stringify!(#name).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_header() -> std::option::Option<::std::string::String> {
|
||||||
|
let type_name = <Self as ::lua_typed::Typed>::type_name();
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
let interfaces: String = [
|
||||||
|
<::automation_lib::lua::traits::Device as ::automation_lib::lua::traits::PartialUserData<#name>>::interface_name(),
|
||||||
|
#(
|
||||||
|
<::automation_lib::lua::traits::#traits as ::automation_lib::lua::traits::PartialUserData<#name>>::interface_name(),
|
||||||
|
)*
|
||||||
|
].into_iter().flatten().intersperse(", ").collect();
|
||||||
|
|
||||||
|
let interfaces = if interfaces.is_empty() {
|
||||||
|
"".into()
|
||||||
|
} else {
|
||||||
|
format!(": {interfaces}")
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(format!("---@class {type_name}{interfaces}\nlocal {type_name}\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_members() -> Option<String> {
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
let type_name = <Self as ::lua_typed::Typed>::type_name();
|
||||||
|
output += &format!("devices.{type_name} = {{}}\n");
|
||||||
|
let config_name = <<Self as ::automation_lib::device::LuaDeviceCreate>::Config as ::lua_typed::Typed>::type_name();
|
||||||
|
output += &format!("---@param config {config_name}\n");
|
||||||
|
output += &format!("---@return {type_name}\n");
|
||||||
|
output += &format!("function devices.{type_name}.new(config) end\n");
|
||||||
|
|
||||||
|
output += &<::automation_lib::lua::traits::Device as ::automation_lib::lua::traits::PartialUserData<#name>>::definitions().unwrap_or("".into());
|
||||||
|
|
||||||
|
#(
|
||||||
|
output += &<::automation_lib::lua::traits::#traits as ::automation_lib::lua::traits::PartialUserData<#name>>::definitions().unwrap_or("".into());
|
||||||
|
)*
|
||||||
|
#(
|
||||||
|
output += &<#extra_user_data as ::automation_lib::lua::traits::PartialUserData<#name>>::definitions().unwrap_or("".into());
|
||||||
|
)*
|
||||||
|
|
||||||
|
|
||||||
|
Some(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -188,7 +220,7 @@ impl Implementations {
|
|||||||
all.extend(&attribute.traits);
|
all.extend(&attribute.traits);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Attr::AddMethods(attribute) => add_methods.push(attribute),
|
Attr::ExtraUserData(attribute) => add_methods.push(attribute),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +238,7 @@ impl Implementations {
|
|||||||
.map(|(alias, traits)| Implementation {
|
.map(|(alias, traits)| Implementation {
|
||||||
name: alias.unwrap_or(name.clone()),
|
name: alias.unwrap_or(name.clone()),
|
||||||
traits,
|
traits,
|
||||||
add_methods: add_methods.clone(),
|
extra_user_data: add_methods.clone(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
@@ -218,7 +250,7 @@ pub fn device(input: DeriveInput) -> TokenStream2 {
|
|||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|attr| attr.path().is_ident("device"))
|
.filter(|attr| attr.path().is_ident("device"))
|
||||||
.map(Attribute::parse_args)
|
.map(Attr::parse)
|
||||||
.try_collect::<Vec<_>>()
|
.try_collect::<Vec<_>>()
|
||||||
{
|
{
|
||||||
Ok(attr) => Implementations::from_attr(attr, input.ident),
|
Ok(attr) => Implementations::from_attr(attr, input.ident),
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ pub fn lua_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream
|
|||||||
/// ```
|
/// ```
|
||||||
/// It can then be registered with:
|
/// It can then be registered with:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[device(add_methods(top_secret))]
|
/// #[device(add_methods = top_secret)]
|
||||||
/// ```
|
/// ```
|
||||||
#[proc_macro_derive(Device, attributes(device))]
|
#[proc_macro_derive(Device, attributes(device))]
|
||||||
pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn device(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
|||||||
42
definitions/interfaces.lua
Normal file
42
definitions/interfaces.lua
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
--- @meta
|
||||||
|
|
||||||
|
---@class DeviceInterface
|
||||||
|
local DeviceInterface
|
||||||
|
---@return string
|
||||||
|
function DeviceInterface:get_id() end
|
||||||
|
|
||||||
|
---@class OnOffInterface: DeviceInterface
|
||||||
|
local OnOffInterface
|
||||||
|
---@async
|
||||||
|
---@param on boolean
|
||||||
|
function OnOffInterface:set_on(on) end
|
||||||
|
---@async
|
||||||
|
---@return boolean
|
||||||
|
function OnOffInterface:on() end
|
||||||
|
|
||||||
|
---@class BrightnessInterface: DeviceInterface
|
||||||
|
local BrightnessInterface
|
||||||
|
---@async
|
||||||
|
---@param brightness integer
|
||||||
|
function BrightnessInterface:set_brightness(brightness) end
|
||||||
|
---@async
|
||||||
|
---@return integer
|
||||||
|
function BrightnessInterface:brightness() end
|
||||||
|
|
||||||
|
---@class ColorSettingInterface: DeviceInterface
|
||||||
|
local ColorSettingInterface
|
||||||
|
---@async
|
||||||
|
---@param temperature integer
|
||||||
|
function ColorSettingInterface:set_color_temperature(temperature) end
|
||||||
|
---@async
|
||||||
|
---@return integer
|
||||||
|
function ColorSettingInterface:color_temperature() end
|
||||||
|
|
||||||
|
---@class OpenCloseInterface: DeviceInterface
|
||||||
|
local OpenCloseInterface
|
||||||
|
---@async
|
||||||
|
---@param open_percent integer
|
||||||
|
function OpenCloseInterface:set_open_percent(open_percent) end
|
||||||
|
---@async
|
||||||
|
---@return integer
|
||||||
|
function OpenCloseInterface:open_percent() end
|
||||||
Reference in New Issue
Block a user