Compare commits

..

6 Commits

Author SHA1 Message Date
295491c5fc feat(config)!: Move mqtt module to actual separate module
Some checks failed
Build and deploy / Deploy container (push) Blocked by required conditions
Build and deploy / build (push) Has been cancelled
The automation:mqtt module now gets loaded in a similar way as the
automation:devices and automation:utils modules.
This leads to a breaking change where instantiating a new mqtt client
the device manager needs to be explicitly passed in.
2025-10-15 03:41:39 +02:00
9f244b3475 feat: Added/expanded Typed impls 2025-10-15 03:41:38 +02:00
15a6e83ad8 feat: Remove automatic automation: module prefix 2025-10-15 03:41:38 +02:00
c727579290 chore: Removed dotenvy
Since secrets can now be set from automation.toml the .env file was no
longer used, so dotenvy can be removed.
2025-10-15 03:41:38 +02:00
19e8663f26 feat: Use Typed type_name for registering proxy 2025-10-15 03:41:38 +02:00
f5c4495cad feat!: Expanded add_methods to extra_user_data
Instead of being a function it now expects a struct with the
PartialUserData trait implemented. This in part ensures the correct
function signature.

It also adds another optional function to PartialUserData that returns
definitions for the added methods.
2025-10-15 03:41:25 +02:00
11 changed files with 122 additions and 27 deletions

4
Cargo.lock generated
View File

