Compare commits
5 Commits
master
...
18467c5541
| Author | SHA1 | Date | |
|---|---|---|---|
|
18467c5541
|
|||
|
9a5e7694c3
|
|||
|
b30988f869
|
|||
|
aacb11aba3
|
|||
|
0ef3f33746
|
@@ -1,2 +1,5 @@
|
|||||||
[env]
|
[env]
|
||||||
RUST_LOG = "automation=debug"
|
RUST_LOG = "automation=debug"
|
||||||
|
|
||||||
|
[target.x86_64-unknown-linux-musl]
|
||||||
|
rustflags = ["-C", "link-arg=-lc"]
|
||||||
|
|||||||
@@ -2,3 +2,5 @@
|
|||||||
.env
|
.env
|
||||||
# Use the rust environment provided by the container
|
# Use the rust environment provided by the container
|
||||||
rust-toolchain.toml
|
rust-toolchain.toml
|
||||||
|
Dockerfile
|
||||||
|
docker-bake.hcl
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ jobs:
|
|||||||
-e AUTOMATION__SECRETS__MQTT_PASSWORD=${{ secrets.MQTT_PASSWORD }} \
|
-e AUTOMATION__SECRETS__MQTT_PASSWORD=${{ secrets.MQTT_PASSWORD }} \
|
||||||
-e AUTOMATION__SECRETS__HUE_TOKEN=${{ secrets.HUE_TOKEN }} \
|
-e AUTOMATION__SECRETS__HUE_TOKEN=${{ secrets.HUE_TOKEN }} \
|
||||||
-e AUTOMATION__SECRETS__NTFY_TOPIC=${{ secrets.NTFY_TOPIC }} \
|
-e AUTOMATION__SECRETS__NTFY_TOPIC=${{ secrets.NTFY_TOPIC }} \
|
||||||
|
-e AUTOMATION__SECRETS__PRINTER_DEVICE_ID=${{ secrets.PRINTER_DEVICE_ID }} \
|
||||||
|
-e AUTOMATION__SECRETS__PRINTER_ACCESS_CODE=${{ secrets.PRINTER_ACCESS_CODE }} \
|
||||||
$(echo ${{ toJSON(needs.build.outputs.images) }} | jq .automation -r)
|
$(echo ${{ toJSON(needs.build.outputs.images) }} | jq .automation -r)
|
||||||
|
|
||||||
docker network connect web automation_rs
|
docker network connect web automation_rs
|
||||||
|
|||||||
Generated
+141
-2
@@ -42,6 +42,18 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-channel"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"event-listener-strategy",
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.89"
|
version = "0.1.89"
|
||||||
@@ -105,6 +117,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"automation_lib",
|
"automation_lib",
|
||||||
"automation_macro",
|
"automation_macro",
|
||||||
|
"bambulab",
|
||||||
"bytes",
|
"bytes",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"eui48",
|
"eui48",
|
||||||
@@ -138,12 +151,14 @@ dependencies = [
|
|||||||
"inventory",
|
"inventory",
|
||||||
"lua_typed",
|
"lua_typed",
|
||||||
"mlua",
|
"mlua",
|
||||||
|
"reqwest",
|
||||||
"rumqttc",
|
"rumqttc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"webpki-root-certs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -231,6 +246,20 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bambulab"
|
||||||
|
version = "0.4.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d25534ead4a23a9ecee1530f1169246eabc2602ab704ffc57d41b59811167d83"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"nanoid",
|
||||||
|
"paho-mqtt",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -349,6 +378,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "concurrent-queue"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.15.22"
|
version = "0.15.22"
|
||||||
@@ -398,6 +436,21 @@ dependencies = [
|
|||||||
"strum",
|
"strum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.11"
|
version = "0.20.11"
|
||||||
@@ -576,6 +629,27 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||||
|
dependencies = [
|
||||||
|
"concurrent-queue",
|
||||||
|
"parking",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "event-listener-strategy"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||||
|
dependencies = [
|
||||||
|
"event-listener",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.9"
|
version = "0.1.9"
|
||||||
@@ -697,6 +771,12 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-timer"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -1402,6 +1482,15 @@ dependencies = [
|
|||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nanoid"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.50.3"
|
version = "0.50.3"
|
||||||
@@ -1443,6 +1532,18 @@ 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 = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.117"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered-float"
|
name = "ordered-float"
|
||||||
version = "2.10.1"
|
version = "2.10.1"
|
||||||
@@ -1452,6 +1553,38 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paho-mqtt"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23c6e899549be260b8c8d7fb6908d72eacb9a1e10229c7e294a7a8ff1c768620"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"futures",
|
||||||
|
"futures-timer",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"paho-mqtt-sys",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paho-mqtt-sys"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00e508bdadfa0d58d67792060fa447ec20729db5dc76e3dc7de9f4d29e521a43"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@@ -2577,6 +2710,12 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wakey"
|
name = "wakey"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -2741,9 +2880,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "webpki-root-certs"
|
name = "webpki-root-certs"
|
||||||
version = "1.0.7"
|
version = "1.0.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
|
checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
]
|
]
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,4 @@
|
|||||||
FROM rust:1.95 AS base
|
FROM rust:1.95-alpine AS base
|
||||||
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
|
||||||
RUN cargo install cargo-chef --locked --version 0.1.71 && \
|
RUN cargo install cargo-chef --locked --version 0.1.71 && \
|
||||||
cargo install cargo-auditable --locked --version 0.6.6
|
cargo install cargo-auditable --locked --version 0.6.6
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -10,6 +9,7 @@ COPY . .
|
|||||||
RUN cargo chef prepare --recipe-path recipe.json
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
|
RUN apk add --no-cache g++=15.2.0-r2 cmake=4.1.3-r0 make=4.4.1-r3 openssl-dev=3.5.7-r0 openssl-libs-static=3.5.7-r0
|
||||||
# HACK: Now we can use unstable feature while on stable rust!
|
# HACK: Now we can use unstable feature while on stable rust!
|
||||||
ENV RUSTC_BOOTSTRAP=1
|
ENV RUSTC_BOOTSTRAP=1
|
||||||
COPY --from=planner /app/recipe.json recipe.json
|
COPY --from=planner /app/recipe.json recipe.json
|
||||||
@@ -20,7 +20,8 @@ ARG RELEASE_VERSION
|
|||||||
ENV RELEASE_VERSION=${RELEASE_VERSION}
|
ENV RELEASE_VERSION=${RELEASE_VERSION}
|
||||||
RUN cargo auditable build --release
|
RUN cargo auditable build --release
|
||||||
|
|
||||||
FROM gcr.io/distroless/cc-debian13:nonroot AS runtime
|
|
||||||
|
FROM scratch AS runtime
|
||||||
COPY --from=builder /app/target/release/automation /app/automation
|
COPY --from=builder /app/target/release/automation /app/automation
|
||||||
ENV AUTOMATION__ENTRYPOINT=/app/config/config.lua
|
ENV AUTOMATION__ENTRYPOINT=/app/config/config.lua
|
||||||
ENV LUA_PATH="/app/?.lua;;"
|
ENV LUA_PATH="/app/?.lua;;"
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ thiserror = { workspace = true }
|
|||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
wakey = { workspace = true }
|
wakey = { workspace = true }
|
||||||
|
bambulab = { version = "0.4.30", default-features = false }
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::config::InfoConfig;
|
use automation_lib::config::InfoConfig;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
|
use automation_lib::reqwest::new_client;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use google_home::device::Name;
|
use google_home::device::Name;
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
@@ -52,20 +53,21 @@ impl AirFilter {
|
|||||||
async fn set_fan_speed(&self, speed: air_filter_types::FanSpeed) -> Result<(), Error> {
|
async fn set_fan_speed(&self, speed: air_filter_types::FanSpeed) -> Result<(), Error> {
|
||||||
let message = air_filter_types::SetFanSpeed::new(speed);
|
let message = air_filter_types::SetFanSpeed::new(speed);
|
||||||
let url = format!("{}/state/fan", self.config.url);
|
let url = format!("{}/state/fan", self.config.url);
|
||||||
let client = reqwest::Client::new();
|
new_client().put(url).json(&message).send().await?;
|
||||||
client.put(url).json(&message).send().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_fan_state(&self) -> Result<air_filter_types::FanState, Error> {
|
async fn get_fan_state(&self) -> Result<air_filter_types::FanState, Error> {
|
||||||
let url = format!("{}/state/fan", self.config.url);
|
let url = format!("{}/state/fan", self.config.url);
|
||||||
Ok(reqwest::get(url).await?.json().await?)
|
let client = new_client();
|
||||||
|
Ok(client.get(url).send().await?.json().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_sensor_data(&self) -> Result<air_filter_types::SensorData, Error> {
|
async fn get_sensor_data(&self) -> Result<air_filter_types::SensorData, Error> {
|
||||||
let url = format!("{}/state/sensor", self.config.url);
|
let url = format!("{}/state/sensor", self.config.url);
|
||||||
Ok(reqwest::get(url).await?.json().await?)
|
let client = new_client();
|
||||||
|
Ok(client.get(url).send().await?.json().await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
use std::convert::Infallible;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use automation_lib::action_callback::ActionCallback;
|
||||||
|
use automation_lib::device::Device;
|
||||||
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
|
use bambulab::client::Client;
|
||||||
|
use bambulab::{Command, Message};
|
||||||
|
use google_home::errors::{self};
|
||||||
|
use google_home::traits::OnOff;
|
||||||
|
use lua_typed::Typed;
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
|
use crate::{DebugWrap, LuaDeviceCreate};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed, Default)]
|
||||||
|
#[typed(as = "BambuCallbacks")]
|
||||||
|
pub struct Callbacks {
|
||||||
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
|
pub state: ActionCallback<Bambu>,
|
||||||
|
#[device_config(from_lua, default)]
|
||||||
|
#[typed(default)]
|
||||||
|
pub connected: ActionCallback<Bambu>,
|
||||||
|
}
|
||||||
|
crate::register_type!(Callbacks);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, LuaDeviceConfig, Typed)]
|
||||||
|
#[typed(as = "BambuConfig")]
|
||||||
|
pub struct Config {
|
||||||
|
pub host: String,
|
||||||
|
pub device_id: String,
|
||||||
|
pub access_code: String,
|
||||||
|
#[device_config(from_lua, default)]
|
||||||
|
pub callbacks: Callbacks,
|
||||||
|
}
|
||||||
|
crate::register_type!(Config);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Device)]
|
||||||
|
#[device(traits(OnOff))]
|
||||||
|
pub struct Bambu {
|
||||||
|
config: Config,
|
||||||
|
|
||||||
|
client: DebugWrap<Client>,
|
||||||
|
|
||||||
|
state: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
crate::register_device!(Bambu);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LuaDeviceCreate for Bambu {
|
||||||
|
type Config = Config;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
async fn create(config: Self::Config) -> Result<Self, Infallible> {
|
||||||
|
trace!(id = config.device_id, "Setting up bambu");
|
||||||
|
|
||||||
|
let (tx, mut rx) = tokio::sync::broadcast::channel(25);
|
||||||
|
let client = Client::new(&config.host, &config.access_code, &config.device_id, tx);
|
||||||
|
|
||||||
|
let state = Arc::new(AtomicBool::new(false));
|
||||||
|
let bambu = Self {
|
||||||
|
config,
|
||||||
|
client: DebugWrap(client.clone()),
|
||||||
|
state: state.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let mut bambu = bambu.clone();
|
||||||
|
async move {
|
||||||
|
// The printer might be offline so periodically try to reconnecct
|
||||||
|
loop {
|
||||||
|
bambu.client.run().await.ok();
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let bambu = bambu.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
let message = rx.recv().await.unwrap();
|
||||||
|
|
||||||
|
match message {
|
||||||
|
Message::Print(data) => 'print: {
|
||||||
|
// Extract the state of the chamber light
|
||||||
|
let Some(light_report) = data.print.lights_report else {
|
||||||
|
break 'print;
|
||||||
|
};
|
||||||
|
|
||||||
|
let on = light_report
|
||||||
|
.iter()
|
||||||
|
.find(|report| report.node == "chamber_light")
|
||||||
|
.map(|report| report.mode == "on")
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
state.store(on, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
|
bambu.config.callbacks.state.call(bambu.clone()).await;
|
||||||
|
}
|
||||||
|
Message::Connected => {
|
||||||
|
debug!(id = bambu.config.device_id, "Connected");
|
||||||
|
client.publish(Command::PushAll).await.unwrap();
|
||||||
|
|
||||||
|
bambu.config.callbacks.connected.call(bambu.clone()).await;
|
||||||
|
}
|
||||||
|
// Ignore everything else
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(bambu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device for Bambu {
|
||||||
|
fn get_id(&self) -> String {
|
||||||
|
self.config.device_id.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl OnOff for Bambu {
|
||||||
|
async fn on(&self) -> Result<bool, errors::ErrorCode> {
|
||||||
|
Ok(self.state.load(std::sync::atomic::Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_on(&self, on: bool) -> Result<(), errors::ErrorCode> {
|
||||||
|
// NOTE: This will error in case the printer is offline, but we don't really care in that
|
||||||
|
// case so we just ignore the error
|
||||||
|
self.client.publish(Command::SetChamberLight(on)).await.ok();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ use std::net::SocketAddr;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::lua::traits::PartialUserData;
|
use automation_lib::lua::traits::PartialUserData;
|
||||||
|
use automation_lib::reqwest::new_client;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use lua_typed::Typed;
|
use lua_typed::Typed;
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
@@ -98,7 +99,7 @@ impl HueBridge {
|
|||||||
);
|
);
|
||||||
|
|
||||||
trace!(?flag, flag_id, value, "Sending request to change flag");
|
trace!(?flag, flag_id, value, "Sending request to change flag");
|
||||||
let res = reqwest::Client::new()
|
let res = new_client()
|
||||||
.put(url)
|
.put(url)
|
||||||
.json(&FlagMessage { flag: value })
|
.json(&FlagMessage { flag: value })
|
||||||
.send()
|
.send()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::net::SocketAddr;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::lua::traits::PartialUserData;
|
use automation_lib::lua::traits::PartialUserData;
|
||||||
|
use automation_lib::reqwest::new_client;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use google_home::errors::ErrorCode;
|
use google_home::errors::ErrorCode;
|
||||||
use google_home::traits::OnOff;
|
use google_home::traits::OnOff;
|
||||||
@@ -74,7 +75,7 @@ impl OnOff for HueGroup {
|
|||||||
message::Action::on(false)
|
message::Action::on(false)
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = reqwest::Client::new()
|
let res = new_client()
|
||||||
.put(self.url_set_action())
|
.put(self.url_set_action())
|
||||||
.json(&message)
|
.json(&message)
|
||||||
.send()
|
.send()
|
||||||
@@ -94,10 +95,7 @@ impl OnOff for HueGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn on(&self) -> Result<bool, ErrorCode> {
|
async fn on(&self) -> Result<bool, ErrorCode> {
|
||||||
let res = reqwest::Client::new()
|
let res = new_client().get(self.url_get_state()).send().await;
|
||||||
.get(self.url_get_state())
|
|
||||||
.send()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
@@ -128,10 +126,7 @@ struct AllOn;
|
|||||||
impl PartialUserData<HueGroup> for AllOn {
|
impl PartialUserData<HueGroup> for AllOn {
|
||||||
fn add_methods<M: mlua::UserDataMethods<HueGroup>>(methods: &mut M) {
|
fn add_methods<M: mlua::UserDataMethods<HueGroup>>(methods: &mut M) {
|
||||||
methods.add_async_method("all_on", async |_lua, this, ()| {
|
methods.add_async_method("all_on", async |_lua, this, ()| {
|
||||||
let res = reqwest::Client::new()
|
let res = new_client().get(this.url_get_state()).send().await;
|
||||||
.get(this.url_get_state())
|
|
||||||
.send()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(res) => {
|
Ok(res) => {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
#![feature(debug_closure_helpers)]
|
||||||
#![feature(iter_intersperse)]
|
#![feature(iter_intersperse)]
|
||||||
mod air_filter;
|
mod air_filter;
|
||||||
|
mod bambu;
|
||||||
mod contact_sensor;
|
mod contact_sensor;
|
||||||
mod hue_bridge;
|
mod hue_bridge;
|
||||||
mod hue_group;
|
mod hue_group;
|
||||||
@@ -13,6 +15,9 @@ mod wake_on_lan;
|
|||||||
mod washer;
|
mod washer;
|
||||||
mod zigbee;
|
mod zigbee;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use automation_lib::Module;
|
use automation_lib::Module;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
@@ -20,6 +25,31 @@ use tracing::{debug, warn};
|
|||||||
type DeviceNameFn = fn() -> String;
|
type DeviceNameFn = fn() -> String;
|
||||||
type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
|
type RegisterDeviceFn = fn(lua: &mlua::Lua) -> mlua::Result<mlua::AnyUserData>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct DebugWrap<T: Clone>(T);
|
||||||
|
|
||||||
|
impl<T: Clone> DerefMut for DebugWrap<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> Deref for DebugWrap<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> fmt::Debug for DebugWrap<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("DebugWrap")
|
||||||
|
.field_with(|f| f.write_str(stringify!(T)))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RegisteredDevice {
|
pub struct RegisteredDevice {
|
||||||
name_fn: DeviceNameFn,
|
name_fn: DeviceNameFn,
|
||||||
register_fn: RegisterDeviceFn,
|
register_fn: RegisterDeviceFn,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::convert::Infallible;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use automation_lib::device::{Device, LuaDeviceCreate};
|
use automation_lib::device::{Device, LuaDeviceCreate};
|
||||||
use automation_lib::lua::traits::PartialUserData;
|
use automation_lib::lua::traits::PartialUserData;
|
||||||
|
use automation_lib::reqwest::new_client;
|
||||||
use automation_macro::{Device, LuaDeviceConfig};
|
use automation_macro::{Device, LuaDeviceConfig};
|
||||||
use lua_typed::Typed;
|
use lua_typed::Typed;
|
||||||
use mlua::LuaSerdeExt;
|
use mlua::LuaSerdeExt;
|
||||||
@@ -143,7 +144,7 @@ impl Ntfy {
|
|||||||
let notification = notification.finalize(&self.config.topic);
|
let notification = notification.finalize(&self.config.topic);
|
||||||
|
|
||||||
// Create the request
|
// Create the request
|
||||||
let res = reqwest::Client::new()
|
let res = new_client()
|
||||||
.post(self.config.url.clone())
|
.post(self.config.url.clone())
|
||||||
.json(¬ification)
|
.json(¬ification)
|
||||||
.send()
|
.send()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
reqwest = { workspace = true }
|
||||||
automation_macro = { workspace = true }
|
automation_macro = { workspace = true }
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
automation_cast = { workspace = true }
|
automation_cast = { workspace = true }
|
||||||
@@ -21,3 +22,4 @@ serde_json = { workspace = true }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
webpki-root-certs = "1.0.8"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod helpers;
|
|||||||
pub mod lua;
|
pub mod lua;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod mqtt;
|
pub mod mqtt;
|
||||||
|
pub mod reqwest;
|
||||||
|
|
||||||
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;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use reqwest::{Certificate, Client};
|
||||||
|
|
||||||
|
pub fn new_client() -> Client {
|
||||||
|
Client::builder()
|
||||||
|
.user_agent(format!(
|
||||||
|
"{}/{}",
|
||||||
|
std::env!("CARGO_PKG_NAME"),
|
||||||
|
std::env!("CARGO_PKG_VERSION")
|
||||||
|
))
|
||||||
|
.tls_certs_only(
|
||||||
|
webpki_root_certs::TLS_SERVER_ROOT_CERTS
|
||||||
|
.iter()
|
||||||
|
.map(|cert| Certificate::from_der(cert).unwrap()),
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.expect("Client should build")
|
||||||
|
}
|
||||||
@@ -4,15 +4,38 @@ local helper = require("config.helper")
|
|||||||
local presence = require("config.presence")
|
local presence = require("config.presence")
|
||||||
local windows = require("config.windows")
|
local windows = require("config.windows")
|
||||||
|
|
||||||
|
local secrets = require("automation:secrets")
|
||||||
|
|
||||||
--- @type Module
|
--- @type Module
|
||||||
local module = {}
|
local module = {}
|
||||||
|
|
||||||
function module.setup(mqtt_client)
|
function module.setup(mqtt_client)
|
||||||
local light = devices.LightOnOff.new({
|
local light = nil
|
||||||
|
|
||||||
|
print(secrets.printer_device_id)
|
||||||
|
print(secrets.printer_access_code)
|
||||||
|
|
||||||
|
local bambu = devices.Bambu.new({
|
||||||
|
host = "thalia.huizinga.lan",
|
||||||
|
device_id = secrets.printer_device_id,
|
||||||
|
access_code = secrets.printer_access_code,
|
||||||
|
callbacks = {
|
||||||
|
connected = function(self)
|
||||||
|
if light ~= nil then
|
||||||
|
self:set_on(light:on())
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
light = devices.LightOnOff.new({
|
||||||
name = "Light",
|
name = "Light",
|
||||||
room = "Guest Room",
|
room = "Guest Room",
|
||||||
topic = helper.mqtt_z2m("guest/light"),
|
topic = helper.mqtt_z2m("guest/light"),
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
|
callback = function(_, state)
|
||||||
|
bambu:set_on(state.state)
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
presence.turn_off_when_away(light)
|
presence.turn_off_when_away(light)
|
||||||
|
|
||||||
@@ -37,6 +60,7 @@ function module.setup(mqtt_client)
|
|||||||
light,
|
light,
|
||||||
window,
|
window,
|
||||||
printer,
|
printer,
|
||||||
|
bambu,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,25 @@ function devices.AirFilter.new(config) end
|
|||||||
---@field url string
|
---@field url string
|
||||||
local AirFilterConfig
|
local AirFilterConfig
|
||||||
|
|
||||||
|
---@class Bambu: DeviceInterface, OnOffInterface
|
||||||
|
local Bambu
|
||||||
|
devices.Bambu = {}
|
||||||
|
---@param config BambuConfig
|
||||||
|
---@return Bambu
|
||||||
|
function devices.Bambu.new(config) end
|
||||||
|
|
||||||
|
---@class BambuCallbacks
|
||||||
|
---@field state (fun(_: Bambu) | fun(_: Bambu)[])?
|
||||||
|
---@field connected (fun(_: Bambu) | fun(_: Bambu)[])?
|
||||||
|
local BambuCallbacks
|
||||||
|
|
||||||
|
---@class BambuConfig
|
||||||
|
---@field host string
|
||||||
|
---@field device_id string
|
||||||
|
---@field access_code string
|
||||||
|
---@field callbacks BambuCallbacks
|
||||||
|
local BambuConfig
|
||||||
|
|
||||||
---@class ConfigLightLightStateBrightness
|
---@class ConfigLightLightStateBrightness
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field room (string)?
|
---@field room (string)?
|
||||||
|
|||||||
+3
-1
@@ -5,7 +5,9 @@ group "default" {
|
|||||||
targets = ["automation"]
|
targets = ["automation"]
|
||||||
}
|
}
|
||||||
|
|
||||||
target "docker-metadata-action" {}
|
target "docker-metadata-action" {
|
||||||
|
tags = []
|
||||||
|
}
|
||||||
|
|
||||||
target "automation" {
|
target "automation" {
|
||||||
inherits = ["docker-metadata-action"]
|
inherits = ["docker-metadata-action"]
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
use std::result;
|
use std::result;
|
||||||
|
|
||||||
|
use automation_lib::reqwest::new_client;
|
||||||
use axum::extract::{FromRef, FromRequestParts};
|
use axum::extract::{FromRef, FromRequestParts};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::http::request::Parts;
|
use axum::http::request::Parts;
|
||||||
@@ -93,7 +94,7 @@ where
|
|||||||
// TODO: Do some discovery to find the correct url for this instead of assuming
|
// TODO: Do some discovery to find the correct url for this instead of assuming
|
||||||
// TODO: I think we can also just run Authlia in front of the endpoint instead
|
// TODO: I think we can also just run Authlia in front of the endpoint instead
|
||||||
// This would then give us a header containing the logged in user info?
|
// This would then give us a header containing the logged in user info?
|
||||||
let mut req = reqwest::Client::new().get(format!("{}/userinfo", openid_url));
|
let mut req = new_client().get(format!("{}/userinfo", openid_url));
|
||||||
|
|
||||||
// Add auth header to the request if it exists
|
// Add auth header to the request if it exists
|
||||||
if let Some(auth) = parts.headers.get(axum::http::header::AUTHORIZATION) {
|
if let Some(auth) = parts.headers.get(axum::http::header::AUTHORIZATION) {
|
||||||
|
|||||||
Reference in New Issue
Block a user