Skip to main content
RKTK API Docs RKTK Home Repo

rktk_drivers_common/magnetic/
matrix.rs

1use crate::magnetic::profile::{KeyProfileMap, SwitchProfile};
2use crate::magnetic::rapid_trigger::RapidTriggerState;
3use rktk::drivers::interface::keyscan::{KeyChangeEvent, KeyscanDriver};
4use rktk::drivers::interface::magnetic::{Adc, Multiplexer};
5use rktk_log::MaybeFormat;
6
7pub trait MagneticScanner {
8    type Error: core::fmt::Debug + MaybeFormat;
9    fn scan(
10        &mut self,
11        row: usize,
12        col: usize,
13    ) -> impl core::future::Future<Output = Result<u16, Self::Error>>;
14}
15
16pub struct DirectScanner<A: Adc, const ROWS: usize, const COLS: usize> {
17    adcs: [[A; COLS]; ROWS],
18}
19
20impl<A: Adc, const ROWS: usize, const COLS: usize> DirectScanner<A, ROWS, COLS> {
21    pub fn new(adcs: [[A; COLS]; ROWS]) -> Self {
22        Self { adcs }
23    }
24}
25
26impl<A: Adc, const ROWS: usize, const COLS: usize> MagneticScanner
27    for DirectScanner<A, ROWS, COLS>
28{
29    type Error = A::Error;
30    async fn scan(&mut self, row: usize, col: usize) -> Result<u16, Self::Error> {
31        self.adcs[row][col].read().await
32    }
33}
34
35pub struct MuxScanner<A: Adc, M: Multiplexer, F: Fn(usize, usize) -> Option<(u8, u8)>> {
36    adc: A,
37    mux: M,
38    map: F,
39}
40
41impl<A: Adc, M: Multiplexer, F: Fn(usize, usize) -> Option<(u8, u8)>> MuxScanner<A, M, F> {
42    pub fn new(adc: A, mux: M, map: F) -> Self {
43        Self { adc, mux, map }
44    }
45}
46
47#[derive(Debug)]
48#[cfg_attr(feature = "defmt", derive(defmt::Format))]
49pub enum MagneticError<AE, ME> {
50    Adc(AE),
51    Mux(ME),
52}
53
54impl<A: Adc, M: Multiplexer, F: Fn(usize, usize) -> Option<(u8, u8)>> MagneticScanner
55    for MuxScanner<A, M, F>
56{
57    type Error = MagneticError<A::Error, M::Error>;
58    async fn scan(&mut self, row: usize, col: usize) -> Result<u16, Self::Error> {
59        if let Some((mux_ch, _adc_ch)) = (self.map)(row, col) {
60            self.mux.select(mux_ch).await.map_err(MagneticError::Mux)?;
61            // Settling delay: let the analog voltage stabilize after mux switching
62            embassy_time::Timer::after_micros(10).await;
63            self.adc.read().await.map_err(MagneticError::Adc)
64        } else {
65            Ok(0)
66        }
67    }
68}
69
70#[derive(Clone, Copy, Debug)]
71pub struct CalibrationEntry {
72    pub min: u16,
73    pub max: u16,
74}
75
76impl CalibrationEntry {
77    pub const fn new() -> Self {
78        Self { min: u16::MAX, max: u16::MIN }
79    }
80}
81
82impl Default for CalibrationEntry {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88pub struct MagneticMatrix<
89    S: MagneticScanner,
90    M: KeyProfileMap<ROWS, COLS>,
91    const ROWS: usize,
92    const COLS: usize,
93> {
94    scanner: S,
95    profiles: M,
96    states: [[RapidTriggerState; COLS]; ROWS],
97    calibration_mode: bool,
98    calibration_data: [[CalibrationEntry; COLS]; ROWS],
99    press_dist: u16,
100    release_dist: u16,
101    top_deadzone: u16,
102}
103
104impl<S: MagneticScanner, M: KeyProfileMap<ROWS, COLS>, const ROWS: usize, const COLS: usize>
105    MagneticMatrix<S, M, ROWS, COLS>
106{
107    pub fn new(
108        scanner: S,
109        profiles: M,
110        press_dist: u16,
111        release_dist: u16,
112        top_deadzone: u16,
113    ) -> Self {
114        Self {
115            scanner,
116            profiles,
117            states: [[RapidTriggerState::new(); COLS]; ROWS],
118            calibration_mode: false,
119            calibration_data: [[CalibrationEntry::new(); COLS]; ROWS],
120            press_dist,
121            release_dist,
122            top_deadzone,
123        }
124    }
125
126    pub fn set_calibration_mode(&mut self, enabled: bool) {
127        self.calibration_mode = enabled;
128        if enabled {
129            // Reset calibration data when starting
130            self.calibration_data = [[CalibrationEntry::new(); COLS]; ROWS];
131        }
132    }
133
134    pub fn get_calibration_data(&self) -> [[CalibrationEntry; COLS]; ROWS] {
135        self.calibration_data
136    }
137
138    pub fn set_calibration_data(&mut self, data: [[CalibrationEntry; COLS]; ROWS]) {
139        self.calibration_data = data;
140    }
141}
142
143#[derive(Debug)]
144pub enum CalibrationError {
145    BufferTooSmall,
146}
147
148impl core::fmt::Display for CalibrationError {
149    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
150        match self {
151            CalibrationError::BufferTooSmall => write!(f, "Calibration buffer is too small"),
152        }
153    }
154}
155impl core::error::Error for CalibrationError {}
156
157impl<S: MagneticScanner, M: KeyProfileMap<ROWS, COLS>, const ROWS: usize, const COLS: usize>
158    KeyscanDriver for MagneticMatrix<S, M, ROWS, COLS>
159{
160    const CALIBRATION_SIZE: usize = ROWS * COLS * 4;
161
162    type CalibrationError = CalibrationError;
163
164    fn save_calibration(&self, buf: &mut [u8]) -> Result<(), Self::CalibrationError> {
165        if buf.len() < Self::CALIBRATION_SIZE {
166            return Err(CalibrationError::BufferTooSmall);
167        }
168        let mut idx = 0;
169        for row in 0..ROWS {
170            for col in 0..COLS {
171                let entry = self.calibration_data[row][col];
172                buf[idx..idx + 2].copy_from_slice(&entry.min.to_le_bytes());
173                buf[idx + 2..idx + 4].copy_from_slice(&entry.max.to_le_bytes());
174                idx += 4;
175            }
176        }
177        Ok(())
178    }
179
180    #[allow(clippy::needless_range_loop)]
181    fn load_calibration(&mut self, buf: &[u8]) -> Result<(), Self::CalibrationError> {
182        if buf.len() < Self::CALIBRATION_SIZE {
183            return Err(CalibrationError::BufferTooSmall);
184        }
185        let mut idx = 0;
186        let mut data = [[CalibrationEntry::new(); COLS]; ROWS];
187        for row in 0..ROWS {
188            for col in 0..COLS {
189                let min = u16::from_le_bytes([buf[idx], buf[idx + 1]]);
190                let max = u16::from_le_bytes([buf[idx + 2], buf[idx + 3]]);
191                data[row][col] = CalibrationEntry { min, max };
192                idx += 4;
193            }
194        }
195        self.set_calibration_data(data);
196        Ok(())
197    }
198
199    async fn scan(&mut self, mut cb: impl FnMut(KeyChangeEvent)) {
200        for row in 0..ROWS {
201            for col in 0..COLS {
202                match self.scanner.scan(row, col).await {
203                    Ok(val) => {
204                        if self.calibration_mode {
205                            let entry = &mut self.calibration_data[row][col];
206                            if val < entry.min {
207                                entry.min = val;
208                            }
209                            if val > entry.max {
210                                entry.max = val;
211                            }
212                        } else {
213                            let entry = &self.calibration_data[row][col];
214                            if entry.min < entry.max && (entry.max - entry.min) >= 300 {
215                                // Normalize value to 0-65535
216                                let normalized = if val <= entry.min {
217                                    0
218                                } else if val >= entry.max {
219                                    65535
220                                } else {
221                                    ((val - entry.min) as u32 * 65535
222                                        / (entry.max - entry.min) as u32)
223                                        as u16
224                                };
225
226                                let profile = self.profiles.get_profile(row, col);
227                                let distance = profile.normalized_to_distance(normalized);
228
229                                if let Some(pressed) = self.states[row][col].update(
230                                    distance,
231                                    self.press_dist,
232                                    self.release_dist,
233                                    self.top_deadzone,
234                                ) {
235                                    rktk_log::debug!(
236                                        "Magnetic Key Event: ({},{}) pressed={} distance={} normalized={}",
237                                        row,
238                                        col,
239                                        pressed,
240                                        distance,
241                                        normalized
242                                    );
243                                    cb(KeyChangeEvent { row: row as u8, col: col as u8, pressed });
244                                }
245                            }
246                        }
247                    }
248                    Err(e) => {
249                        rktk_log::error!("Magnetic scan error at {},{}: {:?}", row, col, e);
250                    }
251                }
252            }
253        }
254    }
255
256    fn start_calibration(&mut self) {
257        self.set_calibration_mode(true);
258        rktk_log::info!("Calibration started. Press all keys to their physical limits.");
259    }
260
261    fn end_calibration(&mut self) {
262        self.set_calibration_mode(false);
263        rktk_log::info!("Calibration finished. Results:");
264        for row in 0..ROWS {
265            for col in 0..COLS {
266                let entry = self.calibration_data[row][col];
267                if entry.min < entry.max && (entry.max - entry.min) >= 300 {
268                    rktk_log::info!(
269                        "  Key ({},{}): min_adc={}, max_adc={}, range={}",
270                        row,
271                        col,
272                        entry.min,
273                        entry.max,
274                        entry.max - entry.min
275                    );
276                } else {
277                    rktk_log::info!("  Key ({},{}): Not calibrated (range too small)", row, col);
278                }
279            }
280        }
281    }
282}