Compare commits

..

7 Commits

Author SHA1 Message Date
5271e5ad81 refactor(config)!: Moved Timeout into utils module and moved module
All checks were successful
Build and deploy / build (push) Successful in 10m43s
Build and deploy / Deploy container (push) Successful in 39s
The module is now setup in automation_lib::lua::utils.
2025-09-10 02:11:11 +02:00
1d28b43264 refactor: Move module load code into separate function 2025-09-10 02:11:11 +02:00
da04fad520 refactor(config)!: Move device proxies into module
Instead of registering the device proxies in the global namespace they
are now registered in a module called `devices`.
2025-09-10 02:11:09 +02:00
84e4b30b6a feat!: Improve lua module registration
Instead of having to call all the module registration functions in one
place it is possible for each module to register itself in a global registry.
During startup all the all the modules will be registered
automatically.

This does currently have one weakness, to need to ensure that the crate
is linked.
2025-09-10 02:10:45 +02:00
95a8a377e8 feat!: Removed AddAdditionalMethods
It has been replaced with the add_methods device attribute.
2025-09-10 01:58:48 +02:00
23355190ca feat: Added attribute to easily register additional lua methods
Previously this could only be done by implementing a trait, like
`AddAdditionalMethods`, that that has an add_methods function where you
can put your custom methods. With this new attribute you can pass in a
register function directly!
2025-09-10 01:58:48 +02:00
2dbd491b81 refactor!: Rewrote device implementation macro once again
This time with a bit more though put into the design of the code, as a
result the macro should be a lot more robust.

This did result in the macro getting renamed from LuaDevice to Device as
this should be _the_ Device macro.
The attribute also got renamed from traits() to device(traits()) and the
syntax got overhauled to allow for a bit more expression.
2025-09-10 01:58:48 +02:00
11 changed files with 56 additions and 52 deletions

3
Cargo.lock generated
View File

@@ -99,8 +99,6 @@ dependencies = [
"dotenvy", "dotenvy",
"git-version", "git-version",
"google_home", "google_home",
"hostname",
"inventory",
"mlua", "mlua",
"reqwest", "reqwest",
"rumqttc", "rumqttc",
@@ -152,6 +150,7 @@ dependencies = [
"dyn-clone", "dyn-clone",
"futures", "futures",
"google_home", "google_home",
"hostname",
"indexmap", "indexmap",
"inventory", "inventory",
"mlua", "mlua",

View File

@@ -77,8 +77,6 @@ config = { version = "0.15.15", default-features = false, features = [
dotenvy = { workspace = true } dotenvy = { workspace = true }
git-version = "0.3.9" git-version = "0.3.9"
google_home = { workspace = true } google_home = { workspace = true }
hostname = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }
reqwest = { workspace = true } reqwest = { workspace = true }
rumqttc = { workspace = true } rumqttc = { workspace = true }

View File

@@ -36,7 +36,7 @@ macro_rules! register_device {
}; };
} }
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<mlua::Table> { pub fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
let devices = lua.create_table()?; let devices = lua.create_table()?;
register_device!(lua, devices, AirFilter); register_device!(lua, devices, AirFilter);
@@ -60,4 +60,4 @@ pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
Ok(devices) Ok(devices)
} }
inventory::submit! {Module::new("devices", register_with_lua)} inventory::submit! {Module::new("devices", create_module)}

View File

@@ -10,6 +10,7 @@ bytes = { workspace = true }
dyn-clone = { workspace = true } dyn-clone = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
google_home = { workspace = true } google_home = { workspace = true }
hostname = { workspace = true }
indexmap = { workspace = true } indexmap = { workspace = true }
inventory = { workspace = true } inventory = { workspace = true }
mlua = { workspace = true } mlua = { workspace = true }

View File

@@ -1,11 +1 @@
pub mod serialization; pub mod serialization;
mod timeout;
pub use timeout::Timeout;
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
lua.globals()
.set("Timeout", lua.create_proxy::<Timeout>()?)?;
Ok(())
}

View File

@@ -1,6 +1,8 @@
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![feature(iterator_try_collect)] #![feature(iterator_try_collect)]
use tracing::debug;
pub mod action_callback; pub mod action_callback;
pub mod config; pub mod config;
pub mod device; pub mod device;
@@ -34,4 +36,15 @@ impl Module {
} }
} }
pub fn load_modules(lua: &mlua::Lua) -> mlua::Result<()> {
debug!("Loading modules...");
for module in inventory::iter::<Module> {
debug!(name = module.get_name(), "Registering");
let table = module.register(lua)?;
lua.register_module(module.get_name(), table)?;
}
Ok(())
}
inventory::collect!(Module); inventory::collect!(Module);

View File

@@ -1 +1,3 @@
pub mod traits; pub mod traits;
mod utils;

View File

