From 5acf00c886d29cdf2b8e9468366b86a679d95afa Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Wed, 17 Sep 2025 00:17:31 +0200 Subject: [PATCH] feat: WIP --- Cargo.lock | 35 ++++++++++++++++ Cargo.toml | 1 + automation_devices/Cargo.toml | 1 + automation_devices/src/lib.rs | 31 +++++++++++++-- automation_devices/src/ntfy.rs | 37 ++++++++++++++--- config.lua | 7 +++- definitions.bak/automation:devices.lua.bak | 42 ++++++++++++++++++++ definitions.bak/automation:secrets.lua.bak | 6 +++ definitions.bak/automation:utils.lua.bak | 27 +++++++++++++ definitions.bak/automation:variables.lua.bak | 6 +++ definitions/automation:devices.lua | 36 +++++++++++++++++ src/bin/generate_definitions.rs | 3 ++ 12 files changed, 222 insertions(+), 10 deletions(-) create mode 100644 definitions.bak/automation:devices.lua.bak create mode 100644 definitions.bak/automation:secrets.lua.bak create mode 100644 definitions.bak/automation:utils.lua.bak create mode 100644 definitions.bak/automation:variables.lua.bak create mode 100644 definitions/automation:devices.lua create mode 100644 src/bin/generate_definitions.rs diff --git a/Cargo.lock b/Cargo.lock index accc16c..0bd599f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -128,6 +128,7 @@ dependencies = [ "eui48", "google_home", "inventory", + "lua_typed", "mlua", "reqwest", "rumqttc", @@ -345,6 +346,15 @@ dependencies = [ "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]] name = "core-foundation" version = "0.9.4" @@ -1094,6 +1104,25 @@ dependencies = [ "cc", ] +[[package]] +name = "lua_typed" +version = "0.1.0" +source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#a5f4b672167335c60d9e50e0e7afc932a755b4cf" +dependencies = [ + "lua_typed_macro", +] + +[[package]] +name = "lua_typed_macro" +version = "0.1.0" +source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#a5f4b672167335c60d9e50e0e7afc932a755b4cf" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "luajit-src" version = "210.6.1+f9140a6" @@ -2256,6 +2285,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 952c0c4..5f401f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ indexmap = { version = "2.11.0", features = ["serde"] } inventory = "0.3.21" itertools = "0.14.0" json_value_merge = "2.0.1" +lua_typed = { git = "https://git.huizinga.dev/Dreaded_X/lua_typed" } mlua = { version = "0.11.3", features = [ "lua54", "vendored", diff --git a/automation_devices/Cargo.toml b/automation_devices/Cargo.toml index 4bfee79..5aba41f 100644 --- a/automation_devices/Cargo.toml +++ b/automation_devices/Cargo.toml @@ -14,6 +14,7 @@ dyn-clone = { workspace = true } eui48 = { workspace = true } google_home = { workspace = true } inventory = { workspace = true } +lua_typed = { workspace = true } mlua = { workspace = true } reqwest = { workspace = true } rumqttc = { workspace = true } diff --git a/automation_devices/src/lib.rs b/automation_devices/src/lib.rs index 80f3f17..f43c9bc 100644 --- a/automation_devices/src/lib.rs +++ b/automation_devices/src/lib.rs @@ -27,15 +27,15 @@ macro_rules! register_device { pub(crate) use register_device; -type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result; +type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result; pub struct RegisteredDevice { name: &'static str, - register_fn: RegisterFn, + register_fn: RegisterDeviceFn, } 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 } } @@ -64,3 +64,28 @@ pub fn create_module(lua: &mlua::Lua) -> mlua::Result { } 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; + +pub struct RegisteredType(RegisterTypeFn); + +inventory::collect!(RegisteredType); + +pub fn generate_definitions() { + println!("---@meta\n\nlocal devices\n"); + for ty in inventory::iter:: { + let def = ty.0().unwrap(); + println!("{def}"); + } + println!("return devices") +} diff --git a/automation_devices/src/ntfy.rs b/automation_devices/src/ntfy.rs index 78c7ba8..4cdf030 100644 --- a/automation_devices/src/ntfy.rs +++ b/automation_devices/src/ntfy.rs @@ -4,12 +4,13 @@ use std::convert::Infallible; use async_trait::async_trait; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_macro::{Device, LuaDeviceConfig}; +use lua_typed::Typed; use mlua::LuaSerdeExt; use serde::{Deserialize, Serialize}; use serde_repr::*; use tracing::{error, trace, warn}; -#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy)] +#[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)] #[repr(u8)] #[serde(rename_all = "snake_case")] pub enum Priority { @@ -19,8 +20,9 @@ pub enum Priority { High, Max, } +crate::register_type!(Priority); -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Typed)] #[serde(rename_all = "snake_case", tag = "action")] pub enum ActionType { Broadcast { @@ -31,22 +33,23 @@ pub enum ActionType { // Http } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, Typed)] pub struct Action { #[serde(flatten)] pub action: ActionType, pub label: String, pub clear: Option, } +crate::register_type!(Action); -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Typed)] struct NotificationFinal { topic: String, #[serde(flatten)] inner: Notification, } -#[derive(Debug, Serialize, Clone, Deserialize)] +#[derive(Debug, Serialize, Clone, Deserialize, Typed)] pub struct Notification { title: String, message: Option, @@ -57,6 +60,7 @@ pub struct Notification { #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] actions: Vec, } +crate::register_type!(Notification); impl Notification { fn finalize(self, topic: &str) -> NotificationFinal { @@ -67,12 +71,14 @@ impl Notification { } } -#[derive(Debug, Clone, LuaDeviceConfig)] +#[derive(Debug, Clone, LuaDeviceConfig, Typed)] +#[typed(as = "NtfyConfig")] pub struct Config { #[device_config(default("https://ntfy.sh".into()))] pub url: String, pub topic: String, } +crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(add_methods(Self::add_methods))] @@ -80,6 +86,7 @@ pub struct Ntfy { config: Config, } crate::register_device!(Ntfy); +crate::register_type!(Ntfy); impl Ntfy { fn add_methods>(methods: &mut M) { @@ -96,6 +103,24 @@ impl Ntfy { } } +impl Typed for Ntfy { + fn type_name() -> String { + "Ntfy".into() + } + + fn generate_header() -> Option { + let type_name = ::type_name(); + Some(format!("---@class {type_name}\nlocal {type_name}\n")) + } + + fn generate_members() -> Option { + Some(format!( + "---@async\n---@param notification Notification\nfunction {}:send_notification(notification) end\n", + ::type_name(), + )) + } +} + #[async_trait] impl LuaDeviceCreate for Ntfy { type Config = Config; diff --git a/config.lua b/config.lua index f3cdc5b..2fdf7fb 100644 --- a/config.lua +++ b/config.lua @@ -2,17 +2,21 @@ local devices = require("automation:devices") local device_manager = require("automation:device_manager") local utils = require("automation:utils") local secrets = require("automation:secrets") -local debug = require("automation:variables").debug or false +local debug = require("automation:variables").debug and true or false print(_VERSION) local host = utils.get_hostname() print("Running @" .. host) +--- @param topic string +--- @return string local function mqtt_z2m(topic) return "zigbee2mqtt/" .. topic end +--- @param topic string +--- @return string local function mqtt_automation(topic) return "automation/" .. topic end @@ -30,6 +34,7 @@ local mqtt_client = require("automation:mqtt").new({ tls = host == "zeus" or host == "hephaestus", }) +--- @type Ntfy local ntfy = devices.Ntfy.new({ topic = secrets.ntfy_topic, }) diff --git a/definitions.bak/automation:devices.lua.bak b/definitions.bak/automation:devices.lua.bak new file mode 100644 index 0000000..d629c6c --- /dev/null +++ b/definitions.bak/automation:devices.lua.bak @@ -0,0 +1,42 @@ +---@meta + +local devices + +---@class Action +---@field action +---| "broadcast" +---| "view" +---@field extras table | nil +---@field label string | nil +---@field clear boolean|nil + +---@alias Priority +---| "min" +---| "low" +---| "default" +---| "high" +---| "max" + +---@class Notification +---@field title string +---@field message string | nil +-- NOTE: It might be possible to specify this down to the actual possible values +---@field tags string[] | nil +---@field priority Priority | nil +---@field actions Action[] | nil + +---@class Ntfy +local Ntfy +---@async +---@param notification Notification +function Ntfy:send_notification(notification) end + +---@class NtfyConfig +---@field topic string + +devices.Ntfy = {} +---@param config NtfyConfig +---@return Ntfy +function devices.Ntfy.new(config) end + +return devices diff --git a/definitions.bak/automation:secrets.lua.bak b/definitions.bak/automation:secrets.lua.bak new file mode 100644 index 0000000..6494943 --- /dev/null +++ b/definitions.bak/automation:secrets.lua.bak @@ -0,0 +1,6 @@ +---@meta + +---@type table +local secrets + +return secrets diff --git a/definitions.bak/automation:utils.lua.bak b/definitions.bak/automation:utils.lua.bak new file mode 100644 index 0000000..d8b380c --- /dev/null +++ b/definitions.bak/automation:utils.lua.bak @@ -0,0 +1,27 @@ +---@meta + +local utils + +---@class Timeout +local Timeout +---@async +---@param timeout number +---@param callback fun() +function Timeout:start(timeout, callback) end +---@async +function Timeout:cancel() end +---@async +---@return boolean +function Timeout:is_waiting() end + +utils.Timeout = {} +---@return Timeout +function utils.Timeout.new() end + +--- @return string hostname +function utils.get_hostname() end + +--- @return number epoch +function utils.get_epoch() end + +return utils diff --git a/definitions.bak/automation:variables.lua.bak b/definitions.bak/automation:variables.lua.bak new file mode 100644 index 0000000..6f09c6d --- /dev/null +++ b/definitions.bak/automation:variables.lua.bak @@ -0,0 +1,6 @@ +---@meta + +---@type table +local variables + +return variables diff --git a/definitions/automation:devices.lua b/definitions/automation:devices.lua new file mode 100644 index 0000000..eb9f1ac --- /dev/null +++ b/definitions/automation:devices.lua @@ -0,0 +1,36 @@ +---@meta + +local devices + +---@class Ntfy +local Ntfy +---@async +---@param notification Notification +function Ntfy:send_notification(notification) end + +---@alias Priority +---| "min" +---| "low" +---| "default" +---| "high" +---| "max" + +---@class Notification +---@field title string +---@field message string? +---@field tags string[]? +---@field priority Priority? +---@field actions Action[]? + +---@class NtfyConfig +---@field url string +---@field topic string + +---@class Action +---@field action +---| "broadcast" +---@field extras table? +---@field label string +---@field clear boolean? + +return devices diff --git a/src/bin/generate_definitions.rs b/src/bin/generate_definitions.rs new file mode 100644 index 0000000..eee496d --- /dev/null +++ b/src/bin/generate_definitions.rs @@ -0,0 +1,3 @@ +fn main() { + automation_devices::generate_definitions() +}