Compare commits

...

2 Commits

Author SHA1 Message Date
92a0bff8c4 refactor(config)!: Move scheduler out of device_manager
All checks were successful
Build and deploy / build (push) Successful in 10m5s
Build and deploy / Deploy container (push) Has been skipped
Due to changes made in mlua the new scheduler is much simpler. It also
had no real business being part of the device manager, so it has now been
moved to be part of the returned config.
2025-10-17 04:31:27 +02:00
187220a49b feat: Receive devices through config return 2025-10-17 04:00:34 +02:00
14 changed files with 290 additions and 180 deletions

234
Cargo.lock generated
View File

@@ -36,12 +36,6 @@ dependencies = [
"serde",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@@ -94,6 +88,7 @@ dependencies = [
"async-trait",
"automation_devices",
"automation_lib",
"automation_macro",
"axum",
"config",
"git-version",
@@ -107,6 +102,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.16",
"tokio",
"tokio-cron-scheduler",
"tracing",
"tracing-subscriber",
]
@@ -153,7 +149,6 @@ dependencies = [
"futures",
"google_home",
"hostname",
"indexmap",
"inventory",
"lua_typed",
"mlua",
@@ -162,9 +157,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.16",
"tokio",
"tokio-cron-scheduler",
"tracing",
"uuid",
]
[[package]]
@@ -323,16 +316,25 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.41"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
name = "chrono-tz"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3"
dependencies = [
"chrono",
"phf",
]
[[package]]
@@ -375,11 +377,48 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "croner"
version = "2.2.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c344b0690c1ad1c7176fe18eb173e0c927008fdaaa256e40dfd43ddd149c0843"
checksum = "4c007081651a19b42931f86f7d4f74ee1c2a7d0cd2c6636a81695b5ffd4e9990"
dependencies = [
"chrono",
"derive_builder",
"strum",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.106",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.106",
]
[[package]]
@@ -423,6 +462,37 @@ dependencies = [
"thiserror 2.0.16",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.106",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -467,12 +537,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
@@ -702,10 +766,10 @@ dependencies = [
]
[[package]]
name = "hashbrown"
version = "0.15.5"
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
@@ -721,7 +785,7 @@ checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65"
dependencies = [
"cfg-if",
"libc",
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -835,9 +899,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.63"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -943,6 +1007,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
@@ -964,17 +1034,6 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
dependencies = [
"equivalent",
"hashbrown",
"serde",
]
[[package]]
name = "inventory"
version = "0.3.21"
@@ -1103,16 +1162,17 @@ dependencies = [
[[package]]
name = "lua_typed"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#08f5c4533a93131e8eda6702c062fb841d14d4e1"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#f6a684291432aae2ef7109712882e7e3ed758d08"
dependencies = [
"eui48",
"lua_typed_macro",
"mlua",
]
[[package]]
name = "lua_typed_macro"
version = "0.1.0"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#08f5c4533a93131e8eda6702c062fb841d14d4e1"
source = "git+https://git.huizinga.dev/Dreaded_X/lua_typed#f6a684291432aae2ef7109712882e7e3ed758d08"
dependencies = [
"convert_case",
"itertools",
@@ -1208,9 +1268,9 @@ dependencies = [
[[package]]
name = "mlua"
version = "0.11.3"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b3dd94c3c4dea0049b22296397040840a8f6b5b5229f438434ba82df402b42d"
checksum = "9be1c2bfc684b8a228fbaebf954af7a47a98ec27721986654a4cc2c40a20cc7e"
dependencies = [
"bstr",
"either",
@@ -1348,6 +1408,24 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -1900,6 +1978,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.11"
@@ -1937,6 +2021,33 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "subtle"
version = "2.6.1"
@@ -2079,11 +2190,12 @@ dependencies = [
[[package]]
name = "tokio-cron-scheduler"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c71ce8f810abc9fabebccc30302a952f9e89c6cf246fafaf170fef164063141"
checksum = "bb73c4033ddcbbf81fd828293fd41a0145cde2cbc30dd782227c5081a523214d"
dependencies = [
"chrono",
"chrono-tz",
"croner",
"num-derive",
"num-traits",
@@ -2478,22 +2590,22 @@ dependencies = [
[[package]]
name = "windows-core"
version = "0.61.2"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-link 0.2.1",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
@@ -2502,9 +2614,9 @@ dependencies = [
[[package]]
name = "windows-interface"
version = "0.59.1"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
@@ -2518,21 +2630,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-result"
version = "0.3.4"
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]

View File

@@ -33,7 +33,6 @@ futures = "0.3.31"
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"
@@ -59,10 +58,9 @@ serde_repr = "0.1.20"
syn = { version = "2.0.106" }
thiserror = "2.0.16"
tokio = { version = "1", features = ["rt-multi-thread"] }
tokio-cron-scheduler = "0.14.0"
tokio-cron-scheduler = "0.15.0"
tracing = "0.1.41"
tracing-subscriber = "0.3.20"
uuid = "1.18.1"
wakey = "0.3.0"
[dependencies]
@@ -70,6 +68,7 @@ anyhow = { workspace = true }
async-trait = { workspace = true }
automation_devices = { workspace = true }
automation_lib = { workspace = true }
automation_macro = { path = "./automation_macro" }
axum = { workspace = true }
config = { version = "0.15.15", default-features = false, features = [
"async",
@@ -86,6 +85,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio-cron-scheduler = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

View File

@@ -11,7 +11,6 @@ dyn-clone = { workspace = true }
futures = { workspace = true }
google_home = { workspace = true }
hostname = { workspace = true }
indexmap = { workspace = true }
inventory = { workspace = true }
lua_typed = { workspace = true }
mlua = { workspace = true }
@@ -20,6 +19,4 @@ serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tokio-cron-scheduler = { workspace = true }
tracing = { workspace = true }
uuid = { workspace = true }

View File

@@ -2,6 +2,7 @@ use std::fmt::Debug;
use automation_cast::Cast;
use dyn_clone::DynClone;
use lua_typed::Typed;
use mlua::ObjectLike;
use crate::event::OnMqtt;
@@ -41,4 +42,10 @@ impl mlua::FromLua for Box<dyn Device> {
}
impl mlua::UserData for Box<dyn Device> {}
impl Typed for Box<dyn Device> {
fn type_name() -> String {
"DeviceInterface".into()
}
}
dyn_clone::clone_trait_object!(Device);

View File

@@ -1,13 +1,10 @@
use std::collections::HashMap;
use std::pin::Pin;
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};
use crate::device::Device;
@@ -19,7 +16,6 @@ pub type DeviceMap = HashMap<String, Box<dyn Device>>;
pub struct DeviceManager {
devices: Arc<RwLock<DeviceMap>>,
event_channel: EventChannel,
scheduler: JobScheduler,
}
impl DeviceManager {
@@ -29,7 +25,6 @@ impl DeviceManager {
let device_manager = Self {
devices: Arc::new(RwLock::new(HashMap::new())),
event_channel,
scheduler: JobScheduler::new().await.unwrap(),
};
tokio::spawn({
@@ -45,8 +40,6 @@ impl DeviceManager {
}
});
device_manager.scheduler.start().await.unwrap();
device_manager
}
@@ -105,42 +98,6 @@ impl mlua::UserData for DeviceManager {
Ok(())
});
methods.add_async_method(
"schedule",
async |lua, this, (schedule, f): (String, mlua::Function)| {
debug!("schedule = {schedule}");
// This creates a function, that returns the actual job we want to run
let create_job = {
let lua = lua.clone();
move |uuid: uuid::Uuid,
_: tokio_cron_scheduler::JobScheduler|
-> Pin<Box<dyn Future<Output = ()> + Send>> {
let lua = lua.clone();
// Create the actual function we want to run on a schedule
let future = async move {
let f: mlua::Function =
lua.named_registry_value(uuid.to_string().as_str()).unwrap();
f.call_async::<()>(()).await.unwrap();
};
Box::pin(future)
}
};
let job = Job::new_async(schedule.as_str(), create_job).unwrap();
let uuid = this.scheduler.add(job).await.unwrap();
// Store the function in the registry
lua.set_named_registry_value(uuid.to_string().as_str(), f)
.unwrap();
Ok(())
},
);
methods.add_method("event_channel", |_lua, this, ()| Ok(this.event_channel()))
}
}

View File

@@ -13,7 +13,6 @@ pub mod helpers;
pub mod lua;
pub mod messages;
pub mod mqtt;
pub mod schedule;
type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::Table>;
type DefinitionsFn = fn() -> String;

View File

@@ -1,17 +0,0 @@
use indexmap::IndexMap;
use serde::Deserialize;
#[derive(Debug, Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
#[serde(rename_all = "snake_case")]
pub enum Action {
On,
Off,
}
pub type Schedule = IndexMap<String, IndexMap<Action, Vec<String>>>;
// #[derive(Debug, Deserialize)]
// pub struct Schedule {
// pub when: String,
// pub actions: IndexMap<Action, Vec<String>>,
// }

View File

@@ -30,6 +30,11 @@ local mqtt_client = require("automation:mqtt").new(device_manager, {
tls = host == "zeus" or host == "hephaestus",
})
local devs = {}
function devs:add(device)
table.insert(self, device)
end
local ntfy_topic = secrets.ntfy_topic
if ntfy_topic == nil then
error("Ntfy topic is not specified")
@@ -37,7 +42,7 @@ end
local ntfy = devices.Ntfy.new({
topic = ntfy_topic,
})
device_manager:add(ntfy)
devs:add(ntfy)
--- @type {[string]: number}
local low_battery = {}
@@ -52,7 +57,7 @@ local function check_battery(device, battery)
low_battery[id] = nil
end
end
device_manager:schedule("0 0 21 */1 * *", function()
local function notify_low_battery()
-- Don't send notifications if there are now devices with low battery
if next(low_battery) == nil then
print("No devices with low battery")
@@ -71,7 +76,7 @@ device_manager:schedule("0 0 21 */1 * *", function()
tags = { "battery" },
priority = "default",
})
end)
end
--- @class OnPresence
--- @field [integer] fun(presence: boolean)
@@ -92,7 +97,7 @@ local presence_system = devices.Presence.new({
end
end,
})
device_manager:add(presence_system)
devs:add(presence_system)
on_presence:add(function(presence)
ntfy:send_notification({
title = "Presence",
@@ -166,7 +171,7 @@ local on_light = {}
function on_light:add(f)
self[#self + 1] = f
end
device_manager:add(devices.LightSensor.new({
devs:add(devices.LightSensor.new({
identifier = "living_light_sensor",
topic = mqtt_z2m("living/light"),
client = mqtt_client,
@@ -202,7 +207,7 @@ local hue_bridge = devices.HueBridge.new({
darkness = 43,
},
})
device_manager:add(hue_bridge)
devs:add(hue_bridge)
on_light:add(function(light)
hue_bridge:set_flag("darkness", not light)
end)
@@ -217,7 +222,7 @@ local kitchen_lights = devices.HueGroup.new({
group_id = 7,
scene_id = "7MJLG27RzeRAEVJ",
})
device_manager:add(kitchen_lights)
devs:add(kitchen_lights)
local living_lights = devices.HueGroup.new({
identifier = "living_lights",
ip = hue_ip,
@@ -225,7 +230,7 @@ local living_lights = devices.HueGroup.new({
group_id = 1,
scene_id = "SNZw7jUhQ3cXSjkj",
})
device_manager:add(living_lights)
devs:add(living_lights)
local living_lights_relax = devices.HueGroup.new({
identifier = "living_lights",
ip = hue_ip,
@@ -233,9 +238,9 @@ local living_lights_relax = devices.HueGroup.new({
group_id = 1,
scene_id = "eRJ3fvGHCcb6yNw",
})
device_manager:add(living_lights_relax)
devs:add(living_lights_relax)
device_manager:add(devices.HueSwitch.new({
devs:add(devices.HueSwitch.new({
name = "Switch",
room = "Living",
client = mqtt_client,
@@ -252,7 +257,7 @@ device_manager:add(devices.HueSwitch.new({
battery_callback = check_battery,
}))
device_manager:add(devices.WakeOnLAN.new({
devs:add(devices.WakeOnLAN.new({
name = "Zeus",
room = "Living Room",
topic = mqtt_automation("appliance/living_room/zeus"),
@@ -268,7 +273,7 @@ local living_mixer = devices.OutletOnOff.new({
client = mqtt_client,
})
turn_off_when_away(living_mixer)
device_manager:add(living_mixer)
devs:add(living_mixer)
local living_speakers = devices.OutletOnOff.new({
name = "Speakers",
room = "Living Room",
@@ -276,9 +281,9 @@ local living_speakers = devices.OutletOnOff.new({
client = mqtt_client,
})
turn_off_when_away(living_speakers)
device_manager:add(living_speakers)
devs:add(living_speakers)
device_manager:add(devices.IkeaRemote.new({
devs:add(devices.IkeaRemote.new({
name = "Remote",
room = "Living Room",
client = mqtt_client,
@@ -329,14 +334,14 @@ local kettle = devices.OutletPower.new({
callback = kettle_timeout(),
})
turn_off_when_away(kettle)
device_manager:add(kettle)
devs:add(kettle)
--- @param on boolean
local function set_kettle(_, on)
kettle:set_on(on)
end
device_manager:add(devices.IkeaRemote.new({
devs:add(devices.IkeaRemote.new({
name = "Remote",
room = "Bedroom",
client = mqtt_client,
@@ -346,7 +351,7 @@ device_manager:add(devices.IkeaRemote.new({
battery_callback = check_battery,
}))
device_manager:add(devices.IkeaRemote.new({
devs:add(devices.IkeaRemote.new({
name = "Remote",
room = "Kitchen",
client = mqtt_client,
@@ -379,9 +384,9 @@ local bathroom_light = devices.LightOnOff.new({
client = mqtt_client,
callback = off_timeout(debug and 60 or 45 * 60),
})
device_manager:add(bathroom_light)
devs:add(bathroom_light)
device_manager:add(devices.Washer.new({
devs:add(devices.Washer.new({
identifier = "bathroom_washer",
topic = mqtt_z2m("bathroom/washer"),
client = mqtt_client,
@@ -396,7 +401,7 @@ device_manager:add(devices.Washer.new({
end,
}))
device_manager:add(devices.OutletOnOff.new({
devs:add(devices.OutletOnOff.new({
name = "Charger",
room = "Workbench",
topic = mqtt_z2m("workbench/charger"),
@@ -411,7 +416,7 @@ local workbench_outlet = devices.OutletOnOff.new({
client = mqtt_client,
})
turn_off_when_away(workbench_outlet)
device_manager:add(workbench_outlet)
devs:add(workbench_outlet)
local workbench_light = devices.LightColorTemperature.new({
name = "Light",
@@ -420,10 +425,10 @@ local workbench_light = devices.LightColorTemperature.new({
client = mqtt_client,
})
turn_off_when_away(workbench_light)
device_manager:add(workbench_light)
devs:add(workbench_light)
local delay_color_temp = utils.Timeout.new()
device_manager:add(devices.IkeaRemote.new({
devs:add(devices.IkeaRemote.new({
name = "Remote",
room = "Workbench",
client = mqtt_client,
@@ -453,7 +458,7 @@ local hallway_top_light = devices.HueGroup.new({
group_id = 83,
scene_id = "QeufkFDICEHWeKJ7",
})
device_manager:add(devices.HueSwitch.new({
devs:add(devices.HueSwitch.new({
name = "SwitchBottom",
room = "Hallway",
client = mqtt_client,
@@ -463,7 +468,7 @@ device_manager:add(devices.HueSwitch.new({
end,
battery_callback = check_battery,
}))
device_manager:add(devices.HueSwitch.new({
devs:add(devices.HueSwitch.new({
name = "SwitchTop",
room = "Hallway",
client = mqtt_client,
@@ -546,7 +551,7 @@ local hallway_storage = devices.LightBrightness.new({
callback = hallway_light_automation:light_callback(),
})
turn_off_when_away(hallway_storage)
device_manager:add(hallway_storage)
devs:add(hallway_storage)
local hallway_bottom_lights = devices.HueGroup.new({
identifier = "hallway_bottom_lights",
@@ -555,7 +560,7 @@ local hallway_bottom_lights = devices.HueGroup.new({
group_id = 81,
scene_id = "3qWKxGVadXFFG4o",
})
device_manager:add(hallway_bottom_lights)
devs:add(hallway_bottom_lights)
hallway_light_automation.group = {
set_on = function(on)
@@ -591,7 +596,7 @@ local function presence(duration)
end
end
device_manager:add(devices.IkeaRemote.new({
devs:add(devices.IkeaRemote.new({
name = "Remote",
room = "Hallway",
client = mqtt_client,
@@ -611,7 +616,7 @@ local hallway_frontdoor = devices.ContactSensor.new({
},
battery_callback = check_battery,
})
device_manager:add(hallway_frontdoor)
devs:add(hallway_frontdoor)
window_sensors:add(hallway_frontdoor)
hallway_light_automation.door = hallway_frontdoor
@@ -624,7 +629,7 @@ local hallway_trash = devices.ContactSensor.new({
callback = hallway_light_automation:trash_callback(),
battery_callback = check_battery,
})
device_manager:add(hallway_trash)
devs:add(hallway_trash)
hallway_light_automation.trash = hallway_trash
local guest_light = devices.LightOnOff.new({
@@ -634,14 +639,14 @@ local guest_light = devices.LightOnOff.new({
client = mqtt_client,
})
turn_off_when_away(guest_light)
device_manager:add(guest_light)
devs:add(guest_light)
local bedroom_air_filter = devices.AirFilter.new({
name = "Air Filter",
room = "Bedroom",
url = "http://10.0.0.103",
})
device_manager:add(bedroom_air_filter)
devs:add(bedroom_air_filter)
local bedroom_lights = devices.HueGroup.new({
identifier = "bedroom_lights",
@@ -650,7 +655,7 @@ local bedroom_lights = devices.HueGroup.new({
group_id = 3,
scene_id = "PvRs-lGD4VRytL9",
})
device_manager:add(bedroom_lights)
devs:add(bedroom_lights)
local bedroom_lights_relax = devices.HueGroup.new({
identifier = "bedroom_lights",
ip = hue_ip,
@@ -658,9 +663,9 @@ local bedroom_lights_relax = devices.HueGroup.new({
group_id = 3,
scene_id = "60tfTyR168v2csz",
})
device_manager:add(bedroom_lights_relax)
devs:add(bedroom_lights_relax)
device_manager:add(devices.HueSwitch.new({
devs:add(devices.HueSwitch.new({
name = "Switch",
room = "Bedroom",
client = mqtt_client,
@@ -682,7 +687,7 @@ local balcony = devices.ContactSensor.new({
client = mqtt_client,
battery_callback = check_battery,
})
device_manager:add(balcony)
devs:add(balcony)
window_sensors:add(balcony)
local living_window = devices.ContactSensor.new({
name = "Window",
@@ -691,7 +696,7 @@ local living_window = devices.ContactSensor.new({
client = mqtt_client,
battery_callback = check_battery,
})
device_manager:add(living_window)
devs:add(living_window)
window_sensors:add(living_window)
local bedroom_window = devices.ContactSensor.new({
name = "Window",
@@ -700,7 +705,7 @@ local bedroom_window = devices.ContactSensor.new({
client = mqtt_client,
battery_callback = check_battery,
})
device_manager:add(bedroom_window)
devs:add(bedroom_window)
window_sensors:add(bedroom_window)
local guest_window = devices.ContactSensor.new({
name = "Window",
@@ -709,7 +714,7 @@ local guest_window = devices.ContactSensor.new({
client = mqtt_client,
battery_callback = check_battery,
})
device_manager:add(guest_window)
devs:add(guest_window)
window_sensors:add(guest_window)
local storage_light = devices.LightBrightness.new({
@@ -719,9 +724,9 @@ local storage_light = devices.LightBrightness.new({
client = mqtt_client,
})
turn_off_when_away(storage_light)
device_manager:add(storage_light)
devs:add(storage_light)
device_manager:add(devices.ContactSensor.new({
devs:add(devices.ContactSensor.new({
name = "Door",
room = "Storage",
sensor_type = "Door",
@@ -737,16 +742,19 @@ device_manager:add(devices.ContactSensor.new({
battery_callback = check_battery,
}))
device_manager:schedule("0 0 19 * * *", function()
bedroom_air_filter:set_on(true)
end)
device_manager:schedule("0 0 20 * * *", function()
bedroom_air_filter:set_on(false)
end)
---@type Config
return {
fulfillment = {
openid_url = "https://login.huizinga.dev/api/oidc",
},
devices = devs,
schedule = {
["0 0 19 * * *"] = function()
bedroom_air_filter:set_on(true)
end,
["0 0 20 * * *"] = function()
bedroom_air_filter:set_on(false)
end,
["0 0 21 */1 * *"] = notify_low_battery,
},
}

View File

@@ -5,8 +5,4 @@ local DeviceManager
---@param device DeviceInterface
function DeviceManager:add(device) end
---@param cron string
---@param callback fun()
function DeviceManager:schedule(cron, callback) end
return DeviceManager

View File

@@ -9,4 +9,6 @@ local FulfillmentConfig
---@class Config
---@field fulfillment FulfillmentConfig
---@field devices DeviceInterface[]?
---@field schedule table<string, function>?
local Config

View File

@@ -6,6 +6,7 @@ use std::process;
use ::config::{Environment, File};
use automation::config::{Config, Setup};
use automation::schedule::start_scheduler;
use automation::secret::EnvironmentSecretFile;
use automation::version::VERSION;
use automation::web::{ApiError, User};
@@ -136,8 +137,13 @@ async fn app() -> anyhow::Result<()> {
lua.register_module("automation:secrets", lua.to_value(&setup.secrets)?)?;
let entrypoint = Path::new(&setup.entrypoint);
let config: mlua::Value = lua.load(entrypoint).eval_async().await?;
let config: Config = lua.from_value(config)?;
let config: Config = lua.load(entrypoint).eval_async().await?;
for device in config.devices {
device_manager.add(device).await;
}
start_scheduler(config.schedule).await?;
// Create google home fulfillment route
let fulfillment = Router::new().route("/google_home", post(fulfillment));

View File

@@ -1,6 +1,8 @@
use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr};
use automation_lib::device::Device;
use automation_macro::LuaDeviceConfig;
use lua_typed::Typed;
use serde::Deserialize;
@@ -29,9 +31,15 @@ pub struct FulfillmentConfig {
pub port: u16,
}
#[derive(Debug, Deserialize, Typed)]
#[derive(Debug, LuaDeviceConfig, Typed)]
pub struct Config {
pub fulfillment: FulfillmentConfig,
#[device_config(from_lua, default)]
#[typed(default)]
pub devices: Vec<Box<dyn Device>>,
#[device_config(from_lua, default)]
#[typed(default)]
pub schedule: HashMap<String, mlua::Function>,
}
impl From<FulfillmentConfig> for SocketAddr {

View File

@@ -1,4 +1,5 @@
pub mod config;
pub mod schedule;
pub mod secret;
pub mod version;
pub mod web;

28
src/schedule.rs Normal file
View File

@@ -0,0 +1,28 @@
use std::collections::HashMap;
use std::pin::Pin;
use tokio_cron_scheduler::{Job, JobScheduler, JobSchedulerError};
pub async fn start_scheduler(
schedule: HashMap<String, mlua::Function>,
) -> Result<(), JobSchedulerError> {
let scheduler = JobScheduler::new().await?;
for (s, f) in schedule {
let job = {
move |_uuid, _lock| -> Pin<Box<dyn Future<Output = ()> + Send>> {
let f = f.clone();
Box::pin(async move {
f.call_async::<()>(()).await.unwrap();
})
}
};
let job = Job::new_async(s, job)?;
scheduler.add(job).await?;
}
scheduler.start().await
}