Compare commits

..

7 Commits

Author SHA1 Message Date
5abdc88a35 feat!: Removed AddAdditionalMethods
All checks were successful
Build and deploy / build (push) Successful in 11m1s
Build and deploy / Deploy container (push) Successful in 40s
It has been replaced with the add_methods device attribute.
2025-09-09 04:24:20 +02:00
19cdb37dfb 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-09 04:23:58 +02:00
3a33e3fa55 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-09 04:05:56 +02:00
e642562ddb chore: Removed old leftover contact sensor presence config
All checks were successful
Build and deploy / build (push) Successful in 11m3s
Build and deploy / Deploy container (push) Successful in 4m8s
2025-09-08 04:29:30 +02:00
a2e65c2d1a refactor: Remove unneeded wrapper functions when specifying callbacks
All checks were successful
Build and deploy / build (push) Successful in 10m32s
Build and deploy / Deploy container (push) Successful in 45s
These wrappers can be moved up to where the callback itself is defined
instead of having to wrap the call manually. This also works a lot nicer
now that it is possible to provide multiple callback functions.
2025-09-08 04:13:01 +02:00
e26fd9f132 fix: Front door presence does not get cleared properly 2025-09-08 04:06:02 +02:00
eb0c80c4ce feat(callback)!: ActionCallback can now receive any amount of arguments
ActionCallback now only has one generics argument that has to implement
IntoLuaMulti, this makes ActionCallback much more flexible as it no
longer always requires two arguments.
2025-09-08 04:06:01 +02:00
8 changed files with 64 additions and 117 deletions

12
Cargo.lock generated
View File

