diff --git a/home-assistant/src/home_assistant.rs b/home-assistant/src/home_assistant.rs index 3e9c417..80cc380 100644 --- a/home-assistant/src/home_assistant.rs +++ b/home-assistant/src/home_assistant.rs @@ -1,4 +1,4 @@ -use std::convert::Infallible; +use std::{convert::Infallible, sync::Arc}; use pyo3::{ 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 { /// couldn't get the `states` attribute on the Home Assistant object - GetStatesAttributeError { source: PyErr }, + GetStatesAttributeError { source: Arc }, /// couldn't extract the `states` as a [`StateMachine`] - ExtractStateMachineError { source: TypeByNameValidationError }, + ExtractStateMachineError { + source: Arc, + }, } -#[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 }, /// couldn't extract the `states` as a [`ServiceRegistry`] - ExtractServiceRegistryError { source: TypeByNameValidationError }, + ExtractServiceRegistryError { + source: Arc, + }, } impl HomeAssistant { @@ -74,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 { 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) } } diff --git a/home-assistant/src/input_number/attributes.rs b/home-assistant/src/input_number/attributes.rs new file mode 100644 index 0000000..5eefdf6 --- /dev/null +++ b/home-assistant/src/input_number/attributes.rs @@ -0,0 +1,16 @@ +use pyo3::FromPyObject; + +use super::InputNumberMode; + +#[derive(Debug, FromPyObject)] +#[pyo3(from_item_all)] +pub struct InputNumberAttributes { + initial: Option, + editable: bool, + min: f64, + max: f64, + step: f64, + mode: InputNumberMode, + // todo: CustomUnitOfMeasurement type? probably not? + unit_of_measurement: Option, +} diff --git a/home-assistant/src/input_number/mod.rs b/home-assistant/src/input_number/mod.rs index 8b13789..5090d76 100644 --- a/home-assistant/src/input_number/mod.rs +++ b/home-assistant/src/input_number/mod.rs @@ -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, StateObjectSignalError>>>>>, + impl Future>, + ), + CreateSignalError, +> { + let entity_id = EntityId(Domain::InputNumber, object_id); + + let (signal, task1) = + StateObject::, InputNumberAttributes, Py>::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(|_| ()) }, + )) +} diff --git a/home-assistant/src/input_number/mode.rs b/home-assistant/src/input_number/mode.rs new file mode 100644 index 0000000..1ce34b1 --- /dev/null +++ b/home-assistant/src/input_number/mode.rs @@ -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, +} diff --git a/home-assistant/src/light/mod.rs b/home-assistant/src/light/mod.rs index 6e6b4a2..ac977a2 100644 --- a/home-assistant/src/light/mod.rs +++ b/home-assistant/src/light/mod.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use attributes::LightAttributes; use pyo3::{FromPyObject, Py, PyAny, Python}; use snafu::{ResultExt, Snafu}; @@ -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, LightAttributes, Py> 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)?; diff --git a/home-assistant/src/light/protocol.rs b/home-assistant/src/light/protocol.rs index f480537..2c8a150 100644 --- a/home-assistant/src/light/protocol.rs +++ b/home-assistant/src/light/protocol.rs @@ -30,9 +30,7 @@ impl GetState for HomeAssistantLight { HomeAssistantState::Err(error_state) => { Err(GetStateError::Error { state: error_state }) } - HomeAssistantState::Unexpected(state) => { - Err(GetStateError::UnexpectedError { state }) - } + HomeAssistantState::Unexpected(state) => Err(GetStateError::UnexpectedError { state }), } } } diff --git a/home-assistant/src/sensor/device_classes/power.rs b/home-assistant/src/sensor/device_classes/power.rs index f624873..09a0b94 100644 --- a/home-assistant/src/sensor/device_classes/power.rs +++ b/home-assistant/src/sensor/device_classes/power.rs @@ -2,7 +2,7 @@ use std::{future::Future, 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; @@ -12,6 +12,7 @@ use crate::{ entity_id::EntityId, home_assistant::HomeAssistant, object_id::ObjectId, + state::HomeAssistantState, state_object::{self, StateObject, StateObjectSignalError}, unit_of_measurement::power::UnitOfMeasurement, }; @@ -47,7 +48,11 @@ pub fn signal<'py>( object_id: ObjectId, ) -> Result< ( - Signal>>>>, + Signal< + Option< + Result, StateObjectSignalError>>, + >, + >, impl Future>, ), CreateSignalError, @@ -55,7 +60,7 @@ pub fn signal<'py>( let entity_id = EntityId(Domain::Sensor, object_id); let (signal, task1) = - StateObject::, PowerSensorAttributes, Py>::signal( + StateObject::, PowerSensorAttributes, Py>::signal( py, home_assistant, entity_id, @@ -65,18 +70,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)) + }, + ) + .map_err(Clone::clone) }) }) .context(MappedSignalSnafu)?; diff --git a/home-assistant/src/state/error_state.rs b/home-assistant/src/state/error_state.rs index 1037256..91e238f 100644 --- a/home-assistant/src/state/error_state.rs +++ b/home-assistant/src/state/error_state.rs @@ -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, diff --git a/home-assistant/src/state/mod.rs b/home-assistant/src/state/mod.rs index ab5eefb..4ae8694 100644 --- a/home-assistant/src/state/mod.rs +++ b/home-assistant/src/state/mod.rs @@ -47,3 +47,42 @@ impl FromStr for HomeAssistantState { Ok(HomeAssistantState::Unexpected(UnexpectedState(s.into()))) } } + +impl HomeAssistantState { + 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(self, f: impl FnOnce(State) -> Mapped) -> HomeAssistantState { + 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 HomeAssistantState<&State> { + pub fn cloned(self) -> HomeAssistantState + where + State: Clone, + { + self.map(Clone::clone) + } + + pub fn copied(self) -> HomeAssistantState + where + State: Copy, + { + self.map(|&s| s) + } +} diff --git a/home-assistant/src/state_object.rs b/home-assistant/src/state_object.rs index 325810f..ddb36a3 100644 --- a/home-assistant/src/state_object.rs +++ b/home-assistant/src/state_object.rs @@ -62,7 +62,7 @@ impl< Arc< Result< Self, - StateObjectSignalError<>::Error>, + StateObjectSignalError>::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();