1use crate::device::PoKeysDevice;
4use crate::error::{PoKeysError, Result};
5use log::info;
6use serde::{Deserialize, Serialize};
7
8mod private;
9
10use private::INVERT_PIN_BIT;
11
12#[allow(deprecated)]
17mod pin_function {
18 use serde::{Deserialize, Serialize};
19
20 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32 pub enum PinFunction {
33 PinRestricted = 0,
34 Reserved = 1,
35 DigitalInput = 2,
36 DigitalOutput = 4,
37 AnalogInput = 8,
38 AnalogOutput = 16,
39 TriggeredInput = 32,
40 DigitalCounter = 64,
41 #[deprecated(
42 since = "0.23.0",
43 note = "Use PoKeysDevice::set_pin_function_with_invert to apply the invert flag; the variant is a protocol bit, not a standalone pin function."
44 )]
45 InvertPin = 128,
46 }
47}
48
49pub use pin_function::PinFunction;
50
51pub(crate) fn decode_pin_function_from_cache(byte: u8) -> PinFunction {
57 match byte & 0x7F {
58 0 => PinFunction::PinRestricted,
59 1 => PinFunction::Reserved,
60 2 => PinFunction::DigitalInput,
61 4 => PinFunction::DigitalOutput,
62 8 => PinFunction::AnalogInput,
63 16 => PinFunction::AnalogOutput,
64 32 => PinFunction::TriggeredInput,
65 64 => PinFunction::DigitalCounter,
66 _ => PinFunction::PinRestricted,
67 }
68}
69
70impl PinFunction {
71 pub fn from_u8(value: u8) -> Result<Self> {
74 #[allow(deprecated)]
75 match value {
76 0 => Ok(PinFunction::PinRestricted),
77 1 => Ok(PinFunction::Reserved),
78 2 => Ok(PinFunction::DigitalInput),
79 4 => Ok(PinFunction::DigitalOutput),
80 8 => Ok(PinFunction::AnalogInput),
81 16 => Ok(PinFunction::AnalogOutput),
82 32 => Ok(PinFunction::TriggeredInput),
83 64 => Ok(PinFunction::DigitalCounter),
84 128 => Ok(PinFunction::InvertPin),
85 v if (v & PinFunction::DigitalOutput as u8) != 0 => Ok(PinFunction::DigitalOutput),
87 v if (v & PinFunction::DigitalInput as u8) != 0 => Ok(PinFunction::DigitalInput),
88 v if (v & PinFunction::AnalogInput as u8) != 0 => Ok(PinFunction::AnalogInput),
89 v if (v & PinFunction::AnalogOutput as u8) != 0 => Ok(PinFunction::AnalogOutput),
90 v if (v & PinFunction::DigitalCounter as u8) != 0 => Ok(PinFunction::DigitalCounter),
91 v if (v & PinFunction::TriggeredInput as u8) != 0 => Ok(PinFunction::TriggeredInput),
92 _ => Ok(PinFunction::PinRestricted), }
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub enum PinCapability {
100 DigitalInput = 1,
101 DigitalOutput,
102 AnalogInput,
103 MfAnalogInput,
104 AnalogOutput,
105 KeyboardMapping,
106 TriggeredInput,
107 DigitalCounter,
108 PwmOutput,
109 FastEncoder1A,
110 FastEncoder1B,
111 FastEncoder1I,
112 FastEncoder2A,
113 FastEncoder2B,
114 FastEncoder2I,
115 FastEncoder3A,
116 FastEncoder3B,
117 FastEncoder3I,
118 UltraFastEncoderA,
119 UltraFastEncoderB,
120 UltraFastEncoderI,
121 LcdE,
122 LcdRw,
123 LcdRs,
124 LcdD4,
125 LcdD5,
126 LcdD6,
127 LcdD7,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct PinData {
133 pub digital_counter_value: u32,
134 pub analog_value: u32,
135 pub pin_function: u8,
136 pub counter_options: u8,
137 pub digital_value_get: u8,
138 pub digital_value_set: u8,
139 pub digital_counter_available: u8,
140 pub mapping_type: u8,
141 pub key_code_macro_id: u8,
142 pub key_modifier: u8,
143 pub down_key_code_macro_id: u8,
144 pub down_key_modifier: u8,
145 pub up_key_code_macro_id: u8,
146 pub up_key_modifier: u8,
147 pub prevent_update: u8,
148}
149
150impl PinData {
151 pub fn new() -> Self {
152 Self {
153 digital_counter_value: 0,
154 analog_value: 0,
155 pin_function: PinFunction::PinRestricted as u8,
156 counter_options: 0,
157 digital_value_get: 0,
158 digital_value_set: 0,
159 digital_counter_available: 0,
160 mapping_type: 0,
161 key_code_macro_id: 0,
162 key_modifier: 0,
163 down_key_code_macro_id: 0,
164 down_key_modifier: 0,
165 up_key_code_macro_id: 0,
166 up_key_modifier: 0,
167 prevent_update: 0,
168 }
169 }
170
171 pub fn is_digital_input(&self) -> bool {
172 (self.pin_function & PinFunction::DigitalInput as u8) != 0
173 }
174
175 pub fn is_digital_output(&self) -> bool {
176 (self.pin_function & PinFunction::DigitalOutput as u8) != 0
177 }
178
179 pub fn is_analog_input(&self) -> bool {
180 (self.pin_function & PinFunction::AnalogInput as u8) != 0
181 }
182
183 pub fn is_analog_output(&self) -> bool {
184 (self.pin_function & PinFunction::AnalogOutput as u8) != 0
185 }
186
187 pub fn is_digital_counter(&self) -> bool {
188 (self.pin_function & PinFunction::DigitalCounter as u8) != 0
189 }
190
191 pub fn is_inverted(&self) -> bool {
194 (self.pin_function & INVERT_PIN_BIT) != 0
195 }
196
197 pub fn base_function(&self) -> PinFunction {
200 decode_pin_function_from_cache(self.pin_function)
201 }
202}
203
204impl Default for PinData {
205 fn default() -> Self {
206 Self::new()
207 }
208}
209
210impl PoKeysDevice {
211 pub fn set_pin_function(
217 &mut self,
218 pin: u32,
219 pin_function: PinFunction,
220 ) -> Result<(u32, PinFunction)> {
221 self.set_pin_function_with_invert(pin, pin_function, false)
222 }
223
224 pub fn set_pin_function_with_invert(
245 &mut self,
246 pin: u32,
247 pin_function: PinFunction,
248 inverted: bool,
249 ) -> Result<(u32, PinFunction)> {
250 self.write_pin_function(pin, pin_function, inverted)
251 }
252
253 pub fn get_pin_function(&self, pin: u32) -> Result<PinFunction> {
257 let pin_index = self.check_pin_range(pin)?;
258 Ok(decode_pin_function_from_cache(
259 self.pins[pin_index].pin_function,
260 ))
261 }
262
263 pub fn get_pin_invert(&self, pin: u32) -> Result<bool> {
270 let pin_index = self.check_pin_range(pin)?;
271 Ok((self.pins[pin_index].pin_function & INVERT_PIN_BIT) != 0)
272 }
273
274 pub fn get_digital_input(&mut self, pin: u32) -> Result<bool> {
276 if pin == 0 || pin as usize > self.pins.len() {
277 return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
278 }
279
280 let res = self.read_digital_input(pin)?;
282
283 let pin_index = (pin - 1) as usize;
284 self.pins[pin_index].digital_value_get = res;
285 Ok(res != 0)
286 }
287
288 pub fn set_digital_output(&mut self, pin: u32, value: bool) -> Result<bool> {
290 if pin == 0 || pin as usize > self.pins.len() {
291 return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
292 }
293
294 match self.write_digital_output(pin, !value) {
296 Ok(_) => {
297 let pin_index = (pin - 1) as usize;
298 self.pins[pin_index].digital_value_set = if value { 1 } else { 0 };
299 info!(
300 "Pin {} set to {:?}",
301 pin,
302 if value { "High" } else { "Low" }
303 );
304 Ok(true)
305 }
306 Err(e) => Err(e),
307 }
308 }
309
310 pub fn get_analog_input(&mut self, pin: u32) -> Result<u32> {
312 if pin == 0 || pin as usize > self.pins.len() {
313 return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
314 }
315
316 self.read_analog_inputs()?;
318
319 let pin_index = (pin - 1) as usize;
320 Ok(self.pins[pin_index].analog_value)
321 }
322
323 pub fn set_analog_output(&mut self, pin: u32, value: u32) -> Result<()> {
325 if pin == 0 || pin as usize > self.pins.len() {
326 return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
327 }
328
329 let pin_index = (pin - 1) as usize;
330 self.pins[pin_index].analog_value = value;
331
332 self.write_analog_outputs()?;
334 Ok(())
335 }
336
337 pub fn get_digital_counter(&mut self, pin: u32) -> Result<u32> {
339 if pin == 0 || pin as usize > self.pins.len() {
340 return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
341 }
342
343 let pin_index = (pin - 1) as usize;
344 if self.pins[pin_index].digital_counter_available == 0 {
345 return Err(PoKeysError::NotSupported);
346 }
347
348 self.read_digital_counters()?;
350
351 Ok(self.pins[pin_index].digital_counter_value)
352 }
353
354 pub fn reset_all_digital_counters(&mut self) -> Result<()> {
359 self.send_request(0x1D, 0, 0, 0, 0)?;
360 Ok(())
361 }
362
363 pub fn get_digital_inputs(&mut self) -> Result<()> {
370 let pin_count = self.pins.len();
371
372 let resp1 = self.send_request(0x31, 0, 0, 0, 0)?;
373 apply_block_inputs_response(&resp1, &mut self.pins, 0..pin_count.min(32), 0);
374
375 if pin_count > 32 {
376 let resp2 = self.send_request(0x32, 0, 0, 0, 0)?;
377 apply_block_inputs_response(&resp2, &mut self.pins, 32..pin_count.min(55), 32);
378 }
379
380 Ok(())
381 }
382
383 pub fn write_digital_outputs(&mut self) -> Result<()> {
393 let mut output_data = [0u8; 8];
394
395 for i in 0..self.pins.len().min(55) {
396 if self.pins[i].is_digital_output() && self.pins[i].digital_value_set != 0 {
397 let byte_index = i / 8;
398 let bit_index = i % 8;
399 output_data[byte_index] |= 1 << bit_index;
400 }
401 }
402
403 self.send_request(
404 0x42,
405 output_data[0],
406 output_data[1],
407 output_data[2],
408 output_data[3],
409 )?;
410
411 if self.pins.len() > 32 {
412 self.send_request(0x43, output_data[4], output_data[5], output_data[6], 0)?;
413 }
414
415 Ok(())
416 }
417
418 pub fn read_analog_inputs(&mut self) -> Result<()> {
420 let response = self.send_request(0x20, 0, 0, 0, 0)?;
421
422 let mut data_index = 8;
424 for i in 0..self.pins.len() {
425 if self.pins[i].is_analog_input() && data_index + 3 < response.len() {
426 self.pins[i].analog_value = u32::from_le_bytes([
427 response[data_index],
428 response[data_index + 1],
429 response[data_index + 2],
430 response[data_index + 3],
431 ]);
432 data_index += 4;
433 }
434 }
435
436 Ok(())
437 }
438
439 pub fn write_analog_outputs(&mut self) -> Result<()> {
446 let targets: Vec<(u8, u32)> = self
447 .pins
448 .iter()
449 .enumerate()
450 .filter_map(|(i, p)| {
451 if p.is_analog_output() {
452 Some((i as u8, p.analog_value))
453 } else {
454 None
455 }
456 })
457 .collect();
458
459 for (pin_code, value) in targets {
460 let (msb, lsb) = encode_analog_output_10bit(value);
461 let response = self.send_request(0x41, pin_code, msb, lsb, 0)?;
462 if response[2] != 0 {
464 return Err(PoKeysError::Protocol(format!(
465 "Analog output write failed for pin code {}: error code {}",
466 pin_code, response[2]
467 )));
468 }
469 }
470
471 Ok(())
472 }
473
474 pub fn read_digital_counters(&mut self) -> Result<()> {
480 let mut pin_ids = [0u8; 13];
482 let mut selected: Vec<usize> = Vec::with_capacity(13);
483 for (i, pin) in self.pins.iter().enumerate() {
484 if selected.len() == 13 {
485 break;
486 }
487 if pin.digital_counter_available != 0 {
488 pin_ids[selected.len()] = i as u8;
489 selected.push(i);
490 }
491 }
492
493 let response = self.send_request_with_data(0xD8, 0, 0, 0, 0, &pin_ids)?;
494
495 for (slot, pin_index) in selected.iter().enumerate() {
497 let start = 8 + slot * 4;
498 if start + 4 <= response.len() {
499 self.pins[*pin_index].digital_counter_value = u32::from_le_bytes([
500 response[start],
501 response[start + 1],
502 response[start + 2],
503 response[start + 3],
504 ]);
505 }
506 }
507
508 Ok(())
509 }
510
511 pub fn read_all_pin_functions(&mut self) -> Result<[PinFunction; 55]> {
515 use crate::io::private::Command;
516
517 let response = self.send_request(
520 Command::InputOutputExtended as u8,
521 0, 0, 0, 0, )?;
526
527 let raw = parse_bulk_pin_settings_response(&response)?;
528
529 let mut functions = [PinFunction::PinRestricted; 55];
531 for i in 0..55 {
532 functions[i] = PinFunction::from_u8(raw[i])?;
533 }
534
535 for i in 0..55 {
539 if i < self.pins.len() {
540 self.pins[i].pin_function = raw[i];
541 }
542 }
543
544 Ok(functions)
545 }
546
547 pub fn read_all_pin_settings_raw(&mut self) -> Result<[u8; 55]> {
554 use crate::io::private::Command;
555
556 let response = self.send_request(Command::InputOutputExtended as u8, 0, 0, 0, 0)?;
557 let raw = parse_bulk_pin_settings_response(&response)?;
558
559 for i in 0..55 {
560 if i < self.pins.len() {
561 self.pins[i].pin_function = raw[i];
562 }
563 }
564
565 Ok(raw)
566 }
567
568 pub fn set_all_pin_settings_raw(&mut self, raw: &[u8; 55]) -> Result<()> {
575 use crate::io::private::Command;
576
577 let response = self.send_request_with_data(
578 Command::InputOutputExtended as u8,
579 1, 0, 0,
582 0,
583 raw,
584 )?;
585
586 if response.len() < 64 {
587 return Err(PoKeysError::Protocol(
588 "Response too short for bulk pin write".to_string(),
589 ));
590 }
591
592 if response[1] != Command::InputOutputExtended as u8 {
593 return Err(PoKeysError::Protocol(
594 "Invalid response command".to_string(),
595 ));
596 }
597
598 for i in 0..55 {
599 if i < self.pins.len() {
600 self.pins[i].pin_function = raw[i];
601 }
602 }
603
604 Ok(())
605 }
606
607 pub fn set_all_pin_functions(&mut self, functions: &[PinFunction; 55]) -> Result<()> {
612 use crate::io::private::Command;
613
614 let payload: [u8; 55] = std::array::from_fn(|i| functions[i] as u8);
617
618 let response = self.send_request_with_data(
619 Command::InputOutputExtended as u8,
620 1, 0, 0, 0, &payload,
625 )?;
626
627 if response.len() < 64 {
628 return Err(PoKeysError::Protocol(
629 "Response too short for bulk pin write".to_string(),
630 ));
631 }
632
633 if response[1] != Command::InputOutputExtended as u8 {
634 return Err(PoKeysError::Protocol(
635 "Invalid response command".to_string(),
636 ));
637 }
638
639 for (i, &function) in functions.iter().enumerate() {
641 if i < self.pins.len() {
642 self.pins[i].pin_function = function as u8;
643 }
644 }
645
646 Ok(())
647 }
648
649 pub fn get_device_status(&mut self) -> Result<()> {
659 let response = self.send_request(0xCC, 0, 0, 0, 0)?;
660 apply_device_status_response(&response, &mut self.pins, &mut self.encoders);
661 Ok(())
662 }
663}
664
665fn encode_analog_output_10bit(value: u32) -> (u8, u8) {
670 let v = value & 0x3FF;
671 let msb = ((v >> 2) & 0xFF) as u8;
672 let lsb = ((v & 0x03) << 6) as u8;
673 (msb, lsb)
674}
675
676pub(crate) fn parse_bulk_pin_settings_response(response: &[u8]) -> Result<[u8; 55]> {
687 use crate::io::private::Command;
688
689 if response.len() < 64 {
690 return Err(PoKeysError::Protocol(
691 "Response too short for bulk pin read".to_string(),
692 ));
693 }
694
695 if response[1] != Command::InputOutputExtended as u8 {
696 return Err(PoKeysError::Protocol(
697 "Invalid response command".to_string(),
698 ));
699 }
700
701 let mut raw = [0u8; 55];
702 raw.copy_from_slice(&response[8..8 + 55]);
703
704 if raw.iter().all(|&b| b == 0) {
705 log::warn!(
706 "Bulk pin-settings read returned all-zero bytes — every pin reports as \
707 PinRestricted. This often indicates the device's configuration is locked \
708 (the firmware echoes 0xC0 but refuses to populate the response). If you \
709 expect non-restricted pins, check is_configuration_locked() and unlock \
710 via clear_configuration() if appropriate."
711 );
712 }
713
714 Ok(raw)
715}
716
717pub(crate) fn apply_block_inputs_response(
737 response: &[u8],
738 pins: &mut [PinData],
739 pin_range: std::ops::Range<usize>,
740 bit_offset: usize,
741) {
742 for i in pin_range {
743 let rel = i - bit_offset;
744 let byte_index = 2 + (rel / 8);
745 let bit_index = rel % 8;
746 if byte_index < response.len() {
747 pins[i].digital_value_get = if (response[byte_index] & (1 << bit_index)) != 0 {
748 1
749 } else {
750 0
751 };
752 }
753 }
754}
755
756fn apply_device_status_response(
760 response: &[u8],
761 pins: &mut [PinData],
762 encoders: &mut [crate::encoders::EncoderData],
763) {
764 for i in 0..pins.len().min(55) {
766 let byte_index = 8 + (i / 8);
767 let bit_index = i % 8;
768 if byte_index < response.len() {
769 pins[i].digital_value_get = if (response[byte_index] & (1 << bit_index)) != 0 {
770 1
771 } else {
772 0
773 };
774 }
775 }
776
777 let mut data_index = 15;
779 let mut channels_consumed = 0;
780 for pin in pins.iter_mut() {
781 if channels_consumed >= 5 {
782 break;
783 }
784 if pin.is_analog_input() && data_index + 1 < response.len() {
785 let msb = response[data_index] as u32;
786 let lsb = response[data_index + 1] as u32;
787 pin.analog_value = (msb << 8) | lsb;
788 data_index += 2;
789 channels_consumed += 1;
790 }
791 }
792
793 for i in 0..encoders.len().min(25) {
795 let idx = 25 + i;
796 if idx < response.len() {
797 encoders[i].encoder_value = response[idx] as i8 as i32;
798 }
799 }
800}
801
802#[cfg(test)]
803mod tests {
804 use super::*;
805
806 #[test]
807 fn test_pin_data_creation() {
808 let pin_data = PinData::new();
809 assert_eq!(pin_data.pin_function, PinFunction::PinRestricted as u8);
810 assert_eq!(pin_data.digital_value_get, 0);
811 assert_eq!(pin_data.digital_value_set, 0);
812 }
813
814 #[test]
815 fn test_pin_function_checks() {
816 let mut pin_data = PinData::new();
817
818 pin_data.pin_function = PinFunction::DigitalInput as u8;
819 assert!(pin_data.is_digital_input());
820 assert!(!pin_data.is_digital_output());
821
822 pin_data.pin_function = PinFunction::DigitalOutput as u8;
823 assert!(!pin_data.is_digital_input());
824 assert!(pin_data.is_digital_output());
825
826 pin_data.pin_function =
827 (PinFunction::DigitalInput as u8) | (PinFunction::DigitalOutput as u8);
828 assert!(pin_data.is_digital_input());
829 assert!(pin_data.is_digital_output());
830 }
831
832 #[test]
833 fn test_encode_analog_output_10bit() {
834 assert_eq!(encode_analog_output_10bit(0), (0x00, 0x00));
835 assert_eq!(encode_analog_output_10bit(1023), (0xFF, 0xC0));
836 assert_eq!(encode_analog_output_10bit(512), (0x80, 0x00));
837 assert_eq!(encode_analog_output_10bit(3), (0x00, 0xC0));
839 assert_eq!(encode_analog_output_10bit(1), (0x00, 0x40));
840 assert_eq!(encode_analog_output_10bit(0xFFFF), (0xFF, 0xC0));
842 }
843
844 #[test]
845 fn test_apply_device_status_response() {
846 use crate::encoders::EncoderData;
847
848 let mut response = [0u8; 64];
849
850 response[8] = 0b0000_0001; response[9] = 0b0000_0001; response[14] = 0b0100_0000; response[15] = 0x12;
857 response[16] = 0x34;
858 response[17] = 0xAB;
860 response[18] = 0xCD;
861
862 response[25] = 0xFF;
864 response[26] = 0x05;
865
866 let mut pins = vec![PinData::new(); 55];
867 pins[0].pin_function = PinFunction::DigitalInput as u8; pins[8].pin_function = PinFunction::DigitalInput as u8; pins[40].pin_function = PinFunction::AnalogInput as u8; pins[41].pin_function = PinFunction::AnalogInput as u8; let mut encoders = vec![EncoderData::new(); 25];
873
874 apply_device_status_response(&response, &mut pins, &mut encoders);
875
876 assert_eq!(pins[0].digital_value_get, 1);
877 assert_eq!(pins[1].digital_value_get, 0);
878 assert_eq!(pins[8].digital_value_get, 1);
879 assert_eq!(pins[54].digital_value_get, 1);
880
881 assert_eq!(pins[40].analog_value, 0x1234);
882 assert_eq!(pins[41].analog_value, 0xABCD);
883
884 assert_eq!(encoders[0].encoder_value, -1);
885 assert_eq!(encoders[1].encoder_value, 5);
886 assert_eq!(encoders[2].encoder_value, 0);
887 }
888
889 #[test]
902 fn test_apply_block_inputs_pin_4() {
903 let mut response = [0u8; 64];
907 response[2] = 0b0000_1000;
908
909 let mut pins = vec![PinData::new(); 55];
910 apply_block_inputs_response(&response, &mut pins, 0..32, 0);
911
912 assert_eq!(pins[2].digital_value_get, 0, "pin 3 should stay low");
913 assert_eq!(pins[3].digital_value_get, 1, "pin 4 should read high");
914 assert_eq!(pins[4].digital_value_get, 0, "pin 5 should stay low");
915 }
916
917 #[test]
918 fn test_apply_block_inputs_pin_28() {
919 let mut response = [0u8; 64];
921 response[5] = 0b0000_1000;
922
923 let mut pins = vec![PinData::new(); 55];
924 apply_block_inputs_response(&response, &mut pins, 0..32, 0);
925
926 assert_eq!(pins[26].digital_value_get, 0, "pin 27 should stay low");
927 assert_eq!(pins[27].digital_value_get, 1, "pin 28 should read high");
928 assert_eq!(pins[28].digital_value_get, 0, "pin 29 should stay low");
929 }
930
931 #[test]
932 fn test_apply_block_inputs_pin_33() {
933 let mut response = [0u8; 64];
935 response[2] = 0b0000_0001;
936
937 let mut pins = vec![PinData::new(); 55];
938 apply_block_inputs_response(&response, &mut pins, 32..55, 32);
939
940 assert_eq!(pins[31].digital_value_get, 0, "pin 32 untouched by 0x32");
941 assert_eq!(pins[32].digital_value_get, 1, "pin 33 should read high");
942 assert_eq!(pins[33].digital_value_get, 0, "pin 34 should stay low");
943 }
944
945 #[test]
946 fn test_apply_block_inputs_pin_50() {
947 let mut response = [0u8; 64];
950 response[4] = 0b0000_0010;
951
952 let mut pins = vec![PinData::new(); 55];
953 apply_block_inputs_response(&response, &mut pins, 32..55, 32);
954
955 assert_eq!(pins[48].digital_value_get, 0);
956 assert_eq!(pins[49].digital_value_get, 1, "pin 50 should read high");
957 assert_eq!(pins[50].digital_value_get, 0);
958 }
959
960 #[test]
961 fn test_apply_block_inputs_pin_55() {
962 let mut response = [0u8; 64];
965 response[4] = 0b0100_0000;
966
967 let mut pins = vec![PinData::new(); 55];
968 apply_block_inputs_response(&response, &mut pins, 32..55, 32);
969
970 assert_eq!(pins[53].digital_value_get, 0);
971 assert_eq!(pins[54].digital_value_get, 1, "pin 55 should read high");
972 }
973
974 #[test]
975 fn test_apply_block_inputs_ignores_trailer_bytes() {
976 let mut response = [0u8; 64];
981 for b in &mut response[6..] {
982 *b = 0xFF;
983 }
984
985 let mut pins = vec![PinData::new(); 55];
986 apply_block_inputs_response(&response, &mut pins, 0..32, 0);
987 apply_block_inputs_response(&response, &mut pins, 32..55, 32);
988
989 for (i, p) in pins.iter().enumerate() {
990 assert_eq!(
991 p.digital_value_get,
992 0,
993 "pin {} must not read trailer zone",
994 i + 1
995 );
996 }
997 }
998
999 #[test]
1000 fn test_decode_read_digital_input_response_uses_byte_3() {
1001 use crate::io::private::decode_read_digital_input_response;
1002
1003 let res = [0u8; 64];
1008 assert_eq!(decode_read_digital_input_response(&res).unwrap(), 0);
1009
1010 let mut res = [0u8; 64];
1012 res[3] = 1;
1013 assert_eq!(decode_read_digital_input_response(&res).unwrap(), 1);
1014
1015 let mut res = [0u8; 64];
1017 res[2] = 1;
1018 res[3] = 1;
1019 assert!(decode_read_digital_input_response(&res).is_err());
1020 }
1021
1022 #[test]
1023 fn test_compose_pin_function_byte() {
1024 use crate::io::private::compose_pin_function_byte;
1025
1026 assert_eq!(
1028 compose_pin_function_byte(PinFunction::DigitalInput, false),
1029 0x02
1030 );
1031 assert_eq!(
1032 compose_pin_function_byte(PinFunction::DigitalOutput, false),
1033 0x04
1034 );
1035 assert_eq!(
1036 compose_pin_function_byte(PinFunction::TriggeredInput, false),
1037 0x20
1038 );
1039
1040 assert_eq!(
1042 compose_pin_function_byte(PinFunction::DigitalInput, true),
1043 0x82
1044 );
1045 assert_eq!(
1046 compose_pin_function_byte(PinFunction::DigitalOutput, true),
1047 0x84
1048 );
1049 assert_eq!(
1050 compose_pin_function_byte(PinFunction::TriggeredInput, true),
1051 0xA0
1052 );
1053 }
1054
1055 #[test]
1056 fn test_decode_pin_function_from_cache() {
1057 assert_eq!(
1061 decode_pin_function_from_cache(0x82),
1062 PinFunction::DigitalInput
1063 );
1064 assert_eq!(
1065 decode_pin_function_from_cache(0x84),
1066 PinFunction::DigitalOutput
1067 );
1068 assert_eq!(
1069 decode_pin_function_from_cache(0xA0),
1070 PinFunction::TriggeredInput
1071 );
1072
1073 assert_eq!(
1075 decode_pin_function_from_cache(0x02),
1076 PinFunction::DigitalInput
1077 );
1078 assert_eq!(
1079 decode_pin_function_from_cache(0x00),
1080 PinFunction::PinRestricted
1081 );
1082
1083 assert_eq!(
1086 decode_pin_function_from_cache(0x80),
1087 PinFunction::PinRestricted
1088 );
1089 }
1090
1091 #[test]
1092 fn test_pin_data_invert_accessors() {
1093 let mut pin_data = PinData::new();
1094
1095 pin_data.pin_function = 0x82; assert!(pin_data.is_inverted());
1097 assert_eq!(pin_data.base_function(), PinFunction::DigitalInput);
1098 assert!(pin_data.is_digital_input());
1100 assert!(!pin_data.is_digital_output());
1101
1102 pin_data.pin_function = 0x02; assert!(!pin_data.is_inverted());
1104 assert_eq!(pin_data.base_function(), PinFunction::DigitalInput);
1105 }
1106
1107 #[test]
1108 fn test_parse_bulk_pin_settings_response_preserves_invert_bit() {
1109 use crate::io::private::Command;
1110
1111 let mut response = [0u8; 64];
1112 response[1] = Command::InputOutputExtended as u8;
1113 response[8] = 0x82; response[9] = 0x04; response[10] = 0xA0; let raw = parse_bulk_pin_settings_response(&response).unwrap();
1118 assert_eq!(raw[0], 0x82);
1119 assert_eq!(raw[1], 0x04);
1120 assert_eq!(raw[2], 0xA0);
1121
1122 assert_eq!(
1125 PinFunction::from_u8(raw[0]).unwrap(),
1126 PinFunction::DigitalInput
1127 );
1128 assert_eq!(
1129 PinFunction::from_u8(raw[2]).unwrap(),
1130 PinFunction::TriggeredInput
1131 );
1132 }
1133
1134 #[test]
1135 fn test_parse_bulk_pin_settings_response_rejects_short() {
1136 let short = [0u8; 32];
1137 assert!(parse_bulk_pin_settings_response(&short).is_err());
1138 }
1139
1140 #[test]
1141 fn test_parse_bulk_pin_settings_response_rejects_wrong_command() {
1142 let mut response = [0u8; 64];
1143 response[1] = 0x12; assert!(parse_bulk_pin_settings_response(&response).is_err());
1145 }
1146
1147 #[test]
1148 fn test_parse_bulk_pin_settings_response_accepts_all_zero() {
1149 use crate::io::private::Command;
1153 let mut response = [0u8; 64];
1154 response[1] = Command::InputOutputExtended as u8;
1155 let raw = parse_bulk_pin_settings_response(&response)
1156 .expect("all-zero pin settings should still parse");
1157 assert!(raw.iter().all(|&b| b == 0));
1158 }
1159}