Compare commits
22 Commits
53a2fc2a35
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4786b1e6ba | ||
|
|
a09a97d06d | ||
| 4eb8a752cc | |||
|
|
8d3cde3c43 | ||
|
|
cb216d0a0a | ||
|
|
8b966c1210 | ||
|
|
a2d6d9f4c2 | ||
|
|
8ab8dd3441 | ||
|
|
224ee7732f | ||
|
|
83b35908f7 | ||
| e3c54a996e | |||
| 38c30d87d0 | |||
| 17fabbf178 | |||
| 6abe278aa3 | |||
| 120ad97c53 | |||
| 338a8c571c | |||
| 34d0266c1f | |||
| ac0f574d2b | |||
| 9863029eb9 | |||
|
|
41cf9d0d09 | ||
|
|
d6418b5fb0 | ||
|
|
c0737c4420 |
160
Cargo.lock
generated
160
Cargo.lock
generated
@@ -234,6 +234,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.1"
|
||||
@@ -242,15 +248,15 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "byteview"
|
||||
version = "0.6.1"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6236364b88b9b6d0bc181ba374cf1ab55ba3ef97a1cb6f8cddad48a273767fb5"
|
||||
checksum = "1c53ba0f290bfc610084c05582d9c5d421662128fc69f4bf236707af6fd321b9"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.66"
|
||||
version = "1.2.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
|
||||
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -596,12 +602,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "double-ended-peekable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0d05e1c0dbad51b52c38bda7adceef61b9efc2baf04acfe8726a8c4630a6f57"
|
||||
|
||||
[[package]]
|
||||
name = "driver-kasa"
|
||||
version = "0.1.0"
|
||||
@@ -723,21 +723,30 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fjall"
|
||||
version = "2.11.2"
|
||||
version = "3.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b25ad44cd4360a0448a9b5a0a6f1c7a621101cca4578706d43c9a821418aebc"
|
||||
checksum = "9fcdc69609906151dff9b534e30eaf8515082055d36f628e382bd0b5d6a1d362"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"byteorder-lite",
|
||||
"byteview",
|
||||
"dashmap 6.2.1",
|
||||
"flume",
|
||||
"log",
|
||||
"lsm-tree",
|
||||
"path-absolutize",
|
||||
"std-semaphore",
|
||||
"lz4_flex",
|
||||
"tempfile",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@@ -872,12 +881,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "guardian"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f"
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.2.1"
|
||||
@@ -974,9 +977,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
@@ -984,9 +987,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
|
||||
checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -1413,33 +1416,34 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||
|
||||
[[package]]
|
||||
name = "lsm-tree"
|
||||
version = "2.10.4"
|
||||
version = "3.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799399117a2bfb37660e08be33f470958babb98386b04185288d829df362ea15"
|
||||
checksum = "39ca67401338b98d58447387dd5230552d2241bc388206e491d137b18dfea9d6"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"byteorder-lite",
|
||||
"byteview",
|
||||
"crossbeam-skiplist",
|
||||
"double-ended-peekable",
|
||||
"enum_dispatch",
|
||||
"guardian",
|
||||
"interval-heap",
|
||||
"log",
|
||||
"lz4_flex",
|
||||
"path-absolutize",
|
||||
"quick_cache",
|
||||
"rustc-hash",
|
||||
"self_cell",
|
||||
"sfa",
|
||||
"tempfile",
|
||||
"value-log",
|
||||
"varint-rs",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lz4_flex"
|
||||
version = "0.11.6"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a"
|
||||
checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
|
||||
dependencies = [
|
||||
"twox-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac_address"
|
||||
@@ -1481,9 +1485,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
|
||||
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
@@ -1590,24 +1594,6 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-absolutize"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4af381fe79fa195b4909485d99f73a80792331df0625188e707854f0b3383f5"
|
||||
dependencies = [
|
||||
"path-dedot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-dedot"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07ba0ad7e047712414213ff67533e6dd477af0a4e1d14fb52343e53d30ea9397"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
@@ -1653,7 +1639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
"rand 0.8.6",
|
||||
"rand 0.8.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1893,9 +1879,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.6"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
@@ -2143,6 +2129,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sfa"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1296838937cab56cd6c4eeeb8718ec777383700c33f060e2869867bd01d1175"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"log",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadow-rs"
|
||||
version = "2.0.0"
|
||||
@@ -2249,9 +2246,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
@@ -2259,9 +2256,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
@@ -2272,12 +2269,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "std-semaphore"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e"
|
||||
|
||||
[[package]]
|
||||
name = "string-literal"
|
||||
version = "0.1.0"
|
||||
@@ -2337,9 +2328,9 @@ checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.118"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2589,6 +2580,12 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "twox-hash"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.23.2"
|
||||
@@ -2683,23 +2680,6 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "value-log"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62fc7c4ce161f049607ecea654dca3f2d727da5371ae85e2e4f14ce2b98ed67c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"byteview",
|
||||
"interval-heap",
|
||||
"log",
|
||||
"path-absolutize",
|
||||
"rustc-hash",
|
||||
"tempfile",
|
||||
"varint-rs",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "varint-rs"
|
||||
version = "2.2.1"
|
||||
@@ -2877,9 +2857,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.16"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576"
|
||||
checksum = "985eec839aaf2a1270af8f4ebcf63cf9401cfd90f0902f97c28d9f104ffbde72"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
@@ -2960,6 +2940,6 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
@@ -4,8 +4,8 @@ use ijson::{IArray, INumber, IObject, IString, IValue};
|
||||
#[cfg(feature = "pyo3")]
|
||||
use pyo3::{
|
||||
exceptions::{PyException, PyTypeError, PyValueError},
|
||||
prelude::*,
|
||||
types::{PyList, PyNone},
|
||||
types::{PyAnyMethods as _, PyList, PyNone, PyTypeMethods as _},
|
||||
Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, Python,
|
||||
};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
@@ -41,9 +41,10 @@ impl From<MapKey> for Arbitrary {
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum MapKeyFromArbitraryError {
|
||||
#[snafu(display("floats aren't supported as map keys yet. got {value:?}"))]
|
||||
/// floats aren't supported as map keys yet. got {value:?}
|
||||
FloatNotSupported { value: FiniteF64 },
|
||||
#[snafu(display("a map cannot be a map key. got {value:?}"))]
|
||||
|
||||
/// a map cannot be a map key. got {value:?}
|
||||
MapCannotBeAMapKey { value: Map },
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use snafu::Snafu;
|
||||
#[derive(Debug, Clone, derive_more::Into)]
|
||||
pub struct FiniteF64(f64);
|
||||
|
||||
/// {value:?} is not finite
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(display("{value:?} is not finite"))]
|
||||
pub struct NotFinite {
|
||||
value: f64,
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use itertools::Itertools;
|
||||
#[cfg(feature = "pyo3")]
|
||||
use pyo3::{
|
||||
exceptions::PyTypeError,
|
||||
prelude::*,
|
||||
types::{PyNone, PyTuple},
|
||||
types::{PyAnyMethods as _, PyNone, PyTuple, PyTypeMethods as _},
|
||||
Borrowed, Bound, FromPyObject, IntoPyObject, PyAny, PyErr, Python,
|
||||
};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::messages::{
|
||||
GetSysInfo, GetSysInfoResponse, LB130USSys, LightState, Off, On, SetLightLastOn, SetLightOff,
|
||||
SetLightState, SetLightStateArgs, SetLightStateResponse, SetLightTo, SysInfo,
|
||||
GetSysInfo, GetSysInfoResponse, LB130USSys, SetLightState, SetLightStateArgs,
|
||||
SetLightStateResponse, SysInfo,
|
||||
};
|
||||
use backon::{FibonacciBuilder, Retryable};
|
||||
|
||||
@@ -186,8 +186,7 @@ async fn send_request<
|
||||
let incoming_length = reader.read_u32().await.context(ReadSnafu)?;
|
||||
tracing::info!(?incoming_length);
|
||||
|
||||
let mut incoming_message = Vec::new();
|
||||
incoming_message.resize(incoming_length as usize, 0);
|
||||
let mut incoming_message = vec![0; incoming_length as usize];
|
||||
reader
|
||||
.read_exact(&mut incoming_message)
|
||||
.await
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod connection;
|
||||
mod impl_protocol;
|
||||
pub mod messages;
|
||||
mod protocol;
|
||||
|
||||
@@ -178,7 +178,7 @@ impl<'de> Deserialize<'de> for MaybeKelvin {
|
||||
match u16::deserialize(deserializer)? {
|
||||
0 => Ok(MaybeKelvin(None)),
|
||||
value => {
|
||||
let kelvin = Kelvin::try_from(value).map_err(|e| {
|
||||
let kelvin = Kelvin::try_from(value).map_err(|_e| {
|
||||
serde::de::Error::custom(format!(
|
||||
"{value} is not in the range {}..{}",
|
||||
Kelvin::MIN,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use palette::{encoding::Srgb, Hsv, IntoColor};
|
||||
use protocol::light::{GetState, SetState, TurnToColor, TurnToTemperature};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
@@ -7,8 +5,8 @@ use snafu::{ResultExt, Snafu};
|
||||
use crate::{
|
||||
connection::{HandleError, LB130USHandle},
|
||||
messages::{
|
||||
Angle, Hsb, LightState, Off, On, Percentage, SetLightHsv, SetLightKelvin, SetLightLastOn,
|
||||
SetLightOff, SetLightStateArgs, SetLightTo,
|
||||
LightState, Off, On, SetLightHsv, SetLightKelvin, SetLightLastOn, SetLightOff,
|
||||
SetLightStateArgs, SetLightTo,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use snafu::Snafu;
|
||||
|
||||
pub mod emitter;
|
||||
pub mod emitter_ext;
|
||||
mod emitter_ext;
|
||||
pub mod signal;
|
||||
pub mod signal_ext;
|
||||
mod signal_ext;
|
||||
|
||||
pub use emitter::Emitter;
|
||||
pub use emitter_ext::EmitterExt;
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
use std::future::Future;
|
||||
|
||||
use ext_trait::extension;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use tokio::select;
|
||||
|
||||
use crate::ProducerExited;
|
||||
|
||||
use super::signal::{JoinError, Signal};
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub struct ProducerAlreadyExited {
|
||||
source: ProducerExited,
|
||||
}
|
||||
|
||||
#[extension(pub trait SignalExt)]
|
||||
impl<T> Signal<T> {
|
||||
fn map<M, F>(
|
||||
self,
|
||||
mut func: F,
|
||||
) -> Result<(Signal<M>, impl Future<Output = Result<(), JoinError>>), ProducerAlreadyExited>
|
||||
) -> Result<(Signal<M>, impl Future<Output = Result<(), JoinError>>), ProducerExited>
|
||||
where
|
||||
T: 'static + Sync + Send + Clone,
|
||||
M: 'static + Sync + Send + Clone,
|
||||
F: 'static + Send + FnMut(T) -> M,
|
||||
{
|
||||
let initial = func(self.subscribe().context(ProducerAlreadyExitedSnafu)?.get());
|
||||
let initial = func(self.subscribe()?.get());
|
||||
|
||||
Ok(Signal::new(initial, |mut publisher_stream| async move {
|
||||
while let Some(publisher) = publisher_stream.wait().await {
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
use std::{num::NonZeroUsize, path::PathBuf, str::FromStr, time::Duration};
|
||||
use std::{path::PathBuf, str::FromStr, time::Duration};
|
||||
|
||||
use clap::Parser;
|
||||
use driver_kasa::connection::LB130USHandle;
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use home_assistant::{
|
||||
event::context::context::Context, home_assistant::HomeAssistant, light::HomeAssistantLight,
|
||||
notify::service::mobile_app::StandardNotification, object_id::ObjectId,
|
||||
use futures::{TryFutureExt, stream::{FuturesUnordered, StreamExt}};
|
||||
use home_assistant::{home_assistant::HomeAssistant, object_id::ObjectId};
|
||||
use protocol::light::{Kelvin, TurnToTemperature};
|
||||
use pyo3::{
|
||||
pyfunction, pymodule,
|
||||
types::{PyModule, PyModuleMethods as _},
|
||||
wrap_pyfunction, Bound, PyAny, PyResult, Python,
|
||||
};
|
||||
use persisted::{persisted, Config, Keyspace, Partition, PartitionCreateOptions};
|
||||
use protocol::light::{IsOff, IsOn, Kelvin, TurnToTemperature};
|
||||
use pyo3::prelude::*;
|
||||
use shadow_rs::shadow;
|
||||
use tokio::{
|
||||
task::{spawn_blocking, JoinSet},
|
||||
time::{interval, MissedTickBehavior},
|
||||
join, task::JoinSet, time::{MissedTickBehavior, interval}
|
||||
};
|
||||
use tracing::{level_filters::LevelFilter, Level};
|
||||
use tracing_appender::rolling::{self, RollingFileAppender};
|
||||
use tracing_subscriber::{
|
||||
fmt::{self, fmt, format::FmtSpan},
|
||||
fmt::{self, format::FmtSpan},
|
||||
layer::SubscriberExt,
|
||||
registry,
|
||||
util::SubscriberInitExt,
|
||||
@@ -191,6 +189,58 @@ async fn real_main(
|
||||
// }, context, target, false).await;
|
||||
// dbg!(total_silence_result);
|
||||
// }
|
||||
},
|
||||
async {
|
||||
let plug_awp04l_1_power_object_id = "plug_awp04l_1_power";
|
||||
let plug_awp04l_2_power_object_id = "plug_awp04l_2_power";
|
||||
|
||||
let plug_awp04l_1_power_object_id = ObjectId::from_str(plug_awp04l_1_power_object_id)
|
||||
.expect("statically written and known to be correct");
|
||||
let plug_awp04l_2_power_object_id = ObjectId::from_str(plug_awp04l_2_power_object_id)
|
||||
.expect("statically written and known to be correct");
|
||||
|
||||
let plug_awp04l_1_power_signal_result = Python::attach(|py| {
|
||||
home_assistant::sensor::device_classes::power::signal::<f64>(
|
||||
py,
|
||||
&home_assistant,
|
||||
plug_awp04l_1_power_object_id,
|
||||
)
|
||||
});
|
||||
let plug_awp04l_2_power_signal_result = Python::attach(|py| {
|
||||
home_assistant::sensor::device_classes::power::signal::<f64>(
|
||||
py,
|
||||
&home_assistant,
|
||||
plug_awp04l_2_power_object_id,
|
||||
)
|
||||
});
|
||||
|
||||
let mut tasks = JoinSet::new();
|
||||
if let Ok((plug_awp04l_1_power_signal, task)) = plug_awp04l_1_power_signal_result {
|
||||
tracing::error!("listening to the plug_awp04l_1_power_signal");
|
||||
tasks.spawn(task.unwrap_or_else(|e| panic!("TODO: {e}")));
|
||||
tasks.spawn(
|
||||
plug_awp04l_1_power_signal
|
||||
.subscribe()
|
||||
.expect("TODO")
|
||||
.for_each(|plug_awp04l_1_power| async move {
|
||||
tracing::warn!(?plug_awp04l_1_power)
|
||||
}),
|
||||
);
|
||||
}
|
||||
if let Ok((plug_awp04l_2_power_signal, task)) = plug_awp04l_2_power_signal_result {
|
||||
tracing::error!("listening to the plug_awp04l_2_power_signal");
|
||||
tasks.spawn(task.unwrap_or_else(|e| panic!("TODO: {e}")));
|
||||
tasks.spawn(
|
||||
plug_awp04l_2_power_signal
|
||||
.subscribe()
|
||||
.expect("TODO")
|
||||
.for_each(|plug_awp04l_2_power| async move {
|
||||
tracing::warn!(?plug_awp04l_2_power)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
tasks.join_all().await;
|
||||
}
|
||||
)
|
||||
.0
|
||||
|
||||
@@ -12,13 +12,13 @@ pub struct EntityId(pub Domain, pub ObjectId);
|
||||
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
pub enum EntityIdParsingError {
|
||||
#[snafu(display("entity IDs have a dot / period in them, e.g. light.kitchen_lamp"))]
|
||||
/// entity IDs have a dot / period in them, e.g. light.kitchen_lamp
|
||||
MissingDot,
|
||||
|
||||
#[snafu(display("could not parse the domain part of the entity ID"))]
|
||||
/// could not parse the domain part of the entity ID
|
||||
ParsingDomain { source: <Domain as FromStr>::Err },
|
||||
|
||||
#[snafu(display("could not parse the object ID part of the entity ID"))]
|
||||
/// could not parse the object ID part of the entity ID
|
||||
ParsingObjectId { source: ObjectIdParsingError },
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
use super::id::Id;
|
||||
use once_cell::sync::OnceCell;
|
||||
use pyo3::{prelude::*, types::PyType};
|
||||
|
||||
/// The context that triggered something.
|
||||
#[derive(Debug, FromPyObject)]
|
||||
pub struct Context<Event> {
|
||||
pub id: Id,
|
||||
pub user_id: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
/// In order to prevent cycles, the user must decide to pass [`Py<PyAny>`] for the `Event` type here
|
||||
/// or for the `Context` type in [`Event`]
|
||||
pub origin_event: Event,
|
||||
}
|
||||
|
||||
impl<'py, Event: IntoPyObject<'py>> IntoPyObject<'py> for Context<Event> {
|
||||
type Target = PyAny;
|
||||
|
||||
type Output = Bound<'py, Self::Target>;
|
||||
|
||||
type Error = PyErr;
|
||||
|
||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||
static HOMEASSISTANT_CORE: OnceCell<Py<PyModule>> = OnceCell::new();
|
||||
|
||||
let homeassistant_core = HOMEASSISTANT_CORE
|
||||
.get_or_try_init(|| Result::<_, PyErr>::Ok(py.import("homeassistant.core")?.unbind()))?
|
||||
.bind(py);
|
||||
|
||||
let context_class = homeassistant_core.getattr("Context")?;
|
||||
let context_class = context_class.cast_into::<PyType>()?;
|
||||
|
||||
let context_instance = context_class.call1((self.user_id, self.parent_id, self.id))?;
|
||||
|
||||
context_instance.setattr("origin_event", self.origin_event)?;
|
||||
|
||||
Ok(context_instance)
|
||||
}
|
||||
}
|
||||
@@ -4,38 +4,38 @@ use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
use ulid::Ulid;
|
||||
|
||||
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
||||
pub enum Id {
|
||||
pub enum ContextId {
|
||||
Ulid(Ulid),
|
||||
Other(Arc<str>),
|
||||
}
|
||||
|
||||
impl From<String> for Id {
|
||||
impl From<String> for ContextId {
|
||||
fn from(s: String) -> Self {
|
||||
if let Ok(ulid) = s.parse() {
|
||||
Id::Ulid(ulid)
|
||||
ContextId::Ulid(ulid)
|
||||
} else {
|
||||
Id::Other(s.into())
|
||||
ContextId::Other(s.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Id {
|
||||
impl FromStr for ContextId {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(ulid) = s.parse() {
|
||||
Ok(Id::Ulid(ulid))
|
||||
Ok(ContextId::Ulid(ulid))
|
||||
} else {
|
||||
Ok(Id::Other(s.into()))
|
||||
Ok(ContextId::Other(s.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Id {
|
||||
impl Display for ContextId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Id::Ulid(ulid) => write!(f, "{ulid}"),
|
||||
Id::Other(other) => write!(f, "{other}"),
|
||||
ContextId::Ulid(ulid) => write!(f, "{ulid}"),
|
||||
ContextId::Other(other) => write!(f, "{other}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,44 @@
|
||||
pub mod context;
|
||||
pub mod id;
|
||||
use once_cell::sync::OnceCell;
|
||||
use pyo3::{
|
||||
types::{PyAnyMethods, PyModule, PyType},
|
||||
Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
||||
};
|
||||
|
||||
mod context_id;
|
||||
pub use context_id::ContextId;
|
||||
|
||||
/// The context that triggered something.
|
||||
#[derive(Debug, FromPyObject)]
|
||||
pub struct Context<Event> {
|
||||
pub id: ContextId,
|
||||
pub user_id: Option<String>,
|
||||
pub parent_id: Option<String>,
|
||||
/// In order to prevent cycles, the user must decide to pass [`Py<PyAny>`] for the `Event` type here
|
||||
/// or for the `Context` type in [`Event`]
|
||||
pub origin_event: Event,
|
||||
}
|
||||
|
||||
impl<'py, Event: IntoPyObject<'py>> IntoPyObject<'py> for Context<Event> {
|
||||
type Target = PyAny;
|
||||
|
||||
type Output = Bound<'py, Self::Target>;
|
||||
|
||||
type Error = PyErr;
|
||||
|
||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||
static HOMEASSISTANT_CORE: OnceCell<Py<PyModule>> = OnceCell::new();
|
||||
|
||||
let homeassistant_core = HOMEASSISTANT_CORE
|
||||
.get_or_try_init(|| Result::<_, PyErr>::Ok(py.import("homeassistant.core")?.unbind()))?
|
||||
.bind(py);
|
||||
|
||||
let context_class = homeassistant_core.getattr("Context")?;
|
||||
let context_class = context_class.cast_into::<PyType>()?;
|
||||
|
||||
let context_instance = context_class.call1((self.user_id, self.parent_id, self.id))?;
|
||||
|
||||
context_instance.setattr("origin_event", self.origin_event)?;
|
||||
|
||||
Ok(context_instance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use super::event_origin::EventOrigin;
|
||||
|
||||
/// Representation of an event within the bus.
|
||||
#[derive(Debug, FromPyObject)]
|
||||
pub struct Event<Type, Data, Context> {
|
||||
pub event_type: Type,
|
||||
pub data: Data,
|
||||
pub origin: EventOrigin,
|
||||
/// In order to prevent cycles, the user must decide to pass [`Py<PyAny>`] for the `Context` type here
|
||||
/// or for the `Event` type in [`Context`]
|
||||
pub context: Context,
|
||||
time_fired_timestamp: f64,
|
||||
}
|
||||
|
||||
impl<Type, Data, Context> Event<Type, Data, Context> {
|
||||
pub fn time_fired(&self) -> Option<DateTime<Utc>> {
|
||||
const NANOS_PER_SEC: i32 = 1_000_000_000;
|
||||
|
||||
let secs = self.time_fired_timestamp as i64;
|
||||
let nsecs = (self.time_fired_timestamp.fract() * (NANOS_PER_SEC as f64)) as u32;
|
||||
|
||||
DateTime::from_timestamp(secs, nsecs)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ use std::str::FromStr;
|
||||
|
||||
use pyo3::{
|
||||
exceptions::{PyException, PyTypeError, PyValueError},
|
||||
prelude::*,
|
||||
types::PyAnyMethods,
|
||||
Borrowed, FromPyObject, PyAny, PyErr,
|
||||
};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
pub mod context;
|
||||
pub mod event;
|
||||
pub mod event_origin;
|
||||
pub mod specific;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use pyo3::FromPyObject;
|
||||
|
||||
pub use event_origin::{EventOrigin, ExtractEventOriginError};
|
||||
|
||||
/// Representation of an event within the bus.
|
||||
#[derive(Debug, FromPyObject)]
|
||||
pub struct Event<Type, Data, Context> {
|
||||
pub event_type: Type,
|
||||
pub data: Data,
|
||||
pub origin: EventOrigin,
|
||||
/// In order to prevent cycles, the user must decide to pass [`Py<PyAny>`] for the `Context` type here
|
||||
/// or for the `Event` type in [`Context`]
|
||||
pub context: Context,
|
||||
time_fired_timestamp: f64,
|
||||
}
|
||||
|
||||
impl<Type, Data, Context> Event<Type, Data, Context> {
|
||||
pub fn time_fired(&self) -> Option<DateTime<Utc>> {
|
||||
const NANOS_PER_SEC: i32 = 1_000_000_000;
|
||||
|
||||
let secs = self.time_fired_timestamp as i64;
|
||||
let nsecs = (self.time_fired_timestamp.fract() * (NANOS_PER_SEC as f64)) as u32;
|
||||
|
||||
DateTime::from_timestamp(secs, nsecs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use pyo3::FromPyObject;
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
use snafu::Snafu;
|
||||
use string_literal::StringLiteral;
|
||||
|
||||
use crate::{entity_id::EntityId, state_object::StateObject};
|
||||
@@ -36,7 +32,7 @@ pub type Event<
|
||||
NewAttributes,
|
||||
NewStateContextEvent,
|
||||
Context,
|
||||
> = super::super::event::Event<
|
||||
> = super::super::Event<
|
||||
Type,
|
||||
Data<
|
||||
OldState,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use std::{convert::Infallible, sync::Arc};
|
||||
|
||||
use pyo3::{
|
||||
types::PyAnyMethods as _, Borrowed, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
||||
};
|
||||
use python_utils::{detach, validate_type_by_name, TypeByNameValidationError};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
@@ -32,22 +33,26 @@ impl<'py> IntoPyObject<'py> for &HomeAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
pub enum GetStatesError {
|
||||
/// couldn't get the `states` attribute on the Home Assistant object
|
||||
GetStatesAttributeError { source: PyErr },
|
||||
GetStatesAttributeError { source: Arc<PyErr> },
|
||||
|
||||
/// couldn't extract the `states` as a [`StateMachine`]
|
||||
ExtractStateMachineError { source: TypeByNameValidationError },
|
||||
ExtractStateMachineError {
|
||||
source: Arc<TypeByNameValidationError>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
pub enum GetServicesError {
|
||||
/// couldn't get the `services` attribute on the Home Assistant object
|
||||
GetServicesAttributeError { source: PyErr },
|
||||
GetServicesAttributeError { source: Arc<PyErr> },
|
||||
|
||||
/// couldn't extract the `states` as a [`ServiceRegistry`]
|
||||
ExtractServiceRegistryError { source: TypeByNameValidationError },
|
||||
ExtractServiceRegistryError {
|
||||
source: Arc<TypeByNameValidationError>,
|
||||
},
|
||||
}
|
||||
|
||||
impl HomeAssistant {
|
||||
@@ -73,15 +78,23 @@ impl HomeAssistant {
|
||||
let states = self
|
||||
.0
|
||||
.getattr(py, "states")
|
||||
.map_err(Arc::new)
|
||||
.context(GetStatesAttributeSnafu)?;
|
||||
states.extract(py).context(ExtractStateMachineSnafu)
|
||||
states
|
||||
.extract(py)
|
||||
.map_err(Arc::new)
|
||||
.context(ExtractStateMachineSnafu)
|
||||
}
|
||||
|
||||
pub fn services(&self, py: Python<'_>) -> Result<ServiceRegistry, GetServicesError> {
|
||||
let services = self
|
||||
.0
|
||||
.getattr(py, "services")
|
||||
.map_err(Arc::new)
|
||||
.context(GetServicesAttributeSnafu)?;
|
||||
services.extract(py).context(ExtractServiceRegistrySnafu)
|
||||
services
|
||||
.extract(py)
|
||||
.map_err(Arc::new)
|
||||
.context(ExtractServiceRegistrySnafu)
|
||||
}
|
||||
}
|
||||
|
||||
16
home-assistant/src/input_number/attributes.rs
Normal file
16
home-assistant/src/input_number/attributes.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use pyo3::FromPyObject;
|
||||
|
||||
use super::InputNumberMode;
|
||||
|
||||
#[derive(Debug, FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
pub struct InputNumberAttributes<Number> {
|
||||
initial: Option<Number>,
|
||||
editable: bool,
|
||||
min: Number,
|
||||
max: Number,
|
||||
step: Number,
|
||||
mode: InputNumberMode,
|
||||
// todo: CustomUnitOfMeasurement type? probably not?
|
||||
unit_of_measurement: Option<String>,
|
||||
}
|
||||
@@ -1 +1,75 @@
|
||||
use std::{future::Future, str::FromStr, sync::Arc};
|
||||
|
||||
use emitter_and_signal::{Signal, SignalExt};
|
||||
use pyo3::{FromPyObject, Py, PyAny, PyErr, Python};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
use crate::{
|
||||
domain::Domain,
|
||||
entity_id::EntityId,
|
||||
home_assistant::HomeAssistant,
|
||||
object_id::ObjectId,
|
||||
state::HomeAssistantState,
|
||||
state_object::{self, StateObject, StateObjectSignalError},
|
||||
};
|
||||
|
||||
mod attributes;
|
||||
mod mode;
|
||||
|
||||
pub use attributes::InputNumberAttributes;
|
||||
pub use mode::InputNumberMode;
|
||||
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
pub enum CreateSignalError {
|
||||
/// couldn't get the underlying state object signal
|
||||
StateObjectSignalError {
|
||||
source: state_object::CreateSignalError,
|
||||
},
|
||||
|
||||
/// couldn't map the state object to a power value
|
||||
MappedSignalError {
|
||||
source: emitter_and_signal::ProducerExited,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn signal<
|
||||
'py,
|
||||
Number: 'static + uom::num::Num + Clone + Send + Sync + FromStr + for<'a, 'py2> FromPyObject<'a, 'py2>,
|
||||
>(
|
||||
py: Python<'py>,
|
||||
home_assistant: &'py HomeAssistant,
|
||||
object_id: ObjectId,
|
||||
) -> Result<
|
||||
(
|
||||
Signal<Option<Arc<Result<HomeAssistantState<Number>, StateObjectSignalError<Arc<PyErr>>>>>>,
|
||||
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
||||
),
|
||||
CreateSignalError,
|
||||
> {
|
||||
let entity_id = EntityId(Domain::InputNumber, object_id);
|
||||
|
||||
let (signal, task1) = StateObject::<
|
||||
HomeAssistantState<Number>,
|
||||
InputNumberAttributes<Number>,
|
||||
Py<PyAny>,
|
||||
>::signal(py, home_assistant, entity_id)
|
||||
.context(StateObjectSignalSnafu)?;
|
||||
|
||||
let (signal, task2) = signal
|
||||
.map(|state_object_arc_result_option| {
|
||||
state_object_arc_result_option.map(|state_object_arc_result| {
|
||||
Arc::new(
|
||||
(*state_object_arc_result)
|
||||
.as_ref()
|
||||
.map(|state_object| state_object.state.clone())
|
||||
.map_err(|e| e.clone()),
|
||||
)
|
||||
})
|
||||
})
|
||||
.context(MappedSignalSnafu)?;
|
||||
|
||||
Ok((
|
||||
signal,
|
||||
async move { tokio::try_join!(task1, task2).map(|_| ()) },
|
||||
))
|
||||
}
|
||||
|
||||
10
home-assistant/src/input_number/mode.rs
Normal file
10
home-assistant/src/input_number/mode.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Debug, Clone, Default, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum InputNumberMode {
|
||||
Box,
|
||||
#[default]
|
||||
Slider,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::FromPyObject;
|
||||
|
||||
#[derive(Debug, FromPyObject)]
|
||||
#[pyo3(from_item_all)]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use attributes::LightAttributes;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{FromPyObject, Py, PyAny, Python};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use state::LightState;
|
||||
|
||||
@@ -29,15 +31,15 @@ impl HomeAssistantLight {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
pub enum GetStateObjectError {
|
||||
/// couldn't get the state machine registry
|
||||
GetStatesError { source: GetStatesError },
|
||||
|
||||
/// this state object exists in the state machine registry, but it couldn't be extracted as a light state object
|
||||
GetStateError { source: GetStateError<
|
||||
GetStateError { source: Arc<GetStateError<
|
||||
<StateObject<HomeAssistantState<LightState>, LightAttributes, Py<PyAny>> as FromPyObject<'static, 'static>>::Error
|
||||
> },
|
||||
>> },
|
||||
|
||||
/// this entity does not have a state object in the registry
|
||||
EntityMissing,
|
||||
@@ -55,6 +57,7 @@ impl HomeAssistantLight {
|
||||
let entity_id = self.entity_id();
|
||||
let state_object = states
|
||||
.get(py, entity_id)
|
||||
.map_err(Arc::new)
|
||||
.context(GetStateSnafu)?
|
||||
.ok_or(GetStateObjectError::EntityMissing)?;
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ use super::{GetStateObjectError, HomeAssistantLight};
|
||||
use crate::home_assistant::GetServicesError;
|
||||
use crate::service_registry::CallServiceError;
|
||||
use crate::{
|
||||
event::context::context::Context,
|
||||
event::context::Context,
|
||||
state::{ErrorState, HomeAssistantState, UnexpectedState},
|
||||
};
|
||||
use protocol::light::{GetState, SetState};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::Python;
|
||||
use python_utils::IsNone;
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
@@ -30,9 +30,7 @@ impl GetState for HomeAssistantLight {
|
||||
HomeAssistantState::Err(error_state) => {
|
||||
Err(GetStateError::Error { state: error_state })
|
||||
}
|
||||
HomeAssistantState::UnexpectedErr(state) => {
|
||||
Err(GetStateError::UnexpectedError { state })
|
||||
}
|
||||
HomeAssistantState::Unexpected(state) => Err(GetStateError::UnexpectedError { state }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,13 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use pyo3::{
|
||||
exceptions::{PyException, PyValueError},
|
||||
prelude::*,
|
||||
};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
use strum::EnumString;
|
||||
|
||||
#[derive(Debug, Clone, EnumString, strum::Display)]
|
||||
#[derive(Debug, Clone, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum LightState {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum ExtractLightStateError {
|
||||
/// couldn't extract the object as a string
|
||||
ExtractStringError { source: PyErr },
|
||||
|
||||
/// couldn't parse the string as a [`LightState`]
|
||||
ParseError {
|
||||
source: <LightState as FromStr>::Err,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ExtractLightStateError> for PyErr {
|
||||
fn from(error: ExtractLightStateError) -> Self {
|
||||
match &error {
|
||||
ExtractLightStateError::ExtractStringError { .. } => {
|
||||
PyException::new_err(error.to_string())
|
||||
}
|
||||
ExtractLightStateError::ParseError { .. } => PyValueError::new_err(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
||||
impl<'a, 'py> FromPyObject<'a, 'py> for LightState {
|
||||
type Error = ExtractLightStateError;
|
||||
|
||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
||||
let s = ob.extract::<&str>().context(ExtractStringSnafu)?;
|
||||
|
||||
let state = LightState::from_str(&s).context(ParseSnafu)?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LightState> for protocol::light::State {
|
||||
fn from(light_state: LightState) -> Self {
|
||||
match light_state {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use arbitrary_value::{arbitrary::Arbitrary, map::Map};
|
||||
use once_cell::sync::OnceCell;
|
||||
use pyo3::{prelude::*, types::PyTuple};
|
||||
use pyo3::{
|
||||
types::{PyAnyMethods as _, PyModule, PyTuple},
|
||||
Borrowed, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
||||
};
|
||||
use python_utils::{detach, validate_type_by_name, TypeByNameValidationError};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -54,7 +57,7 @@ pub struct LogData<ExcInfo> {
|
||||
}
|
||||
|
||||
impl HassLogger {
|
||||
pub fn new(py: Python<'_>, name: &str) -> PyResult<Self> {
|
||||
pub fn new(py: Python<'_>, name: &str) -> Result<Self, PyErr> {
|
||||
static LOGGING_MODULE: OnceCell<Py<PyModule>> = OnceCell::new();
|
||||
|
||||
let logging_module = LOGGING_MODULE
|
||||
@@ -71,7 +74,7 @@ impl HassLogger {
|
||||
msg: &str,
|
||||
args: Vec<Arbitrary>,
|
||||
log_data: Option<LogData<ExcInfo>>,
|
||||
) -> PyResult<()> {
|
||||
) -> Result<(), PyErr> {
|
||||
let mut all_args = vec![msg.into_pyobject(py)?.into_any()];
|
||||
for arg in args {
|
||||
let arg = arg.into_pyobject(py)?;
|
||||
@@ -94,7 +97,7 @@ impl HassLogger {
|
||||
msg: &str,
|
||||
args: Vec<Arbitrary>,
|
||||
log_data: Option<LogData<ExcInfo>>,
|
||||
) -> PyResult<()> {
|
||||
) -> Result<(), PyErr> {
|
||||
let mut all_args = vec![msg.into_pyobject(py)?.into_any()];
|
||||
for arg in args {
|
||||
let arg = arg.into_pyobject(py)?;
|
||||
@@ -117,7 +120,7 @@ impl HassLogger {
|
||||
msg: &str,
|
||||
args: Vec<Arbitrary>,
|
||||
log_data: Option<LogData<ExcInfo>>,
|
||||
) -> PyResult<()> {
|
||||
) -> Result<(), PyErr> {
|
||||
let mut all_args = vec![msg.into_pyobject(py)?.into_any()];
|
||||
for arg in args {
|
||||
let arg = arg.into_pyobject(py)?;
|
||||
@@ -141,7 +144,7 @@ impl HassLogger {
|
||||
msg: &str,
|
||||
args: Vec<Arbitrary>,
|
||||
log_data: Option<LogData<ExcInfo>>,
|
||||
) -> PyResult<()> {
|
||||
) -> Result<(), PyErr> {
|
||||
let mut all_args = vec![msg.into_pyobject(py)?.into_any()];
|
||||
for arg in args {
|
||||
let arg = arg.into_pyobject(py)?;
|
||||
@@ -164,7 +167,7 @@ impl HassLogger {
|
||||
msg: &str,
|
||||
args: Vec<Arbitrary>,
|
||||
log_data: Option<LogData<ExcInfo>>,
|
||||
) -> PyResult<()> {
|
||||
) -> Result<(), PyErr> {
|
||||
let mut all_args = vec![msg.into_pyobject(py)?.into_any()];
|
||||
for arg in args {
|
||||
let arg = arg.into_pyobject(py)?;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use std::str::FromStr;
|
||||
use strum::EnumString;
|
||||
|
||||
use crate::{
|
||||
notify::service::mobile_app::SpecialMessage,
|
||||
object_id::ObjectId,
|
||||
service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall},
|
||||
};
|
||||
use crate::service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall};
|
||||
|
||||
use super::super::{NotifyMobileAppServiceData, NotifyMobileAppServiceDataData};
|
||||
use super::super::{
|
||||
MobileAppDeviceId, NotifyMobileAppServiceData, NotifyMobileAppServiceDataData, SpecialMessage,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, EnumString, strum::Display)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
@@ -33,7 +31,7 @@ pub enum Filter {
|
||||
|
||||
#[derive(Debug, Clone, typed_builder::TypedBuilder)]
|
||||
pub struct DoNotDisturb {
|
||||
pub object_id: ObjectId,
|
||||
pub device_id: MobileAppDeviceId,
|
||||
pub filter: Filter,
|
||||
}
|
||||
|
||||
@@ -41,11 +39,11 @@ impl IntoServiceCall for DoNotDisturb {
|
||||
type ServiceData = NotifyMobileAppServiceData;
|
||||
|
||||
fn into_service_call(self) -> (ServiceDomain, ServiceId, Self::ServiceData) {
|
||||
let DoNotDisturb { object_id, filter } = self;
|
||||
let DoNotDisturb { device_id, filter } = self;
|
||||
|
||||
let service_domain = ServiceDomain::from_str("notify").expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{object_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{device_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_data = NotifyMobileAppServiceData::builder()
|
||||
.message(SpecialMessage::CommandDnd.to_string())
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
notify::service::mobile_app::SpecialMessage,
|
||||
object_id::ObjectId,
|
||||
service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall},
|
||||
};
|
||||
use crate::service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall};
|
||||
|
||||
use super::super::NotifyMobileAppServiceData;
|
||||
use super::super::{MobileAppDeviceId, NotifyMobileAppServiceData, SpecialMessage};
|
||||
|
||||
#[derive(Debug, Clone, typed_builder::TypedBuilder)]
|
||||
pub struct RequestLocationUpdate {
|
||||
pub object_id: ObjectId,
|
||||
pub device_id: MobileAppDeviceId,
|
||||
}
|
||||
|
||||
impl IntoServiceCall for RequestLocationUpdate {
|
||||
type ServiceData = NotifyMobileAppServiceData;
|
||||
|
||||
fn into_service_call(self) -> (ServiceDomain, ServiceId, Self::ServiceData) {
|
||||
let RequestLocationUpdate { object_id } = self;
|
||||
let RequestLocationUpdate { device_id } = self;
|
||||
|
||||
let service_domain = ServiceDomain::from_str("notify").expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{object_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{device_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_data = NotifyMobileAppServiceData::builder()
|
||||
.message(SpecialMessage::RequestLocationUpdate.to_string())
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
|
||||
use crate::slug::Slug;
|
||||
|
||||
pub use crate::slug::SlugParsingError as ParseMobileAppDeviceIdError;
|
||||
|
||||
#[derive(Debug, Clone, derive_more::FromStr, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||
pub struct MobileAppDeviceId(pub Slug);
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::{convert::Infallible, str::FromStr};
|
||||
use std::str::FromStr;
|
||||
|
||||
use pyo3::{
|
||||
types::{PyDict, PyDictMethods, PyString},
|
||||
types::{PyDict, PyDictMethods},
|
||||
Bound, IntoPyObject, PyErr, Python,
|
||||
};
|
||||
use python_utils::{FromPyFromStr, IntoPyObjectViaDisplay, ToStrToPy};
|
||||
@@ -10,10 +10,12 @@ use strum::EnumString;
|
||||
use url::Url;
|
||||
|
||||
pub mod command;
|
||||
pub mod device_id;
|
||||
pub mod standard;
|
||||
pub mod text_to_speech;
|
||||
|
||||
pub use command::*;
|
||||
pub use device_id::MobileAppDeviceId;
|
||||
pub use standard::StandardNotification;
|
||||
pub use text_to_speech::TextToSpeech;
|
||||
|
||||
|
||||
@@ -2,19 +2,16 @@ use std::str::FromStr;
|
||||
|
||||
use mitsein::vec1::Vec1;
|
||||
|
||||
use crate::{
|
||||
object_id::ObjectId,
|
||||
service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall},
|
||||
};
|
||||
use crate::service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall};
|
||||
|
||||
use super::{
|
||||
Action, NonSpecialMessage, NotifyMobileAppServiceData, NotifyMobileAppServiceDataData,
|
||||
Visibility,
|
||||
Action, MobileAppDeviceId, NonSpecialMessage, NotifyMobileAppServiceData,
|
||||
NotifyMobileAppServiceDataData, Visibility,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, typed_builder::TypedBuilder)]
|
||||
pub struct StandardNotification {
|
||||
pub object_id: ObjectId,
|
||||
pub device_id: MobileAppDeviceId,
|
||||
|
||||
#[builder(default, setter(strip_option))]
|
||||
pub title: Option<String>,
|
||||
@@ -32,7 +29,7 @@ impl IntoServiceCall for StandardNotification {
|
||||
|
||||
fn into_service_call(self) -> (ServiceDomain, ServiceId, Self::ServiceData) {
|
||||
let StandardNotification {
|
||||
object_id,
|
||||
device_id,
|
||||
title,
|
||||
message,
|
||||
actions,
|
||||
@@ -41,7 +38,7 @@ impl IntoServiceCall for StandardNotification {
|
||||
|
||||
let service_domain = ServiceDomain::from_str("notify").expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{object_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{device_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_data = NotifyMobileAppServiceData::builder()
|
||||
.title_option(title)
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
notify::service::mobile_app::SpecialMessage,
|
||||
object_id::ObjectId,
|
||||
service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall},
|
||||
};
|
||||
use crate::service::{service_domain::ServiceDomain, service_id::ServiceId, IntoServiceCall};
|
||||
|
||||
use super::{MediaStream, NotifyMobileAppServiceData, NotifyMobileAppServiceDataData};
|
||||
use super::{
|
||||
MediaStream, MobileAppDeviceId, NotifyMobileAppServiceData, NotifyMobileAppServiceDataData,
|
||||
SpecialMessage,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, typed_builder::TypedBuilder)]
|
||||
pub struct TextToSpeech {
|
||||
pub object_id: ObjectId,
|
||||
pub device_id: MobileAppDeviceId,
|
||||
|
||||
pub message: String,
|
||||
pub media_stream: Option<MediaStream>,
|
||||
@@ -21,14 +20,14 @@ impl IntoServiceCall for TextToSpeech {
|
||||
|
||||
fn into_service_call(self) -> (ServiceDomain, ServiceId, Self::ServiceData) {
|
||||
let TextToSpeech {
|
||||
object_id,
|
||||
device_id,
|
||||
message,
|
||||
media_stream,
|
||||
} = self;
|
||||
|
||||
let service_domain = ServiceDomain::from_str("notify").expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{object_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
let service_id = ServiceId::from_str(&format!("mobile_app_{device_id}")).expect("statically written and known to be a valid slug; hoping to get compiler checks instead in the future");
|
||||
|
||||
let service_data = NotifyMobileAppServiceData::builder()
|
||||
.message(SpecialMessage::Tts.to_string())
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use pyo3::{prelude::*, types::PyString};
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
|
||||
use super::slug::Slug;
|
||||
|
||||
pub use super::slug::SlugParsingError as ObjectIdParsingError;
|
||||
|
||||
#[derive(Debug, Clone, derive_more::Display, derive_more::FromStr)]
|
||||
#[derive(Debug, Clone, derive_more::FromStr, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||
pub struct ObjectId(pub Slug);
|
||||
|
||||
impl<'py> IntoPyObject<'py> for ObjectId {
|
||||
type Target = PyString;
|
||||
type Output = Bound<'py, Self::Target>;
|
||||
type Error = Infallible;
|
||||
|
||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||
let s = self.to_string();
|
||||
s.into_pyobject(py)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
use std::{future::Future, sync::Arc};
|
||||
use std::{future::Future, str::FromStr, sync::Arc};
|
||||
|
||||
use emitter_and_signal::{Signal, SignalExt};
|
||||
use pyo3::{FromPyObject, Py, PyAny, PyErr, Python};
|
||||
use python_utils::{FromPyFromStr, FromPyObjectViaParse, ToStrToPy};
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use string_literal::StringLiteral;
|
||||
use uom::{
|
||||
si::{
|
||||
energy::btu,
|
||||
power::{gigawatt, kilowatt, megawatt, milliwatt, terawatt, watt},
|
||||
time::hour,
|
||||
Units, SI,
|
||||
},
|
||||
Conversion,
|
||||
};
|
||||
|
||||
use super::super::state_classes::measurement::Measurement;
|
||||
use crate::{
|
||||
@@ -12,6 +21,7 @@ use crate::{
|
||||
entity_id::EntityId,
|
||||
home_assistant::HomeAssistant,
|
||||
object_id::ObjectId,
|
||||
state::HomeAssistantState,
|
||||
state_object::{self, StateObject, StateObjectSignalError},
|
||||
unit_of_measurement::power::UnitOfMeasurement,
|
||||
};
|
||||
@@ -37,25 +47,45 @@ pub enum CreateSignalError {
|
||||
|
||||
/// couldn't map the state object to a power value
|
||||
MappedSignalError {
|
||||
source: emitter_and_signal::signal_ext::ProducerAlreadyExited,
|
||||
source: emitter_and_signal::ProducerExited,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn signal<'py>(
|
||||
pub fn signal<'py, Number>(
|
||||
py: Python<'py>,
|
||||
home_assistant: &'py HomeAssistant,
|
||||
object_id: ObjectId,
|
||||
) -> Result<
|
||||
(
|
||||
Signal<Option<Arc<Result<uom::si::f64::Power, StateObjectSignalError<PyErr>>>>>,
|
||||
Signal<
|
||||
Option<
|
||||
Result<
|
||||
HomeAssistantState<uom::si::quantities::Power<Number>>,
|
||||
StateObjectSignalError<Arc<PyErr>>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
||||
),
|
||||
CreateSignalError,
|
||||
> {
|
||||
>
|
||||
where
|
||||
Number: 'static + uom::num::Num + Clone + Send + Sync + FromStr,
|
||||
Number: uom::num::Num + uom::Conversion<Number, T = Number>,
|
||||
milliwatt: Conversion<Number, T = Number>,
|
||||
watt: Conversion<Number, T = Number>,
|
||||
kilowatt: Conversion<Number, T = Number>,
|
||||
megawatt: Conversion<Number, T = Number>,
|
||||
gigawatt: Conversion<Number, T = Number>,
|
||||
terawatt: Conversion<Number, T = Number>,
|
||||
btu: Conversion<Number, T = Number>,
|
||||
hour: Conversion<Number, T = Number>,
|
||||
SI<Number>: Units<Number>,
|
||||
{
|
||||
let entity_id = EntityId(Domain::Sensor, object_id);
|
||||
|
||||
let (signal, task1) =
|
||||
StateObject::<FromPyObjectViaParse<f64>, PowerSensorAttributes, Py<PyAny>>::signal(
|
||||
StateObject::<HomeAssistantState<Number>, PowerSensorAttributes, Py<PyAny>>::signal(
|
||||
py,
|
||||
home_assistant,
|
||||
entity_id,
|
||||
@@ -65,18 +95,17 @@ pub fn signal<'py>(
|
||||
let (signal, task2) = signal
|
||||
.map(|state_object_result_option| {
|
||||
state_object_result_option.map(|state_object_result| {
|
||||
Arc::new(
|
||||
Result::as_ref(&state_object_result)
|
||||
.map(|state_object| {
|
||||
let amount = state_object.state.0;
|
||||
let unit_of_measurement = state_object.attributes.unit_of_measurement;
|
||||
|
||||
let power = unit_of_measurement.into_uom(amount);
|
||||
|
||||
power
|
||||
})
|
||||
.map_err(|e| todo!()),
|
||||
)
|
||||
Result::as_ref(&state_object_result)
|
||||
.map(
|
||||
|StateObject {
|
||||
state, attributes, ..
|
||||
}| {
|
||||
state.as_ref().map(|amount| {
|
||||
attributes.unit_of_measurement.into_uom(amount.clone())
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(Clone::clone)
|
||||
})
|
||||
})
|
||||
.context(MappedSignalSnafu)?;
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use pyo3::{prelude::*, types::PyString};
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
|
||||
use super::super::slug::Slug;
|
||||
|
||||
pub use super::super::slug::SlugParsingError as ServiceDomainParsingError;
|
||||
|
||||
#[derive(Debug, Clone, derive_more::Display, derive_more::FromStr)]
|
||||
#[derive(Debug, Clone, derive_more::FromStr, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||
pub struct ServiceDomain(pub Slug);
|
||||
|
||||
impl<'py> IntoPyObject<'py> for ServiceDomain {
|
||||
type Target = PyString;
|
||||
type Output = Bound<'py, Self::Target>;
|
||||
type Error = Infallible;
|
||||
|
||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||
let s = self.to_string();
|
||||
s.into_pyobject(py)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,8 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use pyo3::{prelude::*, types::PyString};
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
|
||||
use super::super::slug::Slug;
|
||||
|
||||
pub use super::super::slug::SlugParsingError as ServiceIdParsingError;
|
||||
|
||||
#[derive(Debug, Clone, derive_more::Display, derive_more::FromStr)]
|
||||
#[derive(Debug, Clone, derive_more::FromStr, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||
pub struct ServiceId(pub Slug);
|
||||
|
||||
impl<'py> IntoPyObject<'py> for ServiceId {
|
||||
type Target = PyString;
|
||||
type Output = Bound<'py, Self::Target>;
|
||||
type Error = Infallible;
|
||||
|
||||
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
|
||||
let s = self.to_string();
|
||||
s.into_pyobject(py)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use super::{event::context::context::Context, service::IntoServiceCall};
|
||||
use super::{event::context::Context, service::IntoServiceCall};
|
||||
use pyo3::{
|
||||
conversion::FromPyObjectOwned,
|
||||
exceptions::{PyException, PyTypeError},
|
||||
prelude::*,
|
||||
types::PyAnyMethods as _,
|
||||
Borrowed, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
||||
};
|
||||
use python_utils::{detach, validate_type_by_name, TypeByNameValidationError};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
@@ -73,7 +75,7 @@ impl ServiceRegistry {
|
||||
return_response,
|
||||
);
|
||||
|
||||
let future = Python::attach::<_, PyResult<_>>(|py| {
|
||||
let future = Python::attach::<_, Result<_, PyErr>>(|py| {
|
||||
let service_registry = self.0.bind(py);
|
||||
let awaitable = service_registry.call_method("async_call", args, None)?;
|
||||
pyo3_async_runtimes::tokio::into_future(awaitable)
|
||||
|
||||
@@ -7,8 +7,8 @@ use snafu::Snafu;
|
||||
#[derive(Debug, Clone, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||
pub struct Slug(Arc<str>);
|
||||
|
||||
/// expected a lowercase ASCII alphabetical character (i.e. a through z) or a digit (i.e. 0 through 9) or an underscore (i.e. _) but encountered {encountered}
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
#[snafu(display("expected a lowercase ASCII alphabetical character (i.e. a through z) or a digit (i.e. 0 through 9) or an underscore (i.e. _) but encountered {encountered}"))]
|
||||
pub struct SlugParsingError {
|
||||
encountered: char,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use strum::EnumString;
|
||||
/// A state in Home Assistant that is known to represent an error of some kind:
|
||||
/// * `unavailable` (the device is likely offline or unreachable from the Home Assistant instance)
|
||||
/// * `unknown` (I don't know how to explain this one)
|
||||
#[derive(Debug, Clone, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||
#[derive(Debug, Clone, Copy, EnumString, strum::Display, FromPyFromStr, ToStrToPy)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ErrorState {
|
||||
Unavailable,
|
||||
|
||||
@@ -12,7 +12,7 @@ pub use unexpected_state::UnexpectedState;
|
||||
pub enum HomeAssistantState<State> {
|
||||
Ok(State),
|
||||
Err(ErrorState),
|
||||
UnexpectedErr(UnexpectedState),
|
||||
Unexpected(UnexpectedState),
|
||||
}
|
||||
|
||||
impl<State> From<String> for HomeAssistantState<State>
|
||||
@@ -28,7 +28,7 @@ where
|
||||
return HomeAssistantState::Err(error);
|
||||
}
|
||||
|
||||
HomeAssistantState::UnexpectedErr(UnexpectedState(s.into()))
|
||||
HomeAssistantState::Unexpected(UnexpectedState(s.into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,45 @@ impl<State: FromStr> FromStr for HomeAssistantState<State> {
|
||||
return Ok(HomeAssistantState::Err(error));
|
||||
}
|
||||
|
||||
Ok(HomeAssistantState::UnexpectedErr(UnexpectedState(s.into())))
|
||||
Ok(HomeAssistantState::Unexpected(UnexpectedState(s.into())))
|
||||
}
|
||||
}
|
||||
|
||||
impl<State> HomeAssistantState<State> {
|
||||
pub fn as_ref(&self) -> HomeAssistantState<&State> {
|
||||
match self {
|
||||
HomeAssistantState::Ok(state) => HomeAssistantState::Ok(state),
|
||||
HomeAssistantState::Err(error_state) => HomeAssistantState::Err(*error_state),
|
||||
HomeAssistantState::Unexpected(unexpected_state) => {
|
||||
// TODO: just considered cheap enough to clone implicitly
|
||||
HomeAssistantState::Unexpected(unexpected_state.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<Mapped>(self, f: impl FnOnce(State) -> Mapped) -> HomeAssistantState<Mapped> {
|
||||
match self {
|
||||
HomeAssistantState::Ok(state) => HomeAssistantState::Ok(f(state)),
|
||||
HomeAssistantState::Err(error_state) => HomeAssistantState::Err(error_state),
|
||||
HomeAssistantState::Unexpected(unexpected_state) => {
|
||||
HomeAssistantState::Unexpected(unexpected_state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<State> HomeAssistantState<&State> {
|
||||
pub fn cloned(self) -> HomeAssistantState<State>
|
||||
where
|
||||
State: Clone,
|
||||
{
|
||||
self.map(Clone::clone)
|
||||
}
|
||||
|
||||
pub fn copied(self) -> HomeAssistantState<State>
|
||||
where
|
||||
State: Copy,
|
||||
{
|
||||
self.map(|&s| s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use std::{convert::Infallible, str::FromStr, sync::Arc};
|
||||
|
||||
use pyo3::{exceptions::PyException, prelude::*};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
|
||||
#[derive(Debug, Clone, derive_more::Display)]
|
||||
#[derive(Debug, Clone, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||
pub struct UnexpectedState(pub Arc<str>);
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
pub enum ExtractUnexpectedStateError {
|
||||
/// couldn't extract the object as a string
|
||||
ExtractStringError { source: PyErr },
|
||||
}
|
||||
impl FromStr for UnexpectedState {
|
||||
type Err = Infallible;
|
||||
|
||||
impl From<ExtractUnexpectedStateError> for PyErr {
|
||||
fn from(error: ExtractUnexpectedStateError) -> Self {
|
||||
match &error {
|
||||
ExtractUnexpectedStateError::ExtractStringError { .. } => {
|
||||
PyException::new_err(error.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace with a derive(PyFromStr) (analogous to serde_with::DeserializeFromStr) once I make one
|
||||
impl<'a, 'py> FromPyObject<'a, 'py> for UnexpectedState {
|
||||
type Error = ExtractUnexpectedStateError;
|
||||
|
||||
fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
|
||||
let s = ob.extract::<String>().context(ExtractStringSnafu)?;
|
||||
let s = s.into();
|
||||
|
||||
Ok(UnexpectedState(s))
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.into()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use super::entity_id::EntityId;
|
||||
use super::state_object::StateObject;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::{conversion::FromPyObjectOwned, Borrowed, FromPyObject, Py, PyAny, PyErr, Python};
|
||||
use python_utils::{detach, validate_type_by_name, TypeByNameValidationError};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
@@ -53,6 +53,7 @@ impl StateMachine {
|
||||
.call_method1(py, "get", args)
|
||||
.map_err(Arc::new)
|
||||
.context(GetStateObjectSnafu)?;
|
||||
Ok(state.extract(py).context(ExtractStateObjectSnafu)?)
|
||||
|
||||
state.extract(py).context(ExtractStateObjectSnafu)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
event::{context::context::Context, specific::state_changed},
|
||||
event::{context::Context, specific::state_changed},
|
||||
home_assistant::HomeAssistant,
|
||||
};
|
||||
use crate::{entity_id::EntityId, home_assistant::GetStatesError, state_machine::GetStateError};
|
||||
@@ -7,8 +7,8 @@ use chrono::{DateTime, Utc};
|
||||
use emitter_and_signal::signal::Signal;
|
||||
use once_cell::sync::OnceCell;
|
||||
use pyo3::{
|
||||
prelude::*,
|
||||
types::{PyCFunction, PyDict, PyTuple},
|
||||
types::{PyAnyMethods as _, PyCFunction, PyDict, PyModule, PyTuple},
|
||||
Bound, FromPyObject, IntoPyObject as _, Py, PyAny, PyErr, Python,
|
||||
};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
use std::{future::Future, sync::Arc};
|
||||
@@ -28,7 +28,7 @@ pub struct StateObject<State, Attributes, ContextEvent> {
|
||||
pub type ExtractStateObjectError<'a, 'py, State, Attributes, ContextEvent> =
|
||||
<StateObject<State, Attributes, ContextEvent> as FromPyObject<'a, 'py>>::Error;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[derive(Debug, Clone, Snafu)]
|
||||
pub enum CreateSignalError {
|
||||
/// couldn't get the state machine from the Home Assistant object
|
||||
GetStatesError { source: GetStatesError },
|
||||
@@ -62,7 +62,7 @@ impl<
|
||||
Arc<
|
||||
Result<
|
||||
Self,
|
||||
StateObjectSignalError<<Self as FromPyObject<'a, 'py>>::Error>,
|
||||
StateObjectSignalError<Arc<<Self as FromPyObject<'a, 'py>>::Error>>,
|
||||
>,
|
||||
>,
|
||||
>,
|
||||
@@ -74,6 +74,16 @@ impl<
|
||||
let state_machine = home_assistant.states(py).context(GetStatesSnafu)?;
|
||||
let current = state_machine
|
||||
.get(py, entity_id.clone())
|
||||
.map_err(|e| match e {
|
||||
GetStateError::GetStateObjectError { source } => {
|
||||
GetStateError::GetStateObjectError { source }
|
||||
}
|
||||
GetStateError::ExtractStateObjectError { source } => {
|
||||
GetStateError::ExtractStateObjectError {
|
||||
source: Arc::new(source),
|
||||
}
|
||||
}
|
||||
})
|
||||
.context(GetFromStateMachineSnafu)
|
||||
.transpose();
|
||||
|
||||
@@ -86,7 +96,7 @@ impl<
|
||||
while let Some(publisher) = publisher_stream.wait().await {
|
||||
let (new_state_sender, mut new_state_receiver) = mpsc::channel(8);
|
||||
|
||||
let untrack = Python::attach::<_, PyResult<_>>(|py| {
|
||||
let untrack = Python::attach::<_, Result<_, PyErr>>(|py| {
|
||||
static EVENT_MODULE: OnceCell<Py<PyModule>> = OnceCell::new();
|
||||
|
||||
let event_module = EVENT_MODULE
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||
use strum::EnumString;
|
||||
use uom::{
|
||||
@@ -36,7 +34,7 @@ pub enum UnitOfMeasurement {
|
||||
impl UnitOfMeasurement {
|
||||
pub fn into_uom<V>(&self, amount: V) -> Power<V>
|
||||
where
|
||||
V: uom::num::Num + uom::Conversion<V, T = V>,
|
||||
V: uom::num::Num + Conversion<V, T = V>,
|
||||
milliwatt: Conversion<V, T = V>,
|
||||
watt: Conversion<V, T = V>,
|
||||
kilowatt: Conversion<V, T = V>,
|
||||
|
||||
@@ -7,7 +7,7 @@ license.workspace = true
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
emitter-and-signal = { path = "../emitter-and-signal" }
|
||||
fjall = "2"
|
||||
fjall = "3"
|
||||
postcard = { version = "1.1", features = ["use-std"] }
|
||||
serde = { workspace = true }
|
||||
snafu = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub use bytes::Bytes;
|
||||
use emitter_and_signal::signal::{JoinError, Signal};
|
||||
pub use fjall::{Config, Keyspace, Partition, PartitionCreateOptions};
|
||||
pub use fjall::{Config, Database, Keyspace, KeyspaceCreateOptions};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{OptionExt, ResultExt, Snafu};
|
||||
use std::{fmt::Debug, future::Future, num::NonZeroUsize, ops::Deref, sync::Arc};
|
||||
@@ -23,7 +23,7 @@ pub enum PersistedError {
|
||||
}
|
||||
|
||||
pub async fn persisted<T: Debug + Send + Sync + 'static + Serialize + for<'a> Deserialize<'a>>(
|
||||
partition: Partition,
|
||||
keyspace: Keyspace,
|
||||
identifier: Bytes,
|
||||
buffer: NonZeroUsize,
|
||||
) -> (
|
||||
@@ -32,9 +32,9 @@ pub async fn persisted<T: Debug + Send + Sync + 'static + Serialize + for<'a> De
|
||||
impl Future<Output = Result<(), JoinError>>,
|
||||
) {
|
||||
let initial = spawn_blocking({
|
||||
let partition = partition.clone();
|
||||
let keyspace = keyspace.clone();
|
||||
let identifier = identifier.clone();
|
||||
move || partition.get(identifier.deref())
|
||||
move || keyspace.get(identifier.deref())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -60,9 +60,9 @@ pub async fn persisted<T: Debug + Send + Sync + 'static + Serialize + for<'a> De
|
||||
// Stand-in for Option::async_and_then
|
||||
let persisted_res = match serialized_res {
|
||||
Ok(serialized) => spawn_blocking({
|
||||
let partition = partition.clone();
|
||||
let keyspace = keyspace.clone();
|
||||
let identifier = identifier.clone();
|
||||
move || partition.insert(identifier.deref(), serialized)
|
||||
move || keyspace.insert(identifier.deref(), serialized)
|
||||
}).await.unwrap().context(SavingSnafu),
|
||||
Err(error) => Err(error),
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use pyo3::{
|
||||
exceptions::{PyException, PyTypeError},
|
||||
prelude::*,
|
||||
types::{PyAnyMethods as _, PyStringMethods, PyTypeMethods as _},
|
||||
Borrowed, Bound, Py, PyAny, PyErr,
|
||||
};
|
||||
use snafu::{ResultExt, Snafu};
|
||||
|
||||
@@ -84,5 +85,5 @@ pub fn validate_type_by_name(
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ impl ToTokens for StringLiteral {
|
||||
}
|
||||
}
|
||||
};
|
||||
dbg!(&output.to_string());
|
||||
|
||||
tokens.extend(output);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ where
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { actual, expected } = self;
|
||||
let expected_str = <&'static str>::from(&expected);
|
||||
let expected_str = <&'static str>::from(expected);
|
||||
|
||||
write!(f, "expected {expected_str:?} but got {actual:?}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user