m5cardputer 0.1.1

esp-idf-hal-based board support for the M5Stack Card Computer
// SPDX-License-Identifier: GPL-3.0-or-later

use std::fmt::Display;
use std::sync::{Arc, Mutex};
use std::mem::MaybeUninit;
use embedded_hal::digital::v2::{OutputPin, InputPin};
use esp_idf_svc::hal::gpio::*;
use esp_idf_svc::sys::EspError;
use keypad::KeypadInput;

#[derive(Debug, Clone, Copy)]
pub enum SpecialCharacter {
    ESC,
    Backspace,
    DEL,
    Tab,
    Fn,
    Shift,
    Ctrl,
    Option,
    Alt,
    OK,
    Up,
    Down,
    Left,
    Right
}

/// A key that has been pressed
#[derive(Debug, Clone, Copy)]
pub enum Key {
    Alphanumeric(char),
    Special(SpecialCharacter)
}

impl Display for Key {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Key::Alphanumeric(c) => write!(f, "{}", c),
            Key::Special(c) => write!(f, "{:?}", c)
        }
    }
}

macro_rules! keymap {
    ($normal: expr, $shift: expr) => {
        (Key::Alphanumeric($normal), Some(Key::Alphanumeric($shift)), None)
    };
    ($normal: expr, $shift: expr, $special: ident) => {
        (Key::Alphanumeric($normal), Some(Key::Alphanumeric($shift)), Some(Key::Special(SpecialCharacter::$special)))
    };
    ($special: ident) => {
        (Key::Special(SpecialCharacter::$special), None, None)
    };
}

/// Default keymap with default-state key value, shift-state value and Fn-state value
const KEYMAP: [[(Key, Option<Key>, Option<Key>); 14]; 4] = [
    [keymap!('`','~',ESC), keymap!('1','!'), keymap!('2','@'), keymap!('3','#'), keymap!('4','$'), keymap!('5','%'), keymap!('6','^'), keymap!('7','&'), keymap!('8','*'), keymap!('9','('), keymap!('0',')'), keymap!('_','-'), keymap!('=','+'), (Key::Special(SpecialCharacter::Backspace), None, Some(Key::Special(SpecialCharacter::DEL)))],
    [keymap!(Tab), keymap!('q','Q'), keymap!('w','W'), keymap!('e','E'), keymap!('r','R'), keymap!('t','T'), keymap!('y','Y'), keymap!('u','U'), keymap!('i','I'), keymap!('o','O'), keymap!('p','P'), keymap!('[','{'), keymap!(']','}'), keymap!('\\','|')],
    [keymap!(Fn), keymap!(Shift), keymap!('a','A'), keymap!('s','S'), keymap!('d','D'), keymap!('f','F'), keymap!('g','G'), keymap!('h','H'), keymap!('j','J'), keymap!('k','K'), keymap!('l','L'), keymap!(';',':',Up), keymap!('\'','"'), keymap!(OK)],
    [keymap!(Ctrl), keymap!(Option), keymap!(Alt), keymap!('z','Z'), keymap!('x','X'), keymap!('c','C'), keymap!('v','V'), keymap!('b','B'), keymap!('n','N'), keymap!('m','M'), keymap!(',','<',Left), keymap!('.','>',Down), keymap!('/','?',Right), (Key::Alphanumeric(' '), None, None)]
];

/// Representation of the 74HC138 3- to 8-line decoder for driving the individual rows of the
/// keyboard matrix
struct MultiplexDecoder<'a> {
    address_pins: Arc<Mutex<(PinDriver<'a, Gpio8, Output>, PinDriver<'a, Gpio9, Output>, PinDriver<'a, Gpio11, Output>)>>
}

