enet_client/
dev.rs

1use enet_proto::{ItemUpdateValue, ProjectItem, SetValue};
2use eventuals::{Eventual, EventualReader, EventualWriter};
3use std::{cmp::Ordering, convert::TryFrom, fmt, future::ready, str::FromStr, sync::Arc};
4use thiserror::Error;
5
6pub(crate) struct DeviceDesc {
7  pub name: String,
8  pub number: u32,
9  pub kind: DeviceKind,
10}
11
12impl TryFrom<ProjectItem> for DeviceDesc {
13  type Error = ProjectItem;
14
15  fn try_from(value: ProjectItem) -> Result<Self, Self::Error> {
16    match value {
17      ProjectItem::Binaer(v) if v.programmable => Ok(DeviceDesc {
18        name: v.name,
19        number: v.number,
20        kind: DeviceKind::Binary,
21      }),
22
23      ProjectItem::Dimmer(v) => Ok(DeviceDesc {
24        name: v.name,
25        number: v.number,
26        kind: DeviceKind::Dimmer,
27      }),
28
29      ProjectItem::Jalousie(v) => Ok(DeviceDesc {
30        name: v.name,
31        number: v.number,
32        kind: DeviceKind::Blinds,
33      }),
34
35      _ => Err(value),
36    }
37  }
38}
39
40#[non_exhaustive]
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum DeviceKind {
43  Binary,
44  Dimmer,
45  Blinds,
46}
47
48#[non_exhaustive]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50pub enum DeviceValue {
51  Undefined,
52  Off,
53  On(DeviceBrightness),
54  AllOff,
55  AllOn,
56}
57
58impl From<DeviceState> for DeviceValue {
59  fn from(state: DeviceState) -> Self {
60    match state {
61      DeviceState::Off => DeviceValue::Off,
62      DeviceState::On => DeviceValue::On(DeviceBrightness::MAX),
63      DeviceState::Unknown => DeviceValue::Undefined,
64    }
65  }
66}
67
68impl From<(DeviceState, DeviceBrightness)> for DeviceValue {
69  fn from((state, brightness): (DeviceState, DeviceBrightness)) -> Self {
70    match state {
71      DeviceState::Off => DeviceValue::Off,
72      DeviceState::On => DeviceValue::On(brightness),
73      DeviceState::Unknown => DeviceValue::Undefined,
74    }
75  }
76}
77
78impl DeviceValue {
79  pub fn is_on(&self) -> bool {
80    matches!(self, DeviceValue::On(_) | DeviceValue::AllOn)
81  }
82
83  pub fn brightness(&self) -> Option<DeviceBrightness> {
84    match self {
85      DeviceValue::On(v) => Some(*v),
86      DeviceValue::AllOn => Some(DeviceBrightness::MAX),
87      _ => None,
88    }
89  }
90}
91
92impl fmt::Display for DeviceValue {
93  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94    match self {
95      DeviceValue::Undefined => f.write_str("undefined"),
96      DeviceValue::Off => f.write_str("off"),
97      DeviceValue::On(v) => fmt::Display::fmt(v, f),
98      DeviceValue::AllOff => f.write_str("all off"),
99      DeviceValue::AllOn => f.write_str("all on"),
100    }
101  }
102}
103
104impl Default for DeviceValue {
105  #[inline]
106  fn default() -> Self {
107    Self::Undefined
108  }
109}
110
111impl TryFrom<ItemUpdateValue> for DeviceValue {
112  type Error = ItemUpdateValue;
113
114  fn try_from(value: ItemUpdateValue) -> Result<Self, Self::Error> {
115    match &*value.state {
116      "UNDEFINED" => Ok(DeviceValue::Undefined),
117      "OFF" => Ok(DeviceValue::Off),
118      "ON" => match DeviceBrightness::from_str(&value.value) {
119        Ok(v) => Ok(DeviceValue::On(v)),
120        Err(_) => Err(value),
121      },
122      "ALL_OFF" => Ok(DeviceValue::AllOff),
123      "ALL_ON" => Ok(DeviceValue::AllOn),
124      _ => Err(value),
125    }
126  }
127}
128
129impl PartialOrd for DeviceValue {
130  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
131    match (self, other) {
132      (DeviceValue::Undefined, DeviceValue::Undefined) => Some(Ordering::Equal),
133      (DeviceValue::Undefined, _) => None,
134      (_, DeviceValue::Undefined) => None,
135      (DeviceValue::Off, DeviceValue::Off) => Some(Ordering::Equal),
136      (DeviceValue::Off, DeviceValue::On(_)) => Some(Ordering::Less),
137      (DeviceValue::Off, DeviceValue::AllOff) => None,
138      (DeviceValue::Off, DeviceValue::AllOn) => None,
139      (DeviceValue::On(_), DeviceValue::Off) => Some(Ordering::Greater),
140      (DeviceValue::On(lhs), DeviceValue::On(rhs)) => Some(lhs.cmp(rhs)),
141      (DeviceValue::On(_), DeviceValue::AllOff) => None,
142      (DeviceValue::On(_), DeviceValue::AllOn) => None,
143      (DeviceValue::AllOff, DeviceValue::Off) => None,
144      (DeviceValue::AllOff, DeviceValue::On(_)) => None,
145      (DeviceValue::AllOff, DeviceValue::AllOff) => Some(Ordering::Equal),
146      (DeviceValue::AllOff, DeviceValue::AllOn) => Some(Ordering::Less),
147      (DeviceValue::AllOn, DeviceValue::Off) => None,
148      (DeviceValue::AllOn, DeviceValue::On(_)) => None,
149      (DeviceValue::AllOn, DeviceValue::AllOff) => Some(Ordering::Greater),
150      (DeviceValue::AllOn, DeviceValue::AllOn) => Some(Ordering::Equal),
151    }
152  }
153}
154
155pub(crate) struct BinaryDeviceWriter {
156  pub(crate) index: u32,
157  pub(crate) desc: Arc<DeviceDesc>,
158  pub(crate) state_writer: EventualWriter<DeviceState>,
159}
160
161impl BinaryDeviceWriter {
162  fn desc(&self) -> &DeviceDesc {
163    &*self.desc
164  }
165
166  pub(crate) fn kind(&self) -> DeviceKind {
167    self.desc().kind
168  }
169
170  pub(crate) fn name(&self) -> &str {
171    &*self.desc().name
172  }
173}
174
175pub(crate) struct DimmerDeviceWriter {
176  pub(crate) index: u32,
177  pub(crate) desc: Arc<DeviceDesc>,
178  pub(crate) state_writer: EventualWriter<DeviceState>,
179  pub(crate) brightness_writer: EventualWriter<DeviceBrightness>,
180}
181
182impl DimmerDeviceWriter {
183  fn desc(&self) -> &DeviceDesc {
184    &*self.desc
185  }
186
187  pub(crate) fn kind(&self) -> DeviceKind {
188    self.desc().kind
189  }
190
191  pub(crate) fn name(&self) -> &str {
192    &*self.desc().name
193  }
194}
195
196pub(crate) enum DeviceWriter {
197  Binary(BinaryDeviceWriter),
198  Dimmer(DimmerDeviceWriter),
199}
200
201impl DeviceWriter {
202  fn new_binary(
203    desc: Arc<DeviceDesc>,
204    index: u32,
205    state_writer: EventualWriter<DeviceState>,
206  ) -> Self {
207    DeviceWriter::Binary(BinaryDeviceWriter {
208      index,
209      desc,
210      state_writer,
211    })
212  }
213
214  fn new_dimmer(
215    desc: Arc<DeviceDesc>,
216    index: u32,
217    state_writer: EventualWriter<DeviceState>,
218    brightness_writer: EventualWriter<DeviceBrightness>,
219  ) -> Self {
220    DeviceWriter::Dimmer(DimmerDeviceWriter {
221      index,
222      desc,
223      state_writer,
224      brightness_writer,
225    })
226  }
227
228  pub(crate) fn index(&self) -> u32 {
229    match self {
230      DeviceWriter::Binary(w) => w.index,
231      DeviceWriter::Dimmer(w) => w.index,
232    }
233  }
234
235  fn desc(&self) -> &DeviceDesc {
236    match self {
237      DeviceWriter::Binary(w) => &*w.desc,
238      DeviceWriter::Dimmer(w) => &*w.desc,
239    }
240  }
241
242  pub(crate) fn kind(&self) -> DeviceKind {
243    self.desc().kind
244  }
245
246  pub(crate) fn name(&self) -> &str {
247    &*self.desc().name
248  }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
252pub enum DeviceState {
253  Off,
254  On,
255  Unknown,
256}
257
258impl From<SetValue> for DeviceState {
259  fn from(v: SetValue) -> Self {
260    match v {
261      SetValue::On(_) => Self::On,
262      SetValue::Off(_) => Self::Off,
263      SetValue::Dimm(0) => Self::Off,
264      SetValue::Dimm(_) => Self::On,
265      SetValue::Blinds(_) => todo!(),
266    }
267  }
268}
269
270impl fmt::Display for DeviceState {
271  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272    match self {
273      DeviceState::Off => f.write_str("OFF"),
274      DeviceState::On => f.write_str("ON"),
275      DeviceState::Unknown => f.write_str("UNKNOWN"),
276    }
277  }
278}
279
280impl FromStr for DeviceState {
281  type Err = ParseDeviceStateError;
282
283  fn from_str(s: &str) -> Result<Self, Self::Err> {
284    match s {
285      "ON" => Ok(Self::On),
286      "OFF" => Ok(Self::Off),
287      "UNKNOWN" | "UNDEFINED" => Ok(Self::Unknown),
288      _ => Err(ParseDeviceStateError),
289    }
290  }
291}
292
293#[repr(transparent)]
294#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
295pub struct DeviceBrightness(u8);
296
297impl DeviceBrightness {
298  pub const MIN: DeviceBrightness = DeviceBrightness(0);
299  pub const MAX: DeviceBrightness = DeviceBrightness(100);
300
301  pub const fn new(value: u8) -> Option<Self> {
302    if value <= 100 {
303      Some(Self(value))
304    } else {
305      None
306    }
307  }
308
309  #[inline]
310  pub const fn get(self) -> u8 {
311    self.0
312  }
313}
314
315impl fmt::Debug for DeviceBrightness {
316  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317    fmt::Debug::fmt(&self.0, f)
318  }
319}
320
321impl fmt::Display for DeviceBrightness {
322  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323    fmt::Display::fmt(&self.0, f)
324  }
325}
326
327impl FromStr for DeviceBrightness {
328  type Err = ParseDeviceBrightnessError;
329
330  fn from_str(s: &str) -> Result<Self, Self::Err> {
331    if s.len() > 3 || s.is_empty() {
332      return Err(ParseDeviceBrightnessError);
333    }
334
335    let mut v = 0u8;
336    for b in s.bytes() {
337      if !(b'0'..=b'9').contains(&b) {
338        return Err(ParseDeviceBrightnessError);
339      }
340
341      v *= 10;
342      v += b - b'0';
343    }
344
345    match DeviceBrightness::new(v) {
346      None => Err(ParseDeviceBrightnessError),
347      Some(v) => Ok(v),
348    }
349  }
350}
351
352#[derive(Clone, Copy, PartialEq, Eq, Hash)]
353pub enum DeviceGroupState {
354  AllOff,
355  AllOn,
356}
357
358pub trait EnetDevice {
359  fn name(&self) -> &str;
360  fn number(&self) -> u32;
361  fn kind(&self) -> DeviceKind;
362  fn subscribe(&self) -> EventualReader<DeviceValue>;
363}
364
365#[derive(Clone)]
366pub struct BinaryDevice {
367  pub(crate) desc: Arc<DeviceDesc>,
368  pub(crate) state: Eventual<DeviceState>,
369}
370
371impl BinaryDevice {
372  fn new(desc: Arc<DeviceDesc>, state: Eventual<DeviceState>) -> Self {
373    Self { desc, state }
374  }
375
376  pub fn subscribe_state(&self) -> EventualReader<DeviceState> {
377    self.state.subscribe()
378  }
379}
380
381impl EnetDevice for BinaryDevice {
382  fn name(&self) -> &str {
383    &*self.desc.name
384  }
385
386  fn number(&self) -> u32 {
387    self.desc.number
388  }
389
390  fn kind(&self) -> DeviceKind {
391    DeviceKind::Binary
392  }
393
394  fn subscribe(&self) -> EventualReader<DeviceValue> {
395    eventuals::map(&self.state, |v| ready(v.into())).subscribe()
396  }
397}
398
399#[derive(Clone)]
400pub struct DimmerDevice {
401  pub(crate) desc: Arc<DeviceDesc>,
402  pub(crate) state: Eventual<DeviceState>,
403  pub(crate) brightness: Eventual<DeviceBrightness>,
404}
405
406impl DimmerDevice {
407  fn new(
408    desc: Arc<DeviceDesc>,
409    state: Eventual<DeviceState>,
410    brightness: Eventual<DeviceBrightness>,
411  ) -> Self {
412    Self {
413      desc,
414      state,
415      brightness,
416    }
417  }
418
419  pub fn subscribe_state(&self) -> EventualReader<DeviceState> {
420    self.state.subscribe()
421  }
422
423  pub fn subscribe_brightness(&self) -> EventualReader<DeviceBrightness> {
424    self.brightness.subscribe()
425  }
426}
427
428impl EnetDevice for DimmerDevice {
429  fn name(&self) -> &str {
430    &*self.desc.name
431  }
432
433  fn number(&self) -> u32 {
434    self.desc.number
435  }
436
437  fn kind(&self) -> DeviceKind {
438    DeviceKind::Dimmer
439  }
440
441  fn subscribe(&self) -> EventualReader<DeviceValue> {
442    let joined = eventuals::join((&self.state, &self.brightness));
443    let mapped = eventuals::map(joined, |v| ready(v.into()));
444    mapped.subscribe()
445  }
446}
447
448#[derive(Clone)]
449pub enum Device {
450  Binary(BinaryDevice),
451  Dimmer(DimmerDevice),
452}
453
454impl EnetDevice for Device {
455  fn name(&self) -> &str {
456    match self {
457      Device::Binary(d) => d.name(),
458      Device::Dimmer(d) => d.name(),
459    }
460  }
461
462  fn number(&self) -> u32 {
463    match self {
464      Device::Binary(d) => d.number(),
465      Device::Dimmer(d) => d.number(),
466    }
467  }
468
469  fn kind(&self) -> DeviceKind {
470    match self {
471      Device::Binary(d) => d.kind(),
472      Device::Dimmer(d) => d.kind(),
473    }
474  }
475
476  fn subscribe(&self) -> EventualReader<DeviceValue> {
477    match self {
478      Device::Binary(d) => d.subscribe(),
479      Device::Dimmer(d) => d.subscribe(),
480    }
481  }
482}
483
484impl Device {
485  pub(crate) fn new(desc: DeviceDesc, index: u32) -> (DeviceWriter, Self) {
486    let desc = Arc::new(desc);
487    match desc.kind {
488      DeviceKind::Binary => Self::new_binary(desc, index),
489      DeviceKind::Dimmer => Self::new_dimmer(desc, index),
490      DeviceKind::Blinds => todo!(),
491    }
492  }
493
494  fn new_binary(desc: Arc<DeviceDesc>, index: u32) -> (DeviceWriter, Self) {
495    debug_assert_eq!(desc.kind, DeviceKind::Binary);
496
497    let (state_writer, state) = Eventual::new();
498
499    (
500      DeviceWriter::new_binary(desc.clone(), index, state_writer),
501      Self::Binary(BinaryDevice::new(desc, state)),
502    )
503  }
504
505  fn new_dimmer(desc: Arc<DeviceDesc>, index: u32) -> (DeviceWriter, Self) {
506    debug_assert_eq!(desc.kind, DeviceKind::Dimmer);
507
508    let (state_writer, state) = Eventual::new();
509    let (brightness_writer, brightness) = Eventual::new();
510
511    (
512      DeviceWriter::new_dimmer(desc.clone(), index, state_writer, brightness_writer),
513      Self::Dimmer(DimmerDevice::new(desc, state, brightness)),
514    )
515  }
516}
517
518#[derive(Debug, Error)]
519#[non_exhaustive]
520#[error("Failed to parse 'on' value. Must be 0..=100.")]
521pub struct ParseDeviceBrightnessError;
522
523#[derive(Debug, Error)]
524#[non_exhaustive]
525#[error("Failed to parse state. Must be either 'ON' or 'OFF'.")]
526pub struct ParseDeviceStateError;