use std::{
ops::{Deref, DerefMut},
time::Duration,
};
use derive_more::{Display, Error};
use futures_core::Stream;
use futures_util::{StreamExt as _, stream};
use smol_str::SmolStr;
use strum::FromRepr;
use crate::{Control, ControlValue};
#[cfg(feature = "blinking-led-task")]
mod blinking_led_task;
#[cfg(feature = "blinking-led-task")]
pub use blinking_led_task::blinking_led_task;
#[cfg(feature = "blinking-led-task-tokio-rt")]
pub use blinking_led_task::spawn_blinking_led_task;
#[derive(Debug, Display, Error)]
pub enum OutputError {
#[display("disconnected")]
Disconnected,
#[display("send: {msg}")]
Send { msg: SmolStr },
}
pub type OutputResult<T> = std::result::Result<T, OutputError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, FromRepr)]
#[repr(u8)]
pub enum LedOutput {
Off = 0,
On = 1,
}
impl From<LedOutput> for ControlValue {
fn from(value: LedOutput) -> Self {
Self::from_bits(value as _)
}
}
impl From<ControlValue> for LedOutput {
fn from(value: ControlValue) -> Self {
match value.to_bits() {
0 => Self::Off,
_ => Self::On,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct DimLedOutput {
pub brightness: u8,
}
impl From<DimLedOutput> for ControlValue {
fn from(value: DimLedOutput) -> Self {
let DimLedOutput { brightness } = value;
Self::from_bits(u32::from(brightness))
}
}
impl From<ControlValue> for DimLedOutput {
fn from(value: ControlValue) -> Self {
let brightness = (value.to_bits() & 0xff) as u8;
Self { brightness }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RgbLedOutput {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl From<RgbLedOutput> for ControlValue {
fn from(value: RgbLedOutput) -> Self {
let RgbLedOutput { red, green, blue } = value;
Self::from_bits((u32::from(red) << 16) | (u32::from(green) << 8) | u32::from(blue))
}
}
impl From<ControlValue> for RgbLedOutput {
fn from(value: ControlValue) -> Self {
let red = ((value.to_bits() >> 16) & 0xff) as u8;
let green = ((value.to_bits() >> 8) & 0xff) as u8;
let blue = (value.to_bits() & 0xff) as u8;
Self { red, green, blue }
}
}
#[derive(Debug)]
pub struct SendOutputsError {
pub sent_ok: Option<usize>,
pub err: OutputError,
}
pub trait ControlOutputGateway {
fn send_output(&mut self, output: &Control) -> OutputResult<()>;
fn send_outputs(&mut self, outputs: &[Control]) -> Result<(), SendOutputsError> {
let mut sent_ok = 0;
for output in outputs {
match self.send_output(output) {
Ok(()) => {
sent_ok += 1;
}
Err(err) => {
return Err(SendOutputsError {
sent_ok: Some(sent_ok),
err,
});
}
}
}
debug_assert_eq!(sent_ok, outputs.len());
Ok(())
}
}
impl<T> ControlOutputGateway for T
where
T: DerefMut + ?Sized,
<T as Deref>::Target: ControlOutputGateway,
{
fn send_output(&mut self, output: &Control) -> OutputResult<()> {
self.deref_mut().send_output(output)
}
fn send_outputs(&mut self, outputs: &[Control]) -> Result<(), SendOutputsError> {
self.deref_mut().send_outputs(outputs)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LedState {
Off,
BlinkFast,
BlinkSlow,
On,
}
impl LedState {
#[must_use]
pub const fn is_blinking(self) -> bool {
match self {
Self::BlinkFast | Self::BlinkSlow => true,
Self::Off | Self::On => false,
}
}
#[must_use]
pub const fn initial_output(self) -> LedOutput {
self.output(BlinkingLedOutput::ON)
}
#[must_use]
pub const fn output(self, blinking_led_output: BlinkingLedOutput) -> LedOutput {
match self {
Self::Off => LedOutput::Off,
Self::BlinkFast => blinking_led_output.fast(),
Self::BlinkSlow => blinking_led_output.slow(),
Self::On => LedOutput::On,
}
}
}
pub const DEFAULT_BLINKING_LED_PERIOD: Duration = Duration::from_millis(250);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlinkingLedOutput(u8);
impl BlinkingLedOutput {
pub const ON: Self = Self(0b11);
#[must_use]
pub const fn fast(self) -> LedOutput {
match self.0 & 0b01 {
0b00 => LedOutput::Off,
0b01 => LedOutput::On,
_ => unreachable!(),
}
}
#[must_use]
pub const fn slow(self) -> LedOutput {
match self.0 & 0b10 {
0b00 => LedOutput::Off,
0b10 => LedOutput::On,
_ => unreachable!(),
}
}
}
#[derive(Debug, Default)]
pub struct BlinkingLedTicker(usize);
impl BlinkingLedTicker {
const fn output_from_value(value: usize) -> BlinkingLedOutput {
#[expect(clippy::cast_possible_truncation)]
BlinkingLedOutput(!value as u8 & 0b11)
}
#[must_use]
pub fn tick(&mut self) -> BlinkingLedOutput {
let value = self.0;
self.0 = self.0.wrapping_add(1);
Self::output_from_value(value)
}
#[must_use]
pub const fn output(&self) -> BlinkingLedOutput {
Self::output_from_value(self.0)
}
pub fn map_into_output_stream(
self,
periodic: impl Stream<Item = ()> + 'static,
) -> impl Stream<Item = BlinkingLedOutput> {
stream::unfold(
(self, Box::pin(periodic)),
|(mut ticker, mut periodic)| async move {
periodic.next().await.map(|()| {
let output = ticker.tick();
(output, (ticker, periodic))
})
},
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VirtualLed {
pub state: LedState,
pub output: LedOutput,
}
impl VirtualLed {
pub const OFF: Self = Self::initial_state(LedState::Off);
#[must_use]
pub const fn initial_state(state: LedState) -> Self {
let output = state.initial_output();
Self { state, output }
}
pub fn update_state(&mut self, state: LedState) -> bool {
if self.state == state {
return false;
}
*self = Self::initial_state(state);
true
}
pub fn update_blinking_output(&mut self, blinking_led_output: BlinkingLedOutput) {
let Self { state, output } = self;
*output = state.output(blinking_led_output);
}
}
impl Default for VirtualLed {
fn default() -> Self {
Self::OFF
}
}
#[cfg(test)]
mod tests {
use crate::{BlinkingLedOutput, BlinkingLedTicker, LedOutput};
#[test]
fn blinking_led_output_on() {
assert_eq!(LedOutput::On, BlinkingLedOutput::ON.fast());
assert_eq!(LedOutput::On, BlinkingLedOutput::ON.slow());
}
#[test]
fn blinking_led_ticker_initial_output_is_on() {
assert_eq!(BlinkingLedOutput::ON, BlinkingLedTicker::default().output());
}
}