aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
authordiogo464 <[email protected]>2025-12-05 12:17:01 +0000
committerdiogo464 <[email protected]>2025-12-05 12:17:01 +0000
commit0c86da392af50c7588b087c3f72602e8368af65e (patch)
tree894cd2f353298b83a56cde06eafd7b1e366aa6b3 /src/lib.rs
parent1d2ee64d0ec917a2c2b66f8d58e1f37dd174a89d (diff)
reworked entity creation
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs344
1 files changed, 71 insertions, 273 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 09ed555..8d7bfaf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,6 @@
1#![no_std] 1#![no_std]
2 2
3use core::{cell::RefCell, str::FromStr, task::Waker}; 3use core::{cell::RefCell, task::Waker};
4 4
5use defmt::Format; 5use defmt::Format;
6use embassy_sync::waitqueue::AtomicWaker; 6use embassy_sync::waitqueue::AtomicWaker;
@@ -12,10 +12,35 @@ use heapless::{
12use serde::Serialize; 12use serde::Serialize;
13 13
14pub mod constants; 14pub mod constants;
15mod transport;
16mod unit;
17 15
16mod binary_state;
17pub use binary_state::*;
18
19mod entity;
20pub use entity::*;
21
22mod entity_binary_sensor;
23pub use entity_binary_sensor::*;
24
25mod entity_button;
26pub use entity_button::*;
27
28mod entity_category;
29pub use entity_category::*;
30
31mod entity_number;
32pub use entity_number::*;
33
34mod entity_sensor;
35pub use entity_sensor::*;
36
37mod entity_switch;
38pub use entity_switch::*;
39
40mod transport;
18pub use transport::Transport; 41pub use transport::Transport;
42
43mod unit;
19pub use unit::*; 44pub use unit::*;
20 45
21#[derive(Debug, Format, Clone, Copy, Serialize)] 46#[derive(Debug, Format, Clone, Copy, Serialize)]
@@ -31,7 +56,8 @@ struct EntityDiscovery<'a> {
31 #[serde(rename = "unique_id")] 56 #[serde(rename = "unique_id")]
32 id: &'a str, 57 id: &'a str,
33 58
34 name: &'a str, 59 #[serde(skip_serializing_if = "Option::is_none")]
60 name: Option<&'a str>,
35 61
36 #[serde(skip_serializing_if = "Option::is_none")] 62 #[serde(skip_serializing_if = "Option::is_none")]
37 device_class: Option<&'a str>, 63 device_class: Option<&'a str>,
@@ -55,6 +81,9 @@ struct EntityDiscovery<'a> {
55 icon: Option<&'a str>, 81 icon: Option<&'a str>,
56 82
57 #[serde(skip_serializing_if = "Option::is_none")] 83 #[serde(skip_serializing_if = "Option::is_none")]
84 entity_picture: Option<&'a str>,
85
86 #[serde(skip_serializing_if = "Option::is_none")]
58 min: Option<f32>, 87 min: Option<f32>,
59 88
60 #[serde(skip_serializing_if = "Option::is_none")] 89 #[serde(skip_serializing_if = "Option::is_none")]
@@ -152,223 +181,6 @@ impl Default for DeviceResources {
152 } 181 }
153} 182}
154 183
155pub struct TemperatureSensor<'a>(Entity<'a>);
156
157impl<'a> TemperatureSensor<'a> {
158 pub fn publish(&mut self, temperature: f32) {
159 use core::fmt::Write;
160 self.0
161 .publish_with(|view| write!(view, "{}", temperature).unwrap());
162 }
163}
164
165pub struct Button<'a>(Entity<'a>);
166
167impl<'a> Button<'a> {
168 pub async fn pressed(&mut self) {
169 self.0.wait_command().await;
170 }
171}
172
173pub struct Number<'a>(Entity<'a>);
174
175impl<'a> Number<'a> {
176 pub fn value(&mut self) -> Option<f32> {
177 self.0.with_data(|data| {
178 str::from_utf8(&data.command_value)
179 .ok()
180 .and_then(|v| v.parse::<f32>().ok())
181 })
182 }
183
184 pub async fn value_wait(&mut self) -> f32 {
185 loop {
186 self.0.wait_command().await;
187 match self.value() {
188 Some(value) => return value,
189 None => continue,
190 }
191 }
192 }
193
194 pub fn value_set(&mut self, value: f32) {
195 use core::fmt::Write;
196 self.0
197 .publish_with(|view| write!(view, "{}", value).unwrap());
198 }
199}
200
201pub struct InvalidSwitchState;
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum SwitchState {
205 On,
206 Off,
207}
208
209impl SwitchState {
210 pub fn as_str(&self) -> &'static str {
211 match self {
212 Self::On => constants::HA_SWITCH_STATE_ON,
213 Self::Off => constants::HA_SWITCH_STATE_OFF,
214 }
215 }
216
217 pub fn flip(self) -> Self {
218 match self {
219 Self::On => Self::Off,
220 Self::Off => Self::On,
221 }
222 }
223}
224
225impl core::fmt::Display for SwitchState {
226 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
227 f.write_str(self.as_str())
228 }
229}
230
231impl FromStr for SwitchState {
232 type Err = InvalidSwitchState;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 if s.eq_ignore_ascii_case(constants::HA_SWITCH_STATE_ON) {
236 return Ok(Self::On);
237 }
238 if s.eq_ignore_ascii_case(constants::HA_SWITCH_STATE_OFF) {
239 return Ok(Self::Off);
240 }
241 Err(InvalidSwitchState)
242 }
243}
244
245impl TryFrom<&[u8]> for SwitchState {
246 type Error = InvalidSwitchState;
247
248 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
249 let string = str::from_utf8(value).map_err(|_| InvalidSwitchState)?;
250 string.parse()
251 }
252}
253
254pub struct Switch<'a>(Entity<'a>);
255
256impl<'a> Switch<'a> {
257 pub fn state(&self) -> Option<SwitchState> {
258 self.0
259 .with_data(|data| SwitchState::try_from(data.command_value.as_slice()).ok())
260 }
261
262 pub fn toggle(&mut self) -> SwitchState {
263 let new_state = self.state().unwrap_or(SwitchState::Off).flip();
264 self.set(new_state);
265 new_state
266 }
267
268 pub fn set(&mut self, state: SwitchState) {
269 self.0.publish(state.as_str().as_bytes());
270 }
271
272 pub async fn wait(&mut self) -> SwitchState {
273 loop {
274 self.0.wait_command().await;
275 if let Some(state) = self.state() {
276 return state;
277 }
278 }
279 }
280}
281
282pub struct InvalidBinarySensorState;
283
284#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285pub enum BinarySensorState {
286 On,
287 Off,
288}
289
290impl BinarySensorState {
291 pub fn as_str(&self) -> &'static str {
292 match self {
293 Self::On => constants::HA_BINARY_SENSOR_STATE_ON,
294 Self::Off => constants::HA_BINARY_SENSOR_STATE_OFF,
295 }
296 }
297
298 pub fn flip(self) -> Self {
299 match self {
300 Self::On => Self::Off,
301 Self::Off => Self::On,
302 }
303 }
304}
305
306impl core::fmt::Display for BinarySensorState {
307 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
308 f.write_str(self.as_str())
309 }
310}
311
312impl FromStr for BinarySensorState {
313 type Err = InvalidBinarySensorState;
314
315 fn from_str(s: &str) -> Result<Self, Self::Err> {
316 if s.eq_ignore_ascii_case(constants::HA_BINARY_SENSOR_STATE_ON) {
317 return Ok(Self::On);
318 }
319 if s.eq_ignore_ascii_case(constants::HA_BINARY_SENSOR_STATE_OFF) {
320 return Ok(Self::Off);
321 }
322 Err(InvalidBinarySensorState)
323 }
324}
325
326impl TryFrom<&[u8]> for BinarySensorState {
327 type Error = InvalidBinarySensorState;
328
329 fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
330 let string = str::from_utf8(value).map_err(|_| InvalidBinarySensorState)?;
331 string.parse()
332 }
333}
334
335pub struct BinarySensor<'a>(Entity<'a>);
336
337impl<'a> BinarySensor<'a> {
338 pub fn set(&mut self, state: BinarySensorState) {
339 self.0.publish(state.as_str().as_bytes());
340 }
341
342 pub fn value(&self) -> Option<BinarySensorState> {
343 self.0
344 .with_data(|data| BinarySensorState::try_from(data.publish_value.as_slice()))
345 .ok()
346 }
347
348 pub fn toggle(&mut self) -> BinarySensorState {
349 let new_state = self.value().unwrap_or(BinarySensorState::Off).flip();
350 self.set(new_state);
351 new_state
352 }
353}
354
355#[derive(Default)]
356pub struct EntityConfig {
357 pub id: &'static str,
358 pub name: &'static str,
359 pub domain: &'static str,
360 pub device_class: Option<&'static str>,
361 pub measurement_unit: Option<&'static str>,
362 pub icon: Option<&'static str>,
363 pub category: Option<&'static str>,
364 pub state_class: Option<&'static str>,
365 pub schema: Option<&'static str>,
366 pub min: Option<f32>,
367 pub max: Option<f32>,
368 pub step: Option<f32>,
369 pub mode: Option<&'static str>,
370}
371
372struct EntityData { 184struct EntityData {
373 config: EntityConfig, 185 config: EntityConfig,
374 publish_dirty: bool, 186 publish_dirty: bool,
@@ -380,8 +192,8 @@ struct EntityData {
380} 192}
381 193
382pub struct Entity<'a> { 194pub struct Entity<'a> {
383 data: &'a RefCell<Option<EntityData>>, 195 pub(crate) data: &'a RefCell<Option<EntityData>>,
384 waker: &'a AtomicWaker, 196 pub(crate) waker: &'a AtomicWaker,
385} 197}
386 198
387impl<'a> Entity<'a> { 199impl<'a> Entity<'a> {
@@ -521,69 +333,54 @@ impl<'a> Device<'a> {
521 pub fn create_temperature_sensor( 333 pub fn create_temperature_sensor(
522 &self, 334 &self,
523 id: &'static str, 335 id: &'static str,
524 name: &'static str, 336 config: TemperatureSensorConfig,
525 unit: TemperatureUnit,
526 ) -> TemperatureSensor<'a> { 337 ) -> TemperatureSensor<'a> {
527 let entity = self.create_entity(EntityConfig { 338 let mut entity_config = EntityConfig::default();
528 id, 339 entity_config.id = id;
529 name, 340 config.populate(&mut entity_config);
530 domain: constants::HA_DOMAIN_SENSOR, 341
531 device_class: Some(constants::HA_DEVICE_CLASS_SENSOR_TEMPERATURE), 342 let entity = self.create_entity(entity_config);
532 measurement_unit: Some(unit.as_str()), 343 TemperatureSensor::new(entity)
533 ..Default::default()
534 });
535 TemperatureSensor(entity)
536 } 344 }
537 345
538 pub fn create_button(&self, id: &'static str, name: &'static str) -> Button<'a> { 346 pub fn create_button(&self, id: &'static str, config: ButtonConfig) -> Button<'a> {
539 let entity = self.create_entity(EntityConfig { 347 let mut entity_config = EntityConfig::default();
540 id, 348 entity_config.id = id;
541 name, 349 config.populate(&mut entity_config);
542 domain: constants::HA_DOMAIN_BUTTON, 350
543 ..Default::default() 351 let entity = self.create_entity(entity_config);
544 }); 352 Button::new(entity)
545 Button(entity)
546 } 353 }
547 354
548 pub fn create_number(&self, id: &'static str, name: &'static str) -> Number<'a> { 355 pub fn create_number(&self, id: &'static str, config: NumberConfig) -> Number<'a> {
549 let entity = self.create_entity(EntityConfig { 356 let mut entity_config = EntityConfig::default();
550 id, 357 entity_config.id = id;
551 name, 358 config.populate(&mut entity_config);
552 domain: constants::HA_DOMAIN_NUMBER, 359
553 measurement_unit: Some("s"), 360 let entity = self.create_entity(entity_config);
554 min: Some(0.0), 361 Number::new(entity)
555 max: Some(200.0),
556 step: Some(2.0),
557 mode: Some(constants::HA_NUMBER_MODE_AUTO),
558 ..Default::default()
559 });
560 Number(entity)
561 } 362 }
562 363
563 pub fn create_switch(&self, id: &'static str, name: &'static str) -> Switch<'a> { 364 pub fn create_switch(&self, id: &'static str, config: SwitchConfig) -> Switch<'a> {
564 let entity = self.create_entity(EntityConfig { 365 let mut entity_config = EntityConfig::default();
565 id, 366 entity_config.id = id;
566 name, 367 config.populate(&mut entity_config);
567 domain: constants::HA_DOMAIN_SWITCH, 368
568 ..Default::default() 369 let entity = self.create_entity(entity_config);
569 }); 370 Switch::new(entity)
570 Switch(entity)
571 } 371 }
572 372
573 pub fn create_binary_sensor( 373 pub fn create_binary_sensor(
574 &self, 374 &self,
575 id: &'static str, 375 id: &'static str,
576 name: &'static str, 376 config: BinarySensorConfig,
577 class: &'static str,
578 ) -> BinarySensor<'a> { 377 ) -> BinarySensor<'a> {
579 let entity = self.create_entity(EntityConfig { 378 let mut entity_config = EntityConfig::default();
580 id, 379 entity_config.id = id;
581 name, 380 config.populate(&mut entity_config);
582 domain: constants::HA_DOMAIN_BINARY_SENSOR, 381
583 device_class: Some(class), 382 let entity = self.create_entity(entity_config);
584 ..Default::default() 383 BinarySensor::new(entity)
585 });
586 BinarySensor(entity)
587 } 384 }
588 385
589 pub async fn run<T: Transport>(&mut self, transport: &mut T) -> ! { 386 pub async fn run<T: Transport>(&mut self, transport: &mut T) -> ! {
@@ -667,6 +464,7 @@ impl<'a> Device<'a> {
667 schema: entity_config.schema, 464 schema: entity_config.schema,
668 state_class: entity_config.state_class, 465 state_class: entity_config.state_class,
669 icon: entity_config.icon, 466 icon: entity_config.icon,
467 entity_picture: entity_config.picture,
670 min: entity_config.min, 468 min: entity_config.min,
671 max: entity_config.max, 469 max: entity_config.max,
672 step: entity_config.step, 470 step: entity_config.step,