impl<'a> MultiplexDecoder<'a> {
    fn new(address_pins: (PinDriver<'a, Gpio8, Output>, PinDriver<'a, Gpio9, Output>, PinDriver<'a, Gpio11, Output>)) -> MultiplexDecoder<'a> {
        Self { address_pins: Arc::new(Mutex::new(address_pins)) }
    }

    fn reset(&mut self) -> Result<(), EspError> {
        let mut pins_write = self.address_pins.lock().unwrap();
        pins_write.0.set_low()?;
        pins_write.1.set_low()?;
        pins_write.2.set_low()?;
        Ok(())
    }

    fn set(&mut self, active: (bool, bool, bool)) -> Result<(), EspError> {
        let mut pins_write = self.address_pins.lock().unwrap();
        pins_write.0.set_level(active.0.into())?;
        pins_write.1.set_level(active.1.into())?;
        pins_write.2.set_level(active.2.into())?;
        Ok(())
    }
}

impl Clone for MultiplexDecoder<'_> {
    fn clone(&self) -> Self {
        Self { address_pins: self.address_pins.clone() }
    }
}

/// Virtual pin representing one possible active low output state of the 74HC138 decoder. Writing to
/// this pin configures the decoder such that the corresponding output reads as low.
struct AnyMultiplexedOutputPin<'a>(MultiplexDecoder<'a>, (bool, bool, bool));
impl<'a> AnyMultiplexedOutputPin<'a> {
    fn get_active_pins(&self) -> (bool, bool, bool) {
        self.1
    }
    fn get_decoder(&mut self) -> &mut MultiplexDecoder<'a> {
        &mut self.0
    }
}

impl OutputPin for AnyMultiplexedOutputPin<'_> {
    type Error = EspError;
    
    // This resets the address pins for interoperability with the keypad library, even though
    // the decoder is actually active high on the address input
    fn set_high(&mut self) -> Result<(), Self::Error> {
        self.get_decoder().reset()?;
        Ok(())
    }

    // Keypad library expects active low input on decoder: this is actually inverted, and we
    // instead set the address pins high here
    fn set_low(&mut self) -> Result<(), Self::Error> {
        let active = self.get_active_pins().clone();
        let dec = self.get_decoder();
        dec.set(active)?;

        Ok(())
    }
}

/// Creates an instance of [`AnyMultiplexedOutputPin`] given the provided decoder and address pin
/// states.
macro_rules! multiplex {
    ($d:ident, $a:expr) => {
        AnyMultiplexedOutputPin($d.clone(), $a)
    };
}

/// Enables a pull up on the given pin.
macro_rules! configure_row_pin {
    ($i:expr) => {
        $i.set_pull(Pull::Up).unwrap();
    };
}

keypad_struct! {
    pub struct CardputerKeyboard<Error = EspError> {
        // Keyboard is multiplexed as 7x8, rows and columns switched due to Input/Output
        // type
        rows: (
            PinDriver<'static, Gpio13, Input>,
            PinDriver<'static, Gpio15, Input>,
            PinDriver<'static, Gpio3, Input>,
            PinDriver<'static, Gpio4, Input>,
            PinDriver<'static, Gpio5, Input>,
            PinDriver<'static, Gpio6, Input>,
            PinDriver<'static, Gpio7, Input>,
        ),
        columns: (
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
            AnyMultiplexedOutputPin<'static>,
        ),
    }
}

pub struct Keyboard {
    keypad: CardputerKeyboard,
    /// Map of physical keys to `Key`s, with the first tuple entry being the default key value, the
    /// second one being the shift state, the third one being the value when "Fn" is pressed
    keymap: [[(Key, Option<Key>, Option<Key>); 14]; 4]
}

impl Keyboard {
    /// Create a new cardputer keyboard given the specified column pins and address pins for
    /// the 74HC138 3- to 8-line decoder.
    pub fn new(row_pins: (Gpio13, Gpio15, Gpio3, Gpio4, Gpio5, Gpio6, Gpio7), address_pins: (Gpio8, Gpio9, Gpio11)) -> Result<Self, EspError> {
        let address_pin_drivers = (
            PinDriver::output(address_pins.0)?,
            PinDriver::output(address_pins.1)?,
            PinDriver::output(address_pins.2)?,
        );
        let dec = MultiplexDecoder::new(address_pin_drivers);
        let mut keypad = keypad_new!(CardputerKeyboard {
            rows: (
                PinDriver::input(row_pins.0)?,
                PinDriver::input(row_pins.1)?,
                PinDriver::input(row_pins.2)?,
                PinDriver::input(row_pins.3)?,
                PinDriver::input(row_pins.4)?,
                PinDriver::input(row_pins.5)?,
                PinDriver::input(row_pins.6)?,
            ),
            columns: (
                multiplex!(dec, (false, false, false)),
                multiplex!(dec, (true, false, false)),
                multiplex!(dec, (false, true, false)),
                multiplex!(dec, (true, true, false)),
                multiplex!(dec, (false, false, true)),
                multiplex!(dec, (true, false, true)),
                multiplex!(dec, (false, true, true)),
                multiplex!(dec, (true, true, true)),
            ),
        });

        configure_row_pin!(keypad.rows.0);
        configure_row_pin!(keypad.rows.1);
        configure_row_pin!(keypad.rows.2);
        configure_row_pin!(keypad.rows.3);
        configure_row_pin!(keypad.rows.4);
        configure_row_pin!(keypad.rows.5);
        configure_row_pin!(keypad.rows.6);

        Ok(Self {
            keypad,
            keymap: KEYMAP
        })
    }

