refactor(config)!: Move scheduler out of device_manager

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.
This commit is contained in:
2025-10-17 04:29:14 +02:00
parent 948380ea9b
commit 1ffccd955c
13 changed files with 224 additions and 138 deletions

233
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"
@@ -108,6 +102,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.16",
"tokio",
"tokio-cron-scheduler",
"tracing",
"tracing-subscriber",
]
@@ -154,7 +149,6 @@ dependencies = [
"futures",
"google_home",
"hostname",
"indexmap",
"inventory",
"lua_typed",
"mlua",
@@ -163,9 +157,7 @@ dependencies = [
"serde_json",
"thiserror 2.0.16",
"tokio",
"tokio-cron-scheduler",
"tracing",
"uuid",
]
[[package]]
@@ -324,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]]
@@ -376,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]]
@@ -424,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"
@@ -468,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"
@@ -703,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"
@@ -722,7 +785,7 @@ checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65"
dependencies = [
"cfg-if",
"libc",
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -836,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",
@@ -944,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"
@@ -965,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"
@@ -1104,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",
@@ -1209,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",
@@ -1349,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"
@@ -1901,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"
@@ -1938,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"
@@ -2080,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",
@@ -2479,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",
@@ -2503,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",
@@ -2519,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]
@@ -87,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

@@ -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

@@ -57,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")
@@ -76,7 +76,7 @@ device_manager:schedule("0 0 21 */1 * *", function()
tags = { "battery" },
priority = "default",
})
end)
end
--- @class OnPresence
--- @field [integer] fun(presence: boolean)
@@ -742,17 +742,19 @@ devs: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

@@ -10,4 +10,5 @@ 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};
@@ -142,6 +143,8 @@ async fn app() -> anyhow::Result<()> {
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

@@ -37,6 +37,9 @@ pub struct Config {
#[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
}