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;