    /// Overrides the default keymap with a custom one
    pub fn set_keymap(&mut self, keymap: [[(Key, Option<Key>, Option<Key>); 14]; 4]) {
        self.keymap = keymap;
    }

    /// Returns an array of [`keypad::KeypadInput`] for each row, with the inner index
    /// describing the column. Call [`is_low`](embedded_hal::digital::v2::InputPin::is_low) to actually read the key state from
    /// hardware.
    pub fn decompose(&self) -> [[KeypadInput<EspError>; 14]; 4] {
        let keys = self.keypad.decompose();

        // The code below is for transmuting the keypad inputs back into an owned
        // representation that actually represents the 14x4 keyboard matrix on the
        // M5Cardputer. Unsafe code is required to shift around the `KeypadInput` elements
        // without copying. This is safe as the keys decomposition above is dropped, and
        // because each element in the returned array is initialized to exactly one value of
        // `keys`.
        unsafe {
            const ARRAY_UNINIT: MaybeUninit<KeypadInput<EspError>> = MaybeUninit::<KeypadInput<EspError>>::uninit();
            let mut row0: [MaybeUninit<KeypadInput<EspError>>; 14] = [ARRAY_UNINIT; 14];
            let mut row1: [MaybeUninit<KeypadInput<EspError>>; 14] = [ARRAY_UNINIT; 14];
            let mut row2: [MaybeUninit<KeypadInput<EspError>>; 14] = [ARRAY_UNINIT; 14];
            let mut row3: [MaybeUninit<KeypadInput<EspError>>; 14] = [ARRAY_UNINIT; 14];

            for (row_ix, row) in keys.into_iter().enumerate() {
                for (col_ix, key) in row.into_iter().enumerate() {
                    match col_ix {
                        7 => row0[row_ix*2] = MaybeUninit::new(key),
                        3 => row0[1+row_ix*2] = MaybeUninit::new(key),
                        6 => row1[row_ix*2] = MaybeUninit::new(key),
                        2 => row1[1+row_ix*2] = MaybeUninit::new(key),
                        5 => row2[row_ix*2] = MaybeUninit::new(key),
                        1 => row2[1+row_ix*2] = MaybeUninit::new(key),
                        4 => row3[row_ix*2] = MaybeUninit::new(key),
                        0 => row3[1+row_ix*2] = MaybeUninit::new(key),
                        _ => panic!("access to invalid column index")
                    }
                }
            }
            let row0_init: [KeypadInput<EspError>; 14] = std::mem::transmute(row0);
            let row1_init: [KeypadInput<EspError>; 14] = std::mem::transmute(row1);
            let row2_init: [KeypadInput<EspError>; 14] = std::mem::transmute(row2);
            let row3_init: [KeypadInput<EspError>; 14] = std::mem::transmute(row3);
            [row0_init, row1_init, row2_init, row3_init]
        }
    }

    /// Gets a list of currently pressed keys based on the configured keymap
    pub fn get_keys(&self) -> Result<Vec<Key>, EspError> {
        let mut keys = Vec::new();
        let dec = self.decompose();
        let shift_pressed = dec[2][1].is_low()?;
        let fn_pressed = dec[2][0].is_low()?;

        for (row_ix, row) in dec.into_iter().enumerate() {
            for (col_ix, key) in row.into_iter().enumerate() {
                if key.is_low()? {
                    let map_entry = self.keymap[row_ix][col_ix];
                    if fn_pressed && map_entry.2.is_some() {
                        keys.push(map_entry.2.unwrap());
                    } else if shift_pressed && map_entry.1.is_some() {
                        keys.push(map_entry.1.unwrap());
                    } else {
                        keys.push(map_entry.0);
                    }
                }
            }
        }
        Ok(keys)
    }
}