1786298825
This commit is contained in:
226
Cargo.lock
generated
226
Cargo.lock
generated
@@ -346,6 +346,12 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "2.0.1"
|
version = "2.0.1"
|
||||||
@@ -684,6 +690,35 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie_store"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "15b2c103cf610ec6cae3da84a766285b42fd16aad564758459e6ecf128c75206"
|
||||||
|
dependencies = [
|
||||||
|
"cookie",
|
||||||
|
"document-features",
|
||||||
|
"idna",
|
||||||
|
"indexmap",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "copypasta"
|
name = "copypasta"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@@ -849,12 +884,14 @@ dependencies = [
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
"hass-rs",
|
"hass-rs",
|
||||||
"paste",
|
"paste",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"slint",
|
"slint",
|
||||||
"slint-build",
|
"slint-build",
|
||||||
"thiserror 2.0.20",
|
"thiserror 2.0.20",
|
||||||
"tokio",
|
"tokio",
|
||||||
"trekstor",
|
"trekstor",
|
||||||
|
"ureq",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -870,6 +907,12 @@ version = "0.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -950,6 +993,15 @@ dependencies = [
|
|||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenvy"
|
name = "dotenvy"
|
||||||
version = "0.15.7"
|
version = "0.15.7"
|
||||||
@@ -1445,6 +1497,17 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -2513,6 +2576,12 @@ version = "0.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@@ -2788,6 +2857,12 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -3459,6 +3534,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -3841,6 +3922,20 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rowan"
|
name = "rowan"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
@@ -3922,6 +4017,41 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.103.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.23"
|
version = "1.0.23"
|
||||||
@@ -4352,6 +4482,12 @@ dependencies = [
|
|||||||
"syn 2.0.119",
|
"syn 2.0.119",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "svgtypes"
|
name = "svgtypes"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
@@ -4521,6 +4657,36 @@ dependencies = [
|
|||||||
"zune-jpeg",
|
"zune-jpeg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -4855,12 +5021,49 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unty"
|
name = "unty"
|
||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.23.1",
|
||||||
|
"cookie_store",
|
||||||
|
"log",
|
||||||
|
"percent-encoding",
|
||||||
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"ureq-proto",
|
||||||
|
"utf8-zero",
|
||||||
|
"webpki-roots",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ureq-proto"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.23.1",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.8"
|
version = "2.5.8"
|
||||||
@@ -4879,7 +5082,7 @@ version = "0.47.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d46cf96c5f498d36b7a9693bc6a7075c0bb9303189d61b2249b0dc3d309c07de"
|
checksum = "d46cf96c5f498d36b7a9693bc6a7075c0bb9303189d61b2249b0dc3d309c07de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"data-url",
|
"data-url",
|
||||||
"flate2",
|
"flate2",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
@@ -4901,6 +5104,12 @@ dependencies = [
|
|||||||
"xmlwriter",
|
"xmlwriter",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-zero"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8_iter"
|
name = "utf8_iter"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -5074,6 +5283,15 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "webpki-roots"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
@@ -5694,6 +5912,12 @@ dependencies = [
|
|||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ url = "2.5.8"
|
|||||||
chrono = "0.4.45"
|
chrono = "0.4.45"
|
||||||
thiserror = "2.0.20"
|
thiserror = "2.0.20"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
|
ureq = { version = "3.4.0", default-features = false, features = ["json", "rustls"] }
|
||||||
|
serde = { version = "1.0.229", features = ["derive"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dev = ["slint/backend-winit-x11"]
|
dev = ["slint/backend-winit-x11"]
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
#[allow(clippy::all)]
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum AppError {
|
pub enum AppError {
|
||||||
#[error("HASS_URL is not set")]
|
#[error("HASS_URL is not set")]
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl HassEntityExt for HassEntity {
|
|||||||
impl From<&HassEntity> for LightData {
|
impl From<&HassEntity> for LightData {
|
||||||
fn from(value: &HassEntity) -> Self {
|
fn from(value: &HassEntity) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: value.entity_id.clone().into(),
|
id: value.entity_id.to_shared_string(),
|
||||||
name: value
|
name: value
|
||||||
.friendly_name()
|
.friendly_name()
|
||||||
.replace(" Lights", "")
|
.replace(" Lights", "")
|
||||||
@@ -61,7 +61,7 @@ impl From<&HassEntity> for ThermometerData {
|
|||||||
let v: f32 = value.state.parse().unwrap_or_default();
|
let v: f32 = value.state.parse().unwrap_or_default();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
id: value.entity_id.clone().into(),
|
id: value.entity_id.to_shared_string(),
|
||||||
name: value.friendly_name().into(),
|
name: value.friendly_name().into(),
|
||||||
value: format!("{:.1}", v).into(),
|
value: format!("{:.1}", v).into(),
|
||||||
unit: value.unit_of_measurement().into(),
|
unit: value.unit_of_measurement().into(),
|
||||||
@@ -71,21 +71,19 @@ impl From<&HassEntity> for ThermometerData {
|
|||||||
|
|
||||||
impl From<&HassEntity> for DateTimeData {
|
impl From<&HassEntity> for DateTimeData {
|
||||||
fn from(value: &HassEntity) -> Self {
|
fn from(value: &HassEntity) -> Self {
|
||||||
|
let mut data = Self {
|
||||||
|
id: value.entity_id.to_shared_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
if let Ok(datetime) =
|
if let Ok(datetime) =
|
||||||
chrono::NaiveDateTime::parse_from_str(&value.state, "%Y-%m-%dT%H:%M:%S")
|
chrono::NaiveDateTime::parse_from_str(&value.state, "%Y-%m-%dT%H:%M:%S")
|
||||||
{
|
{
|
||||||
return Self {
|
data.date = datetime.format("%a %m %b").to_shared_string();
|
||||||
id: value.entity_id.clone().into(),
|
data.time = datetime.format("%H:%M").to_shared_string();
|
||||||
date: datetime.format("%a %m %b").to_shared_string(),
|
|
||||||
time: datetime.format("%H:%M").to_shared_string(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
data
|
||||||
id: value.entity_id.clone().into(),
|
|
||||||
date: "0-0-0000".into(),
|
|
||||||
time: "00:00".into(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,64 +137,6 @@ impl From<&HassEntity> for WeatherData {
|
|||||||
|
|
||||||
let label = pretty_label(&value.state).to_shared_string();
|
let label = pretty_label(&value.state).to_shared_string();
|
||||||
|
|
||||||
let attribution = value
|
|
||||||
.attributes
|
|
||||||
.get("attribution")
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_shared_string();
|
|
||||||
|
|
||||||
let cloud_coverage = value
|
|
||||||
.attributes
|
|
||||||
.get("cloud_coverage")
|
|
||||||
.and_then(|x| x.as_i64())
|
|
||||||
.unwrap_or_default() as i32;
|
|
||||||
|
|
||||||
let dew_point = value
|
|
||||||
.attributes
|
|
||||||
.get("dew_point")
|
|
||||||
.and_then(|x| x.as_f64())
|
|
||||||
.unwrap_or_default() as f32;
|
|
||||||
|
|
||||||
let friendly_name = value
|
|
||||||
.attributes
|
|
||||||
.get("friendly_name")
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_shared_string();
|
|
||||||
|
|
||||||
let humidity = value
|
|
||||||
.attributes
|
|
||||||
.get("humidity")
|
|
||||||
.and_then(|x| x.as_i64())
|
|
||||||
.unwrap_or_default() as i32;
|
|
||||||
|
|
||||||
let precipitation_unit = value
|
|
||||||
.attributes
|
|
||||||
.get("precipitation_unit")
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_shared_string();
|
|
||||||
|
|
||||||
let pressure = value
|
|
||||||
.attributes
|
|
||||||
.get("pressure")
|
|
||||||
.and_then(|x| x.as_f64())
|
|
||||||
.unwrap_or_default() as f32;
|
|
||||||
|
|
||||||
let pressure_unit = value
|
|
||||||
.attributes
|
|
||||||
.get("pressure_unit")
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_shared_string();
|
|
||||||
|
|
||||||
let supported_features = value
|
|
||||||
.attributes
|
|
||||||
.get("supported_features")
|
|
||||||
.and_then(|x| x.as_i64())
|
|
||||||
.unwrap_or_default() as i32;
|
|
||||||
|
|
||||||
let temperature = value
|
let temperature = value
|
||||||
.attributes
|
.attributes
|
||||||
.get("temperature")
|
.get("temperature")
|
||||||
@@ -210,57 +150,24 @@ impl From<&HassEntity> for WeatherData {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_shared_string();
|
.to_shared_string();
|
||||||
|
|
||||||
let uv_index = value
|
|
||||||
.attributes
|
|
||||||
.get("uv_index")
|
|
||||||
.and_then(|x| x.as_f64())
|
|
||||||
.unwrap_or_default() as f32;
|
|
||||||
|
|
||||||
let visibility_unit = value
|
|
||||||
.attributes
|
|
||||||
.get("visibility_unit")
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_shared_string();
|
|
||||||
|
|
||||||
let wind_bearing = value
|
|
||||||
.attributes
|
|
||||||
.get("wind_bearing")
|
|
||||||
.and_then(|x| x.as_f64())
|
|
||||||
.unwrap_or_default() as f32;
|
|
||||||
|
|
||||||
let wind_speed = value
|
|
||||||
.attributes
|
|
||||||
.get("wind_speed")
|
|
||||||
.and_then(|x| x.as_f64())
|
|
||||||
.unwrap_or_default() as f32;
|
|
||||||
|
|
||||||
let wind_speed_unit = value
|
|
||||||
.attributes
|
|
||||||
.get("wind_speed_unit")
|
|
||||||
.and_then(|x| x.as_str())
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_shared_string();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
icon_index,
|
icon_index,
|
||||||
label,
|
label,
|
||||||
attribution,
|
|
||||||
cloud_coverage,
|
|
||||||
dew_point,
|
|
||||||
friendly_name,
|
|
||||||
humidity,
|
|
||||||
precipitation_unit,
|
|
||||||
pressure,
|
|
||||||
pressure_unit,
|
|
||||||
supported_features,
|
|
||||||
temperature,
|
temperature,
|
||||||
temperature_unit,
|
temperature_unit,
|
||||||
uv_index,
|
}
|
||||||
visibility_unit,
|
}
|
||||||
wind_bearing,
|
}
|
||||||
wind_speed,
|
|
||||||
wind_speed_unit,
|
// weird spot for this but okay
|
||||||
|
impl From<&crate::home_assistant::CalendarEvent> for CalendarEventData {
|
||||||
|
fn from(value: &crate::home_assistant::CalendarEvent) -> Self {
|
||||||
|
Self {
|
||||||
|
calendar: value.calendar.to_shared_string(),
|
||||||
|
date: value.date.to_shared_string(),
|
||||||
|
id: value.entity_id.to_shared_string(),
|
||||||
|
summary: value.summary.to_shared_string(),
|
||||||
|
time: value.time.to_shared_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
use chrono::{DateTime, Local, NaiveDate};
|
||||||
use dotenvy::var;
|
use dotenvy::var;
|
||||||
use hass_rs::{HassClient, HassEntity};
|
use hass_rs::{HassClient, HassEntity};
|
||||||
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, time::Duration};
|
||||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
use tokio::{
|
||||||
|
sync::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||||
|
time::sleep,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::AppError,
|
error::AppError,
|
||||||
@@ -114,6 +119,18 @@ pub async fn hass(
|
|||||||
initial_state.sort_by(|a, b| a.entity_id.cmp(&b.entity_id));
|
initial_state.sort_by(|a, b| a.entity_id.cmp(&b.entity_id));
|
||||||
s2c_tx.send(EventMessage::InitialState(initial_state)).ok();
|
s2c_tx.send(EventMessage::InitialState(initial_state)).ok();
|
||||||
|
|
||||||
|
tokio::spawn({
|
||||||
|
let s2c_tx = s2c_tx.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
let ha_client = HaClient::from_environment().unwrap();
|
||||||
|
let events = ha_client.fetch_calendar_events().unwrap();
|
||||||
|
s2c_tx.send(EventMessage::CalendarUpdated(events)).ok();
|
||||||
|
sleep(Duration::from_secs(30)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let mut event_receiver = client
|
let mut event_receiver = client
|
||||||
.subscribe_event("state_changed")
|
.subscribe_event("state_changed")
|
||||||
.await
|
.await
|
||||||
@@ -162,3 +179,143 @@ fn toggle_action(current_state: &str, on_state: &str) -> &'static str {
|
|||||||
"turn_on"
|
"turn_on"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const REQUEST_TIMEOUT: Duration = Duration::from_secs(12);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HaClient {
|
||||||
|
pub agent: ureq::Agent,
|
||||||
|
pub base_url: String,
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct HaCalendar {
|
||||||
|
pub entity_id: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct HaCalendarEvent {
|
||||||
|
pub uid: String,
|
||||||
|
pub summary: String,
|
||||||
|
pub start: HaCalendarTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct HaCalendarTime {
|
||||||
|
#[serde(rename = "dateTime")]
|
||||||
|
pub date_time: Option<String>,
|
||||||
|
pub date: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct CalendarEvent {
|
||||||
|
pub entity_id: String,
|
||||||
|
pub summary: String,
|
||||||
|
pub date: String,
|
||||||
|
pub time: String,
|
||||||
|
pub calendar: String,
|
||||||
|
pub sort_epoch: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HaClient {
|
||||||
|
pub fn from_environment() -> Result<Self, String> {
|
||||||
|
let base_url = std::env::var("HASS_URL")
|
||||||
|
.map_err(|_| "HASS_URL is not configured".to_string())?
|
||||||
|
.trim_end_matches('/')
|
||||||
|
.to_string();
|
||||||
|
let token =
|
||||||
|
std::env::var("HASS_TOKEN").map_err(|_| "HASS_TOKEN is not configured".to_string())?;
|
||||||
|
let agent = ureq::Agent::new_with_config(
|
||||||
|
ureq::config::Config::builder()
|
||||||
|
.timeout_send_request(Some(REQUEST_TIMEOUT))
|
||||||
|
.timeout_recv_response(Some(REQUEST_TIMEOUT))
|
||||||
|
.timeout_send_body(Some(REQUEST_TIMEOUT))
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
Ok(Self {
|
||||||
|
agent,
|
||||||
|
base_url,
|
||||||
|
token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_calendars(&self) -> Result<Vec<HaCalendar>, String> {
|
||||||
|
self.agent
|
||||||
|
.get(&format!("{}/api/calendars", self.base_url))
|
||||||
|
.header("Authorization", &format!("Bearer {}", self.token))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.call()
|
||||||
|
.map_err(|error| format!("calendars: {error}"))?
|
||||||
|
.body_mut()
|
||||||
|
.read_json()
|
||||||
|
.map_err(|error| format!("calendars: invalid response: {error}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_calendar_events(
|
||||||
|
&self,
|
||||||
|
entity_id: &str,
|
||||||
|
start: &str,
|
||||||
|
end: &str,
|
||||||
|
) -> Result<Vec<HaCalendarEvent>, String> {
|
||||||
|
self.agent
|
||||||
|
.get(&format!("{}/api/calendars/{entity_id}", self.base_url))
|
||||||
|
.query("start", start)
|
||||||
|
.query("end", end)
|
||||||
|
.header("Authorization", &format!("Bearer {}", self.token))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.call()
|
||||||
|
.map_err(|error| format!("{entity_id}: {error}"))?
|
||||||
|
.body_mut()
|
||||||
|
.read_json()
|
||||||
|
.map_err(|error| format!("{entity_id}: invalid response: {error}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_calendar_events(&self) -> Result<Vec<CalendarEvent>, String> {
|
||||||
|
let now = Local::now();
|
||||||
|
let start = now.to_rfc3339();
|
||||||
|
let end = (now + chrono::Duration::days(30)).to_rfc3339();
|
||||||
|
let mut upcoming = Vec::new();
|
||||||
|
|
||||||
|
for calendar in self.get_calendars()? {
|
||||||
|
let Ok(events) = self.get_calendar_events(&calendar.entity_id, &start, &end) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
for event in events {
|
||||||
|
let Some((sort_epoch, date, time)) = format_calendar_time(&event.start) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
upcoming.push(CalendarEvent {
|
||||||
|
entity_id: event.uid,
|
||||||
|
summary: event.summary,
|
||||||
|
date,
|
||||||
|
time,
|
||||||
|
calendar: calendar.name.clone(),
|
||||||
|
sort_epoch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
upcoming.sort_by_key(|event| event.sort_epoch);
|
||||||
|
upcoming.truncate(7);
|
||||||
|
Ok(upcoming)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_calendar_time(value: &HaCalendarTime) -> Option<(i64, String, String)> {
|
||||||
|
if let Some(date_time) = value.date_time.as_deref() {
|
||||||
|
let parsed = DateTime::parse_from_rfc3339(date_time).ok()?;
|
||||||
|
return Some((
|
||||||
|
parsed.timestamp(),
|
||||||
|
parsed.format("%a %d %b").to_string().to_uppercase(),
|
||||||
|
parsed.format("%H:%M").to_string().to_uppercase(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let date = NaiveDate::parse_from_str(value.date.as_deref()?, "%Y-%m-%d").ok()?;
|
||||||
|
let sort_epoch = date.and_hms_opt(0, 0, 0)?.and_utc().timestamp();
|
||||||
|
Some((
|
||||||
|
sort_epoch,
|
||||||
|
date.format("%a %d %b").to_string().to_uppercase(),
|
||||||
|
"ALL DAY".into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use hass_rs::HassEntity;
|
use hass_rs::HassEntity;
|
||||||
|
|
||||||
|
use crate::home_assistant::CalendarEvent;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum CommandMessage {
|
pub enum CommandMessage {
|
||||||
ToggleLight { entity_id: String },
|
ToggleLight { entity_id: String },
|
||||||
@@ -14,4 +16,5 @@ pub enum EventMessage {
|
|||||||
InitialState(Vec<HassEntity>),
|
InitialState(Vec<HassEntity>),
|
||||||
EntityUpdated(HassEntity),
|
EntityUpdated(HassEntity),
|
||||||
EntityRemoved(HassEntity),
|
EntityRemoved(HassEntity),
|
||||||
|
CalendarUpdated(Vec<CalendarEvent>),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ slint::include_modules!();
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ha_ext::*,
|
ha_ext::*,
|
||||||
|
home_assistant::CalendarEvent,
|
||||||
messages::{CommandMessage, EventMessage},
|
messages::{CommandMessage, EventMessage},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,6 +97,18 @@ pub fn apply_thermostats(app: &AppWindow, entities: &[HassEntity]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_calendars(app: &Weak<AppWindow>, entities: Vec<CalendarEvent>) {
|
||||||
|
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
||||||
|
let mut entities = entities.clone();
|
||||||
|
entities.sort_by_key(|a| a.sort_epoch);
|
||||||
|
|
||||||
|
let entities = entities.iter().map(Into::into).collect::<Vec<_>>();
|
||||||
|
app.set_calendar_events(ModelRc::new(VecModel::<CalendarEventData>::from(entities)));
|
||||||
|
}) {
|
||||||
|
eprintln!("Failed to upgrade in event loop.\n\t{:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_state(app: &Weak<AppWindow>, entities: Vec<HassEntity>) {
|
pub fn apply_state(app: &Weak<AppWindow>, entities: Vec<HassEntity>) {
|
||||||
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
if let Err(err) = app.upgrade_in_event_loop(move |app| {
|
||||||
apply_theme(&app, &entities);
|
apply_theme(&app, &entities);
|
||||||
@@ -161,6 +174,9 @@ pub async fn update_listener(
|
|||||||
EventMessage::EntityRemoved(hass_entity) => {
|
EventMessage::EntityRemoved(hass_entity) => {
|
||||||
apply_state(&app_weak, vec![hass_entity]);
|
apply_state(&app_weak, vec![hass_entity]);
|
||||||
}
|
}
|
||||||
|
EventMessage::CalendarUpdated(events) => {
|
||||||
|
apply_calendars(&app_weak, events);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,41 @@
|
|||||||
import { FilledButton, SnackBar } from "material/material.slint";
|
import { Palette } from "std-widgets.slint";
|
||||||
|
import {
|
||||||
|
FilledButton,
|
||||||
|
SnackBar,
|
||||||
|
ListTile,
|
||||||
|
MaterialText,
|
||||||
|
MaterialPalette,
|
||||||
|
MaterialTypography,
|
||||||
|
} from "material/material.slint";
|
||||||
|
import { Icons } from "material/ui/icons/icons.slint";
|
||||||
|
|
||||||
|
export struct CalendarEventData {
|
||||||
|
id: string,
|
||||||
|
date: string,
|
||||||
|
time: string,
|
||||||
|
summary: string,
|
||||||
|
calendar: string,
|
||||||
|
}
|
||||||
|
|
||||||
export component Calendar inherits Rectangle {
|
export component Calendar inherits Rectangle {
|
||||||
|
in property <[CalendarEventData]> calendar-events;
|
||||||
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
// ..
|
for event[index] in calendar-events: ListTile {
|
||||||
|
avatar_icon: Icons.calendar_today;
|
||||||
|
avatar_foreground: Palette.foreground;
|
||||||
|
text: event.date;
|
||||||
|
supporting-text: event.summary;
|
||||||
|
height: 72px;
|
||||||
|
|
||||||
|
MaterialText {
|
||||||
|
text: event.time;
|
||||||
|
color: MaterialPalette.on_surface;
|
||||||
|
overflow: elide;
|
||||||
|
style: MaterialTypography.title_medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle { }
|
Rectangle { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint";
|
import { Palette, HorizontalBox, VerticalBox } from "std-widgets.slint";
|
||||||
|
import { Icons } from "material/ui/icons/icons.slint";
|
||||||
import {
|
import {
|
||||||
ListTile,
|
ListTile,
|
||||||
Vertical,
|
Vertical,
|
||||||
@@ -7,26 +8,12 @@ import {
|
|||||||
MaterialPalette,
|
MaterialPalette,
|
||||||
FilledButton,
|
FilledButton,
|
||||||
} from "material/material.slint";
|
} from "material/material.slint";
|
||||||
import { Icons } from "material/ui/icons/icons.slint";
|
|
||||||
export struct WeatherData {
|
export struct WeatherData {
|
||||||
icon-index: int,
|
icon-index: int,
|
||||||
label: string,
|
label: string,
|
||||||
temperature: float,
|
temperature: float,
|
||||||
dew_point: float,
|
|
||||||
temperature_unit: string,
|
temperature_unit: string,
|
||||||
humidity: int,
|
|
||||||
cloud_coverage: int,
|
|
||||||
uv_index: float,
|
|
||||||
pressure: float,
|
|
||||||
pressure_unit: string,
|
|
||||||
wind_bearing: float,
|
|
||||||
wind_speed: float,
|
|
||||||
wind_speed_unit: string,
|
|
||||||
visibility_unit: string,
|
|
||||||
precipitation_unit: string,
|
|
||||||
attribution: string,
|
|
||||||
friendly_name: string,
|
|
||||||
supported_features: int,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export struct ThermostatData {
|
export struct ThermostatData {
|
||||||
@@ -114,7 +101,6 @@ export component Climate inherits Rectangle {
|
|||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
Text {
|
Text {
|
||||||
text: thermostat.target;
|
text: thermostat.target;
|
||||||
// color: MaterialPalette.primary;
|
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
vertical-alignment: center;
|
vertical-alignment: center;
|
||||||
horizontal-alignment: right;
|
horizontal-alignment: right;
|
||||||
@@ -126,7 +112,6 @@ export component Climate inherits Rectangle {
|
|||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
Text {
|
Text {
|
||||||
text: thermostat.unit;
|
text: thermostat.unit;
|
||||||
// color: MaterialPalette.primary;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
horizontal-alignment: left;
|
horizontal-alignment: left;
|
||||||
vertical-alignment: top;
|
vertical-alignment: top;
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
} from "std-widgets.slint";
|
} from "std-widgets.slint";
|
||||||
|
|
||||||
import { Climate, WeatherData, ThermostatData } from "./climate.slint";
|
import { Climate, WeatherData, ThermostatData } from "./climate.slint";
|
||||||
|
import { CalendarEventData } from "./calendar.slint";
|
||||||
|
|
||||||
import { Calendar } from "calendar.slint";
|
import { Calendar } from "calendar.slint";
|
||||||
import { Energy } from "energy.slint";
|
import { Energy } from "energy.slint";
|
||||||
import {
|
import {
|
||||||
@@ -132,6 +134,8 @@ export component AppWindow inherits Window {
|
|||||||
callback set_temperature(string, float);
|
callback set_temperature(string, float);
|
||||||
callback toggle_thermostat(string);
|
callback toggle_thermostat(string);
|
||||||
|
|
||||||
|
in property <[CalendarEventData]> calendar-events;
|
||||||
|
|
||||||
in property <[ThermometerData]> thermometers;
|
in property <[ThermometerData]> thermometers;
|
||||||
|
|
||||||
in property <[LightData]> lights;
|
in property <[LightData]> lights;
|
||||||
@@ -177,7 +181,11 @@ export component AppWindow inherits Window {
|
|||||||
set_temperature(entity_id, value) => { root.set_temperature(entity_id, value); }
|
set_temperature(entity_id, value) => { root.set_temperature(entity_id, value); }
|
||||||
toggle_thermostat(entity_id) => { toggle_thermostat(entity_id) }
|
toggle_thermostat(entity_id) => { toggle_thermostat(entity_id) }
|
||||||
}
|
}
|
||||||
if tab-bar.current-index == 1: Calendar { }
|
|
||||||
|
if tab-bar.current-index == 1: Calendar {
|
||||||
|
calendar-events: calendar-events;
|
||||||
|
}
|
||||||
|
|
||||||
if tab-bar.current-index == 2: Energy { }
|
if tab-bar.current-index == 2: Energy { }
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
Reference in New Issue
Block a user