feat: input_number signal support, implement Clone for a lot of error types
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use std::convert::Infallible;
|
use std::{convert::Infallible, sync::Arc};
|
||||||
|
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
types::PyAnyMethods as _, Borrowed, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
types::PyAnyMethods as _, Borrowed, Bound, FromPyObject, IntoPyObject, Py, PyAny, PyErr, Python,
|
||||||
@@ -33,22 +33,26 @@ impl<'py> IntoPyObject<'py> for &HomeAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
pub enum GetStatesError {
|
pub enum GetStatesError {
|
||||||
/// couldn't get the `states` attribute on the Home Assistant object
|
/// 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`]
|
/// 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 {
|
pub enum GetServicesError {
|
||||||
/// couldn't get the `services` attribute on the Home Assistant object
|
/// 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`]
|
/// couldn't extract the `states` as a [`ServiceRegistry`]
|
||||||
ExtractServiceRegistryError { source: TypeByNameValidationError },
|
ExtractServiceRegistryError {
|
||||||
|
source: Arc<TypeByNameValidationError>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HomeAssistant {
|
impl HomeAssistant {
|
||||||
@@ -74,15 +78,23 @@ impl HomeAssistant {
|
|||||||
let states = self
|
let states = self
|
||||||
.0
|
.0
|
||||||
.getattr(py, "states")
|
.getattr(py, "states")
|
||||||
|
.map_err(Arc::new)
|
||||||
.context(GetStatesAttributeSnafu)?;
|
.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> {
|
pub fn services(&self, py: Python<'_>) -> Result<ServiceRegistry, GetServicesError> {
|
||||||
let services = self
|
let services = self
|
||||||
.0
|
.0
|
||||||
.getattr(py, "services")
|
.getattr(py, "services")
|
||||||
|
.map_err(Arc::new)
|
||||||
.context(GetServicesAttributeSnafu)?;
|
.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 {
|
||||||
|
initial: Option<f64>,
|
||||||
|
editable: bool,
|
||||||
|
min: f64,
|
||||||
|
max: f64,
|
||||||
|
step: f64,
|
||||||
|
mode: InputNumberMode,
|
||||||
|
// todo: CustomUnitOfMeasurement type? probably not?
|
||||||
|
unit_of_measurement: Option<String>,
|
||||||
|
}
|
||||||
@@ -1 +1,73 @@
|
|||||||
|
use std::{future::Future, sync::Arc};
|
||||||
|
|
||||||
|
use emitter_and_signal::{Signal, SignalExt};
|
||||||
|
use pyo3::{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, 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::signal_ext::ProducerAlreadyExited,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signal<'py>(
|
||||||
|
py: Python<'py>,
|
||||||
|
home_assistant: &'py HomeAssistant,
|
||||||
|
object_id: ObjectId,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
Signal<Option<Arc<Result<HomeAssistantState<f64>, 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<f64>, InputNumberAttributes, 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,3 +1,5 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use attributes::LightAttributes;
|
use attributes::LightAttributes;
|
||||||
use pyo3::{FromPyObject, Py, PyAny, Python};
|
use pyo3::{FromPyObject, Py, PyAny, Python};
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
@@ -29,15 +31,15 @@ impl HomeAssistantLight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Snafu)]
|
#[derive(Debug, Clone, Snafu)]
|
||||||
pub enum GetStateObjectError {
|
pub enum GetStateObjectError {
|
||||||
/// couldn't get the state machine registry
|
/// couldn't get the state machine registry
|
||||||
GetStatesError { source: GetStatesError },
|
GetStatesError { source: GetStatesError },
|
||||||
|
|
||||||
/// this state object exists in the state machine registry, but it couldn't be extracted as a light state object
|
/// 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
|
<StateObject<HomeAssistantState<LightState>, LightAttributes, Py<PyAny>> as FromPyObject<'static, 'static>>::Error
|
||||||
> },
|
>> },
|
||||||
|
|
||||||
/// this entity does not have a state object in the registry
|
/// this entity does not have a state object in the registry
|
||||||
EntityMissing,
|
EntityMissing,
|
||||||
@@ -55,6 +57,7 @@ impl HomeAssistantLight {
|
|||||||
let entity_id = self.entity_id();
|
let entity_id = self.entity_id();
|
||||||
let state_object = states
|
let state_object = states
|
||||||
.get(py, entity_id)
|
.get(py, entity_id)
|
||||||
|
.map_err(Arc::new)
|
||||||
.context(GetStateSnafu)?
|
.context(GetStateSnafu)?
|
||||||
.ok_or(GetStateObjectError::EntityMissing)?;
|
.ok_or(GetStateObjectError::EntityMissing)?;
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ impl GetState for HomeAssistantLight {
|
|||||||
HomeAssistantState::Err(error_state) => {
|
HomeAssistantState::Err(error_state) => {
|
||||||
Err(GetStateError::Error { state: error_state })
|
Err(GetStateError::Error { state: error_state })
|
||||||
}
|
}
|
||||||
HomeAssistantState::Unexpected(state) => {
|
HomeAssistantState::Unexpected(state) => Err(GetStateError::UnexpectedError { state }),
|
||||||
Err(GetStateError::UnexpectedError { state })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{future::Future, 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, FromPyObjectViaParse, ToStrToPy};
|
use python_utils::{FromPyFromStr, ToStrToPy};
|
||||||
use snafu::{ResultExt, Snafu};
|
use snafu::{ResultExt, Snafu};
|
||||||
use string_literal::StringLiteral;
|
use string_literal::StringLiteral;
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ use crate::{
|
|||||||
entity_id::EntityId,
|
entity_id::EntityId,
|
||||||
home_assistant::HomeAssistant,
|
home_assistant::HomeAssistant,
|
||||||
object_id::ObjectId,
|
object_id::ObjectId,
|
||||||
|
state::HomeAssistantState,
|
||||||
state_object::{self, StateObject, StateObjectSignalError},
|
state_object::{self, StateObject, StateObjectSignalError},
|
||||||
unit_of_measurement::power::UnitOfMeasurement,
|
unit_of_measurement::power::UnitOfMeasurement,
|
||||||
};
|
};
|
||||||
@@ -47,7 +48,11 @@ pub fn signal<'py>(
|
|||||||
object_id: ObjectId,
|
object_id: ObjectId,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
Signal<Option<Arc<Result<uom::si::f64::Power, StateObjectSignalError<PyErr>>>>>,
|
Signal<
|
||||||
|
Option<
|
||||||
|
Result<HomeAssistantState<uom::si::f64::Power>, StateObjectSignalError<Arc<PyErr>>>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
impl Future<Output = Result<(), emitter_and_signal::signal::JoinError>>,
|
||||||
),
|
),
|
||||||
CreateSignalError,
|
CreateSignalError,
|
||||||
@@ -55,7 +60,7 @@ pub fn signal<'py>(
|
|||||||
let entity_id = EntityId(Domain::Sensor, object_id);
|
let entity_id = EntityId(Domain::Sensor, object_id);
|
||||||
|
|
||||||
let (signal, task1) =
|
let (signal, task1) =
|
||||||
StateObject::<FromPyObjectViaParse<f64>, PowerSensorAttributes, Py<PyAny>>::signal(
|
StateObject::<HomeAssistantState<f64>, PowerSensorAttributes, Py<PyAny>>::signal(
|
||||||
py,
|
py,
|
||||||
home_assistant,
|
home_assistant,
|
||||||
entity_id,
|
entity_id,
|
||||||
@@ -65,18 +70,17 @@ pub fn signal<'py>(
|
|||||||
let (signal, task2) = signal
|
let (signal, task2) = signal
|
||||||
.map(|state_object_result_option| {
|
.map(|state_object_result_option| {
|
||||||
state_object_result_option.map(|state_object_result| {
|
state_object_result_option.map(|state_object_result| {
|
||||||
Arc::new(
|
|
||||||
Result::as_ref(&state_object_result)
|
Result::as_ref(&state_object_result)
|
||||||
.map(|state_object| {
|
.map(
|
||||||
let amount = state_object.state.0;
|
|StateObject {
|
||||||
let unit_of_measurement = state_object.attributes.unit_of_measurement;
|
state, attributes, ..
|
||||||
|
}| {
|
||||||
let power = unit_of_measurement.into_uom(amount);
|
state
|
||||||
|
.as_ref()
|
||||||
power
|
.map(|&amount| attributes.unit_of_measurement.into_uom(amount))
|
||||||
})
|
},
|
||||||
.map_err(|e| todo!()),
|
|
||||||
)
|
)
|
||||||
|
.map_err(Clone::clone)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.context(MappedSignalSnafu)?;
|
.context(MappedSignalSnafu)?;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use strum::EnumString;
|
|||||||
/// A state in Home Assistant that is known to represent an error of some kind:
|
/// 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)
|
/// * `unavailable` (the device is likely offline or unreachable from the Home Assistant instance)
|
||||||
/// * `unknown` (I don't know how to explain this one)
|
/// * `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")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
pub enum ErrorState {
|
pub enum ErrorState {
|
||||||
Unavailable,
|
Unavailable,
|
||||||
|
|||||||
@@ -47,3 +47,42 @@ impl<State: FromStr> FromStr for HomeAssistantState<State> {
|
|||||||
Ok(HomeAssistantState::Unexpected(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ impl<
|
|||||||
Arc<
|
Arc<
|
||||||
Result<
|
Result<
|
||||||
Self,
|
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 state_machine = home_assistant.states(py).context(GetStatesSnafu)?;
|
||||||
let current = state_machine
|
let current = state_machine
|
||||||
.get(py, entity_id.clone())
|
.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)
|
.context(GetFromStateMachineSnafu)
|
||||||
.transpose();
|
.transpose();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user