pokeys_lib/
matrix.rs

1//! Matrix LED support
2
3use crate::device::PoKeysDevice;
4use crate::error::{PoKeysError, Result};
5use serde::{Deserialize, Serialize};
6
7/// Matrix LED configuration
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct MatrixLed {
10    pub display_enabled: u8,
11    pub rows: u8,
12    pub columns: u8,
13    pub refresh_flag: u8,
14    pub data: [u8; 8],
15}
16
17impl MatrixLed {
18    pub fn new() -> Self {
19        Self {
20            display_enabled: 0,
21            rows: 0,
22            columns: 0,
23            refresh_flag: 0,
24            data: [0; 8],
25        }
26    }
27
28    pub fn is_enabled(&self) -> bool {
29        self.display_enabled != 0
30    }
31
32    pub fn set_led(&mut self, row: usize, col: usize, state: bool) -> Result<()> {
33        if row >= self.rows as usize || col >= 8 {
34            return Err(PoKeysError::Parameter("Invalid LED position".to_string()));
35        }
36
37        if state {
38            self.data[row] |= 1 << col;
39        } else {
40            self.data[row] &= !(1 << col);
41        }
42
43        self.refresh_flag = 1;
44        Ok(())
45    }
46
47    pub fn get_led(&self, row: usize, col: usize) -> bool {
48        if row >= self.rows as usize || col >= 8 {
49            return false;
50        }
51        (self.data[row] & (1 << col)) != 0
52    }
53
54    pub fn clear_all(&mut self) {
55        self.data.fill(0);
56        self.refresh_flag = 1;
57    }
58
59    pub fn set_all(&mut self) {
60        self.data.fill(0xFF);
61        self.refresh_flag = 1;
62    }
63}
64
65impl Default for MatrixLed {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71/// 7-Segment Display Configuration for LED Matrix
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct LedMatrixConfig {
74    pub name: String,
75    pub description: Option<String>,
76    pub matrix_id: u8, // 1 or 2
77    pub enabled: bool,
78    pub characters: u8, // 1-8 (number of 7-segment characters/digits)
79}
80
81/// 7-Segment Display Helper
82pub struct SevenSegmentDisplay {
83    pub matrix_id: u8,
84    pub character_count: u8, // Number of 7-segment characters
85    pub decimal_points: Vec<bool>,
86}
87
88/// Protocol structures for LED Matrix
89pub struct MatrixLedProtocolConfig {
90    pub display1_enabled: bool,
91    pub display2_enabled: bool,
92    pub display1_characters: u8, // Number of characters (becomes rows in protocol)
93    pub display2_characters: u8, // Number of characters (becomes rows in protocol)
94}
95
96/// Constants for 7-segment displays
97pub const SEVEN_SEGMENT_COLUMNS: u8 = 8; // Always 8: 7 segments + decimal point
98
99/// Pin assignments for LED matrices
100pub const LED_MATRIX_1_PINS: [u8; 3] = [9, 10, 11]; // Data, Latch, Clock
101pub const LED_MATRIX_2_PINS: [u8; 3] = [23, 24, 25]; // Data, Latch, Clock
102
103/// 7-Segment digit patterns with correct bit mapping
104/// Bit mapping: 0=DP, 1=G, 2=F, 3=E, 4=D, 5=C, 6=B, 7=A
105pub const SEVEN_SEGMENT_DIGITS: [u8; 10] = [
106    0b11111100, // 0: A,B,C,D,E,F (bits 7,6,5,4,3,2)
107    0b01100000, // 1: B,C (bits 6,5)
108    0b11011010, // 2: A,B,D,E,G (bits 7,6,4,3,1)
109    0b11110010, // 3: A,B,C,D,G (bits 7,6,5,4,1)
110    0b01100110, // 4: B,C,F,G (bits 6,5,2,1)
111    0b10110110, // 5: A,C,D,F,G (bits 7,5,4,2,1)
112    0b10111110, // 6: A,C,D,E,F,G (bits 7,5,4,3,2,1)
113    0b11100000, // 7: A,B,C (bits 7,6,5)
114    0b11111110, // 8: A,B,C,D,E,F,G (bits 7,6,5,4,3,2,1)
115    0b11110110, // 9: A,B,C,D,F,G (bits 7,6,5,4,2,1)
116];
117
118/// 7-Segment letter patterns with correct bit mapping
119/// Bit mapping: 0=DP, 1=G, 2=F, 3=E, 4=D, 5=C, 6=B, 7=A
120pub const SEVEN_SEGMENT_LETTERS: [(char, u8); 19] = [
121    ('a', 0b11101110), // a: A,B,C,E,F,G (bits 7,6,5,3,2,1)
122    ('b', 0b00111110), // b: C,D,E,F,G (bits 5,4,3,2,1)
123    ('c', 0b10011100), // c: A,D,E,F (bits 7,4,3,2)
124    ('d', 0b01111010), // d: B,C,D,E,G (bits 6,5,4,3,1)
125    ('e', 0b10011110), // e: A,D,E,F,G (bits 7,4,3,2,1)
126    ('f', 0b10001110), // f: A,E,F,G (bits 7,3,2,1)
127    ('h', 0b01101110), // h: B,C,E,F,G (bits 6,5,3,2,1)
128    ('i', 0b01100000), // i: B,C (bits 6,5)
129    ('j', 0b01110000), // j: B,C,D (bits 6,5,4)
130    ('l', 0b00011100), // L: D,E,F (bits 4,3,2)
131    ('n', 0b00101010), // n: C,E,G (bits 5,3,1)
132    ('o', 0b11111100), // o: A,B,C,D,E,F (bits 7,6,5,4,3,2) - same as 0
133    ('p', 0b11001110), // p: A,B,E,F,G (bits 7,6,3,2,1)
134    ('q', 0b11100110), // q: A,B,C,F,G (bits 7,6,5,2,1)
135    ('r', 0b00001010), // r: E,G (bits 3,1)
136    ('s', 0b10110110), // S: A,C,D,F,G (bits 7,5,4,2,1) - same as 5
137    ('t', 0b00011110), // t: D,E,F,G (bits 4,3,2,1)
138    ('u', 0b01111000), // u: B,C,D,E,F (bits 6,5,4,3,2)
139    ('y', 0b01110110), // y: B,C,D,F,G (bits 6,5,4,2,1)
140];
141
142/// Matrix action types for LED matrix updates
143#[derive(Debug, Clone, Copy)]
144pub enum MatrixAction {
145    UpdateWhole,
146    SetPixel,
147    ClearPixel,
148}
149
150/// Helper function to get pattern for a character
151pub fn get_seven_segment_pattern(ch: char) -> Option<u8> {
152    match ch {
153        '0'..='9' => {
154            let digit = (ch as u8) - b'0';
155            Some(SEVEN_SEGMENT_DIGITS[digit as usize])
156        }
157        // Special case for capital N
158        'N' => Some(0b11101100), // N: E,F,A,B,C segments (bits 3,2,7,6,5)
159        'a'..='z' | 'A'..='Z' => {
160            let lower_ch = ch.to_ascii_lowercase();
161            SEVEN_SEGMENT_LETTERS
162                .iter()
163                .find(|(c, _)| *c == lower_ch)
164                .map(|(_, pattern)| *pattern)
165        }
166        // Special characters
167        '-' => Some(0b00000010), // Minus: G segment only (bit 1)
168        '_' => Some(0b00010000), // Underscore: D segment only (bit 4)
169        ']' => Some(0b11110000), // Right bracket: A,B,C,D segments (bits 7,6,5,4)
170        '[' => Some(0b10011100), // Left bracket: A,D,E,F segments (bits 7,4,3,2)
171        ' ' => Some(0b00000000), // Space: all segments off
172        _ => None,
173    }
174}
175
176impl SevenSegmentDisplay {
177    pub fn new(matrix_id: u8, character_count: u8) -> Self {
178        Self {
179            matrix_id,
180            character_count,
181            decimal_points: vec![false; character_count as usize],
182        }
183    }
184
185    pub fn display_number(&self, device: &mut PoKeysDevice, number: u32) -> Result<()> {
186        let digits = self.number_to_digits(number);
187        let mut row_data = [0u8; 8];
188
189        for (row, digit) in digits.iter().enumerate() {
190            if row < self.character_count as usize {
191                row_data[row] = SEVEN_SEGMENT_DIGITS[*digit as usize];
192                if self.decimal_points[row] {
193                    row_data[row] |= 0b00000001; // Set decimal point (bit 0)
194                }
195            }
196        }
197
198        device.update_led_matrix(self.matrix_id, MatrixAction::UpdateWhole, 0, 0, &row_data)
199    }
200
201    pub fn display_text(&self, device: &mut PoKeysDevice, text: &str) -> Result<()> {
202        let mut row_data = [0u8; 8];
203        let chars: Vec<char> = text.chars().collect();
204
205        for (row, &ch) in chars.iter().enumerate() {
206            if row < self.character_count as usize {
207                if let Some(pattern) = get_seven_segment_pattern(ch) {
208                    row_data[row] = pattern;
209                    if self.decimal_points[row] {
210                        row_data[row] |= 0b00000001; // Set decimal point (bit 0)
211                    }
212                } else {
213                    // Unknown character - display nothing (all segments off)
214                    row_data[row] = 0;
215                }
216            }
217        }
218
219        device.update_led_matrix(self.matrix_id, MatrixAction::UpdateWhole, 0, 0, &row_data)
220    }
221
222    pub fn display_mixed(&self, device: &mut PoKeysDevice, text: &str) -> Result<()> {
223        // Alias for display_text - handles both numbers and letters
224        self.display_text(device, text)
225    }
226
227    pub fn set_decimal_point(&mut self, character: u8, enabled: bool) {
228        if character < self.character_count {
229            self.decimal_points[character as usize] = enabled;
230        }
231    }
232
233    fn number_to_digits(&self, number: u32) -> Vec<u8> {
234        let mut digits = Vec::new();
235        let mut n = number;
236
237        if n == 0 {
238            digits.push(0);
239        } else {
240            while n > 0 {
241                digits.push((n % 10) as u8);
242                n /= 10;
243            }
244            digits.reverse();
245        }
246
247        // Pad with leading zeros if needed
248        while digits.len() < self.character_count as usize {
249            digits.insert(0, 0);
250        }
251
252        digits.truncate(self.character_count as usize);
253        digits
254    }
255}
256
257impl PoKeysDevice {
258    /// Configure matrix LED display
259    pub fn configure_matrix_led(&mut self, led_index: usize, rows: u8, columns: u8) -> Result<()> {
260        if led_index >= self.matrix_led.len() {
261            return Err(PoKeysError::Parameter(
262                "Invalid LED matrix index".to_string(),
263            ));
264        }
265
266        if rows > 8 || columns > 8 {
267            return Err(PoKeysError::Parameter(
268                "Matrix LED size too large".to_string(),
269            ));
270        }
271
272        self.matrix_led[led_index].display_enabled = 1;
273        self.matrix_led[led_index].rows = rows;
274        self.matrix_led[led_index].columns = columns;
275
276        // Send configuration to device
277        self.send_request(0x62, led_index as u8, rows, columns, 1)?;
278        Ok(())
279    }
280
281    /// Update matrix LED display
282    pub fn update_matrix_led(&mut self, led_index: usize) -> Result<()> {
283        if led_index >= self.matrix_led.len() {
284            return Err(PoKeysError::Parameter(
285                "Invalid LED matrix index".to_string(),
286            ));
287        }
288
289        if !self.matrix_led[led_index].is_enabled() {
290            return Err(PoKeysError::NotSupported);
291        }
292
293        // Copy data to avoid borrow checker issues
294        let data = self.matrix_led[led_index].data;
295        let rows = self.matrix_led[led_index].rows;
296
297        // Send LED data to device
298        self.send_request(0x63, led_index as u8, data[0], data[1], data[2])?;
299
300        // Send remaining data if needed
301        if rows > 3 {
302            self.send_request(0x64, led_index as u8, data[3], data[4], data[5])?;
303        }
304
305        if rows > 6 {
306            self.send_request(0x65, led_index as u8, data[6], data[7], 0)?;
307        }
308
309        self.matrix_led[led_index].refresh_flag = 0;
310        Ok(())
311    }
312
313    /// Set individual LED in matrix
314    pub fn set_matrix_led(
315        &mut self,
316        led_index: usize,
317        row: usize,
318        col: usize,
319        state: bool,
320    ) -> Result<()> {
321        if led_index >= self.matrix_led.len() {
322            return Err(PoKeysError::Parameter(
323                "Invalid LED matrix index".to_string(),
324            ));
325        }
326
327        self.matrix_led[led_index].set_led(row, col, state)?;
328        self.update_matrix_led(led_index)?;
329        Ok(())
330    }
331
332    /// Get individual LED state in matrix
333    pub fn get_matrix_led(&self, led_index: usize, row: usize, col: usize) -> Result<bool> {
334        if led_index >= self.matrix_led.len() {
335            return Err(PoKeysError::Parameter(
336                "Invalid LED matrix index".to_string(),
337            ));
338        }
339
340        Ok(self.matrix_led[led_index].get_led(row, col))
341    }
342
343    /// Clear all LEDs in matrix
344    pub fn clear_matrix_led(&mut self, led_index: usize) -> Result<()> {
345        if led_index >= self.matrix_led.len() {
346            return Err(PoKeysError::Parameter(
347                "Invalid LED matrix index".to_string(),
348            ));
349        }
350
351        self.matrix_led[led_index].clear_all();
352        self.update_matrix_led(led_index)?;
353        Ok(())
354    }
355
356    /// Set all LEDs in matrix
357    pub fn set_all_matrix_led(&mut self, led_index: usize) -> Result<()> {
358        if led_index >= self.matrix_led.len() {
359            return Err(PoKeysError::Parameter(
360                "Invalid LED matrix index".to_string(),
361            ));
362        }
363
364        self.matrix_led[led_index].set_all();
365        self.update_matrix_led(led_index)?;
366        Ok(())
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373
374    #[test]
375    fn test_matrix_led_creation() {
376        let mut led = MatrixLed::new();
377        assert!(!led.is_enabled());
378
379        led.display_enabled = 1;
380        led.rows = 8;
381        led.columns = 8;
382
383        assert!(led.is_enabled());
384        assert!(!led.get_led(0, 0));
385
386        assert!(led.set_led(0, 0, true).is_ok());
387        assert!(led.get_led(0, 0));
388
389        assert!(led.set_led(0, 0, false).is_ok());
390        assert!(!led.get_led(0, 0));
391    }
392
393    #[test]
394    fn test_matrix_led_bounds_checking() {
395        let mut led = MatrixLed::new();
396        led.rows = 4;
397        led.columns = 8;
398
399        // Valid positions
400        assert!(led.set_led(0, 0, true).is_ok());
401        assert!(led.set_led(3, 7, true).is_ok());
402
403        // Invalid positions
404        assert!(led.set_led(4, 0, true).is_err()); // Row out of bounds
405        assert!(led.set_led(0, 8, true).is_err()); // Column out of bounds
406    }
407}