1use std::{
8 ops::{Deref, DerefMut},
9 time::Duration,
10};
11
12use derive_more::{Display, Error};
13use futures_core::Stream;
14use futures_util::{StreamExt as _, stream};
15use smol_str::SmolStr;
16use strum::FromRepr;
17
18use crate::{Control, ControlValue};
19
20#[cfg(feature = "blinking-led-task")]
21mod blinking_led_task;
22#[cfg(feature = "blinking-led-task")]
23pub use blinking_led_task::blinking_led_task;
24#[cfg(feature = "blinking-led-task-tokio-rt")]
25pub use blinking_led_task::spawn_blinking_led_task;
26
27#[derive(Debug, Display, Error)]
28pub enum OutputError {
29 #[display("disconnected")]
30 Disconnected,
31 #[display("send: {msg}")]
32 Send { msg: SmolStr },
33}
34
35pub type OutputResult<T> = std::result::Result<T, OutputError>;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)]
39#[repr(u8)]
40pub enum LedOutput {
41 Off = 0,
42 On = 1,
43}
44
45impl From<LedOutput> for ControlValue {
46 fn from(value: LedOutput) -> Self {
47 Self::from_bits(value as _)
48 }
49}
50
51impl From<ControlValue> for LedOutput {
52 fn from(value: ControlValue) -> Self {
53 match value.to_bits() {
54 0 => Self::Off,
55 _ => Self::On,
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62#[repr(transparent)]
63pub struct DimLedOutput {
64 pub brightness: u8,
65}
66
67impl From<DimLedOutput> for ControlValue {
68 fn from(value: DimLedOutput) -> Self {
69 let DimLedOutput { brightness } = value;
70 Self::from_bits(u32::from(brightness))
71 }
72}
73
74impl From<ControlValue> for DimLedOutput {
75 fn from(value: ControlValue) -> Self {
76 let brightness = (value.to_bits() & 0xff) as u8;
77 Self { brightness }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct RgbLedOutput {
84 pub red: u8,
85 pub green: u8,
86 pub blue: u8,
87}
88
89impl From<RgbLedOutput> for ControlValue {
90 fn from(value: RgbLedOutput) -> Self {
91 let RgbLedOutput { red, green, blue } = value;
92 Self::from_bits((u32::from(red) << 16) | (u32::from(green) << 8) | u32::from(blue))
93 }
94}
95
96impl From<ControlValue> for RgbLedOutput {
97 fn from(value: ControlValue) -> Self {
98 let red = ((value.to_bits() >> 16) & 0xff) as u8;
99 let green = ((value.to_bits() >> 8) & 0xff) as u8;
100 let blue = (value.to_bits() & 0xff) as u8;
101 Self { red, green, blue }
102 }
103}
104
105#[derive(Debug)]
107pub struct SendOutputsError {
108 pub sent_ok: Option<usize>,
114
115 pub err: OutputError,
117}
118
119pub trait ControlOutputGateway {
120 fn send_output(&mut self, output: &Control) -> OutputResult<()>;
122
123 fn send_outputs(&mut self, outputs: &[Control]) -> Result<(), SendOutputsError> {
127 let mut sent_ok = 0;
128 for output in outputs {
129 match self.send_output(output) {
130 Ok(()) => {
131 sent_ok += 1;
132 }
133 Err(err) => {
134 return Err(SendOutputsError {
135 sent_ok: Some(sent_ok),
136 err,
137 });
138 }
139 }
140 }
141 debug_assert_eq!(sent_ok, outputs.len());
142 Ok(())
143 }
144}
145
146impl<T> ControlOutputGateway for T
147where
148 T: DerefMut + ?Sized,
149 <T as Deref>::Target: ControlOutputGateway,
150{
151 fn send_output(&mut self, output: &Control) -> OutputResult<()> {
152 self.deref_mut().send_output(output)
153 }
154
155 fn send_outputs(&mut self, outputs: &[Control]) -> Result<(), SendOutputsError> {
156 self.deref_mut().send_outputs(outputs)
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum LedState {
162 Off,
163 BlinkFast,
164 BlinkSlow,
165 On,
166}
167
168impl LedState {
169 #[must_use]
170 pub const fn is_blinking(self) -> bool {
171 match self {
172 Self::BlinkFast | Self::BlinkSlow => true,
173 Self::Off | Self::On => false,
174 }
175 }
176
177 #[must_use]
182 pub const fn initial_output(self) -> LedOutput {
183 self.output(BlinkingLedOutput::ON)
184 }
185
186 #[must_use]
188 pub const fn output(self, blinking_led_output: BlinkingLedOutput) -> LedOutput {
189 match self {
190 Self::Off => LedOutput::Off,
191 Self::BlinkFast => blinking_led_output.fast(),
192 Self::BlinkSlow => blinking_led_output.slow(),
193 Self::On => LedOutput::On,
194 }
195 }
196}
197
198pub const DEFAULT_BLINKING_LED_PERIOD: Duration = Duration::from_millis(250);
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub struct BlinkingLedOutput(u8);
202
203impl BlinkingLedOutput {
204 pub const ON: Self = Self(0b11);
205
206 #[must_use]
207 pub const fn fast(self) -> LedOutput {
208 match self.0 & 0b01 {
209 0b00 => LedOutput::Off,
210 0b01 => LedOutput::On,
211 _ => unreachable!(),
212 }
213 }
214
215 #[must_use]
216 pub const fn slow(self) -> LedOutput {
217 match self.0 & 0b10 {
218 0b00 => LedOutput::Off,
219 0b10 => LedOutput::On,
220 _ => unreachable!(),
221 }
222 }
223}
224
225#[derive(Debug, Default)]
226pub struct BlinkingLedTicker(usize);
227
228impl BlinkingLedTicker {
229 const fn output_from_value(value: usize) -> BlinkingLedOutput {
230 #[expect(clippy::cast_possible_truncation)]
231 BlinkingLedOutput(!value as u8 & 0b11)
232 }
233
234 #[must_use]
235 pub fn tick(&mut self) -> BlinkingLedOutput {
236 let value = self.0;
237 self.0 = self.0.wrapping_add(1);
238 Self::output_from_value(value)
239 }
240
241 #[must_use]
242 pub const fn output(&self) -> BlinkingLedOutput {
243 Self::output_from_value(self.0)
244 }
245
246 pub fn map_into_output_stream(
247 self,
248 periodic: impl Stream<Item = ()> + 'static,
249 ) -> impl Stream<Item = BlinkingLedOutput> {
250 stream::unfold(
251 (self, Box::pin(periodic)),
252 |(mut ticker, mut periodic)| async move {
253 periodic.next().await.map(|()| {
254 let output = ticker.tick();
255 (output, (ticker, periodic))
256 })
257 },
258 )
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266pub struct VirtualLed {
267 pub state: LedState,
268 pub output: LedOutput,
269}
270
271impl VirtualLed {
272 pub const OFF: Self = Self::initial_state(LedState::Off);
273
274 #[must_use]
276 pub const fn initial_state(state: LedState) -> Self {
277 let output = state.initial_output();
278 Self { state, output }
279 }
280
281 pub fn update_state(&mut self, state: LedState) -> bool {
287 if self.state == state {
288 return false;
290 }
291 *self = Self::initial_state(state);
292 true
293 }
294
295 pub fn update_blinking_output(&mut self, blinking_led_output: BlinkingLedOutput) {
299 let Self { state, output } = self;
300 *output = state.output(blinking_led_output);
301 }
302}
303
304impl Default for VirtualLed {
305 fn default() -> Self {
306 Self::OFF
307 }
308}
309
310#[cfg(test)]
311mod tests {
312 use crate::{BlinkingLedOutput, BlinkingLedTicker, LedOutput};
313
314 #[test]
315 fn blinking_led_output_on() {
316 assert_eq!(LedOutput::On, BlinkingLedOutput::ON.fast());
317 assert_eq!(LedOutput::On, BlinkingLedOutput::ON.slow());
318 }
319
320 #[test]
321 fn blinking_led_ticker_initial_output_is_on() {
322 assert_eq!(BlinkingLedOutput::ON, BlinkingLedTicker::default().output());
323 }
324}