1use crate::device::PoKeysDevice;
4use crate::error::{PoKeysError, Result};
5use crate::types::LcdMode;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct LcdData {
11 pub configuration: u8,
12 pub rows: u8,
13 pub columns: u8,
14 pub row_refresh_flags: u8,
15 pub line1: [u8; 20],
16 pub line2: [u8; 20],
17 pub line3: [u8; 20],
18 pub line4: [u8; 20],
19 pub custom_characters: [[u8; 8]; 8],
20}
21
22impl LcdData {
23 pub fn new() -> Self {
24 Self {
25 configuration: 0,
26 rows: 0,
27 columns: 0,
28 row_refresh_flags: 0,
29 line1: [0; 20],
30 line2: [0; 20],
31 line3: [0; 20],
32 line4: [0; 20],
33 custom_characters: [[0; 8]; 8],
34 }
35 }
36
37 pub fn is_enabled(&self) -> bool {
38 self.configuration != 0
39 }
40
41 pub fn get_line(&self, line: usize) -> Option<&[u8; 20]> {
42 match line {
43 1 => Some(&self.line1),
44 2 => Some(&self.line2),
45 3 => Some(&self.line3),
46 4 => Some(&self.line4),
47 _ => None,
48 }
49 }
50
51 pub fn get_line_mut(&mut self, line: usize) -> Option<&mut [u8; 20]> {
52 match line {
53 1 => Some(&mut self.line1),
54 2 => Some(&mut self.line2),
55 3 => Some(&mut self.line3),
56 4 => Some(&mut self.line4),
57 _ => None,
58 }
59 }
60
61 pub fn set_line_text(&mut self, line: usize, text: &str) -> Result<()> {
62 if !(1..=4).contains(&line) {
63 return Err(PoKeysError::Parameter("Invalid line number".to_string()));
64 }
65
66 if text.len() > 20 {
67 return Err(PoKeysError::Parameter(
68 "Text too long for LCD line".to_string(),
69 ));
70 }
71
72 let line_buffer = self.get_line_mut(line).unwrap();
73 line_buffer.fill(0);
74
75 let text_bytes = text.as_bytes();
76 line_buffer[..text_bytes.len()].copy_from_slice(text_bytes);
77
78 self.row_refresh_flags |= 1 << (line - 1);
80
81 Ok(())
82 }
83
84 pub fn get_line_text(&self, line: usize) -> Result<String> {
85 if !(1..=4).contains(&line) {
86 return Err(PoKeysError::Parameter("Invalid line number".to_string()));
87 }
88
89 let line_buffer = self.get_line(line).unwrap();
90
91 let end = line_buffer.iter().position(|&b| b == 0).unwrap_or(20);
93
94 String::from_utf8(line_buffer[..end].to_vec())
95 .map_err(|_| PoKeysError::Protocol("Invalid UTF-8 in LCD text".to_string()))
96 }
97
98 pub fn clear_line(&mut self, line: usize) -> Result<()> {
99 self.set_line_text(line, "")
100 }
101
102 pub fn clear_all(&mut self) {
103 self.line1.fill(0);
104 self.line2.fill(0);
105 self.line3.fill(0);
106 self.line4.fill(0);
107 self.row_refresh_flags = 0x0F; }
109
110 pub fn set_custom_character(&mut self, char_index: usize, pattern: &[u8; 8]) -> Result<()> {
111 if char_index >= 8 {
112 return Err(PoKeysError::Parameter(
113 "Invalid custom character index".to_string(),
114 ));
115 }
116
117 self.custom_characters[char_index] = *pattern;
118 Ok(())
119 }
120
121 pub fn get_custom_character(&self, char_index: usize) -> Result<[u8; 8]> {
122 if char_index >= 8 {
123 return Err(PoKeysError::Parameter(
124 "Invalid custom character index".to_string(),
125 ));
126 }
127
128 Ok(self.custom_characters[char_index])
129 }
130}
131
132impl Default for LcdData {
133 fn default() -> Self {
134 Self::new()
135 }
136}
137
138impl PoKeysDevice {
139 pub fn configure_lcd(&mut self, rows: u8, columns: u8, mode: LcdMode) -> Result<()> {
141 if rows > 4 || columns > 20 {
142 return Err(PoKeysError::Parameter("LCD size not supported".to_string()));
143 }
144
145 self.lcd.configuration = match mode {
146 LcdMode::Direct => 1,
147 LcdMode::Buffered => 2,
148 };
149 self.lcd.rows = rows;
150 self.lcd.columns = columns;
151
152 self.send_request(0x70, self.lcd.configuration, rows, columns, 0)?;
154 Ok(())
155 }
156
157 pub fn enable_lcd(&mut self, enable: bool) -> Result<()> {
159 if enable {
160 if self.lcd.configuration == 0 {
161 self.lcd.configuration = 1; self.lcd.rows = 2;
164 self.lcd.columns = 16;
165 }
166 } else {
167 self.lcd.configuration = 0;
168 }
169
170 self.send_request(
171 0x70,
172 self.lcd.configuration,
173 self.lcd.rows,
174 self.lcd.columns,
175 0,
176 )?;
177 Ok(())
178 }
179
180 pub fn lcd_write_line(&mut self, line: usize, text: &str) -> Result<()> {
182 self.lcd.set_line_text(line, text)?;
183
184 self.send_lcd_line_data(line)?;
186 Ok(())
187 }
188
189 pub fn lcd_read_line(&self, line: usize) -> Result<String> {
191 self.lcd.get_line_text(line)
192 }
193
194 pub fn lcd_clear_line(&mut self, line: usize) -> Result<()> {
196 self.lcd.clear_line(line)?;
197 self.send_lcd_line_data(line)?;
198 Ok(())
199 }
200
201 pub fn lcd_clear_all(&mut self) -> Result<()> {
203 self.lcd.clear_all();
204
205 for line in 1..=self.lcd.rows {
207 self.send_lcd_line_data(line as usize)?;
208 }
209
210 Ok(())
211 }
212
213 pub fn lcd_write_at(&mut self, line: usize, column: usize, text: &str) -> Result<()> {
215 if line < 1 || line > self.lcd.rows as usize {
216 return Err(PoKeysError::Parameter("Invalid line number".to_string()));
217 }
218
219 if column >= self.lcd.columns as usize {
220 return Err(PoKeysError::Parameter("Invalid column number".to_string()));
221 }
222
223 let mut current_text = self.lcd.get_line_text(line).unwrap_or_default();
225
226 while current_text.len() < column {
228 current_text.push(' ');
229 }
230
231 let mut chars: Vec<char> = current_text.chars().collect();
233 let new_chars: Vec<char> = text.chars().collect();
234
235 for (i, &ch) in new_chars.iter().enumerate() {
236 if column + i < self.lcd.columns as usize {
237 if column + i < chars.len() {
238 chars[column + i] = ch;
239 } else {
240 chars.push(ch);
241 }
242 }
243 }
244
245 let new_text: String = chars.into_iter().collect();
246 self.lcd_write_line(line, &new_text)
247 }
248
249 pub fn lcd_set_custom_character(&mut self, char_index: usize, pattern: &[u8; 8]) -> Result<()> {
251 self.lcd.set_custom_character(char_index, pattern)?;
252
253 self.send_request(0x75, char_index as u8, pattern[0], pattern[1], pattern[2])?;
255
256 self.send_request(0x76, char_index as u8, pattern[3], pattern[4], pattern[5])?;
257
258 self.send_request(0x77, char_index as u8, pattern[6], pattern[7], 0)?;
259
260 Ok(())
261 }
262
263 pub fn lcd_update(&mut self) -> Result<()> {
265 for line in 1..=self.lcd.rows {
266 if (self.lcd.row_refresh_flags & (1 << (line - 1))) != 0 {
267 self.send_lcd_line_data(line as usize)?;
268 }
269 }
270
271 self.lcd.row_refresh_flags = 0;
272 Ok(())
273 }
274
275 fn send_lcd_line_data(&mut self, line: usize) -> Result<()> {
277 if !(1..=4).contains(&line) {
278 return Err(PoKeysError::Parameter("Invalid line number".to_string()));
279 }
280
281 let line_data = *self.lcd.get_line(line).unwrap();
283
284 self.send_request(0x71, line as u8, line_data[0], line_data[1], line_data[2])?;
286
287 self.send_request(0x72, line as u8, line_data[3], line_data[4], line_data[5])?;
288
289 self.send_request(0x73, line as u8, line_data[6], line_data[7], line_data[8])?;
290
291 self.send_request(0x74, line as u8, line_data[9], line_data[10], line_data[11])?;
292
293 if self.lcd.columns > 12 {
295 }
298
299 Ok(())
300 }
301}
302
303pub fn lcd_display_message(device: &mut PoKeysDevice, message: &str) -> Result<()> {
307 device.lcd_clear_all()?;
308
309 let lines: Vec<&str> = message.lines().collect();
311
312 for (i, line) in lines.iter().enumerate().take(device.lcd.rows as usize) {
313 device.lcd_write_line(i + 1, line)?;
314 }
315
316 Ok(())
317}
318
319pub fn lcd_display_two_lines(device: &mut PoKeysDevice, line1: &str, line2: &str) -> Result<()> {
321 device.lcd_clear_all()?;
322 device.lcd_write_line(1, line1)?;
323 device.lcd_write_line(2, line2)?;
324 Ok(())
325}
326
327pub fn lcd_progress_bar(
329 device: &mut PoKeysDevice,
330 line: usize,
331 progress: f32,
332 width: usize,
333) -> Result<()> {
334 if !(0.0..=1.0).contains(&progress) {
335 return Err(PoKeysError::Parameter(
336 "Progress must be between 0.0 and 1.0".to_string(),
337 ));
338 }
339
340 let filled_chars = (progress * width as f32) as usize;
341 let mut bar = String::new();
342
343 bar.push('[');
344 for i in 0..width {
345 if i < filled_chars {
346 bar.push('█');
347 } else {
348 bar.push(' ');
349 }
350 }
351 bar.push(']');
352
353 device.lcd_write_line(line, &bar)
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_lcd_data_creation() {
362 let lcd = LcdData::new();
363 assert!(!lcd.is_enabled());
364 assert_eq!(lcd.rows, 0);
365 assert_eq!(lcd.columns, 0);
366 }
367
368 #[test]
369 fn test_lcd_line_operations() {
370 let mut lcd = LcdData::new();
371
372 assert!(lcd.set_line_text(1, "Hello").is_ok());
373 assert_eq!(lcd.get_line_text(1).unwrap(), "Hello");
374
375 assert!(lcd.clear_line(1).is_ok());
376 assert_eq!(lcd.get_line_text(1).unwrap(), "");
377
378 assert!(lcd.set_line_text(0, "Test").is_err());
380 assert!(lcd.set_line_text(5, "Test").is_err());
381 }
382
383 #[test]
384 fn test_lcd_text_length_limit() {
385 let mut lcd = LcdData::new();
386
387 let text_20 = "12345678901234567890";
389 assert!(lcd.set_line_text(1, text_20).is_ok());
390
391 let text_21 = "123456789012345678901";
393 assert!(lcd.set_line_text(1, text_21).is_err());
394 }
395
396 #[test]
397 fn test_custom_characters() {
398 let mut lcd = LcdData::new();
399 let pattern = [0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F];
400
401 assert!(lcd.set_custom_character(0, &pattern).is_ok());
402 assert_eq!(lcd.get_custom_character(0).unwrap(), pattern);
403
404 assert!(lcd.set_custom_character(8, &pattern).is_err());
406 }
407
408 #[test]
409 fn test_progress_bar_generation() {
410 let width = 10;
412 let progress = 0.5;
413 let filled_chars = (progress * width as f32) as usize;
414
415 assert_eq!(filled_chars, 5);
416
417 let mut bar = String::new();
418 bar.push('[');
419 for i in 0..width {
420 if i < filled_chars {
421 bar.push('█');
422 } else {
423 bar.push(' ');
424 }
425 }
426 bar.push(']');
427
428 assert_eq!(bar, "[█████ ]");
429 }
430}