keebrs 0.3.0

Keyboard firmware building blocks
//! Backlight functionality

use smart_leds::{
    hsv::{
        hsv2rgb,
        Hsv,
    },
    RGB8,
};

use crate::{
    key::PhysKey,
    serial::Msg,
};

use futures::prelude::*;

/// Trait representing an effect to display on the backlight.
pub trait BacklightEffect {
    /// Update the internal state of the effect based on a key press or release
    fn on_key_event(&mut self, key: PhysKey);
    /**
      Update the internal state of the effect when a backlight 'tick' occurs

      The length of a tick depends on the frequency of the timer controlling the
      backlight system
    */
    fn on_tick(&mut self);
    /**
      Returns the colour of the LED at the specified position based on the
      current state of the effect
    */
    fn get_hsv(&self, position: (u8, u8)) -> Option<Hsv>;
    /**
      Triggered when a "Backlight effect change" message is received.
      In general, this should not do anything. It is only used for effects
      which are unions of other effects
    */
    fn on_backlight_change(&mut self) {}
}

/// Mapping between LED indices and a physical location on the keyboard
pub trait BacklightMap {
    /// Return the position of the specified LED
    fn translate(&self, led: usize) -> Option<(u8, u8)>;
}

/// Trait for a struct that can drive an RGB led strip
pub trait RgbDriver {
    /// Prepare to set the colour of the specified LED
    fn prepare_color(&mut self, index: usize, color: RGB8);
    /// Transmit all the configured colours
    fn transmit(&mut self);
}

/// A struct for controlling the keyboard backlight.
pub struct BacklightManager<T> {
    driver: T,
    led_count: usize,
}

impl<T> BacklightManager<T>
where
    T: RgbDriver,
{
    /// Initialise the RGB manager
    pub fn new(driver: T, led_count: usize) -> Self {
        Self { driver, led_count }
    }

    /**
      Runs the RGB manager.

      Consumes keyboard messages from `msg_rx` which are used to update the
      internal state of effects.

      Every tick of `timer` updates the effect, then sends the resulting colors
      to the leds.
    */
    pub async fn run<S, Ti>(
        &mut self,
        msg_rx: S,
        timer: Ti,
        led_map: impl BacklightMap,
        mut effect: impl BacklightEffect,
    ) where
        S: Stream<Item = Msg> + Unpin,
        Ti: Stream + Unpin,
    {
        // Combine the timer and message future streams
        let mut event = stream::select(timer.map(|_| Event::AnimationTick), msg_rx.map(Event::Msg));

        loop {
            match event.next().await {
                Some(Event::AnimationTick) => {
                    effect.on_tick();

                    for i in 0..self.led_count {
                        let hsv = led_map.translate(i).and_then(|pos| effect.get_hsv(pos));

                        let color = match hsv {
                            Some(hsv) => hsv2rgb(hsv),
                            None => RGB8 { r: 0, g: 0, b: 0 },
                        };
                        self.driver.prepare_color(i, color);
                    }

                    self.driver.transmit();
                }
                Some(Event::Msg(Msg::KeyEvent(e))) => {
                    effect.on_key_event(e);
                }
                Some(_) => {}
                None => unreachable!(),
            }
        }
    }
}

/// Compound type for events from multiple backlight related futures.
enum Event {
    AnimationTick,
    Msg(Msg),
}