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

View File

@@ -33,7 +33,6 @@ futures = "0.3.31"
google_home = { path = "./google_home/google_home" } google_home = { path = "./google_home/google_home" }
google_home_macro = { path = "./google_home/google_home_macro" } google_home_macro = { path = "./google_home/google_home_macro" }
hostname = "0.4.1" hostname = "0.4.1"
indexmap = { version = "2.11.0", features = ["serde"] }
inventory = "0.3.21" inventory = "0.3.21"
itertools = "0.14.0" itertools = "0.14.0"
json_value_merge = "2.0.1" json_value_merge = "2.0.1"
@@ -59,10 +58,9 @@ serde_repr = "0.1.20"
syn = { version = "2.0.106" } syn = { version = "2.0.106" }
thiserror = "2.0.16" thiserror = "2.0.16"
tokio = { version = "1", features = ["rt-multi-thread"] } tokio = { version = "1", features = ["rt-multi-thread"] }
tokio-cron-scheduler = "0.14.0" tokio-cron-scheduler = "0.15.0"
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = "0.3.20" tracing-subscriber = "0.3.20"
uuid = "1.18.1"
wakey = "0.3.0" wakey = "0.3.0"
[dependencies] [dependencies]
@@ -70,6 +68,7 @@ anyhow = { workspace = true }
async-trait = { workspace = true } async-trait = { workspace = true }
automation_devices = { workspace = true } automation_devices = { workspace = true }
automation_lib = { workspace = true } automation_lib = { workspace = true }
automation_macro = { path = "./automation_macro" }
axum = { workspace = true } axum = { workspace = true }
config = { version = "0.15.15", default-features = false, features = [ config = { version = "0.15.15", default-features = false, features = [
"async", "async",
@@ -86,6 +85,7 @@ serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tokio = { workspace = true } tokio = { workspace = true }
tokio-cron-scheduler = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
tracing-subscriber = { workspace = true } tracing-subscriber = { workspace = true }

View File

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

View File

@@ -2,6 +2,7 @@ use std::fmt::Debug;
use automation_cast::Cast; use automation_cast::Cast;
use dyn_clone::DynClone; use dyn_clone::DynClone;
use lua_typed::Typed;
use mlua::ObjectLike; use mlua::ObjectLike;
use crate::event::OnMqtt; use crate::event::OnMqtt;
@@ -41,4 +42,10 @@ impl mlua::FromLua for Box<dyn Device> {
} }
impl mlua::UserData 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); dyn_clone::clone_trait_object!(Device);

View File

@@ -1,13 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use futures::Future;
use futures::future::join_all; use futures::future::join_all;
use lua_typed::Typed; use lua_typed::Typed;
use mlua::FromLua; use mlua::FromLua;
use tokio::sync::{RwLock, RwLockReadGuard}; use tokio::sync::{RwLock, RwLockReadGuard};
use tokio_cron_scheduler::{Job, JobScheduler};
use tracing::{debug, instrument, trace}; use tracing::{debug, instrument, trace};
use crate::device::Device; use crate::device::Device;
@@ -19,7 +16,6 @@ pub type DeviceMap = HashMap<String, Box<dyn Device>>;
pub struct DeviceManager { pub struct DeviceManager {
devices: Arc<RwLock<DeviceMap>>, devices: Arc<RwLock<DeviceMap>>,
event_channel: EventChannel, event_channel: EventChannel,
scheduler: JobScheduler,
} }
impl DeviceManager { impl DeviceManager {
@@ -29,7 +25,6 @@ impl DeviceManager {
let device_manager = Self { let device_manager = Self {
devices: Arc::new(RwLock::new(HashMap::new())), devices: Arc::new(RwLock::new(HashMap::new())),
event_channel, event_channel,
scheduler: JobScheduler::new().await.unwrap(),
}; };
tokio::spawn({ tokio::spawn({
@@ -45,8 +40,6 @@ impl DeviceManager {
} }
}); });
device_manager.scheduler.start().await.unwrap();
device_manager device_manager
} }
@@ -105,42 +98,6 @@ impl mlua::UserData for DeviceManager {
Ok(()) 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())) 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 lua;
pub mod messages; pub mod messages;
pub mod mqtt; pub mod mqtt;
pub mod schedule;
type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::Table>; type RegisterFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::Table>;
type DefinitionsFn = fn() -> String; 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", tls = host == "zeus" or host == "hephaestus",
}) })
local devs = {}
function devs:add(device)
table.insert(self, device)
end
local ntfy_topic = secrets.ntfy_topic local ntfy_topic = secrets.ntfy_topic
if ntfy_topic == nil then if ntfy_topic == nil then
error("Ntfy topic is not specified") error("Ntfy topic is not specified")
@@ -37,7 +42,7 @@ end
local ntfy = devices.Ntfy.new({ local ntfy = devices.Ntfy.new({
topic = ntfy_topic, topic = ntfy_topic,
}) })
device_manager:add(ntfy) devs:add(ntfy)
--- @type {[string]: number} --- @type {[string]: number}
local low_battery = {} local low_battery = {}
@@ -52,7 +57,7 @@ local function check_battery(device, battery)
low_battery[id] = nil low_battery[id] = nil
end end
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 -- Don't send notifications if there are now devices with low battery
if next(low_battery) == nil then if next(low_battery) == nil then
print("No devices with low battery") print("No devices with low battery")
@@ -71,7 +76,7 @@ device_manager:schedule("0 0 21 */1 * *", function()
tags = { "battery" }, tags = { "battery" },
priority = "default", priority = "default",
}) })
end) end
--- @class OnPresence --- @class OnPresence
--- @field [integer] fun(presence: boolean) --- @field [integer] fun(presence: boolean)
@@ -92,7 +97,7 @@ local presence_system = devices.Presence.new({
end end
end, end,
}) })
device_manager:add(presence_system) devs:add(presence_system)
on_presence:add(function(presence) on_presence:add(function(presence)
ntfy:send_notification({ ntfy:send_notification({
title = "Presence", title = "Presence",
@@ -166,7 +171,7 @@ local on_light = {}
function on_light:add(f) function on_light:add(f)
self[#self + 1] = f self[#self + 1] = f
end end
device_manager:add(devices.LightSensor.new({ devs:add(devices.LightSensor.new({
identifier = "living_light_sensor", identifier = "living_light_sensor",
topic = mqtt_z2m("living/light"), topic = mqtt_z2m("living/light"),
client = mqtt_client, client = mqtt_client,
@@ -202,7 +207,7 @@ local hue_bridge = devices.HueBridge.new({
darkness = 43, darkness = 43,
}, },
}) })
device_manager:add(hue_bridge) devs:add(hue_bridge)
on_light:add(function(light) on_light:add(function(light)
hue_bridge:set_flag("darkness", not light) hue_bridge:set_flag("darkness", not light)
end) end)
@@ -217,7 +222,7 @@ local kitchen_lights = devices.HueGroup.new({
group_id = 7, group_id = 7,
scene_id = "7MJLG27RzeRAEVJ", scene_id = "7MJLG27RzeRAEVJ",
}) })
device_manager:add(kitchen_lights) devs:add(kitchen_lights)
local living_lights = devices.HueGroup.new({ local living_lights = devices.HueGroup.new({
identifier = "living_lights", identifier = "living_lights",
ip = hue_ip, ip = hue_ip,
@@ -225,7 +230,7 @@ local living_lights = devices.HueGroup.new({
group_id = 1, group_id = 1,
scene_id = "SNZw7jUhQ3cXSjkj", scene_id = "SNZw7jUhQ3cXSjkj",
}) })
device_manager:add(living_lights) devs:add(living_lights)
local living_lights_relax = devices.HueGroup.new({ local living_lights_relax = devices.HueGroup.new({
identifier = "living_lights", identifier = "living_lights",
ip = hue_ip, ip = hue_ip,
@@ -233,9 +238,9 @@ local living_lights_relax = devices.HueGroup.new({
group_id = 1, group_id = 1,
scene_id = "eRJ3fvGHCcb6yNw", 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", name = "Switch",
room = "Living", room = "Living",
client = mqtt_client, client = mqtt_client,
@@ -252,7 +257,7 @@ device_manager:add(devices.HueSwitch.new({
battery_callback = check_battery, battery_callback = check_battery,
})) }))
device_manager:add(devices.WakeOnLAN.new({ devs:add(devices.WakeOnLAN.new({
name = "Zeus", name = "Zeus",
room = "Living Room", room = "Living Room",
topic = mqtt_automation("appliance/living_room/zeus"), topic = mqtt_automation("appliance/living_room/zeus"),
@@ -268,7 +273,7 @@ local living_mixer = devices.OutletOnOff.new({
client = mqtt_client, client = mqtt_client,
}) })
turn_off_when_away(living_mixer) turn_off_when_away(living_mixer)
device_manager:add(living_mixer) devs:add(living_mixer)
local living_speakers = devices.OutletOnOff.new({ local living_speakers = devices.OutletOnOff.new({
name = "Speakers", name = "Speakers",
room = "Living Room", room = "Living Room",
@@ -276,9 +281,9 @@ local living_speakers = devices.OutletOnOff.new({
client = mqtt_client, client = mqtt_client,
}) })
turn_off_when_away(living_speakers) 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", name = "Remote",
room = "Living Room", room = "Living Room",
client = mqtt_client, client = mqtt_client,
@@ -329,14 +334,14 @@ local kettle = devices.OutletPower.new({
callback = kettle_timeout(), callback = kettle_timeout(),
}) })
turn_off_when_away(kettle) turn_off_when_away(kettle)
device_manager:add(kettle) devs:add(kettle)
--- @param on boolean --- @param on boolean
local function set_kettle(_, on) local function set_kettle(_, on)
kettle:set_on(on) kettle:set_on(on)
end end
device_manager:add(devices.IkeaRemote.new({ devs:add(devices.IkeaRemote.new({
name = "Remote", name = "Remote",
room = "Bedroom", room = "Bedroom",
client = mqtt_client, client = mqtt_client,
@@ -346,7 +351,7 @@ device_manager:add(devices.IkeaRemote.new({
battery_callback = check_battery, battery_callback = check_battery,
})) }))
device_manager:add(devices.IkeaRemote.new({ devs:add(devices.IkeaRemote.new({
name = "Remote", name = "Remote",
room = "Kitchen", room = "Kitchen",
client = mqtt_client, client = mqtt_client,
@@ -379,9 +384,9 @@ local bathroom_light = devices.LightOnOff.new({
client = mqtt_client, client = mqtt_client,
callback = off_timeout(debug and 60 or 45 * 60), 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", identifier = "bathroom_washer",
topic = mqtt_z2m("bathroom/washer"), topic = mqtt_z2m("bathroom/washer"),
client = mqtt_client, client = mqtt_client,
@@ -396,7 +401,7 @@ device_manager:add(devices.Washer.new({
end, end,
})) }))
device_manager:add(devices.OutletOnOff.new({ devs:add(devices.OutletOnOff.new({
name = "Charger", name = "Charger",
room = "Workbench", room = "Workbench",
topic = mqtt_z2m("workbench/charger"), topic = mqtt_z2m("workbench/charger"),
@@ -411,7 +416,7 @@ local workbench_outlet = devices.OutletOnOff.new({
client = mqtt_client, client = mqtt_client,
}) })
turn_off_when_away(workbench_outlet) turn_off_when_away(workbench_outlet)
device_manager:add(workbench_outlet) devs:add(workbench_outlet)
local workbench_light = devices.LightColorTemperature.new({ local workbench_light = devices.LightColorTemperature.new({
name = "Light", name = "Light",
@@ -420,10 +425,10 @@ local workbench_light = devices.LightColorTemperature.new({
client = mqtt_client, client = mqtt_client,
}) })
turn_off_when_away(workbench_light) turn_off_when_away(workbench_light)
device_manager:add(workbench_light) devs:add(workbench_light)
local delay_color_temp = utils.Timeout.new() local delay_color_temp = utils.Timeout.new()
device_manager:add(devices.IkeaRemote.new({ devs:add(devices.IkeaRemote.new({
name = "Remote", name = "Remote",
room = "Workbench", room = "Workbench",
client = mqtt_client, client = mqtt_client,
@@ -453,7 +458,7 @@ local hallway_top_light = devices.HueGroup.new({
group_id = 83, group_id = 83,
scene_id = "QeufkFDICEHWeKJ7", scene_id = "QeufkFDICEHWeKJ7",
}) })
device_manager:add(devices.HueSwitch.new({ devs:add(devices.HueSwitch.new({
name = "SwitchBottom", name = "SwitchBottom",
room = "Hallway", room = "Hallway",
client = mqtt_client, client = mqtt_client,
@@ -463,7 +468,7 @@ device_manager:add(devices.HueSwitch.new({
end, end,
battery_callback = check_battery, battery_callback = check_battery,
})) }))
device_manager:add(devices.HueSwitch.new({ devs:add(devices.HueSwitch.new({
name = "SwitchTop", name = "SwitchTop",
room = "Hallway", room = "Hallway",
client = mqtt_client, client = mqtt_client,
@@ -546,7 +551,7 @@ local hallway_storage = devices.LightBrightness.new({
callback = hallway_light_automation:light_callback(), callback = hallway_light_automation:light_callback(),
}) })
turn_off_when_away(hallway_storage) turn_off_when_away(hallway_storage)
device_manager:add(hallway_storage) devs:add(hallway_storage)
local hallway_bottom_lights = devices.HueGroup.new({ local hallway_bottom_lights = devices.HueGroup.new({
identifier = "hallway_bottom_lights", identifier = "hallway_bottom_lights",
@@ -555,7 +560,7 @@ local hallway_bottom_lights = devices.HueGroup.new({
group_id = 81, group_id = 81,
scene_id = "3qWKxGVadXFFG4o", scene_id = "3qWKxGVadXFFG4o",
}) })
device_manager:add(hallway_bottom_lights) devs:add(hallway_bottom_lights)
hallway_light_automation.group = { hallway_light_automation.group = {
set_on = function(on) set_on = function(on)
@@ -591,7 +596,7 @@ local function presence(duration)
end end
end end
device_manager:add(devices.IkeaRemote.new({ devs:add(devices.IkeaRemote.new({
name = "Remote", name = "Remote",
room = "Hallway", room = "Hallway",
client = mqtt_client, client = mqtt_client,
@@ -611,7 +616,7 @@ local hallway_frontdoor = devices.ContactSensor.new({
}, },
battery_callback = check_battery, battery_callback = check_battery,
}) })
device_manager:add(hallway_frontdoor) devs:add(hallway_frontdoor)
window_sensors:add(hallway_frontdoor) window_sensors:add(hallway_frontdoor)
hallway_light_automation.door = hallway_frontdoor hallway_light_automation.door = hallway_frontdoor
@@ -624,7 +629,7 @@ local hallway_trash = devices.ContactSensor.new({
callback = hallway_light_automation:trash_callback(), callback = hallway_light_automation:trash_callback(),
battery_callback = check_battery, battery_callback = check_battery,
}) })
device_manager:add(hallway_trash) devs:add(hallway_trash)
hallway_light_automation.trash = hallway_trash hallway_light_automation.trash = hallway_trash
local guest_light = devices.LightOnOff.new({ local guest_light = devices.LightOnOff.new({
@@ -634,14 +639,14 @@ local guest_light = devices.LightOnOff.new({
client = mqtt_client, client = mqtt_client,
}) })
turn_off_when_away(guest_light) turn_off_when_away(guest_light)
device_manager:add(guest_light) devs:add(guest_light)
local bedroom_air_filter = devices.AirFilter.new({ local bedroom_air_filter = devices.AirFilter.new({
name = "Air Filter", name = "Air Filter",
room = "Bedroom", room = "Bedroom",
url = "http://10.0.0.103", url = "http://10.0.0.103",
}) })
device_manager:add(bedroom_air_filter) devs:add(bedroom_air_filter)
local bedroom_lights = devices.HueGroup.new({ local bedroom_lights = devices.HueGroup.new({
identifier = "bedroom_lights", identifier = "bedroom_lights",
@@ -650,7 +655,7 @@ local bedroom_lights = devices.HueGroup.new({
group_id = 3, group_id = 3,
scene_id = "PvRs-lGD4VRytL9", scene_id = "PvRs-lGD4VRytL9",
}) })
device_manager:add(bedroom_lights) devs:add(bedroom_lights)
local bedroom_lights_relax = devices.HueGroup.new({ local bedroom_lights_relax = devices.HueGroup.new({
identifier = "bedroom_lights", identifier = "bedroom_lights",
ip = hue_ip, ip = hue_ip,
@@ -658,9 +663,9 @@ local bedroom_lights_relax = devices.HueGroup.new({
group_id = 3, group_id = 3,
scene_id = "60tfTyR168v2csz", 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", name = "Switch",
room = "Bedroom", room = "Bedroom",
client = mqtt_client, client = mqtt_client,
@@ -682,7 +687,7 @@ local balcony = devices.ContactSensor.new({
client = mqtt_client, client = mqtt_client,
battery_callback = check_battery, battery_callback = check_battery,
}) })
device_manager:add(balcony) devs:add(balcony)
window_sensors:add(balcony) window_sensors:add(balcony)
local living_window = devices.ContactSensor.new({ local living_window = devices.ContactSensor.new({
name = "Window", name = "Window",
@@ -691,7 +696,7 @@ local living_window = devices.ContactSensor.new({
client = mqtt_client, client = mqtt_client,
battery_callback = check_battery, battery_callback = check_battery,
}) })
device_manager:add(living_window) devs:add(living_window)
window_sensors:add(living_window) window_sensors:add(living_window)
local bedroom_window = devices.ContactSensor.new({ local bedroom_window = devices.ContactSensor.new({
name = "Window", name = "Window",
@@ -700,7 +705,7 @@ local bedroom_window = devices.ContactSensor.new({
client = mqtt_client, client = mqtt_client,
battery_callback = check_battery, battery_callback = check_battery,
}) })
device_manager:add(bedroom_window) devs:add(bedroom_window)
window_sensors:add(bedroom_window) window_sensors:add(bedroom_window)
local guest_window = devices.ContactSensor.new({ local guest_window = devices.ContactSensor.new({
name = "Window", name = "Window",
@@ -709,7 +714,7 @@ local guest_window = devices.ContactSensor.new({
client = mqtt_client, client = mqtt_client,
battery_callback = check_battery, battery_callback = check_battery,
}) })
device_manager:add(guest_window) devs:add(guest_window)
window_sensors:add(guest_window) window_sensors:add(guest_window)
local storage_light = devices.LightBrightness.new({ local storage_light = devices.LightBrightness.new({
@@ -719,9 +724,9 @@ local storage_light = devices.LightBrightness.new({
client = mqtt_client, client = mqtt_client,
}) })
turn_off_when_away(storage_light) 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", name = "Door",
room = "Storage", room = "Storage",
sensor_type = "Door", sensor_type = "Door",
@@ -737,16 +742,19 @@ device_manager:add(devices.ContactSensor.new({
battery_callback = check_battery, 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 ---@type Config
return { return {
fulfillment = { fulfillment = {
openid_url = "https://login.huizinga.dev/api/oidc", 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 ---@param device DeviceInterface
function DeviceManager:add(device) end function DeviceManager:add(device) end
---@param cron string
---@param callback fun()
function DeviceManager:schedule(cron, callback) end
return DeviceManager return DeviceManager

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::net::{Ipv4Addr, SocketAddr}; use std::net::{Ipv4Addr, SocketAddr};
use automation_lib::device::Device;
use automation_macro::LuaDeviceConfig;
use lua_typed::Typed; use lua_typed::Typed;
use serde::Deserialize; use serde::Deserialize;
@@ -29,9 +31,15 @@ pub struct FulfillmentConfig {
pub port: u16, pub port: u16,
} }
#[derive(Debug, Deserialize, Typed)] #[derive(Debug, LuaDeviceConfig, Typed)]
pub struct Config { pub struct Config {
pub fulfillment: FulfillmentConfig, 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 { impl From<FulfillmentConfig> for SocketAddr {

View File

@@ -1,4 +1,5 @@
pub mod config; pub mod config;
pub mod schedule;
pub mod secret; pub mod secret;
pub mod version; pub mod version;
pub mod web; 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
}