@@ -1101,7 +1101,7 @@ dependencies = [
[[package]]
name = "lua_typed"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#08f5c4533a93131e8eda6702c062fb841d14d4e1"
dependencies = [
"eui48",
"lua_typed_macro",
@@ -1110,7 +1110,7 @@ dependencies = [
[[package]]
name = "lua_typed_macro"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#d5d6fc1638bd108514899a792ee64335af50fc8b"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#08f5c4533a93131e8eda6702c062fb841d14d4e1"
dependencies = [
"convert_case",
"itertools",

View File

@@ -71,7 +71,7 @@ pub fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
Ok(devices)
}
inventory::submit! {Module::new("devices", create_module)}
inventory::submit! {Module::new("automation:devices", create_module)}
macro_rules! register_type {
($ty:ty) => {

View File

@@ -10,6 +10,12 @@ pub struct ActionCallback<P> {
_parameters: PhantomData<P>,
}
impl Typed for ActionCallback<()> {
fn type_name() -> String {
"fun() | fun()[]".into()
}
}
impl<A: Typed> Typed for ActionCallback<A> {
fn type_name() -> String {
let type_name = A::type_name();

View File

@@ -5,7 +5,7 @@ use lua_typed::Typed;
use rumqttc::{MqttOptions, Transport};
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, Deserialize, Typed)]
pub struct MqttConfig {
pub host: String,
pub port: u16,

View File

@@ -4,6 +4,8 @@ use std::sync::Arc;
use futures::Future;
use futures::future::join_all;
use lua_typed::Typed;
use mlua::FromLua;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio_cron_scheduler::{Job, JobScheduler};
use tracing::{debug, instrument, trace};
@@ -13,7 +15,7 @@ use crate::event::{Event, EventChannel, OnMqtt};
pub type DeviceMap = HashMap<String, Box<dyn Device>>;
#[derive(Clone)]
#[derive(Clone, FromLua)]
pub struct DeviceManager {
devices: Arc<RwLock<DeviceMap>>,
event_channel: EventChannel,
@@ -142,3 +144,9 @@ impl mlua::UserData for DeviceManager {
methods.add_method("event_channel", |_lua, this, ()| Ok(this.event_channel()))
}
}
impl Typed for DeviceManager {
fn type_name() -> String {
"DeviceManager".into()
}
}

View File

@@ -41,7 +41,7 @@ pub fn load_modules(lua: &mlua::Lua) -> mlua::Result<()> {
for module in inventory::iter::<Module> {
debug!(name = module.get_name(), "Loading module");
let table = module.register(lua)?;
lua.register_module(&format!("automation:{}", module.get_name()), table)?;
lua.register_module(module.get_name(), table)?;
}
Ok(())

View File

@@ -28,4 +28,4 @@ fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
Ok(utils)
}
inventory::submit! {Module::new("utils", create_module)}
inventory::submit! {Module::new("automation:utils", create_module)}

View File

@@ -1,6 +1,7 @@
use std::sync::Arc;
use std::time::Duration;
use lua_typed::Typed;
use tokio::sync::RwLock;
use tokio::task::JoinHandle;
use tracing::debug;
@@ -74,3 +75,44 @@ impl mlua::UserData for Timeout {
});
}
}
impl Typed for Timeout {
fn type_name() -> String {
"Timeout".into()
}
fn generate_header() -> Option<String> {
let type_name = Self::type_name();
Some(format!("---@class {type_name}\nlocal {type_name}\n"))
}
fn generate_members() -> Option<String> {
let mut output = String::new();
let type_name = Self::type_name();
output += &format!(
"---@async\n---@param timeout number\n---@param callback {}\nfunction {type_name}:start(timeout, callback) end\n",
ActionCallback::<()>::type_name()
);
output += &format!("---@async\nfunction {type_name}:cancel() end\n",);
output +=
&format!("---@async\n---@return boolean\nfunction {type_name}:is_waiting() end\n",);
Some(output)
}
fn generate_footer() -> Option<String> {
let mut output = String::new();
let type_name = Self::type_name();
output += &format!("utils.{type_name} = {{}}\n");
output += &format!("---@return {type_name}\n");
output += &format!("function utils.{type_name}.new() end\n");
Some(output)
}
}

View File

@@ -1,10 +1,13 @@
use std::ops::{Deref, DerefMut};
use lua_typed::Typed;
use mlua::FromLua;
use mlua::{FromLua, LuaSerdeExt};
use rumqttc::{AsyncClient, Event, EventLoop, Incoming};
use tracing::{debug, warn};
use crate::Module;
use crate::config::MqttConfig;
use crate::device_manager::DeviceManager;
use crate::event::{self, EventChannel};
#[derive(Debug, Clone, FromLua)]
@@ -14,6 +17,37 @@ impl Typed for WrappedAsyncClient {
fn type_name() -> String {
"AsyncClient".into()
}
fn generate_header() -> Option<String> {
let type_name = Self::type_name();
Some(format!("---@class {type_name}\nlocal {type_name}\n"))
}
fn generate_members() -> Option<String> {
let mut output = String::new();
let type_name = Self::type_name();
output += &format!(
"---@async\n---@param topic string\n---@param message table?\nfunction {type_name}:send_message(topic, message) end\n"
);
Some(output)
}
fn generate_footer() -> Option<String> {
let mut output = String::new();
let type_name = Self::type_name();
output += &format!("mqtt.{type_name} = {{}}\n");
output += &format!("---@param device_manager {}\n", DeviceManager::type_name());
output += &format!("---@param config {}\n", MqttConfig::type_name());
output += &format!("---@return {type_name}\n");
output += "function mqtt.new(device_manager, config) end\n";
Some(output)
}
}
impl Deref for WrappedAsyncClient {
@@ -77,3 +111,25 @@ pub fn start(mut eventloop: EventLoop, event_channel: &EventChannel) {
}
});
}
fn create_module(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
let mqtt = lua.create_table()?;
let mqtt_new = lua.create_function(
move |lua, (device_manager, config): (DeviceManager, mlua::Value)| {
let event_channel = device_manager.event_channel();
let config: MqttConfig = lua.from_value(config)?;
// Create a mqtt client
// TODO: When starting up, the devices are not yet created, this could lead to a device being out of sync
let (client, eventloop) = AsyncClient::new(config.into(), 100);
start(eventloop, &event_channel);
Ok(WrappedAsyncClient(client))
},
)?;
mqtt.set("new", mqtt_new)?;
Ok(mqtt)
}
inventory::submit! {Module::new("automation:mqtt", create_module)}

View File

@@ -21,7 +21,7 @@ local fulfillment = {
openid_url = "https://login.huizinga.dev/api/oidc",
}
local mqtt_client = require("automation:mqtt").new({
local mqtt_client = require("automation:mqtt").new(device_manager, {
host = ((host == "zeus" or host == "hephaestus") and "olympus.lan.huizinga.dev") or "mosquitto",
port = 8883,
client_name = "automation-" .. host,

View File

@@ -9,9 +9,8 @@ use std::path::Path;
use std::process;
use ::config::{Environment, File};
use automation_lib::config::{FulfillmentConfig, MqttConfig};
use automation_lib::config::FulfillmentConfig;
use automation_lib::device_manager::DeviceManager;
use automation_lib::mqtt::{self, WrappedAsyncClient};
use axum::extract::{FromRef, State};
use axum::http::StatusCode;
use axum::routing::post;
@@ -19,7 +18,6 @@ use axum::{Json, Router};
use config::Config;
use google_home::{GoogleHome, Request, Response};
use mlua::LuaSerdeExt;
use rumqttc::AsyncClient;
use tokio::net::TcpListener;
use tracing::{debug, error, info, warn};
use web::{ApiError, User};
@@ -138,21 +136,6 @@ async fn app() -> anyhow::Result<()> {
automation_lib::load_modules(&lua)?;
let mqtt = lua.create_table()?;
let event_channel = device_manager.event_channel();
let mqtt_new = lua.create_function(move |lua, config: mlua::Value| {
let config: MqttConfig = lua.from_value(config)?;
// Create a mqtt client
// TODO: When starting up, the devices are not yet created, this could lead to a device being out of sync
let (client, eventloop) = AsyncClient::new(config.into(), 100);
mqtt::start(eventloop, &event_channel);
Ok(WrappedAsyncClient(client))
})?;
mqtt.set("new", mqtt_new)?;
lua.register_module("automation:mqtt", mqtt)?;
lua.register_module("automation:device_manager", device_manager.clone())?;
lua.register_module("automation:variables", lua.to_value(&config.variables)?)?;