@@ -0,0 +1,31 @@
mod timeout;
use std::time::{SystemTime, UNIX_EPOCH};
pub use timeout::Timeout;
use crate::Module;
fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
let utils = lua.create_table()?;
utils.set("Timeout", lua.create_proxy::<Timeout>()?)?;
let get_hostname = lua.create_function(|_lua, ()| {
hostname::get()
.map(|name| name.to_str().unwrap_or("unknown").to_owned())
.map_err(mlua::ExternalError::into_lua_err)
})?;
utils.set("get_hostname", get_hostname)?;
let get_epoch = lua.create_function(|_lua, ()| {
Ok(SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time is after UNIX EPOCH")
.as_millis())
})?;
utils.set("get_epoch", get_epoch)?;
Ok(utils)
}
inventory::submit! {Module::new("utils", create_module)}

View File

@@ -259,7 +259,7 @@ device_manager:add(devices.IkeaRemote.new({
})) }))
local function kettle_timeout() local function kettle_timeout()
local timeout = Timeout.new() local timeout = utils.Timeout.new()
return function(self, state) return function(self, state)
if state.state and state.power < 100 then if state.state and state.power < 100 then
@@ -308,7 +308,7 @@ device_manager:add(devices.IkeaRemote.new({
})) }))
local function off_timeout(duration) local function off_timeout(duration)
local timeout = Timeout.new() local timeout = utils.Timeout.new()
return function(self, state) return function(self, state)
if state.state then if state.state then
@@ -371,7 +371,7 @@ local workbench_light = devices.LightColorTemperature.new({
turn_off_when_away(workbench_light) turn_off_when_away(workbench_light)
device_manager:add(workbench_light) device_manager:add(workbench_light)
local delay_color_temp = Timeout.new() local delay_color_temp = utils.Timeout.new()
device_manager:add(devices.IkeaRemote.new({ device_manager:add(devices.IkeaRemote.new({
name = "Remote", name = "Remote",
room = "Workbench", room = "Workbench",
@@ -424,7 +424,7 @@ device_manager:add(devices.HueSwitch.new({
})) }))
local hallway_light_automation = { local hallway_light_automation = {
timeout = Timeout.new(), timeout = utils.Timeout.new(),
forced = false, forced = false,
switch_callback = function(self) switch_callback = function(self)
return function(_, on) return function(_, on)
@@ -512,7 +512,7 @@ hallway_light_automation.group = {
} }
local function presence(duration) local function presence(duration)
local timeout = Timeout.new() local timeout = utils.Timeout.new()
return function(_, open) return function(_, open)
if open then if open then

View File

@@ -7,13 +7,11 @@ mod web;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::path::Path; use std::path::Path;
use std::process; use std::process;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use ::config::{Environment, File}; use ::config::{Environment, File};
use automation_lib::config::{FulfillmentConfig, MqttConfig}; use automation_lib::config::{FulfillmentConfig, MqttConfig};
use automation_lib::device_manager::DeviceManager; use automation_lib::device_manager::DeviceManager;
use automation_lib::mqtt::{self, WrappedAsyncClient}; use automation_lib::mqtt::{self, WrappedAsyncClient};
use automation_lib::{Module, helpers};
use axum::extract::{FromRef, State}; use axum::extract::{FromRef, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::routing::post; use axum::routing::post;
@@ -141,12 +139,7 @@ async fn app() -> anyhow::Result<()> {
})?; })?;
lua.globals().set("print", print)?; lua.globals().set("print", print)?;
debug!("Loading modules..."); automation_lib::load_modules(&lua)?;
for module in inventory::iter::<Module> {
debug!(name = module.get_name(), "Registering");
let table = module.register(&lua)?;
lua.register_module(module.get_name(), table)?;
}
let mqtt = lua.create_table()?; let mqtt = lua.create_table()?;
let event_channel = device_manager.event_channel(); let event_channel = device_manager.event_channel();
@@ -168,29 +161,6 @@ async fn app() -> anyhow::Result<()> {
lua.register_module("variables", lua.to_value(&config.variables)?)?; lua.register_module("variables", lua.to_value(&config.variables)?)?;
lua.register_module("secrets", lua.to_value(&config.secrets)?)?; lua.register_module("secrets", lua.to_value(&config.secrets)?)?;
let utils = lua.create_table()?;
let get_hostname = lua.create_function(|_lua, ()| {
hostname::get()
.map(|name| name.to_str().unwrap_or("unknown").to_owned())
.map_err(mlua::ExternalError::into_lua_err)
})?;
utils.set("get_hostname", get_hostname)?;
let get_epoch = lua.create_function(|_lua, ()| {
Ok(SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time is after UNIX EPOCH")
.as_millis())
})?;
utils.set("get_epoch", get_epoch)?;
let sleep = lua.create_async_function(async |_lua, duration: u64| {
tokio::time::sleep(Duration::from_millis(duration)).await;
Ok(())
})?;
utils.set("sleep", sleep)?;
lua.register_module("utils", utils)?;
helpers::register_with_lua(&lua)?;
let entrypoint = Path::new(&config.entrypoint); let entrypoint = Path::new(&config.entrypoint);
let fulfillment_config: mlua::Value = lua.load(entrypoint).eval_async().await?; let fulfillment_config: mlua::Value = lua.load(entrypoint).eval_async().await?;
let fulfillment_config: FulfillmentConfig = lua.from_value(fulfillment_config)?; let fulfillment_config: FulfillmentConfig = lua.from_value(fulfillment_config)?;