Skip to main content

oxstack_input/
cardputer.rs

1//! M5Stack Cardputer keyboard: 56 keys in 4 rows × 14 columns.
2
3use crate::key::{Action, Key};
4
5/// The physical layout, top-left to bottom-right.
6#[rustfmt::skip]
7pub const LAYOUT: [[Key; 14]; 4] = {
8    use Key::*;
9    [
10        [Grave, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, Minus, Equal, Backspace],
11        [Tab, Q, W, E, R, T, Y, U, I, O, P, LeftBracket, RightBracket, Backslash],
12        [Fn, Shift, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Enter],
13        [Ctrl, Opt, Alt, Z, X, C, V, B, N, M, Comma, Period, Slash, Space],
14    ]
15};
16
17/// Scanner rows used on the Cardputer ADV's TCA8418 (`ROW0..ROW6`).
18pub const ADV_MATRIX_ROWS: u8 = 7;
19/// Scanner columns used on the Cardputer ADV's TCA8418 (`COL0..COL7`).
20pub const ADV_MATRIX_COLS: u8 = 8;
21
22/// Maps a TCA8418 matrix position on the **Cardputer ADV** to its key.
23///
24/// The ADV wires the 4×14 keyboard as a 7×8 matrix: each scanner row drives
25/// two adjacent physical columns, and scanner columns 0–3 / 4–7 select the
26/// physical row for the left / right one. (Wiring per the ADV schematic
27/// and M5Stack's `M5Cardputer` library.)
28pub const fn from_adv_matrix(row: u8, col: u8) -> Option<Key> {
29    if row >= ADV_MATRIX_ROWS || col >= ADV_MATRIX_COLS {
30        return None;
31    }
32    let phys_col = row * 2 + (col >= 4) as u8;
33    let phys_row = col % 4;
34    Some(LAYOUT[phys_row as usize][phys_col as usize])
35}
36
37/// The orange Fn-layer legends.
38pub const fn fn_layer(key: Key) -> Option<Action> {
39    use Key::*;
40    Some(match key {
41        Grave => Action::Escape,
42        Num1 => Action::F(1),
43        Num2 => Action::F(2),
44        Num3 => Action::F(3),
45        Num4 => Action::F(4),
46        Num5 => Action::F(5),
47        Num6 => Action::F(6),
48        Num7 => Action::F(7),
49        Num8 => Action::F(8),
50        Num9 => Action::F(9),
51        Num0 => Action::F(10),
52        Minus => Action::F(11),
53        Equal => Action::F(12),
54        Backspace => Action::Delete,
55        Semicolon => Action::Up,
56        Comma => Action::Left,
57        Period => Action::Down,
58        Slash => Action::Right,
59        _ => return None,
60    })
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn layout_lists_every_key_once_in_discriminant_order() {
69        let flat = LAYOUT.as_flattened();
70        assert_eq!(flat.len(), Key::COUNT);
71        for (i, k) in flat.iter().enumerate() {
72            assert_eq!(*k as usize, i);
73        }
74    }
75
76    #[test]
77    fn adv_matrix_maps_every_key_once() {
78        let mut seen = 0u64;
79        for row in 0..ADV_MATRIX_ROWS {
80            for col in 0..ADV_MATRIX_COLS {
81                let key = from_adv_matrix(row, col).unwrap();
82                assert_eq!(seen & key.bit(), 0, "{key:?} mapped twice");
83                seen |= key.bit();
84            }
85        }
86        assert_eq!(seen.count_ones() as usize, Key::COUNT);
87        assert_eq!(from_adv_matrix(7, 0), None);
88        assert_eq!(from_adv_matrix(0, 8), None);
89
90        // A bijection could still be the wrong permutation; pin the corners.
91        assert_eq!(from_adv_matrix(0, 0), Some(Key::Grave));
92        assert_eq!(from_adv_matrix(0, 4), Some(Key::Num1));
93        assert_eq!(from_adv_matrix(0, 3), Some(Key::Ctrl));
94        assert_eq!(from_adv_matrix(6, 7), Some(Key::Space));
95        assert_eq!(from_adv_matrix(6, 6), Some(Key::Enter));
96    }
97}