@@ -100,7 +100,6 @@ dependencies = [
"git-version",
"google_home",
"hostname",
"inventory",
"mlua",
"reqwest",
"rumqttc",
@@ -129,7 +128,6 @@ dependencies = [
"dyn-clone",
"eui48",
"google_home",
"inventory",
"mlua",
"reqwest",
"rumqttc",
@@ -153,7 +151,6 @@ dependencies = [
"futures",
"google_home",
"indexmap",
"inventory",
"mlua",
"rumqttc",
"serde",
@@ -970,15 +967,6 @@ dependencies = [
"serde",
]
[[package]]
name = "inventory"
version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
dependencies = [
"rustversion",
]
[[package]]
name = "io-uring"
version = "0.7.10"

View File

@@ -34,7 +34,6 @@ google_home = { path = "./google_home/google_home" }
google_home_macro = { path = "./google_home/google_home_macro" }
hostname = "0.4.1"
indexmap = { version = "2.11.0", features = ["serde"] }
inventory = "0.3.21"
itertools = "0.14.0"
json_value_merge = "2.0.1"
mlua = { version = "0.11.3", features = [
@@ -78,7 +77,6 @@ dotenvy = { workspace = true }
git-version = "0.3.9"
google_home = { workspace = true }
hostname = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true }
reqwest = { workspace = true }
rumqttc = { workspace = true }

View File

@@ -13,7 +13,6 @@ bytes = { workspace = true }
dyn-clone = { workspace = true }
eui48 = { workspace = true }
google_home = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true }
reqwest = { workspace = true }
rumqttc = { workspace = true }

View File

@@ -12,7 +12,6 @@ mod wake_on_lan;
mod washer;
mod zigbee;
use automation_lib::Module;
use automation_lib::device::{Device, LuaDeviceCreate};
use zigbee::light::{LightBrightness, LightColorTemperature, LightOnOff};
use zigbee::outlet::{OutletOnOff, OutletPower};
@@ -31,33 +30,30 @@ pub use self::wake_on_lan::WakeOnLAN;
pub use self::washer::Washer;
macro_rules! register_device {
($lua:expr, $table:expr, $device:ty) => {
$table.set(stringify!($device), $lua.create_proxy::<$device>()?)?;
($lua:expr, $device:ty) => {
$lua.globals()
.set(stringify!($device), $lua.create_proxy::<$device>()?)?;
};
}
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
let devices = lua.create_table()?;
pub fn register_with_lua(lua: &mlua::Lua) -> mlua::Result<()> {
register_device!(lua, AirFilter);
register_device!(lua, ContactSensor);
register_device!(lua, HueBridge);
register_device!(lua, HueGroup);
register_device!(lua, HueSwitch);
register_device!(lua, IkeaRemote);
register_device!(lua, KasaOutlet);
register_device!(lua, LightBrightness);
register_device!(lua, LightColorTemperature);
register_device!(lua, LightOnOff);
register_device!(lua, LightSensor);
register_device!(lua, Ntfy);
register_device!(lua, OutletOnOff);
register_device!(lua, OutletPower);
register_device!(lua, Presence);
register_device!(lua, WakeOnLAN);
register_device!(lua, Washer);
register_device!(lua, devices, AirFilter);
register_device!(lua, devices, ContactSensor);
register_device!(lua, devices, HueBridge);
register_device!(lua, devices, HueGroup);
register_device!(lua, devices, HueSwitch);
register_device!(lua, devices, IkeaRemote);
register_device!(lua, devices, KasaOutlet);
register_device!(lua, devices, LightBrightness);
register_device!(lua, devices, LightColorTemperature);
register_device!(lua, devices, LightOnOff);
register_device!(lua, devices, LightSensor);
register_device!(lua, devices, Ntfy);
register_device!(lua, devices, OutletOnOff);
register_device!(lua, devices, OutletPower);
register_device!(lua, devices, Presence);
register_device!(lua, devices, WakeOnLAN);
register_device!(lua, devices, Washer);
Ok(devices)
Ok(())
}
inventory::submit! {Module::new("devices", register_with_lua)}

View File

@@ -11,7 +11,6 @@ dyn-clone = { workspace = true }
futures = { workspace = true }
google_home = { workspace = true }
indexmap = { workspace = true }
inventory = { workspace = true }
mlua = { workspace = true }
rumqttc = { workspace = true }
serde = { workspace = true }

View File

@@ -12,26 +12,3 @@ pub mod lua;
pub mod messages;
pub mod mqtt;
pub mod schedule;
type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::Table>;
pub struct Module {
name: &'static str,
register_fn: RegisterFn,
}
impl Module {
pub const fn new(name: &'static str, register_fn: RegisterFn) -> Self {
Self { name, register_fn }
}
pub const fn get_name(&self) -> &'static str {
self.name
}
pub fn register(&self, lua: &mlua::Lua) -> mlua::Result<mlua::Table> {
(self.register_fn)(lua)
}
}
inventory::collect!(Module);

View File

@@ -1,4 +1,3 @@
local devices = require("devices")
local device_manager = require("device_manager")
local utils = require("utils")
local secrets = require("secrets")
@@ -30,7 +29,7 @@ local mqtt_client = require("mqtt").new({
tls = host == "zeus" or host == "hephaestus",
})
local ntfy = devices.Ntfy.new({
local ntfy = Ntfy.new({
topic = secrets.ntfy_topic,
})
device_manager:add(ntfy)
@@ -72,7 +71,7 @@ local on_presence = {
end,
}
local presence_system = devices.Presence.new({
local presence_system = Presence.new({
topic = mqtt_automation("presence/+/#"),
client = mqtt_client,
callback = function(_, presence)
@@ -123,7 +122,7 @@ local on_light = {
self[#self + 1] = f
end,
}
device_manager:add(devices.LightSensor.new({
device_manager:add(LightSensor.new({
identifier = "living_light_sensor",
topic = mqtt_z2m("living/light"),
client = mqtt_client,
@@ -147,7 +146,7 @@ end)
local hue_ip = "10.0.0.102"
local hue_token = secrets.hue_token
local hue_bridge = devices.HueBridge.new({
local hue_bridge = HueBridge.new({
identifier = "hue_bridge",
ip = hue_ip,
login = hue_token,
@@ -164,7 +163,7 @@ on_presence:add(function(presence)
hue_bridge:set_flag("presence", presence)
end)
local kitchen_lights = devices.HueGroup.new({
local kitchen_lights = HueGroup.new({
identifier = "kitchen_lights",
ip = hue_ip,
login = hue_token,
@@ -172,7 +171,7 @@ local kitchen_lights = devices.HueGroup.new({
scene_id = "7MJLG27RzeRAEVJ",
})
device_manager:add(kitchen_lights)
local living_lights = devices.HueGroup.new({
local living_lights = HueGroup.new({
identifier = "living_lights",
ip = hue_ip,
login = hue_token,
@@ -180,7 +179,7 @@ local living_lights = devices.HueGroup.new({
scene_id = "SNZw7jUhQ3cXSjkj",
})
device_manager:add(living_lights)
local living_lights_relax = devices.HueGroup.new({
local living_lights_relax = HueGroup.new({
identifier = "living_lights",
ip = hue_ip,
login = hue_token,
@@ -189,7 +188,7 @@ local living_lights_relax = devices.HueGroup.new({
})
device_manager:add(living_lights_relax)
device_manager:add(devices.HueSwitch.new({
device_manager:add(HueSwitch.new({
name = "Switch",
room = "Living",
client = mqtt_client,
@@ -206,7 +205,7 @@ device_manager:add(devices.HueSwitch.new({
battery_callback = check_battery,
}))
device_manager:add(devices.WakeOnLAN.new({
device_manager:add(WakeOnLAN.new({
name = "Zeus",
room = "Living Room",
topic = mqtt_automation("appliance/living_room/zeus"),
@@ -215,7 +214,7 @@ device_manager:add(devices.WakeOnLAN.new({
broadcast_ip = "10.0.3.255",
}))
local living_mixer = devices.OutletOnOff.new({
local living_mixer = OutletOnOff.new({
name = "Mixer",
room = "Living Room",
topic = mqtt_z2m("living/mixer"),
@@ -223,7 +222,7 @@ local living_mixer = devices.OutletOnOff.new({
})
turn_off_when_away(living_mixer)
device_manager:add(living_mixer)
local living_speakers = devices.OutletOnOff.new({
local living_speakers = OutletOnOff.new({
name = "Speakers",
room = "Living Room",
topic = mqtt_z2m("living/speakers"),
@@ -232,7 +231,7 @@ local living_speakers = devices.OutletOnOff.new({
turn_off_when_away(living_speakers)
device_manager:add(living_speakers)
device_manager:add(devices.IkeaRemote.new({
device_manager:add(IkeaRemote.new({
name = "Remote",
room = "Living Room",
client = mqtt_client,
@@ -272,7 +271,7 @@ local function kettle_timeout()
end
end
local kettle = devices.OutletPower.new({
local kettle = OutletPower.new({
outlet_type = "Kettle",
name = "Kettle",
room = "Kitchen",
@@ -287,7 +286,7 @@ local function set_kettle(_, on)
kettle:set_on(on)
end
device_manager:add(devices.IkeaRemote.new({
device_manager:add(IkeaRemote.new({
name = "Remote",
room = "Bedroom",
client = mqtt_client,
@@ -297,7 +296,7 @@ device_manager:add(devices.IkeaRemote.new({
battery_callback = check_battery,
}))
device_manager:add(devices.IkeaRemote.new({
device_manager:add(IkeaRemote.new({
name = "Remote",
room = "Kitchen",
client = mqtt_client,
@@ -321,7 +320,7 @@ local function off_timeout(duration)
end
end
local bathroom_light = devices.LightOnOff.new({
local bathroom_light = LightOnOff.new({
name = "Light",
room = "Bathroom",
topic = mqtt_z2m("bathroom/light"),
@@ -330,7 +329,7 @@ local bathroom_light = devices.LightOnOff.new({
})
device_manager:add(bathroom_light)
device_manager:add(devices.Washer.new({
device_manager:add(Washer.new({
identifier = "bathroom_washer",
topic = mqtt_z2m("bathroom/washer"),
client = mqtt_client,
@@ -345,7 +344,7 @@ device_manager:add(devices.Washer.new({
end,
}))
device_manager:add(devices.OutletOnOff.new({
device_manager:add(OutletOnOff.new({
name = "Charger",
room = "Workbench",
topic = mqtt_z2m("workbench/charger"),
@@ -353,7 +352,7 @@ device_manager:add(devices.OutletOnOff.new({
callback = off_timeout(debug and 5 or 20 * 3600),
}))
local workbench_outlet = devices.OutletOnOff.new({
local workbench_outlet = OutletOnOff.new({
name = "Outlet",
room = "Workbench",
topic = mqtt_z2m("workbench/outlet"),
@@ -362,7 +361,7 @@ local workbench_outlet = devices.OutletOnOff.new({
turn_off_when_away(workbench_outlet)
device_manager:add(workbench_outlet)
local workbench_light = devices.LightColorTemperature.new({
local workbench_light = LightColorTemperature.new({
name = "Light",
room = "Workbench",
topic = mqtt_z2m("workbench/light"),
@@ -372,7 +371,7 @@ turn_off_when_away(workbench_light)
device_manager:add(workbench_light)
local delay_color_temp = Timeout.new()
device_manager:add(devices.IkeaRemote.new({
device_manager:add(IkeaRemote.new({
name = "Remote",
room = "Workbench",
client = mqtt_client,
@@ -395,14 +394,14 @@ device_manager:add(devices.IkeaRemote.new({
battery_callback = check_battery,
}))
local hallway_top_light = devices.HueGroup.new({
local hallway_top_light = HueGroup.new({
identifier = "hallway_top_light",
ip = hue_ip,
login = hue_token,
group_id = 83,
scene_id = "QeufkFDICEHWeKJ7",
})
device_manager:add(devices.HueSwitch.new({
device_manager:add(HueSwitch.new({
name = "SwitchBottom",
room = "Hallway",
client = mqtt_client,
@@ -412,7 +411,7 @@ device_manager:add(devices.HueSwitch.new({
end,
battery_callback = check_battery,
}))
device_manager:add(devices.HueSwitch.new({
device_manager:add(HueSwitch.new({
name = "SwitchTop",
room = "Hallway",
client = mqtt_client,
@@ -481,7 +480,7 @@ local hallway_light_automation = {
end,
}
local hallway_storage = devices.LightBrightness.new({
local hallway_storage = LightBrightness.new({
name = "Storage",
room = "Hallway",
topic = mqtt_z2m("hallway/storage"),
@@ -491,7 +490,7 @@ local hallway_storage = devices.LightBrightness.new({
turn_off_when_away(hallway_storage)
device_manager:add(hallway_storage)
local hallway_bottom_lights = devices.HueGroup.new({
local hallway_bottom_lights = HueGroup.new({
identifier = "hallway_bottom_lights",
ip = hue_ip,
login = hue_token,
@@ -532,7 +531,7 @@ local function presence(duration)
end
end
device_manager:add(devices.IkeaRemote.new({
device_manager:add(IkeaRemote.new({
name = "Remote",
room = "Hallway",
client = mqtt_client,
@@ -540,7 +539,7 @@ device_manager:add(devices.IkeaRemote.new({
callback = hallway_light_automation:switch_callback(),
battery_callback = check_battery,
}))
local hallway_frontdoor = devices.ContactSensor.new({
local hallway_frontdoor = ContactSensor.new({
name = "Frontdoor",
room = "Hallway",
sensor_type = "Door",
@@ -555,7 +554,7 @@ local hallway_frontdoor = devices.ContactSensor.new({
device_manager:add(hallway_frontdoor)
hallway_light_automation.door = hallway_frontdoor
local hallway_trash = devices.ContactSensor.new({
local hallway_trash = ContactSensor.new({
name = "Trash",
room = "Hallway",
sensor_type = "Drawer",
@@ -567,7 +566,7 @@ local hallway_trash = devices.ContactSensor.new({
device_manager:add(hallway_trash)
hallway_light_automation.trash = hallway_trash
local guest_light = devices.LightOnOff.new({
local guest_light = LightOnOff.new({
name = "Light",
room = "Guest Room",
topic = mqtt_z2m("guest/light"),
@@ -576,14 +575,14 @@ local guest_light = devices.LightOnOff.new({
turn_off_when_away(guest_light)
device_manager:add(guest_light)
local bedroom_air_filter = devices.AirFilter.new({
local bedroom_air_filter = AirFilter.new({
name = "Air Filter",
room = "Bedroom",
url = "http://10.0.0.103",
})
device_manager:add(bedroom_air_filter)
local bedroom_lights = devices.HueGroup.new({
local bedroom_lights = HueGroup.new({
identifier = "bedroom_lights",
ip = hue_ip,
login = hue_token,
@@ -591,7 +590,7 @@ local bedroom_lights = devices.HueGroup.new({
scene_id = "PvRs-lGD4VRytL9",
})
device_manager:add(bedroom_lights)
local bedroom_lights_relax = devices.HueGroup.new({
local bedroom_lights_relax = HueGroup.new({
identifier = "bedroom_lights",
ip = hue_ip,
login = hue_token,
@@ -600,7 +599,7 @@ local bedroom_lights_relax = devices.HueGroup.new({
})
device_manager:add(bedroom_lights_relax)
device_manager:add(devices.HueSwitch.new({
device_manager:add(HueSwitch.new({
name = "Switch",
room = "Bedroom",
client = mqtt_client,
@@ -614,7 +613,7 @@ device_manager:add(devices.HueSwitch.new({
battery_callback = check_battery,
}))
device_manager:add(devices.ContactSensor.new({
device_manager:add(ContactSensor.new({
name = "Balcony",
room = "Living Room",
sensor_type = "Door",
@@ -622,21 +621,21 @@ device_manager:add(devices.ContactSensor.new({
client = mqtt_client,
battery_callback = check_battery,
}))
device_manager:add(devices.ContactSensor.new({
device_manager:add(ContactSensor.new({
name = "Window",
room = "Living Room",
topic = mqtt_z2m("living/window"),
client = mqtt_client,
battery_callback = check_battery,
}))
device_manager:add(devices.ContactSensor.new({
device_manager:add(ContactSensor.new({
name = "Window",
room = "Bedroom",
topic = mqtt_z2m("bedroom/window"),
client = mqtt_client,
battery_callback = check_battery,
}))
device_manager:add(devices.ContactSensor.new({
device_manager:add(ContactSensor.new({
name = "Window",
room = "Guest Room",
topic = mqtt_z2m("guest/window"),
@@ -644,7 +643,7 @@ device_manager:add(devices.ContactSensor.new({
battery_callback = check_battery,
}))
local storage_light = devices.LightBrightness.new({
local storage_light = LightBrightness.new({
name = "Light",
room = "Storage",
topic = mqtt_z2m("storage/light"),
@@ -653,7 +652,7 @@ local storage_light = devices.LightBrightness.new({
turn_off_when_away(storage_light)
device_manager:add(storage_light)
device_manager:add(devices.ContactSensor.new({
device_manager:add(ContactSensor.new({
name = "Door",
room = "Storage",
sensor_type = "Door",

View File

@@ -12,8 +12,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
use ::config::{Environment, File};
use automation_lib::config::{FulfillmentConfig, MqttConfig};
use automation_lib::device_manager::DeviceManager;
use automation_lib::helpers;
use automation_lib::mqtt::{self, WrappedAsyncClient};
use automation_lib::{Module, helpers};
use axum::extract::{FromRef, State};
use axum::http::StatusCode;
use axum::routing::post;
@@ -30,9 +30,6 @@ use web::{ApiError, User};
use crate::secret::EnvironmentSecretFile;
use crate::version::VERSION;
// Force automation_devices to link so that it gets registered as a module
extern crate automation_devices;
#[derive(Clone)]
struct AppState {
pub openid_url: String,
@@ -141,13 +138,6 @@ async fn app() -> anyhow::Result<()> {
})?;
lua.globals().set("print", print)?;
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)?;
}
let mqtt = lua.create_table()?;
let event_channel = device_manager.event_channel();
let mqtt_new = lua.create_function(move |lua, config: mlua::Value| {
@@ -189,6 +179,7 @@ async fn app() -> anyhow::Result<()> {
utils.set("sleep", sleep)?;
lua.register_module("utils", utils)?;
automation_devices::register_with_lua(&lua)?;
helpers::register_with_lua(&lua)?;
let entrypoint = Path::new(&config.entrypoint);