Compare commits
2 Commits
master
..
ce7f37144e
| Author | SHA1 | Date | |
|---|---|---|---|
|
ce7f37144e
|
|||
|
b87f27f149
|
@@ -9,7 +9,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
uses: dreaded_x/workflows/.gitea/workflows/docker-kubernetes.yaml@ef78704b98c72e4a6b8340f9bff7b085a7bdd95c
|
uses: dreaded_x/workflows/.gitea/workflows/docker-kubernetes.yaml@c50a935d1f1745f8c2775e828e57fc486048188c
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
push_manifests: false
|
push_manifests: false
|
||||||
@@ -26,10 +26,6 @@ jobs:
|
|||||||
docker stop automation_rs || true
|
docker stop automation_rs || true
|
||||||
docker rm automation_rs || true
|
docker rm automation_rs || true
|
||||||
|
|
||||||
- name: Login to registry
|
|
||||||
run: |
|
|
||||||
docker login git.huizinga.dev -u ${{ gitea.actor }} -p ${{ secrets.REGISTRY_TOKEN }} \
|
|
||||||
|
|
||||||
- name: Create container
|
- name: Create container
|
||||||
run: |
|
run: |
|
||||||
docker create \
|
docker create \
|
||||||
@@ -41,7 +37,7 @@ 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 }} \
|
||||||
$(echo ${{ toJSON(needs.build.outputs.images) }} | jq .automation -r)
|
git.huizinga.dev/dreaded_x/automation_rs@${{ needs.build.outputs.digest }}
|
||||||
|
|
||||||
docker network connect web automation_rs
|
docker network connect web automation_rs
|
||||||
|
|
||||||
|
|||||||
Generated
+575
-908
File diff suppressed because it is too large
Load Diff
+21
-21
@@ -16,28 +16,28 @@ members = [
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
air_filter_types = { git = "https://git.huizinga.dev/Dreaded_X/airfilter", tag = "v0.4.4" }
|
air_filter_types = { git = "https://git.huizinga.dev/Dreaded_X/airfilter", tag = "v0.4.4" }
|
||||||
anyhow = "1.0.102"
|
anyhow = "1.0.99"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
automation_cast = { path = "./automation_cast" }
|
automation_cast = { path = "./automation_cast" }
|
||||||
automation_devices = { path = "./automation_devices" }
|
automation_devices = { path = "./automation_devices" }
|
||||||
automation_lib = { path = "./automation_lib" }
|
automation_lib = { path = "./automation_lib" }
|
||||||
automation_macro = { path = "./automation_macro" }
|
automation_macro = { path = "./automation_macro" }
|
||||||
axum = "0.8.9"
|
axum = "0.8.4"
|
||||||
bytes = "1.11.1"
|
bytes = "1.10.1"
|
||||||
dyn-clone = "1.0.20"
|
dyn-clone = "1.0.20"
|
||||||
eui48 = { version = "1.1.0", features = [
|
eui48 = { version = "1.1.0", features = [
|
||||||
"disp_hexstring",
|
"disp_hexstring",
|
||||||
"serde",
|
"serde",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
futures = "0.3.32"
|
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.2"
|
hostname = "0.4.1"
|
||||||
inventory = "0.3.24"
|
inventory = "0.3.21"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
json_value_merge = "2.0.1"
|
json_value_merge = "2.0.1"
|
||||||
lua_typed = { git = "https://git.huizinga.dev/Dreaded_X/lua_typed" }
|
lua_typed = { git = "https://git.huizinga.dev/Dreaded_X/lua_typed" }
|
||||||
mlua = { version = "0.11.6", features = [
|
mlua = { version = "0.11.3", features = [
|
||||||
"lua54",
|
"lua54",
|
||||||
"vendored",
|
"vendored",
|
||||||
"macros",
|
"macros",
|
||||||
@@ -45,23 +45,23 @@ mlua = { version = "0.11.6", features = [
|
|||||||
"async",
|
"async",
|
||||||
"send",
|
"send",
|
||||||
] }
|
] }
|
||||||
proc-macro2 = "1.0.106"
|
proc-macro2 = "1.0.101"
|
||||||
quote = "1.0.45"
|
quote = "1.0.40"
|
||||||
reqwest = { version = "0.13.3", features = [
|
reqwest = { version = "0.12.23", features = [
|
||||||
"json",
|
"json",
|
||||||
"rustls",
|
"rustls-tls",
|
||||||
], default-features = false } # Use rustls, since the other packages also use rustls
|
], default-features = false } # Use rustls, since the other packages also use rustls
|
||||||
rumqttc = "0.25.1"
|
rumqttc = "0.24.0"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.143"
|
||||||
serde_repr = "0.1.20"
|
serde_repr = "0.1.20"
|
||||||
syn = { version = "2.0.117" }
|
syn = { version = "2.0.106" }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.16"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
tokio = { version = "1", features = ["rt-multi-thread"] }
|
||||||
tokio-cron-scheduler = "0.15.1"
|
tokio-cron-scheduler = "0.15.0"
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.23"
|
tracing-subscriber = "0.3.20"
|
||||||
wakey = "0.4.1"
|
wakey = "0.3.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
@@ -70,7 +70,7 @@ automation_devices = { workspace = true }
|
|||||||
automation_lib = { workspace = true }
|
automation_lib = { workspace = true }
|
||||||
automation_macro = { path = "./automation_macro" }
|
automation_macro = { path = "./automation_macro" }
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
config = { version = "0.15.22", default-features = false, features = [
|
config = { version = "0.15.15", default-features = false, features = [
|
||||||
"async",
|
"async",
|
||||||
"toml",
|
"toml",
|
||||||
] }
|
] }
|
||||||
|
|||||||
+3
-2
@@ -1,8 +1,9 @@
|
|||||||
FROM rust:1.95 AS base
|
FROM rust:1.89 AS base
|
||||||
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
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
|
||||||
|
COPY ./rust-toolchain.toml .
|
||||||
RUN rustup toolchain install
|
RUN rustup toolchain install
|
||||||
|
|
||||||
FROM base AS planner
|
FROM base AS planner
|
||||||
@@ -20,7 +21,7 @@ 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 gcr.io/distroless/cc-debian12:nonroot 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;;"
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ Custom home automation solution with Google Home integration and lua scripting.
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
This repository uses [prek](https://prek.j178.dev/) to make sure everything is ready to go when committing.
|
This repository uses [pre-commit](https://pre-commit.com) to make sure everything is ready to go when committing.
|
||||||
Install the pre-commit hooks by running the following command:
|
Install the pre-commit hooks by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
prek install
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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_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;
|
||||||
@@ -26,7 +25,6 @@ crate::register_type!(Config);
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Device)]
|
#[derive(Debug, Clone, Device)]
|
||||||
#[device(traits(OnOff))]
|
#[device(traits(OnOff))]
|
||||||
#[device(extra_user_data = AllOn)]
|
|
||||||
pub struct HueGroup {
|
pub struct HueGroup {
|
||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
@@ -124,47 +122,6 @@ impl OnOff for HueGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AllOn;
|
|
||||||
impl PartialUserData<HueGroup> for AllOn {
|
|
||||||
fn add_methods<M: mlua::UserDataMethods<HueGroup>>(methods: &mut M) {
|
|
||||||
methods.add_async_method("all_on", async |_lua, this, ()| {
|
|
||||||
let res = reqwest::Client::new()
|
|
||||||
.get(this.url_get_state())
|
|
||||||
.send()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
|
||||||
Ok(res) => {
|
|
||||||
let status = res.status();
|
|
||||||
if !status.is_success() {
|
|
||||||
warn!(id = this.get_id(), "Status code is not success: {status}");
|
|
||||||
}
|
|
||||||
|
|
||||||
let on = match res.json::<message::Info>().await {
|
|
||||||
Ok(info) => info.all_on(),
|
|
||||||
Err(err) => {
|
|
||||||
error!(id = this.get_id(), "Failed to parse message: {err}");
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(on);
|
|
||||||
}
|
|
||||||
Err(err) => error!(id = this.get_id(), "Error: {err}"),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(false)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn definitions() -> Option<String> {
|
|
||||||
Some(format!(
|
|
||||||
"---@async\n---@return boolean\nfunction {}:all_on() end\n",
|
|
||||||
<HueGroup as Typed>::type_name(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod message {
|
mod message {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -207,9 +164,5 @@ mod message {
|
|||||||
pub fn any_on(&self) -> bool {
|
pub fn any_on(&self) -> bool {
|
||||||
self.state.any_on
|
self.state.any_on
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn all_on(&self) -> bool {
|
|
||||||
self.state.all_on
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,12 +122,16 @@ impl OnMqtt for HueSwitch {
|
|||||||
Action::LeftHold => self.config.left_hold_callback.call(self.clone()).await,
|
Action::LeftHold => self.config.left_hold_callback.call(self.clone()).await,
|
||||||
Action::RightHold => self.config.right_hold_callback.call(self.clone()).await,
|
Action::RightHold => self.config.right_hold_callback.call(self.clone()).await,
|
||||||
// If there is no hold action, the switch will act like a normal release
|
// If there is no hold action, the switch will act like a normal release
|
||||||
Action::RightHoldRelease if self.config.right_hold_callback.is_empty() => {
|
Action::RightHoldRelease => {
|
||||||
|
if self.config.right_hold_callback.is_empty() {
|
||||||
self.config.right_callback.call(self.clone()).await
|
self.config.right_callback.call(self.clone()).await
|
||||||
}
|
}
|
||||||
Action::LeftHoldRelease if self.config.left_hold_callback.is_empty() => {
|
}
|
||||||
|
Action::LeftHoldRelease => {
|
||||||
|
if self.config.left_hold_callback.is_empty() {
|
||||||
self.config.left_callback.call(self.clone()).await
|
self.config.left_callback.call(self.clone()).await
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![feature(iter_intersperse)]
|
||||||
#![feature(iterator_try_collect)]
|
#![feature(iterator_try_collect)]
|
||||||
mod device;
|
mod device;
|
||||||
mod lua_device_config;
|
mod lua_device_config;
|
||||||
|
|||||||
@@ -25,13 +25,6 @@ function module.setup(mqtt_client)
|
|||||||
group_id = 3,
|
group_id = 3,
|
||||||
scene_id = "60tfTyR168v2csz",
|
scene_id = "60tfTyR168v2csz",
|
||||||
})
|
})
|
||||||
local wardrobe_light = devices.HueGroup.new({
|
|
||||||
identifier = "bedroom_lights_wardrobe",
|
|
||||||
ip = hue_bridge.ip,
|
|
||||||
login = hue_bridge.token,
|
|
||||||
group_id = 3,
|
|
||||||
scene_id = "1IDvpsN2YLZsDV95",
|
|
||||||
})
|
|
||||||
|
|
||||||
air_filter = devices.AirFilter.new({
|
air_filter = devices.AirFilter.new({
|
||||||
name = "Air Filter",
|
name = "Air Filter",
|
||||||
@@ -39,36 +32,13 @@ function module.setup(mqtt_client)
|
|||||||
url = "http://10.0.0.103",
|
url = "http://10.0.0.103",
|
||||||
})
|
})
|
||||||
|
|
||||||
local wardrobe_door = devices.ContactSensor.new({
|
|
||||||
name = "Wardrobe Door",
|
|
||||||
room = "Bedroom",
|
|
||||||
sensor_type = "Door",
|
|
||||||
topic = helper.mqtt_z2m("bedroom/wardrobe_door"),
|
|
||||||
client = mqtt_client,
|
|
||||||
callback = function(_, open)
|
|
||||||
-- Technically this has an edge case where if one of the spots is
|
|
||||||
-- on, but that is not something I ever do
|
|
||||||
if not lights:all_on() then
|
|
||||||
wardrobe_light:set_on(open)
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
battery_callback = battery.callback,
|
|
||||||
})
|
|
||||||
|
|
||||||
local switch = devices.HueSwitch.new({
|
local switch = devices.HueSwitch.new({
|
||||||
name = "Switch",
|
name = "Switch",
|
||||||
room = "Bedroom",
|
room = "Bedroom",
|
||||||
client = mqtt_client,
|
client = mqtt_client,
|
||||||
topic = helper.mqtt_z2m("bedroom/switch"),
|
topic = helper.mqtt_z2m("bedroom/switch"),
|
||||||
left_callback = function()
|
left_callback = function()
|
||||||
local on = not lights:all_on()
|
lights:set_on(not lights:on())
|
||||||
lights:set_on(on)
|
|
||||||
-- This is a bit janky as the light will start to dim before turning
|
|
||||||
-- back on, however this is really and edge case that probably won't
|
|
||||||
-- happen often, so for now it's fine
|
|
||||||
if not on and wardrobe_door:open_percent() == 100 then
|
|
||||||
wardrobe_light:set_on(true)
|
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
left_hold_callback = function()
|
left_hold_callback = function()
|
||||||
lights_relax:set_on(true)
|
lights_relax:set_on(true)
|
||||||
@@ -91,7 +61,6 @@ function module.setup(mqtt_client)
|
|||||||
lights,
|
lights,
|
||||||
lights_relax,
|
lights_relax,
|
||||||
air_filter,
|
air_filter,
|
||||||
wardrobe_door,
|
|
||||||
switch,
|
switch,
|
||||||
window,
|
window,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,18 +25,10 @@ function module.setup(mqtt_client)
|
|||||||
})
|
})
|
||||||
windows.add(window)
|
windows.add(window)
|
||||||
|
|
||||||
local printer = devices.OutletOnOff.new({
|
|
||||||
name = "3D Printer",
|
|
||||||
room = "Guest Room",
|
|
||||||
topic = helper.mqtt_z2m("guest/printer"),
|
|
||||||
client = mqtt_client,
|
|
||||||
})
|
|
||||||
|
|
||||||
--- @type Module
|
--- @type Module
|
||||||
return {
|
return {
|
||||||
light,
|
light,
|
||||||
window,
|
window,
|
||||||
printer,
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -116,9 +116,6 @@ devices.HueGroup = {}
|
|||||||
---@param config HueGroupConfig
|
---@param config HueGroupConfig
|
||||||
---@return HueGroup
|
---@return HueGroup
|
||||||
function devices.HueGroup.new(config) end
|
function devices.HueGroup.new(config) end
|
||||||
---@async
|
|
||||||
---@return boolean
|
|
||||||
function HueGroup:all_on() end
|
|
||||||
|
|
||||||
---@class HueGroupConfig
|
---@class HueGroupConfig
|
||||||
---@field identifier string
|
---@field identifier string
|
||||||
|
|||||||
+20
-5
@@ -7,12 +7,27 @@ group "default" {
|
|||||||
|
|
||||||
target "docker-metadata-action" {}
|
target "docker-metadata-action" {}
|
||||||
|
|
||||||
target "automation" {
|
# TODO: Auto include this from the workflow
|
||||||
inherits = ["docker-metadata-action"]
|
target "common" {
|
||||||
context = "./"
|
cache-from = [
|
||||||
dockerfile = "Dockerfile"
|
{
|
||||||
tags = [for tag in target.docker-metadata-action.tags : "${TAG_BASE}:${tag}"]
|
type = "gha"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
cache-to = [
|
||||||
|
{
|
||||||
|
type = "gha"
|
||||||
|
mode = "max"
|
||||||
|
}
|
||||||
|
]
|
||||||
args = {
|
args = {
|
||||||
RELEASE_VERSION = "${RELEASE_VERSION}"
|
RELEASE_VERSION = "${RELEASE_VERSION}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
target "automation" {
|
||||||
|
inherits = ["docker-metadata-action", "common"]
|
||||||
|
context = "./"
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
tags = [for tag in target.docker-metadata-action.tags : "${TAG_BASE}:${tag}"]
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "nightly-2026-05-12"
|
channel = "nightly-2025-08-20"
|
||||||
components = ["rustfmt", "clippy", "rust-analyzer"]
|
components = ["rustfmt", "clippy", "rust-analyzer"]
|
||||||
profile = "minimal"
|
profile = "minimal"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![feature(if_let_guard)]
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
|
|||||||
Reference in New Issue
Block a user