From e5416e848c8caeed59e8aca6fb3e191fbc621b1b Mon Sep 17 00:00:00 2001 From: diogo464 Date: Sat, 6 Dec 2025 14:31:54 +0000 Subject: Refactor sensor API to support generic sensor types with configurable display precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced TemperatureSensor with a generic Sensor type that accepts sensor class, state class, unit, and display precision to support various sensor types. Added suggested_display_precision field to control decimal places in Home Assistant UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- examples/sensor.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ examples/temperature.rs | 68 --------------------------------------------- src/entity.rs | 1 + src/entity_sensor.rs | 27 ++++++++++-------- src/lib.rs | 12 +++++--- 5 files changed, 99 insertions(+), 83 deletions(-) create mode 100644 examples/sensor.rs delete mode 100644 examples/temperature.rs diff --git a/examples/sensor.rs b/examples/sensor.rs new file mode 100644 index 0000000..69bd87a --- /dev/null +++ b/examples/sensor.rs @@ -0,0 +1,74 @@ +mod common; + +use common::AsyncTcp; +use embassy_executor::{Executor, Spawner}; +use embassy_time::Timer; +use static_cell::StaticCell; + +static RESOURCES: StaticCell = StaticCell::new(); + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let mut stream = AsyncTcp::connect(std::env!("MQTT_ADDRESS")); + + let mut device = embassy_ha::Device::new( + RESOURCES.init(Default::default()), + embassy_ha::DeviceConfig { + device_id: "example-device-id", + device_name: "Example Device Name", + manufacturer: "Example Device Manufacturer", + model: "Example Device Model", + }, + ); + + let temperature_sensor = device.create_sensor( + "random-temperature-sensor-id", + embassy_ha::SensorConfig { + common: embassy_ha::EntityCommonConfig { + name: Some("Random Temperature Sensor"), + ..Default::default() + }, + class: embassy_ha::SensorClass::Temperature, + state_class: embassy_ha::StateClass::Measurement, + unit: Some(embassy_ha::constants::HA_UNIT_TEMPERATURE_CELSIUS), + suggested_display_precision: Some(1), + }, + ); + + let humidity_sensor = device.create_sensor( + "random-humidity-sensor-id", + embassy_ha::SensorConfig { + common: embassy_ha::EntityCommonConfig { + name: Some("Random Humidity Sensor"), + ..Default::default() + }, + class: embassy_ha::SensorClass::Humidity, + state_class: embassy_ha::StateClass::Measurement, + unit: Some(embassy_ha::constants::HA_UNIT_PERCENTAGE), + suggested_display_precision: Some(0), + }, + ); + + spawner.must_spawn(random_temperature_task(temperature_sensor)); + spawner.must_spawn(random_humidity_task(humidity_sensor)); + + device.run(&mut stream).await.unwrap(); +} + +#[embassy_executor::task] +async fn random_temperature_task(mut sensor: embassy_ha::Sensor<'static>) { + loop { + sensor.publish(rand::random_range(0.0..50.0)); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn random_humidity_task(mut sensor: embassy_ha::Sensor<'static>) { + loop { + sensor.publish(rand::random_range(0.0..100.0)); + Timer::after_secs(1).await; + } +} + +example_main!(); diff --git a/examples/temperature.rs b/examples/temperature.rs deleted file mode 100644 index d449cd4..0000000 --- a/examples/temperature.rs +++ /dev/null @@ -1,68 +0,0 @@ -mod common; - -use common::AsyncTcp; -use embassy_executor::{Executor, Spawner}; -use embassy_time::Timer; -use static_cell::StaticCell; - -static RESOURCES: StaticCell = StaticCell::new(); - -#[embassy_executor::task] -async fn main_task(spawner: Spawner) { - let mut stream = AsyncTcp::connect(std::env!("MQTT_ADDRESS")); - - let mut device = embassy_ha::Device::new( - RESOURCES.init(Default::default()), - embassy_ha::DeviceConfig { - device_id: "example-device-id", - device_name: "Example Device Name", - manufacturer: "Example Device Manufacturer", - model: "Example Device Model", - }, - ); - - let constant_temperature_sensor = device.create_temperature_sensor( - "constant-temperature-sensor-id", - embassy_ha::TemperatureSensorConfig { - common: embassy_ha::EntityCommonConfig { - name: Some("Constant Temperature Sensor"), - ..Default::default() - }, - unit: embassy_ha::TemperatureUnit::Celcius, - }, - ); - - let random_temperature_sensor = device.create_temperature_sensor( - "random-temperature-sensor-id", - embassy_ha::TemperatureSensorConfig { - common: embassy_ha::EntityCommonConfig { - name: Some("Random Temperature Sensor"), - ..Default::default() - }, - unit: embassy_ha::TemperatureUnit::Celcius, - }, - ); - - spawner.must_spawn(constant_temperature_task(constant_temperature_sensor)); - spawner.must_spawn(random_temperature_task(random_temperature_sensor)); - - device.run(&mut stream).await.unwrap(); -} - -#[embassy_executor::task] -async fn constant_temperature_task(mut sensor: embassy_ha::TemperatureSensor<'static>) { - loop { - sensor.publish(42.0); - Timer::after_secs(1).await; - } -} - -#[embassy_executor::task] -async fn random_temperature_task(mut sensor: embassy_ha::TemperatureSensor<'static>) { - loop { - sensor.publish(rand::random_range(0.0..50.0)); - Timer::after_secs(1).await; - } -} - -example_main!(); diff --git a/src/entity.rs b/src/entity.rs index ac15921..ef2b9ad 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -33,4 +33,5 @@ pub struct EntityConfig { pub max: Option, pub step: Option, pub mode: Option<&'static str>, + pub suggested_display_precision: Option, } diff --git a/src/entity_sensor.rs b/src/entity_sensor.rs index 702c9de..1a99754 100644 --- a/src/entity_sensor.rs +++ b/src/entity_sensor.rs @@ -1,5 +1,5 @@ use crate::{ - Entity, EntityCommonConfig, EntityConfig, NumericSensorState, TemperatureUnit, constants, + Entity, EntityCommonConfig, EntityConfig, NumericSensorState, constants, }; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -144,36 +144,41 @@ impl SensorClass { } #[derive(Debug, Default)] -pub struct TemperatureSensorConfig { +pub struct SensorConfig { pub common: EntityCommonConfig, - pub unit: TemperatureUnit, + pub class: SensorClass, + pub state_class: StateClass, + pub unit: Option<&'static str>, + pub suggested_display_precision: Option, } -impl TemperatureSensorConfig { +impl SensorConfig { pub(crate) fn populate(&self, config: &mut EntityConfig) { self.common.populate(config); config.domain = constants::HA_DOMAIN_SENSOR; - config.device_class = Some(constants::HA_DEVICE_CLASS_SENSOR_TEMPERATURE); - config.measurement_unit = Some(self.unit.as_str()); + config.device_class = self.class.as_str(); + config.state_class = Some(self.state_class.as_str()); + config.measurement_unit = self.unit; + config.suggested_display_precision = self.suggested_display_precision; } } -pub struct TemperatureSensor<'a>(Entity<'a>); +pub struct Sensor<'a>(Entity<'a>); -impl<'a> TemperatureSensor<'a> { +impl<'a> Sensor<'a> { pub(crate) fn new(entity: Entity<'a>) -> Self { Self(entity) } - pub fn publish(&mut self, temperature: f32) { + pub fn publish(&mut self, value: f32) { let publish = self.0.with_data(|data| { let storage = data.storage.as_numeric_sensor_mut(); let prev_state = storage.state.replace(NumericSensorState { - value: temperature, + value, timestamp: embassy_time::Instant::now(), }); match prev_state { - Some(state) => state.value != temperature, + Some(state) => state.value != value, None => true, } }); diff --git a/src/lib.rs b/src/lib.rs index 1571255..0107116 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -115,6 +115,9 @@ struct EntityDiscovery<'a> { #[serde(skip_serializing_if = "Option::is_none")] mode: Option<&'a str>, + #[serde(skip_serializing_if = "Option::is_none")] + suggested_display_precision: Option, + device: &'a DeviceDiscovery<'a>, } @@ -427,11 +430,11 @@ impl<'a> Device<'a> { } } - pub fn create_temperature_sensor( + pub fn create_sensor( &self, id: &'static str, - config: TemperatureSensorConfig, - ) -> TemperatureSensor<'a> { + config: SensorConfig, + ) -> Sensor<'a> { let mut entity_config = EntityConfig::default(); entity_config.id = id; config.populate(&mut entity_config); @@ -440,7 +443,7 @@ impl<'a> Device<'a> { entity_config, EntityStorage::NumericSensor(Default::default()), ); - TemperatureSensor::new(entity) + Sensor::new(entity) } pub fn create_button(&self, id: &'static str, config: ButtonConfig) -> Button<'a> { @@ -573,6 +576,7 @@ impl<'a> Device<'a> { max: entity_config.max, step: entity_config.step, mode: entity_config.mode, + suggested_display_precision: entity_config.suggested_display_precision, device: &device_discovery, }; crate::log::debug!( -- cgit