Compare commits
8 Commits
224ee7732f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4786b1e6ba | ||
|
|
a09a97d06d | ||
| 4eb8a752cc | |||
|
|
8d3cde3c43 | ||
|
|
cb216d0a0a | ||
|
|
8b966c1210 | ||
|
|
a2d6d9f4c2 | ||
|
|
8ab8dd3441 |
@@ -41,9 +41,10 @@ impl From<MapKey> for Arbitrary {
|
|||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
pub enum MapKeyFromArbitraryError {
|
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 },
|
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 },
|
MapCannotBeAMapKey { value: Map },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use snafu::Snafu;
|
|||||||
#[derive(Debug, Clone, derive_more::Into)]
|
#[derive(Debug, Clone, derive_more::Into)]
|
||||||
pub struct FiniteF64(f64);
|
pub struct FiniteF64(f64);
|
||||||
|
|
||||||
|
/// {value:?} is not finite
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Snafu)]
|
||||||
#[snafu(display("{value:?} is not finite"))]
|
|
||||||
pub struct NotFinite {
|
pub struct NotFinite {
|
||||||
value: f64,
|
value: f64,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,8 +186,7 @@ async fn send_request<
|
|||||||
let incoming_length = reader.read_u32().await.context(ReadSnafu)?;
|
let incoming_length = reader.read_u32().await.context(ReadSnafu)?;
|
||||||
tracing::info!(?incoming_length);
|
tracing::info!(?incoming_length);
|
||||||
|
|
||||||
let mut incoming_message = Vec::new();
|
let mut incoming_message = vec![0; incoming_length as usize];
|
||||||
incoming_message.resize(incoming_length as usize, 0);
|
|
||||||
reader
|
reader
|
||||||
.read_exact(&mut incoming_message)
|
.read_exact(&mut incoming_message)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
pub mod connection;
|
pub mod connection;
|
||||||
mod impl_protocol;
|
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
|
mod protocol;
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ impl<'de> Deserialize<'de> for MaybeKelvin {
|
|||||||
match u16::deserialize(deserializer)? {
|
match u16::deserialize(deserializer)? {
|
||||||
0 => Ok(MaybeKelvin(None)),
|
0 => Ok(MaybeKelvin(None)),
|
||||||
value => {
|
value => {
|
||||||
let kelvin = Kelvin::try_from(value).map_err(|e| {
|
let kelvin = Kelvin::try_from(value).map_err(|_e| {
|
||||||
serde::de::Error::custom(format!(
|
serde::de::Error::custom(format!(
|
||||||
"{value} is not in the range {}..{}",
|
"{value} is not in the range {}..{}",
|
||||||
Kelvin::MIN,
|
Kelvin::MIN,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use snafu::Snafu;
|
use snafu::Snafu;
|
||||||
|
|
||||||
pub mod emitter;
|
pub mod emitter;
|
||||||
pub mod emitter_ext;
|
mod emitter_ext;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
pub mod signal_ext;
|
mod signal_ext;
|
||||||
|
|
||||||
pub use emitter::Emitter;
|
pub use emitter::Emitter;
|
||||||
pub use emitter_ext::EmitterExt;
|
pub use emitter_ext::EmitterExt;
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use ext_trait::extension;
|
use ext_trait::extension;
|
||||||
use snafu::{ResultExt, Snafu};
|
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
|
|
||||||
use crate::ProducerExited;
|
use crate::ProducerExited;
|
||||||
|
|
||||||
use super::signal::{JoinError, Signal};
|
use super::signal::{JoinError, Signal};
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
|
||||||
pub struct ProducerAlreadyExited {
|
|
||||||
source: ProducerExited,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[extension(pub trait SignalExt)]
|
#[extension(pub trait SignalExt)]
|
||||||
impl<T> Signal<T> {
|
impl<T> Signal<T> {
|
||||||
fn map<M, F>(
|
fn map<M, F>(
|
||||||
self,
|
self,
|
||||||
mut func: F,
|
mut func: F,
|
||||||
) -> Result<(Signal<M>, impl Future<Output = Result<(), JoinError>>), ProducerAlreadyExited>
|
) -> Result<(Signal<M>, impl Future<Output = Result<(), JoinError>>), ProducerExited>
|
||||||
where
|
where
|
||||||
T: 'static + Sync + Send + Clone,
|
T: 'static + Sync + Send + Clone,
|
||||||
M: 'static + Sync + Send + Clone,
|
M: 'static + Sync + Send + Clone,
|
||||||
F: 'static + Send + FnMut(T) -> M,
|
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 {
|
Ok(Signal::new(initial, |mut publisher_stream| async move {
|
||||||
while let Some(publisher) = publisher_stream.wait().await {
|
while let Some(publisher) = publisher_stream.wait().await {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{path::PathBuf, str::FromStr, time::Duration};
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use driver_kasa::connection::LB130USHandle;
|
use driver_kasa::connection::LB130USHandle;
|
||||||
use futures::stream::{FuturesUnordered, StreamExt};
|
use futures::{TryFutureExt, stream::{FuturesUnordered, StreamExt}};
|
||||||
use home_assistant::{home_assistant::HomeAssistant, object_id::ObjectId};
|
use home_assistant::{home_assistant::HomeAssistant, object_id::ObjectId};
|
||||||
use protocol::light::{Kelvin, TurnToTemperature};
|
use protocol::light::{Kelvin, TurnToTemperature};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
@@ -11,7 +11,9 @@ use pyo3::{
|
|||||||
wrap_pyfunction, Bound, PyAny, PyResult, Python,
|
wrap_pyfunction, Bound, PyAny, PyResult, Python,
|
||||||
};
|
};
|
||||||
use shadow_rs::shadow;
|
use shadow_rs::shadow;
|
||||||
use tokio::time::{interval, MissedTickBehavior};
|
use tokio::{
|
||||||
|
join, task::JoinSet, time::{MissedTickBehavior, interval}
|
||||||
|
};
|
||||||
use tracing::{level_filters::LevelFilter, Level};
|
use tracing::{level_filters::LevelFilter, Level};
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
fmt::{self, format::FmtSpan},
|
fmt::{self, format::FmtSpan},
|
||||||
@@ -187,6 +189,58 @@ async fn real_main(
|
|||||||
// }, context, target, false).await;
|
// }, context, target, false).await;
|
||||||
// dbg!(total_silence_result);
|
// 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
|
.0
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ pub struct EntityId(pub Domain, pub ObjectId);
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
pub enum EntityIdParsingError {
|
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,
|
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 },
|
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 },
|
ParsingObjectId { source: ObjectIdParsingError },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
use super::id::Id;
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use pyo3::{
|
|
||||||
types::{PyAnyMethods, PyModule, PyType},
|
|
||||||
Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// 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;
|
use ulid::Ulid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
#[derive(Debug, Clone, FromPyFromStr, ToStrToPy)]
|
||||||
pub enum Id {
|
pub enum ContextId {
|
||||||
Ulid(Ulid),
|
Ulid(Ulid),
|
||||||
Other(Arc<str>),
|
Other(Arc<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Id {
|
impl From<String> for ContextId {
|
||||||
fn from(s: String) -> Self {
|
fn from(s: String) -> Self {
|
||||||
if let Ok(ulid) = s.parse() {
|
if let Ok(ulid) = s.parse() {
|
||||||
Id::Ulid(ulid)
|
ContextId::Ulid(ulid)
|
||||||
} else {
|
} else {
|
||||||
Id::Other(s.into())
|
ContextId::Other(s.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Id {
|
impl FromStr for ContextId {
|
||||||
type Err = Infallible;
|
type Err = Infallible;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
if let Ok(ulid) = s.parse() {
|
if let Ok(ulid) = s.parse() {
|
||||||
Ok(Id::Ulid(ulid))
|
Ok(ContextId::Ulid(ulid))
|
||||||
} else {
|
} 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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Id::Ulid(ulid) => write!(f, "{ulid}"),
|
ContextId::Ulid(ulid) => write!(f, "{ulid}"),
|
||||||
Id::Other(other) => write!(f, "{other}"),
|
ContextId::Other(other) => write!(f, "{other}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,2 +1,44 @@
|
|||||||
pub mod context;
|
use once_cell::sync::OnceCell;
|
||||||
pub mod id;
|
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::FromPyObject;
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,31 @@
|
|||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod event;
|
|
||||||
pub mod event_origin;
|
pub mod event_origin;
|
||||||
pub mod specific;
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub type Event<
|
|||||||
NewAttributes,
|
NewAttributes,
|
||||||
NewStateContextEvent,
|
NewStateContextEvent,
|
||||||
Context,
|
Context,
|
||||||
> = super::super::event::Event<
|
> = super::super::Event<
|
||||||
Type,
|
Type,
|
||||||
Data<
|
Data<
|
||||||
OldState,
|
OldState,
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ use super::InputNumberMode;
|
|||||||
|
|
||||||
#[derive(Debug, FromPyObject)]
|
#[derive(Debug, FromPyObject)]
|
||||||
#[pyo3(from_item_all)]
|
#[pyo3(from_item_all)]
|
||||||
pub struct InputNumberAttributes {
|
pub struct InputNumberAttributes<Number> {
|
||||||
initial: Option<f64>,
|
initial: Option<Number>,
|
||||||
editable: bool,
|
editable: bool,
|
||||||
min: f64,
|
min: Number,
|
||||||
max: f64,
|
max: Number,
|
||||||
step: f64,
|
step: Number,
|
||||||
mode: InputNumberMode,
|
mode: InputNumberMode,
|
||||||
// todo: CustomUnitOfMeasurement type? probably not?
|
// todo: CustomUnitOfMeasurement type? probably not?
|
||||||
unit_of_measurement: Option<String>,
|
unit_of_measurement: Option<String>,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{future::Future, sync::Arc};
|
use std::{future::Future, str::FromStr, sync::Arc};
|
||||||
|
|
||||||
use emitter_and_signal::{Signal, SignalExt};
|
use emitter_and_signal::{Signal, SignalExt};
|
||||||
use pyo3::{Py, PyAny, PyErr, Python};
|
use pyo3::{FromPyObject, Py, PyAny, PyErr, Python};
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -19,7 +19,7 @@ mod mode;
|
|||||||
pub use attributes::InputNumberAttributes;
|
pub use attributes::InputNumberAttributes;
|
||||||
pub use mode::InputNumberMode;
|
pub use mode::InputNumberMode;
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
pub enum CreateSignalError {
|
pub enum CreateSignalError {
|
||||||
/// couldn't get the underlying state object signal
|
/// couldn't get the underlying state object signal
|
||||||
StateObjectSignalError {
|
StateObjectSignalError {
|
||||||
@@ -28,36 +28,38 @@ pub enum CreateSignalError {
|
|||||||
|
|
||||||
/// couldn't map the state object to a power value
|
/// couldn't map the state object to a power value
|
||||||
MappedSignalError {
|
MappedSignalError {
|
||||||
source: emitter_and_signal::signal_ext::ProducerAlreadyExited,
|
source: emitter_and_signal::ProducerExited,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn signal<'py>(
|
pub fn signal<
|
||||||
|
'py,
|
||||||
|
Number: 'static + uom::num::Num + Clone + Send + Sync + FromStr + for<'a, 'py2> FromPyObject<'a, 'py2>,
|
||||||
|
>(
|
||||||
py: Python<'py>,
|
py: Python<'py>,
|
||||||
home_assistant: &'py HomeAssistant,
|
home_assistant: &'py HomeAssistant,
|
||||||
object_id: ObjectId,
|
object_id: ObjectId,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
Signal<Option<Arc<Result<HomeAssistantState<f64>, StateObjectSignalError<Arc<PyErr>>>>>>,
|
Signal<Option<Arc<Result<HomeAssistantState<Number>, StateObjectSignalError<Arc<PyErr>>>>>>,
|
||||||
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
||||||
),
|
),
|
||||||
CreateSignalError,
|
CreateSignalError,
|
||||||
> {
|
> {
|
||||||
let entity_id = EntityId(Domain::InputNumber, object_id);
|
let entity_id = EntityId(Domain::InputNumber, object_id);
|
||||||
|
|
||||||
let (signal, task1) =
|
let (signal, task1) = StateObject::<
|
||||||
StateObject::<HomeAssistantState<f64>, InputNumberAttributes, Py<PyAny>>::signal(
|
HomeAssistantState<Number>,
|
||||||
py,
|
InputNumberAttributes<Number>,
|
||||||
home_assistant,
|
Py<PyAny>,
|
||||||
entity_id,
|
>::signal(py, home_assistant, entity_id)
|
||||||
)
|
.context(StateObjectSignalSnafu)?;
|
||||||
.context(StateObjectSignalSnafu)?;
|
|
||||||
|
|
||||||
let (signal, task2) = signal
|
let (signal, task2) = signal
|
||||||
.map(|state_object_arc_result_option| {
|
.map(|state_object_arc_result_option| {
|
||||||
state_object_arc_result_option.map(|state_object_arc_result| {
|
state_object_arc_result_option.map(|state_object_arc_result| {
|
||||||
Arc::new(
|
Arc::new(
|
||||||
(&*state_object_arc_result)
|
(*state_object_arc_result)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|state_object| state_object.state.clone())
|
.map(|state_object| state_object.state.clone())
|
||||||
.map_err(|e| e.clone()),
|
.map_err(|e| e.clone()),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use super::{GetStateObjectError, HomeAssistantLight};
|
|||||||
use crate::home_assistant::GetServicesError;
|
use crate::home_assistant::GetServicesError;
|
||||||
use crate::service_registry::CallServiceError;
|
use crate::service_registry::CallServiceError;
|
||||||
use crate::{
|
use crate::{
|
||||||
event::context::context::Context,
|
event::context::Context,
|
||||||
state::{ErrorState, HomeAssistantState, UnexpectedState},
|
state::{ErrorState, HomeAssistantState, UnexpectedState},
|
||||||
};
|
};
|
||||||
use protocol::light::{GetState, SetState};
|
use protocol::light::{GetState, SetState};
|
||||||
|
|||||||
@@ -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 emitter_and_signal::{Signal, SignalExt};
|
||||||
use pyo3::{FromPyObject, Py, PyAny, PyErr, Python};
|
use pyo3::{FromPyObject, Py, PyAny, PyErr, Python};
|
||||||
use python_utils::{FromPyFromStr, ToStrToPy};
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
use string_literal::StringLiteral;
|
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 super::super::state_classes::measurement::Measurement;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -38,11 +47,11 @@ pub enum CreateSignalError {
|
|||||||
|
|
||||||
/// couldn't map the state object to a power value
|
/// couldn't map the state object to a power value
|
||||||
MappedSignalError {
|
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>,
|
py: Python<'py>,
|
||||||
home_assistant: &'py HomeAssistant,
|
home_assistant: &'py HomeAssistant,
|
||||||
object_id: ObjectId,
|
object_id: ObjectId,
|
||||||
@@ -50,17 +59,33 @@ pub fn signal<'py>(
|
|||||||
(
|
(
|
||||||
Signal<
|
Signal<
|
||||||
Option<
|
Option<
|
||||||
Result<HomeAssistantState<uom::si::f64::Power>, StateObjectSignalError<Arc<PyErr>>>,
|
Result<
|
||||||
|
HomeAssistantState<uom::si::quantities::Power<Number>>,
|
||||||
|
StateObjectSignalError<Arc<PyErr>>,
|
||||||
|
>,
|
||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
||||||
),
|
),
|
||||||
CreateSignalError,
|
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 entity_id = EntityId(Domain::Sensor, object_id);
|
||||||
|
|
||||||
let (signal, task1) =
|
let (signal, task1) =
|
||||||
StateObject::<HomeAssistantState<f64>, PowerSensorAttributes, Py<PyAny>>::signal(
|
StateObject::<HomeAssistantState<Number>, PowerSensorAttributes, Py<PyAny>>::signal(
|
||||||
py,
|
py,
|
||||||
home_assistant,
|
home_assistant,
|
||||||
entity_id,
|
entity_id,
|
||||||
@@ -75,9 +100,9 @@ pub fn signal<'py>(
|
|||||||
|StateObject {
|
|StateObject {
|
||||||
state, attributes, ..
|
state, attributes, ..
|
||||||
}| {
|
}| {
|
||||||
state
|
state.as_ref().map(|amount| {
|
||||||
.as_ref()
|
attributes.unit_of_measurement.into_uom(amount.clone())
|
||||||
.map(|&amount| attributes.unit_of_measurement.into_uom(amount))
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(Clone::clone)
|
.map_err(Clone::clone)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{event::context::context::Context, service::IntoServiceCall};
|
use super::{event::context::Context, service::IntoServiceCall};
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
conversion::FromPyObjectOwned,
|
conversion::FromPyObjectOwned,
|
||||||
exceptions::{PyException, PyTypeError},
|
exceptions::{PyException, PyTypeError},
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use snafu::Snafu;
|
|||||||
#[derive(Debug, Clone, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
#[derive(Debug, Clone, derive_more::Display, FromPyFromStr, ToStrToPy)]
|
||||||
pub struct Slug(Arc<str>);
|
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)]
|
#[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 {
|
pub struct SlugParsingError {
|
||||||
encountered: char,
|
encountered: char,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ impl StateMachine {
|
|||||||
.call_method1(py, "get", args)
|
.call_method1(py, "get", args)
|
||||||
.map_err(Arc::new)
|
.map_err(Arc::new)
|
||||||
.context(GetStateObjectSnafu)?;
|
.context(GetStateObjectSnafu)?;
|
||||||
Ok(state.extract(py).context(ExtractStateObjectSnafu)?)
|
|
||||||
|
state.extract(py).context(ExtractStateObjectSnafu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{
|
use super::{
|
||||||
event::{context::context::Context, specific::state_changed},
|
event::{context::Context, specific::state_changed},
|
||||||
home_assistant::HomeAssistant,
|
home_assistant::HomeAssistant,
|
||||||
};
|
};
|
||||||
use crate::{entity_id::EntityId, home_assistant::GetStatesError, state_machine::GetStateError};
|
use crate::{entity_id::EntityId, home_assistant::GetStatesError, state_machine::GetStateError};
|
||||||
@@ -28,7 +28,7 @@ pub struct StateObject<State, Attributes, ContextEvent> {
|
|||||||
pub type ExtractStateObjectError<'a, 'py, State, Attributes, ContextEvent> =
|
pub type ExtractStateObjectError<'a, 'py, State, Attributes, ContextEvent> =
|
||||||
<StateObject<State, Attributes, ContextEvent> as FromPyObject<'a, 'py>>::Error;
|
<StateObject<State, Attributes, ContextEvent> as FromPyObject<'a, 'py>>::Error;
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
pub enum CreateSignalError {
|
pub enum CreateSignalError {
|
||||||
/// couldn't get the state machine from the Home Assistant object
|
/// couldn't get the state machine from the Home Assistant object
|
||||||
GetStatesError { source: GetStatesError },
|
GetStatesError { source: GetStatesError },
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub enum UnitOfMeasurement {
|
|||||||
impl UnitOfMeasurement {
|
impl UnitOfMeasurement {
|
||||||
pub fn into_uom<V>(&self, amount: V) -> Power<V>
|
pub fn into_uom<V>(&self, amount: V) -> Power<V>
|
||||||
where
|
where
|
||||||
V: uom::num::Num + uom::Conversion<V, T = V>,
|
V: uom::num::Num + Conversion<V, T = V>,
|
||||||
milliwatt: Conversion<V, T = V>,
|
milliwatt: Conversion<V, T = V>,
|
||||||
watt: Conversion<V, T = V>,
|
watt: Conversion<V, T = V>,
|
||||||
kilowatt: Conversion<V, T = V>,
|
kilowatt: Conversion<V, T = V>,
|
||||||
|
|||||||
@@ -85,5 +85,5 @@ pub fn validate_type_by_name(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ where
|
|||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let Self { actual, expected } = self;
|
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:?}")
|
write!(f, "expected {expected_str:?} but got {actual:?}")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user