1use crate::communication::{CommunicationManager, NetworkInterface, UsbHidInterface};
4use crate::encoders::EncoderData;
5use crate::error::{PoKeysError, Result};
6use crate::io::{PinData, PinFunction};
7use crate::keyboard_matrix::{MatrixKeyboard, MatrixKeyboardConfig};
8use crate::lcd::LcdData;
9use crate::matrix::MatrixLed;
10use crate::pulse_engine::PulseEngineV2;
11use crate::pwm::PwmData;
12use crate::sensors::EasySensor;
13use crate::types::*;
14use std::sync::{LazyLock, Mutex};
15use std::time::Duration;
16
17const MATRIX_KEYBOARD_ID: u8 = 0;
25
26const MATRIX_KB_OPT_WRITE_CONFIG: u8 = 1;
36
37const MATRIX_KB_OPT_READ_CONFIG: u8 = 10;
45
46fn encode_matrix_kb_mapping_chunk(
54 codes: &[u8],
55 modifiers: &[u8],
56 triggered: Option<&[u8]>,
57 chunk_base: usize,
58) -> [u8; 34] {
59 let mut data = [0u8; 34];
60 for i in 0..16 {
61 let idx = chunk_base + i;
62 if idx < codes.len() {
63 data[i] = codes[idx];
64 }
65 if idx < modifiers.len() {
66 data[16 + i] = modifiers[idx];
67 }
68 }
69 if let Some(triggered) = triggered {
70 let mut bitmap: u16 = 0;
71 for i in 0..16 {
72 let idx = chunk_base + i;
73 if idx < triggered.len() && triggered[idx] != 0 {
74 bitmap |= 1 << i;
75 }
76 }
77 data[32] = (bitmap & 0xFF) as u8;
78 data[33] = (bitmap >> 8) as u8;
79 }
80 data
81}
82
83fn decode_matrix_kb_mapping_chunk(
86 response: &[u8],
87 codes: &mut [u8],
88 modifiers: &mut [u8],
89 triggered: Option<&mut [u8]>,
90 chunk_base: usize,
91) {
92 for i in 0..16 {
94 let idx = chunk_base + i;
95 if idx < codes.len() {
96 codes[idx] = response[8 + i];
97 }
98 if idx < modifiers.len() {
99 modifiers[idx] = response[24 + i];
100 }
101 }
102 if let Some(triggered) = triggered {
103 let bitmap = u16::from_le_bytes([response[40], response[41]]);
104 for i in 0..16 {
105 let idx = chunk_base + i;
106 if idx < triggered.len() {
107 triggered[idx] = if (bitmap & (1 << i)) != 0 { 1 } else { 0 };
108 }
109 }
110 }
111}
112
113fn encode_matrix_kb_config_payload(
131 enabled: bool,
132 width: u8,
133 height: u8,
134 column_pins: &[u8],
135 row_pins: &[u8],
136) -> [u8; 55] {
137 let mut data = [0u8; 55];
138 data[0] = if enabled { 1 } else { 0 };
139 data[1] = ((width - 1) << 4) | (height - 1);
140
141 for (i, &pin) in row_pins.iter().enumerate().take(8) {
142 data[2 + i] = pin.saturating_sub(1);
143 }
144 for (i, &pin) in column_pins.iter().enumerate().take(8) {
145 data[10 + i] = pin.saturating_sub(1);
146 }
147 for (offset, &pin) in row_pins.iter().enumerate().skip(8).take(8) {
151 data[34 + (offset - 8)] = pin.saturating_sub(1);
152 }
153
154 data
155}
156
157fn validate_matrix_kb_mapping_response(response: &[u8], option: u8) -> Result<()> {
158 if response[1] != 0xCA {
159 return Err(PoKeysError::Protocol(format!(
160 "Invalid response command for key mapping read (option {option}): 0x{:02X}",
161 response[1]
162 )));
163 }
164 if response.len() < 42 {
165 return Err(PoKeysError::Protocol(format!(
166 "Key mapping response too short for option {option}: {} bytes (need 42)",
167 response.len()
168 )));
169 }
170 Ok(())
171}
172
173fn parse_matrix_kb_config_readback(response: &[u8]) -> Result<MatrixKeyboardConfig> {
186 if response.len() < 2 || response[1] != 0xCA {
187 return Err(PoKeysError::Protocol(format!(
188 "matrix keyboard config readback: echo mismatch: 0x{:02X}",
189 response.get(1).copied().unwrap_or(0)
190 )));
191 }
192 if response.len() < 52 {
193 return Err(PoKeysError::Protocol(format!(
194 "matrix keyboard config readback too short: {} bytes (need 52)",
195 response.len()
196 )));
197 }
198
199 let enabled = (response[8] & 0x01) != 0;
200 let size_byte = response[9];
201 let width = (size_byte >> 4) + 1;
202 let height = (size_byte & 0x0F) + 1;
203
204 let mut row_pins = [0u8; 16];
205 for (i, slot) in row_pins.iter_mut().enumerate().take(8) {
206 *slot = response[10 + i].wrapping_add(1);
209 }
210 for (offset, slot) in row_pins.iter_mut().enumerate().skip(8).take(8) {
211 *slot = response[42 + (offset - 8)].wrapping_add(1);
212 }
213
214 let mut column_pins = [0u8; 8];
215 for (i, slot) in column_pins.iter_mut().enumerate() {
216 *slot = response[18 + i].wrapping_add(1);
217 }
218
219 let mut direct_macro_bitmap = [0u8; 16];
220 direct_macro_bitmap.copy_from_slice(&response[26..42]);
221
222 Ok(MatrixKeyboardConfig {
223 enabled,
224 width,
225 height,
226 row_pins,
227 column_pins,
228 direct_macro_bitmap,
229 alternate_function_pin: response[50],
230 scanning_decimation: response[51],
231 })
232}
233
234fn verify_matrix_kb_config_matches(
241 actual: &MatrixKeyboardConfig,
242 expected_width: u8,
243 expected_height: u8,
244 expected_column_pins: &[u8],
245 expected_row_pins: &[u8],
246 expected_enabled: bool,
247) -> Result<()> {
248 if actual.enabled != expected_enabled {
249 return Err(PoKeysError::Protocol(format!(
250 "configure_matrix_keyboard: device reports enabled={}, expected {expected_enabled} \
251 (configuration may be locked; raw config byte=0x{:02X})",
252 actual.enabled,
253 if actual.enabled { 0x01 } else { 0x00 }
254 )));
255 }
256 if !expected_enabled {
259 return Ok(());
260 }
261
262 if actual.width != expected_width || actual.height != expected_height {
263 return Err(PoKeysError::Protocol(format!(
264 "configure_matrix_keyboard: device reports width={} height={}, \
265 expected width={expected_width} height={expected_height} (raw size byte=0x{:02X})",
266 actual.width,
267 actual.height,
268 actual.size_byte()
269 )));
270 }
271 for (i, &pin) in expected_row_pins
272 .iter()
273 .enumerate()
274 .take(expected_height as usize)
275 .take(8)
276 {
277 if actual.row_pins[i] != pin {
278 return Err(PoKeysError::Protocol(format!(
279 "configure_matrix_keyboard: row pin {i} mismatch: device has pin {}, expected pin {pin}",
280 actual.row_pins[i]
281 )));
282 }
283 }
284 for (i, &pin) in expected_column_pins
285 .iter()
286 .enumerate()
287 .take(expected_width as usize)
288 .take(8)
289 {
290 if actual.column_pins[i] != pin {
291 return Err(PoKeysError::Protocol(format!(
292 "configure_matrix_keyboard: column pin {i} mismatch: device has pin {}, expected pin {pin}",
293 actual.column_pins[i]
294 )));
295 }
296 }
297 if expected_height > 8 {
298 for (offset, &pin) in expected_row_pins
299 .iter()
300 .enumerate()
301 .skip(8)
302 .take((expected_height as usize).saturating_sub(8))
303 .take(8)
304 {
305 if actual.row_pins[offset] != pin {
306 return Err(PoKeysError::Protocol(format!(
307 "configure_matrix_keyboard: extended row pin {offset} mismatch: device has pin {}, expected pin {pin}",
308 actual.row_pins[offset]
309 )));
310 }
311 }
312 }
313 Ok(())
314}
315
316pub struct PoKeysDevice {
318 connection_type: DeviceConnectionType,
320 connection_param: ConnectionParam,
321
322 pub info: DeviceInfo,
324 pub device_data: DeviceData,
325 pub network_device_data: Option<NetworkDeviceInfo>,
326
327 pub model: Option<crate::models::DeviceModel>,
329
330 pub pins: Vec<PinData>,
332 pub encoders: Vec<EncoderData>,
333 pub pwm: PwmData,
334
335 pub matrix_keyboard: MatrixKeyboard,
337 pub matrix_led: Vec<MatrixLed>,
338 pub lcd: LcdData,
339 pub pulse_engine_v2: PulseEngineV2,
340 pub easy_sensors: Vec<EasySensor>,
341 pub rtc: RealTimeClock,
342
343 pub(crate) communication: CommunicationManager,
345 usb_interface: Option<Box<dyn UsbHidInterface>>,
346 network_interface: Option<Box<dyn NetworkInterface>>,
347
348 pub fast_encoders_configuration: u8,
350 pub fast_encoders_options: u8,
351 pub ultra_fast_encoder_configuration: u8,
352 pub ultra_fast_encoder_options: u8,
353 pub ultra_fast_encoder_filter: u32,
354 pub po_ext_bus_data: Vec<u8>,
355
356 pub i2c_config: I2cConfig,
358 pub i2c_metrics: I2cMetrics,
359 pub validation_level: ValidationLevel,
360}
361
362impl PoKeysDevice {
363 fn new(connection_type: DeviceConnectionType) -> Self {
365 Self {
366 connection_type,
367 connection_param: ConnectionParam::Tcp,
368 info: DeviceInfo::default(),
369 device_data: DeviceData::default(),
370 network_device_data: None,
371 model: None,
372 pins: Vec::new(),
373 encoders: Vec::new(),
374 pwm: PwmData::new(),
375 matrix_keyboard: MatrixKeyboard::new(),
376 matrix_led: Vec::new(),
377 lcd: LcdData::new(),
378 pulse_engine_v2: PulseEngineV2::new(),
379 easy_sensors: Vec::new(),
380 rtc: RealTimeClock::default(),
381 communication: CommunicationManager::new(connection_type),
382 usb_interface: None,
383 network_interface: None,
384 fast_encoders_configuration: 0,
385 fast_encoders_options: 0,
386 ultra_fast_encoder_configuration: 0,
387 ultra_fast_encoder_options: 0,
388 ultra_fast_encoder_filter: 0,
389 po_ext_bus_data: Vec::new(),
390 i2c_config: I2cConfig::default(),
391 i2c_metrics: I2cMetrics::default(),
392 validation_level: ValidationLevel::None,
393 }
394 }
395
396 pub fn get_device_data(&mut self) -> Result<()> {
398 self.read_device_data()?;
400 self.initialize_device_structures()?;
401
402 self.load_device_model()?;
404
405 Ok(())
406 }
407
408 fn load_device_model(&mut self) -> Result<()> {
410 use crate::models::load_model;
411
412 let device_type_name = self.device_data.device_type_name();
414 let model_name = match device_type_name.as_str() {
415 "PoKeys56U" => "PoKeys56U",
416 "PoKeys56E" => "PoKeys56E",
417 "PoKeys57U" => "PoKeys57U",
418 "PoKeys57E" => "PoKeys57E",
419 "PoKeys57Ev1.1" => "PoKeys57E", "PoKeys57CNC" => "PoKeys57CNC",
421 _ => return Ok(()), };
423
424 match load_model(model_name, None) {
426 Ok(model) => {
427 log::info!("Loaded device model: {}", model_name);
428 self.model = Some(model);
429 Ok(())
430 }
431 Err(e) => {
432 log::warn!("Failed to load device model {}: {}", model_name, e);
433 Ok(()) }
435 }
436 }
437
438 pub fn is_pin_capability_supported(&self, pin: u32, capability: &str) -> bool {
449 if let Some(model) = &self.model {
450 model.is_pin_capability_supported(pin as u8, capability)
451 } else {
452 true
454 }
455 }
456
457 pub fn get_pin_capabilities(&self, pin: u32) -> Vec<String> {
467 if let Some(model) = &self.model {
468 model.get_pin_capabilities(pin as u8)
469 } else {
470 Vec::new()
472 }
473 }
474
475 pub fn validate_pin_capability(&self, pin: u32, capability: &str) -> Result<()> {
486 if let Some(model) = &self.model {
487 model.validate_pin_capability(pin as u8, capability)
488 } else {
489 Ok(())
491 }
492 }
493
494 pub fn get_related_capabilities(&self, pin: u32, capability: &str) -> Vec<(String, u8)> {
505 if let Some(model) = &self.model {
506 model.get_related_capabilities(pin as u8, capability)
507 } else {
508 Vec::new()
510 }
511 }
512
513 pub fn save_configuration(&mut self) -> Result<()> {
515 self.send_request(0x50, 0xAA, 0x55, 0, 0)?;
519 Ok(())
520 }
521
522 pub fn get_system_load(&mut self) -> Result<u8> {
528 let response = self.send_request(0x05, 0, 0, 0, 0)?;
529 Ok(parse_system_load_response(&response))
530 }
531
532 pub fn set_device_name(&mut self, name: &str) -> Result<()> {
546 let mut name_bytes = [0u8; 20];
556 let name_str = if name.len() > 20 { &name[..20] } else { name };
557 let name_bytes_slice = name_str.as_bytes();
558 name_bytes[..name_bytes_slice.len()].copy_from_slice(name_bytes_slice);
559
560 let mut request = [0u8; 64];
562 request[0] = 0xBB; request[1] = 0x06; request[2] = 0x01; request[3] = 0x01; request[4] = 0x00; request[5] = 0x00; request[6] = self.communication.get_next_request_id();
569
570 request[35..55].copy_from_slice(&name_bytes);
572
573 let mut checksum: u8 = 0;
576 for i in 0..7 {
577 checksum = checksum.wrapping_add(request[i]);
578 }
579 request[7] = checksum;
580
581 match self.connection_type {
583 DeviceConnectionType::UsbDevice | DeviceConnectionType::FastUsbDevice => {
584 if let Some(ref mut interface) = self.usb_interface {
585 let mut hid_packet = [0u8; 65];
586 hid_packet[0] = 0; hid_packet[1..65].copy_from_slice(&request);
588
589 interface.write(&hid_packet)?;
590
591 let mut response = [0u8; 65];
592 interface.read(&mut response)?;
593 } else {
594 return Err(PoKeysError::NotConnected);
595 }
596 }
597 DeviceConnectionType::NetworkDevice => {
598 if let Some(ref mut interface) = self.network_interface {
599 interface.send(&request)?;
600
601 let mut response = [0u8; 64];
603 let _ = interface
604 .receive_timeout(&mut response, std::time::Duration::from_millis(2000));
605 } else {
606 return Err(PoKeysError::NotConnected);
607 }
608 }
609 }
610
611 self.save_configuration()
612 }
613
614 pub fn clear_configuration(&mut self) -> Result<()> {
627 match self.send_request(0x52, 0xAA, 0x55, 0, 0) {
628 Ok(_) => Ok(()),
629 Err(PoKeysError::Transfer(_)) => Ok(()),
630 Err(e) => Err(e),
631 }
632 }
633
634 pub fn is_configuration_locked(&mut self) -> Result<bool> {
646 self.read_device_data()?;
647 Ok(self.device_data.device_lock_status != 0)
648 }
649
650 pub fn reboot_device(&mut self) -> Result<()> {
661 match self.send_request(0xF3, 0, 0, 0, 0) {
662 Ok(_) => Ok(()),
663 Err(PoKeysError::Transfer(_)) => Ok(()),
666 Err(e) => Err(e),
667 }
668 }
669
670 pub fn custom_request(
672 &mut self,
673 request_type: u8,
674 param1: u8,
675 param2: u8,
676 param3: u8,
677 param4: u8,
678 ) -> Result<[u8; RESPONSE_BUFFER_SIZE]> {
679 self.send_request(request_type, param1, param2, param3, param4)
680 }
681
682 #[deprecated(
684 since = "1.0.5",
685 note = "Use `set_network_timeout` and `set_network_retries` separately. \
686 The `read_retries` parameter has never been honoured by the \
687 retry loops in this crate; passing any value here is a no-op."
688 )]
689 pub fn set_ethernet_retry_count_and_timeout(
690 &mut self,
691 send_retries: u32,
692 _read_retries: u32,
693 timeout_ms: u32,
694 ) {
695 self.communication.set_retries_and_timeout(
696 send_retries,
697 0,
698 Duration::from_millis(timeout_ms as u64),
699 );
700 }
701
702 pub fn set_network_timeout(&mut self, timeout: Duration) {
718 let send_retries = self.communication.send_retries();
719 self.communication
720 .set_retries_and_timeout(send_retries, 0, timeout);
721 }
722
723 pub fn set_network_retries(&mut self, retries: u32) {
734 let timeout = self.communication.socket_timeout();
735 self.communication
736 .set_retries_and_timeout(retries, 0, timeout);
737 }
738
739 pub fn network_timeout(&self) -> Duration {
741 self.communication.socket_timeout()
742 }
743
744 pub fn send_retries(&self) -> u32 {
747 self.communication.send_retries()
748 }
749
750 pub fn tune_for_realtime_polling(&mut self) {
766 self.communication
767 .set_retries_and_timeout(1, 0, Duration::from_millis(100));
768 }
769
770 pub fn get_connection_type(&self) -> DeviceConnectionType {
772 self.connection_type
773 }
774
775 pub fn set_i2c_config(&mut self, config: I2cConfig) {
777 self.i2c_config = config;
778 }
779
780 pub fn get_i2c_config(&self) -> &I2cConfig {
782 &self.i2c_config
783 }
784
785 pub fn set_validation_level(&mut self, level: ValidationLevel) {
787 self.validation_level = level;
788 }
789
790 pub fn get_i2c_metrics(&self) -> &I2cMetrics {
792 &self.i2c_metrics
793 }
794
795 pub fn reset_i2c_metrics(&mut self) {
797 self.i2c_metrics = I2cMetrics::default();
798 }
799
800 pub fn health_check(&mut self) -> HealthStatus {
802 HealthStatus {
803 connectivity: self.test_connectivity(),
804 i2c_health: self.test_i2c_health(),
805 error_rate: self.calculate_error_rate(),
806 performance: self.get_performance_summary(),
807 }
808 }
809
810 fn test_connectivity(&mut self) -> ConnectivityStatus {
812 match self.get_device_data() {
813 Ok(_) => ConnectivityStatus::Healthy,
814 Err(e) => ConnectivityStatus::Degraded(e.to_string()),
815 }
816 }
817
818 fn test_i2c_health(&mut self) -> I2cHealthStatus {
820 match self.i2c_get_status() {
821 Ok(I2cStatus::Ok) => I2cHealthStatus::Healthy,
822 Ok(status) => I2cHealthStatus::Degraded(format!("I2C status: {:?}", status)),
823 Err(e) => I2cHealthStatus::Failed(e.to_string()),
824 }
825 }
826
827 fn calculate_error_rate(&self) -> f64 {
829 if self.i2c_metrics.total_commands == 0 {
830 0.0
831 } else {
832 self.i2c_metrics.failed_commands as f64 / self.i2c_metrics.total_commands as f64
833 }
834 }
835
836 fn get_performance_summary(&self) -> PerformanceSummary {
838 let success_rate = if self.i2c_metrics.total_commands == 0 {
839 1.0
840 } else {
841 self.i2c_metrics.successful_commands as f64 / self.i2c_metrics.total_commands as f64
842 };
843
844 PerformanceSummary {
845 avg_response_time_ms: self.i2c_metrics.average_response_time.as_millis() as f64,
846 success_rate,
847 throughput_commands_per_sec: 0.0, }
849 }
850
851 #[allow(dead_code)]
853 fn validate_packet(&self, data: &[u8]) -> Result<()> {
854 match &self.validation_level {
855 ValidationLevel::None => Ok(()),
856 ValidationLevel::Basic => self.validate_basic_structure(data),
857 ValidationLevel::Strict => self.validate_strict_protocol(data),
858 ValidationLevel::Custom(config) => self.validate_custom(data, config),
859 }
860 }
861
862 #[allow(dead_code)]
864 fn validate_basic_structure(&self, data: &[u8]) -> Result<()> {
865 if data.len() < 3 {
866 return Err(PoKeysError::InvalidPacketStructure(
867 "Minimum packet size is 3 bytes".to_string(),
868 ));
869 }
870 Ok(())
871 }
872
873 #[allow(dead_code)]
875 fn validate_strict_protocol(&self, data: &[u8]) -> Result<()> {
876 if data.len() < 3 {
877 return Err(PoKeysError::InvalidPacketStructure(
878 "Minimum packet size is 3 bytes".to_string(),
879 ));
880 }
881
882 let command = data[0];
883 let device_id = data[1];
884 let checksum = data[data.len() - 1];
885
886 if !matches!(command, 0x11..=0x42) {
888 return Err(PoKeysError::InvalidCommand(command));
889 }
890
891 if device_id >= 16 {
893 return Err(PoKeysError::InvalidDeviceId(device_id));
895 }
896
897 let calculated_checksum = self.calculate_checksum(&data[..data.len() - 1]);
899 if checksum != calculated_checksum {
900 return Err(PoKeysError::InvalidChecksumDetailed {
901 expected: calculated_checksum,
902 received: checksum,
903 });
904 }
905
906 Ok(())
907 }
908
909 #[allow(dead_code)]
911 fn validate_custom(&self, data: &[u8], config: &crate::types::ValidationConfig) -> Result<()> {
912 if config.validate_packet_structure && data.len() < 3 {
913 return Err(PoKeysError::InvalidPacketStructure(
914 "Minimum packet size is 3 bytes".to_string(),
915 ));
916 }
917
918 if data.len() >= 3 {
919 let command = data[0];
920 let device_id = data[1];
921 let checksum = data[data.len() - 1];
922
923 if config.validate_command_ids
924 && !config.valid_commands.is_empty()
925 && !config.valid_commands.contains(&command)
926 {
927 return Err(PoKeysError::InvalidCommand(command));
928 }
929
930 if config.validate_device_ids && device_id > config.max_device_id {
931 return Err(PoKeysError::InvalidDeviceId(device_id));
932 }
933
934 if config.validate_checksums {
935 let calculated_checksum = self.calculate_checksum(&data[..data.len() - 1]);
936 if checksum != calculated_checksum {
937 return Err(PoKeysError::InvalidChecksumDetailed {
938 expected: calculated_checksum,
939 received: checksum,
940 });
941 }
942 }
943 }
944
945 Ok(())
946 }
947
948 #[allow(dead_code)]
950 fn calculate_checksum(&self, data: &[u8]) -> u8 {
951 data.iter().fold(0, |acc, &byte| acc ^ byte)
952 }
953
954 pub fn check_pin_capability(&self, pin: u32, capability: crate::io::PinCapability) -> bool {
956 if pin as usize >= self.pins.len() {
957 return false;
958 }
959
960 match self.device_data.device_type_id {
963 32 => check_pokeys57cnc_pin_capability(pin, capability), _ => false, }
966 }
967
968 pub fn get_network_configuration(
970 &mut self,
971 timeout_ms: u32,
972 ) -> Result<(Option<NetworkDeviceSummary>, NetworkDeviceInfo)> {
973 let discovery_info = enumerate_network_devices(timeout_ms)?
975 .into_iter()
976 .find(|d| d.serial_number == self.device_data.serial_number);
977
978 let response = self.send_request(0xE0, 0x00, 0x00, 0, 0)?;
980
981 let config = NetworkDeviceInfo {
983 dhcp: response.get(8).copied().unwrap_or(0),
984 ip_address_setup: [
985 response.get(9).copied().unwrap_or(0),
986 response.get(10).copied().unwrap_or(0),
987 response.get(11).copied().unwrap_or(0),
988 response.get(12).copied().unwrap_or(0),
989 ],
990 ip_address_current: [
991 response.get(13).copied().unwrap_or(0),
992 response.get(14).copied().unwrap_or(0),
993 response.get(15).copied().unwrap_or(0),
994 response.get(16).copied().unwrap_or(0),
995 ],
996 tcp_timeout: u16::from_le_bytes([
997 response.get(17).copied().unwrap_or(0),
998 response.get(18).copied().unwrap_or(0),
999 ])
1000 .saturating_mul(100),
1001 gateway_ip: [
1002 response.get(19).copied().unwrap_or(0),
1003 response.get(20).copied().unwrap_or(0),
1004 response.get(21).copied().unwrap_or(0),
1005 response.get(22).copied().unwrap_or(0),
1006 ],
1007 subnet_mask: [
1008 response.get(23).copied().unwrap_or(0),
1009 response.get(24).copied().unwrap_or(0),
1010 response.get(25).copied().unwrap_or(0),
1011 response.get(26).copied().unwrap_or(0),
1012 ],
1013 additional_network_options: response.get(27).copied().unwrap_or(0),
1014 };
1015
1016 Ok((discovery_info, config))
1017 }
1018
1019 pub fn set_network_configuration(&mut self, config: &NetworkDeviceInfo) -> Result<()> {
1038 let timeout_units = (config.tcp_timeout / 100).max(1);
1040 let timeout_bytes = timeout_units.to_le_bytes();
1041
1042 let gateway_subnet_set: u8 =
1044 if config.gateway_ip != [0, 0, 0, 0] || config.subnet_mask != [0, 0, 0, 0] {
1045 1
1046 } else {
1047 0
1048 };
1049
1050 let options = (config.additional_network_options & 0x0F) | 0xA0;
1052
1053 let mut data = [0u8; 21];
1055 data[0] = config.dhcp; data[1..5].copy_from_slice(&config.ip_address_setup); data[9] = timeout_bytes[0]; data[10] = timeout_bytes[1];
1060 data[11..15].copy_from_slice(&config.gateway_ip); data[15..19].copy_from_slice(&config.subnet_mask); data[19] = gateway_subnet_set; data[20] = options; self.send_request_with_data(0xE0, 10, 0, 0, 0, &data)?;
1067 Ok(())
1068 }
1069
1070 pub fn send_request(
1073 &mut self,
1074 request_type: u8,
1075 param1: u8,
1076 param2: u8,
1077 param3: u8,
1078 param4: u8,
1079 ) -> Result<[u8; RESPONSE_BUFFER_SIZE]> {
1080 match self.connection_type {
1081 DeviceConnectionType::UsbDevice | DeviceConnectionType::FastUsbDevice => {
1082 if let Some(ref mut interface) = self.usb_interface {
1083 self.communication.send_usb_request(
1084 interface,
1085 request_type,
1086 param1,
1087 param2,
1088 param3,
1089 param4,
1090 )
1091 } else {
1092 Err(PoKeysError::NotConnected)
1093 }
1094 }
1095 DeviceConnectionType::NetworkDevice => {
1096 if let Some(ref mut interface) = self.network_interface {
1097 self.communication.send_network_request(
1098 interface,
1099 request_type,
1100 param1,
1101 param2,
1102 param3,
1103 param4,
1104 )
1105 } else {
1106 Err(PoKeysError::NotConnected)
1107 }
1108 }
1109 }
1110 }
1111
1112 pub fn send_request_with_data(
1114 &mut self,
1115 request_type: u8,
1116 param1: u8,
1117 param2: u8,
1118 param3: u8,
1119 param4: u8,
1120 data: &[u8],
1121 ) -> Result<[u8; RESPONSE_BUFFER_SIZE]> {
1122 let request = self.communication.prepare_request_with_data(
1123 request_type,
1124 param1,
1125 param2,
1126 param3,
1127 param4,
1128 Some(data),
1129 );
1130
1131 match self.connection_type {
1132 DeviceConnectionType::UsbDevice | DeviceConnectionType::FastUsbDevice => {
1133 if let Some(ref mut interface) = self.usb_interface {
1134 self.communication.send_usb_request_raw(interface, &request)
1135 } else {
1136 Err(PoKeysError::NotConnected)
1137 }
1138 }
1139 DeviceConnectionType::NetworkDevice => {
1140 if let Some(ref mut interface) = self.network_interface {
1141 self.communication
1142 .send_network_request_raw(interface, &request)
1143 } else {
1144 Err(PoKeysError::NotConnected)
1145 }
1146 }
1147 }
1148 }
1149
1150 pub fn read_device_data(&mut self) -> Result<()> {
1153 let response = self.send_request(0x00, 0, 0, 0, 0)?;
1155
1156 if response.len() < 64 {
1157 return Err(PoKeysError::Protocol(
1158 "Read device data response too short".to_string(),
1159 ));
1160 }
1161
1162 self.parse_device_data_response(&response)?;
1163 Ok(())
1164 }
1165
1166 fn parse_device_data_response(&mut self, response: &[u8]) -> Result<()> {
1168 if response.len() >= 64 && (&response[8..12] == b"PK58" || &response[8..12] == b"PKEx") {
1170 let software_version_encoded = response[4]; let revision_number = response[5]; let major_bits = (software_version_encoded >> 4) & 0x0F; let minor_bits = software_version_encoded & 0x0F; let decoded_major = 1 + major_bits;
1180 let decoded_minor = minor_bits;
1181
1182 let serial_32bit =
1184 u32::from_le_bytes([response[12], response[13], response[14], response[15]]);
1185
1186 let firmware_version = response[16];
1188 let firmware_revision = response[17];
1189
1190 let hw_id = response[18];
1192
1193 let user_id = response[19];
1195
1196 let build_date_bytes = &response[20..31];
1198
1199 let device_name_bytes = &response[31..41];
1201
1202 let firmware_type = response[41];
1204
1205 let _app_firmware_major = response[42];
1207 let _app_firmware_minor = response[43];
1208
1209 let product_id = response[57];
1211
1212 let configuration_lock_status = response[58];
1216
1217 self.device_data.serial_number = serial_32bit;
1219 self.device_data.device_lock_status = configuration_lock_status;
1220
1221 self.device_data.firmware_version_major = decoded_major;
1223 self.device_data.firmware_version_minor = decoded_minor;
1224 self.device_data.firmware_revision = revision_number;
1225
1226 self.device_data.secondary_firmware_version_major = firmware_version;
1228 self.device_data.secondary_firmware_version_minor = firmware_revision;
1229
1230 self.device_data.hw_type = hw_id;
1231 self.device_data.user_id = user_id;
1232 self.device_data.fw_type = firmware_type;
1233 self.device_data.product_id = product_id;
1234
1235 self.device_data.device_name.fill(0);
1237 let name_len =
1238 std::cmp::min(device_name_bytes.len(), self.device_data.device_name.len());
1239 self.device_data.device_name[..name_len]
1240 .copy_from_slice(&device_name_bytes[..name_len]);
1241
1242 self.device_data.build_date.fill(0);
1244 let date_len = std::cmp::min(build_date_bytes.len(), self.device_data.build_date.len());
1245 self.device_data.build_date[..date_len].copy_from_slice(&build_date_bytes[..date_len]);
1246
1247 log::debug!("Extended device info parsed:");
1248 log::debug!(" Serial: {}", serial_32bit);
1249 log::debug!(
1250 " Decoded firmware: {}.{}.{}",
1251 decoded_major,
1252 decoded_minor,
1253 revision_number
1254 );
1255 log::debug!(
1256 " Raw software version byte: 0x{:02X}",
1257 software_version_encoded
1258 );
1259 log::debug!(
1260 " Extended firmware: {}.{}",
1261 firmware_version,
1262 firmware_revision
1263 );
1264 log::debug!(" Hardware type: {}", hw_id);
1265 } else {
1266 let serial_16bit = ((response[2] as u32) << 8) | (response[3] as u32);
1270
1271 let software_version_encoded = response[4];
1273 let revision_number = response[5];
1274
1275 let major_bits = (software_version_encoded >> 4) & 0x0F;
1277 let minor_bits = software_version_encoded & 0x0F;
1278 let decoded_major = 1 + major_bits;
1279 let decoded_minor = minor_bits;
1280
1281 self.device_data.serial_number = serial_16bit;
1282 self.device_data.firmware_version_major = decoded_major;
1283 self.device_data.firmware_version_minor = decoded_minor;
1284 self.device_data.firmware_revision = revision_number;
1285 self.device_data.hw_type = 0; self.device_data.device_name.fill(0);
1287 self.device_data.build_date.fill(0);
1288
1289 log::debug!("Legacy device info parsed:");
1290 log::debug!(" Serial: {}", serial_16bit);
1291 log::debug!(
1292 " Decoded firmware: {}.{}.{}",
1293 decoded_major,
1294 decoded_minor,
1295 revision_number
1296 );
1297 }
1298
1299 Ok(())
1300 }
1301
1302 fn initialize_device_structures(&mut self) -> Result<()> {
1303 match self.device_data.device_type_id {
1305 32 => self.initialize_pokeys57cnc(), _ => self.initialize_generic_device(),
1307 }
1308 }
1309
1310 fn initialize_pokeys57cnc(&mut self) -> Result<()> {
1311 self.info.pin_count = 55;
1313 self.info.pwm_count = 6;
1314 self.info.encoders_count = 25;
1315 self.info.fast_encoders = 3;
1316 self.info.ultra_fast_encoders = 1;
1317 self.info.analog_inputs = 8;
1318 self.info.pulse_engine_v2 = 1;
1319
1320 self.pins = vec![PinData::new(); self.info.pin_count as usize];
1322 self.encoders = vec![EncoderData::new(); self.info.encoders_count as usize];
1323 self.po_ext_bus_data = vec![0; 10]; Ok(())
1327 }
1328
1329 fn initialize_generic_device(&mut self) -> Result<()> {
1330 self.info.pin_count = 55; self.pins = vec![PinData::new(); self.info.pin_count as usize];
1333 self.encoders = vec![EncoderData::new(); 25]; Ok(())
1335 }
1336
1337 pub fn configure_led_matrix(
1341 &mut self,
1342 config: &crate::matrix::MatrixLedProtocolConfig,
1343 ) -> Result<()> {
1344 let enabled_flags = self.encode_matrix_enabled(config);
1345 let display1_size = self.encode_display_size(
1346 config.display1_characters,
1347 crate::matrix::SEVEN_SEGMENT_COLUMNS,
1348 );
1349 let display2_size = self.encode_display_size(
1350 config.display2_characters,
1351 crate::matrix::SEVEN_SEGMENT_COLUMNS,
1352 );
1353
1354 let _response =
1355 self.send_request(0xD5, 0x00, enabled_flags, display1_size, display2_size)?;
1356 Ok(())
1357 }
1358
1359 pub fn read_led_matrix_config(&mut self) -> Result<crate::matrix::MatrixLedProtocolConfig> {
1361 let response = self.send_request(0xD5, 0x01, 0, 0, 0)?;
1363
1364 let enabled_flags = response[2];
1366 let display1_size = response[3];
1367 let display2_size = response[4];
1368
1369 let display1_enabled = (enabled_flags & 0x01) != 0;
1371 let display2_enabled = (enabled_flags & 0x02) != 0;
1372
1373 let display1_characters = display2_size & 0x0F; let display2_characters = display1_size & 0x0F; Ok(crate::matrix::MatrixLedProtocolConfig {
1379 display1_enabled,
1380 display2_enabled,
1381 display1_characters,
1382 display2_characters,
1383 })
1384 }
1385
1386 pub fn update_led_matrix(
1388 &mut self,
1389 matrix_id: u8,
1390 action: crate::matrix::MatrixAction,
1391 row: u8,
1392 column: u8,
1393 data: &[u8],
1394 ) -> Result<()> {
1395 let action_code = match (matrix_id, action) {
1396 (1, crate::matrix::MatrixAction::UpdateWhole) => 1,
1397 (1, crate::matrix::MatrixAction::SetPixel) => 5,
1398 (1, crate::matrix::MatrixAction::ClearPixel) => 6,
1399 (2, crate::matrix::MatrixAction::UpdateWhole) => 11,
1400 (2, crate::matrix::MatrixAction::SetPixel) => 15,
1401 (2, crate::matrix::MatrixAction::ClearPixel) => 16,
1402 _ => {
1403 return Err(PoKeysError::Parameter(format!(
1404 "Invalid matrix ID: {}",
1405 matrix_id
1406 )));
1407 }
1408 };
1409
1410 let request = self.communication.prepare_request_with_data(
1412 0xD6,
1413 action_code,
1414 row,
1415 column,
1416 0,
1417 Some(data),
1418 );
1419
1420 match self.connection_type {
1422 DeviceConnectionType::NetworkDevice => {
1423 if let Some(ref mut interface) = self.network_interface {
1424 let request_id = request[6];
1425
1426 interface.send(&request[..64])?;
1428
1429 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
1431 interface.receive(&mut response)?;
1432
1433 self.communication
1435 .validate_response(&response, request_id)?;
1436 }
1437 }
1438 _ => {
1439 return Err(PoKeysError::NotSupported);
1440 }
1441 }
1442
1443 Ok(())
1444 }
1445
1446 pub fn configure_matrix_keyboard(
1500 &mut self,
1501 width: u8,
1502 height: u8,
1503 column_pins: &[u8],
1504 row_pins: &[u8],
1505 ) -> Result<()> {
1506 if width == 0 || width > 8 {
1507 return Err(PoKeysError::Parameter(format!(
1508 "Matrix width must be 1-8 (got {width})"
1509 )));
1510 }
1511 if height == 0 || height > 16 {
1512 return Err(PoKeysError::Parameter(format!(
1513 "Matrix height must be 1-16 (got {height})"
1514 )));
1515 }
1516 if column_pins.len() < width as usize {
1517 return Err(PoKeysError::Parameter(format!(
1518 "column_pins has {} entries, need at least {width}",
1519 column_pins.len()
1520 )));
1521 }
1522 if row_pins.len() < height as usize {
1523 return Err(PoKeysError::Parameter(format!(
1524 "row_pins has {} entries, need at least {height}",
1525 row_pins.len()
1526 )));
1527 }
1528
1529 let used_rows = &row_pins[..height as usize];
1530 let used_cols = &column_pins[..width as usize];
1531 for &pin in used_rows.iter().chain(used_cols.iter()) {
1532 if pin == 0 || pin > 55 {
1533 return Err(PoKeysError::Parameter(format!(
1534 "Matrix keyboard pin {pin} is out of range (must be 1-55)"
1535 )));
1536 }
1537 }
1538 for &row in used_rows {
1539 if used_cols.contains(&row) {
1540 return Err(PoKeysError::Parameter(format!(
1541 "Pin {row} cannot be used as both a row and a column"
1542 )));
1543 }
1544 }
1545
1546 let mut raw = self.read_all_pin_settings_raw()?;
1551 for &pin in used_rows {
1552 let idx = (pin - 1) as usize;
1553 let invert = raw[idx] & 0x80;
1554 raw[idx] = (PinFunction::DigitalOutput as u8) | invert;
1555 }
1556 for &pin in used_cols {
1557 let idx = (pin - 1) as usize;
1558 let invert = raw[idx] & 0x80;
1559 raw[idx] = (PinFunction::DigitalInput as u8) | invert;
1560 }
1561 self.set_all_pin_settings_raw(&raw)?;
1562
1563 let deactivate_payload =
1566 encode_matrix_kb_config_payload(false, width, height, column_pins, row_pins);
1567 self.send_matrix_kb_config_write(&deactivate_payload, "deactivate phase")?;
1568
1569 let activate_payload =
1573 encode_matrix_kb_config_payload(true, width, height, column_pins, row_pins);
1574 self.send_matrix_kb_config_write(&activate_payload, "activate phase")?;
1575
1576 let actual = self.get_matrix_keyboard_configuration()?;
1580 verify_matrix_kb_config_matches(&actual, width, height, column_pins, row_pins, true)?;
1581
1582 self.matrix_keyboard.configuration = 1;
1584 self.matrix_keyboard.width = width;
1585 self.matrix_keyboard.height = height;
1586
1587 self.matrix_keyboard.column_pins.fill(0);
1589 self.matrix_keyboard.row_pins.fill(0);
1590 for (i, &pin) in column_pins.iter().enumerate().take(8) {
1591 self.matrix_keyboard.column_pins[i] = pin;
1592 }
1593 for (i, &pin) in row_pins.iter().enumerate().take(16) {
1594 self.matrix_keyboard.row_pins[i] = pin;
1595 }
1596
1597 Ok(())
1598 }
1599
1600 fn send_matrix_kb_config_write(&mut self, data: &[u8; 55], phase: &str) -> Result<()> {
1605 let response = self.send_request_with_data(
1606 0xCA,
1607 MATRIX_KB_OPT_WRITE_CONFIG,
1608 MATRIX_KEYBOARD_ID,
1609 0,
1610 0,
1611 data,
1612 )?;
1613 if response[1] != 0xCA {
1614 return Err(PoKeysError::Protocol(format!(
1615 "Invalid response command for matrix keyboard {phase}: 0x{:02X}",
1616 response[1]
1617 )));
1618 }
1619 Ok(())
1620 }
1621
1622 pub fn disable_matrix_keyboard(&mut self) -> Result<()> {
1628 let data = [0u8; 55]; self.send_matrix_kb_config_write(&data, "disable")?;
1630
1631 let actual = self.get_matrix_keyboard_configuration()?;
1632 verify_matrix_kb_config_matches(&actual, 0, 0, &[], &[], false)?;
1633
1634 self.matrix_keyboard.configuration = 0;
1635 Ok(())
1636 }
1637
1638 pub fn get_matrix_keyboard_configuration(&mut self) -> Result<MatrixKeyboardConfig> {
1650 let response =
1651 self.send_request(0xCA, MATRIX_KB_OPT_READ_CONFIG, MATRIX_KEYBOARD_ID, 0, 0)?;
1652 parse_matrix_kb_config_readback(&response)
1653 }
1654
1655 pub fn read_matrix_keyboard(&mut self) -> Result<()> {
1665 let response = self.send_request(0xCA, 20, MATRIX_KEYBOARD_ID, 0, 0)?;
1666
1667 if response[1] != 0xCA {
1668 return Err(PoKeysError::Protocol(format!(
1669 "Invalid response command for matrix keyboard read: 0x{:02X}",
1670 response[1]
1671 )));
1672 }
1673 if response.len() < 24 {
1674 return Err(PoKeysError::Protocol(format!(
1675 "Matrix keyboard status response too short: {} bytes (need 24)",
1676 response.len()
1677 )));
1678 }
1679
1680 self.matrix_keyboard.key_values.fill(0);
1681 for (row, &byte_val) in response[8..24].iter().enumerate() {
1682 for col in 0..8 {
1683 let key_index = row * 8 + col;
1684 if key_index < self.matrix_keyboard.key_values.len() && (byte_val & (1 << col)) != 0
1685 {
1686 self.matrix_keyboard.key_values[key_index] = 1;
1687 }
1688 }
1689 }
1690
1691 Ok(())
1692 }
1693
1694 pub fn set_matrix_keyboard_scanning_decimation(&mut self, decimation: u8) -> Result<()> {
1698 if decimation > 50 {
1699 return Err(PoKeysError::Parameter(format!(
1700 "scanning decimation must be 0-50 (got {decimation})"
1701 )));
1702 }
1703
1704 let mut data = [0u8; 1];
1705 data[0] = decimation;
1706 let response = self.send_request_with_data(0xCA, 50, MATRIX_KEYBOARD_ID, 0, 0, &data)?;
1707 if response[1] != 0xCA {
1708 return Err(PoKeysError::Protocol(format!(
1709 "Invalid response command for scanning decimation: 0x{:02X}",
1710 response[1]
1711 )));
1712 }
1713
1714 self.matrix_keyboard.scanning_decimation = decimation;
1715 Ok(())
1716 }
1717
1718 pub fn set_matrix_keyboard_key_mapping(&mut self) -> Result<()> {
1730 for option in 2u8..=9u8 {
1731 let chunk_base = (option as usize - 2) * 16;
1732 let data = encode_matrix_kb_mapping_chunk(
1733 &self.matrix_keyboard.key_mapping_key_code,
1734 &self.matrix_keyboard.key_mapping_key_modifier,
1735 Some(&self.matrix_keyboard.key_mapping_triggered_key),
1736 chunk_base,
1737 );
1738 let response =
1739 self.send_request_with_data(0xCA, option, MATRIX_KEYBOARD_ID, 0, 0, &data)?;
1740 if response[1] != 0xCA {
1741 return Err(PoKeysError::Protocol(format!(
1742 "Invalid response command for key mapping write (option {option}): 0x{:02X}",
1743 response[1]
1744 )));
1745 }
1746 }
1747 Ok(())
1748 }
1749
1750 pub fn set_matrix_keyboard_key_mapping_up(&mut self) -> Result<()> {
1757 for option in 22u8..=29u8 {
1758 let chunk_base = (option as usize - 22) * 16;
1759 let data = encode_matrix_kb_mapping_chunk(
1760 &self.matrix_keyboard.key_mapping_key_code_up,
1761 &self.matrix_keyboard.key_mapping_key_modifier_up,
1762 None,
1763 chunk_base,
1764 );
1765 let response =
1766 self.send_request_with_data(0xCA, option, MATRIX_KEYBOARD_ID, 0, 0, &data)?;
1767 if response[1] != 0xCA {
1768 return Err(PoKeysError::Protocol(format!(
1769 "Invalid response command for up-key mapping write (option {option}): 0x{:02X}",
1770 response[1]
1771 )));
1772 }
1773 }
1774 Ok(())
1775 }
1776
1777 pub fn get_matrix_keyboard_key_mapping(&mut self) -> Result<()> {
1783 for option in 12u8..=19u8 {
1784 let chunk_base = (option as usize - 12) * 16;
1785 let response = self.send_request(0xCA, option, MATRIX_KEYBOARD_ID, 0, 0)?;
1786 validate_matrix_kb_mapping_response(&response, option)?;
1787 decode_matrix_kb_mapping_chunk(
1788 &response,
1789 &mut self.matrix_keyboard.key_mapping_key_code,
1790 &mut self.matrix_keyboard.key_mapping_key_modifier,
1791 Some(&mut self.matrix_keyboard.key_mapping_triggered_key),
1792 chunk_base,
1793 );
1794 }
1795 Ok(())
1796 }
1797
1798 pub fn get_matrix_keyboard_key_mapping_up(&mut self) -> Result<()> {
1803 for option in 32u8..=39u8 {
1804 let chunk_base = (option as usize - 32) * 16;
1805 let response = self.send_request(0xCA, option, MATRIX_KEYBOARD_ID, 0, 0)?;
1806 validate_matrix_kb_mapping_response(&response, option)?;
1807 decode_matrix_kb_mapping_chunk(
1808 &response,
1809 &mut self.matrix_keyboard.key_mapping_key_code_up,
1810 &mut self.matrix_keyboard.key_mapping_key_modifier_up,
1811 None,
1812 chunk_base,
1813 );
1814 }
1815 Ok(())
1816 }
1817
1818 fn encode_display_size(&self, characters: u8, columns: u8) -> u8 {
1820 (characters & 0x0F) | ((columns & 0x0F) << 4)
1823 }
1824
1825 fn encode_matrix_enabled(&self, config: &crate::matrix::MatrixLedProtocolConfig) -> u8 {
1827 let mut enabled = 0u8;
1828 if config.display1_enabled {
1829 enabled |= 1 << 0;
1830 }
1831 if config.display2_enabled {
1832 enabled |= 1 << 1;
1833 }
1834 enabled
1835 }
1836
1837 pub fn configure_led_matrices(
1850 &mut self,
1851 configs: &[crate::matrix::LedMatrixConfig],
1852 ) -> Result<()> {
1853 for config in configs {
1855 if config.matrix_id < 1 || config.matrix_id > 2 {
1857 return Err(PoKeysError::InvalidConfiguration(
1858 "Matrix ID must be 1 or 2".to_string(),
1859 ));
1860 }
1861
1862 if config.characters < 1 || config.characters > 8 {
1864 return Err(PoKeysError::InvalidConfiguration(
1865 "Character count must be between 1 and 8 for 7-segment displays".to_string(),
1866 ));
1867 }
1868 }
1869
1870 for config in configs {
1872 if let Some(model) = &self.model {
1873 model.validate_led_matrix_config(config)?;
1874 }
1875 }
1876
1877 let mut protocol_config = crate::matrix::MatrixLedProtocolConfig {
1879 display1_enabled: false,
1880 display2_enabled: false,
1881 display1_characters: 1,
1882 display2_characters: 1,
1883 };
1884
1885 for config in configs {
1886 match config.matrix_id {
1887 1 => {
1888 protocol_config.display1_enabled = config.enabled;
1889 protocol_config.display1_characters = config.characters;
1890 }
1891 2 => {
1892 protocol_config.display2_enabled = config.enabled;
1893 protocol_config.display2_characters = config.characters;
1894 }
1895 _ => {
1896 return Err(PoKeysError::InvalidConfiguration(
1897 "Invalid matrix ID".to_string(),
1898 ));
1899 }
1900 }
1901 }
1902
1903 self.configure_led_matrix(&protocol_config)?;
1904
1905 if let Some(model) = &mut self.model {
1907 for config in configs {
1908 if config.enabled {
1909 model.reserve_led_matrix_pins(config.matrix_id)?;
1910 }
1911 }
1912 }
1913
1914 Ok(())
1915 }
1916
1917 pub fn configure_servo(&mut self, config: crate::pwm::ServoConfig) -> Result<()> {
1919 if self.pwm.pwm_period == 0 {
1921 self.set_pwm_period(500000)?; }
1923
1924 self.enable_pwm_for_pin(config.pin, true)?;
1926
1927 Ok(())
1928 }
1929
1930 pub fn set_servo_angle(&mut self, config: &crate::pwm::ServoConfig, angle: f32) -> Result<()> {
1932 config.set_angle(self, angle)
1933 }
1934
1935 pub fn set_servo_speed(&mut self, config: &crate::pwm::ServoConfig, speed: f32) -> Result<()> {
1937 config.set_speed(self, speed)
1938 }
1939
1940 pub fn stop_servo(&mut self, config: &crate::pwm::ServoConfig) -> Result<()> {
1942 config.stop(self)
1943 }
1944}
1945
1946impl Default for DeviceInfo {
1948 fn default() -> Self {
1949 Self {
1950 pin_count: 0,
1951 pwm_count: 0,
1952 basic_encoder_count: 0,
1953 encoders_count: 0,
1954 fast_encoders: 0,
1955 ultra_fast_encoders: 0,
1956 pwm_internal_frequency: 0,
1957 analog_inputs: 0,
1958 key_mapping: 0,
1959 triggered_key_mapping: 0,
1960 key_repeat_delay: 0,
1961 digital_counters: 0,
1962 joystick_button_axis_mapping: 0,
1963 joystick_analog_to_digital_mapping: 0,
1964 macros: 0,
1965 matrix_keyboard: 0,
1966 matrix_keyboard_triggered_mapping: 0,
1967 lcd: 0,
1968 matrix_led: 0,
1969 connection_signal: 0,
1970 po_ext_bus: 0,
1971 po_net: 0,
1972 analog_filtering: 0,
1973 init_outputs_start: 0,
1974 prot_i2c: 0,
1975 prot_1wire: 0,
1976 additional_options: 0,
1977 load_status: 0,
1978 custom_device_name: 0,
1979 po_tlog27_support: 0,
1980 sensor_list: 0,
1981 web_interface: 0,
1982 fail_safe_settings: 0,
1983 joystick_hat_switch: 0,
1984 pulse_engine: 0,
1985 pulse_engine_v2: 0,
1986 easy_sensors: 0,
1987 }
1988 }
1989}
1990
1991impl Default for RealTimeClock {
1992 fn default() -> Self {
1993 Self {
1994 sec: 0,
1995 min: 0,
1996 hour: 0,
1997 dow: 0,
1998 dom: 0,
1999 tmp: 0,
2000 doy: 0,
2001 month: 0,
2002 year: 0,
2003 }
2004 }
2005}
2006
2007fn check_pokeys57cnc_pin_capability(pin: u32, capability: crate::io::PinCapability) -> bool {
2009 use crate::io::PinCapability;
2010
2011 match pin {
2012 1..=2 => matches!(
2013 capability,
2014 PinCapability::DigitalInput
2015 | PinCapability::DigitalOutput
2016 | PinCapability::DigitalCounter
2017 | PinCapability::FastEncoder1A
2018 ),
2019 3..=6 => matches!(
2020 capability,
2021 PinCapability::DigitalInput
2022 | PinCapability::DigitalCounter
2023 | PinCapability::FastEncoder1A
2024 ),
2025 8 | 12..=13 => matches!(
2026 capability,
2027 PinCapability::DigitalInput | PinCapability::DigitalOutput
2028 ),
2029 9..=11 | 15..=16 => matches!(
2030 capability,
2031 PinCapability::DigitalInput | PinCapability::DigitalCounter
2032 ),
2033 14 => matches!(capability, PinCapability::DigitalInput),
2034 17..=18 | 20..=21 => matches!(
2035 capability,
2036 PinCapability::DigitalOutput | PinCapability::PwmOutput
2037 ),
2038 19 => matches!(
2039 capability,
2040 PinCapability::DigitalInput | PinCapability::DigitalCounter
2041 ),
2042 _ => false,
2043 }
2044}
2045
2046static USB_DEVICE_LIST: LazyLock<Mutex<Vec<UsbDeviceInfo>>> =
2049 LazyLock::new(|| Mutex::new(Vec::new()));
2050#[allow(dead_code)]
2051static NETWORK_DEVICE_LIST: LazyLock<Mutex<Vec<NetworkDeviceSummary>>> =
2052 LazyLock::new(|| Mutex::new(Vec::new()));
2053
2054#[derive(Debug, Clone)]
2055#[allow(dead_code)]
2056struct UsbDeviceInfo {
2057 vendor_id: u16,
2058 product_id: u16,
2059 serial_number: Option<String>,
2060 path: String,
2061}
2062
2063pub fn enumerate_usb_devices() -> Result<i32> {
2065 let mut device_list = USB_DEVICE_LIST
2066 .lock()
2067 .map_err(|_| PoKeysError::InternalError("Failed to lock USB device list".to_string()))?;
2068 device_list.clear();
2069
2070 #[cfg(target_os = "macos")]
2072 {
2073 enumerate_usb_devices_macos(&mut device_list)
2074 }
2075 #[cfg(target_os = "linux")]
2076 {
2077 enumerate_usb_devices_linux(&mut device_list)
2078 }
2079 #[cfg(target_os = "windows")]
2080 {
2081 enumerate_usb_devices_windows(&mut device_list)
2082 }
2083 #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
2084 {
2085 Err(PoKeysError::NotSupported)
2086 }
2087}
2088
2089pub fn enumerate_network_devices(timeout_ms: u32) -> Result<Vec<NetworkDeviceSummary>> {
2091 use crate::network::discover_all_devices;
2092
2093 log::info!("Enumerating network devices with timeout {timeout_ms}ms");
2094
2095 discover_all_devices(timeout_ms)
2097}
2098
2099pub fn connect_to_device(device_index: u32) -> Result<PoKeysDevice> {
2101 let device_list = USB_DEVICE_LIST
2102 .lock()
2103 .map_err(|_| PoKeysError::InternalError("Failed to lock USB device list".to_string()))?;
2104
2105 if device_index as usize >= device_list.len() {
2106 return Err(PoKeysError::Parameter("Invalid device index".to_string()));
2107 }
2108
2109 let device_info = device_list[device_index as usize].clone();
2110 drop(device_list); let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2114
2115 #[cfg(target_os = "macos")]
2117 {
2118 device.usb_interface = Some(Box::new(connect_usb_device_macos(&device_info.path)?));
2119 }
2120 #[cfg(target_os = "linux")]
2121 {
2122 device.usb_interface = Some(Box::new(connect_usb_device_linux(&device_info.path)?));
2123 }
2124 #[cfg(target_os = "windows")]
2125 {
2126 device.usb_interface = Some(Box::new(connect_usb_device_windows(&device_info.path)?));
2127 }
2128
2129 device.get_device_data()?;
2131
2132 Ok(device)
2133}
2134
2135pub fn connect_to_device_with_serial(
2137 serial_number: u32,
2138 check_network: bool,
2139 timeout_ms: u32,
2140) -> Result<PoKeysDevice> {
2141 if check_network {
2142 let network_devices = enumerate_network_devices(timeout_ms)?;
2143 for network_device in network_devices {
2144 if network_device.serial_number == serial_number {
2145 return connect_to_network_device(&network_device);
2146 }
2147 }
2148 }
2149
2150 let usb_count = enumerate_usb_devices()?;
2152
2153 for i in 0..usb_count {
2154 if let Ok(device) = connect_to_device(i as u32) {
2155 if device.device_data.serial_number == serial_number {
2156 return Ok(device);
2157 }
2158 }
2159 }
2160
2161 Err(PoKeysError::CannotConnect)
2162}
2163
2164pub fn connect_to_network_device(device_summary: &NetworkDeviceSummary) -> Result<PoKeysDevice> {
2166 let mut device = PoKeysDevice::new(DeviceConnectionType::NetworkDevice);
2167
2168 if device_summary.use_udp != 0 {
2170 device.connection_param = ConnectionParam::Udp;
2171 } else {
2172 device.connection_param = ConnectionParam::Tcp;
2173 }
2174
2175 #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
2177 {
2178 device.network_interface = Some(connect_network_device(device_summary)?);
2179 }
2180
2181 device.get_device_data()?;
2183
2184 Ok(device)
2185}
2186
2187struct StubUsbInterface;
2189
2190impl UsbHidInterface for StubUsbInterface {
2191 fn write(&mut self, _data: &[u8]) -> Result<usize> {
2192 Err(PoKeysError::NotSupported)
2193 }
2194
2195 fn read(&mut self, _buffer: &mut [u8]) -> Result<usize> {
2196 Err(PoKeysError::NotSupported)
2197 }
2198
2199 fn read_timeout(&mut self, _buffer: &mut [u8], _timeout: Duration) -> Result<usize> {
2200 Err(PoKeysError::NotSupported)
2201 }
2202}
2203
2204#[cfg(target_os = "macos")]
2205fn enumerate_usb_devices_macos(device_list: &mut Vec<UsbDeviceInfo>) -> Result<i32> {
2206 use std::process::Command;
2207
2208 let output = Command::new("system_profiler")
2210 .args(["SPUSBDataType", "-xml"])
2211 .output()
2212 .map_err(|e| PoKeysError::InternalError(format!("Failed to run system_profiler: {}", e)))?;
2213
2214 if !output.status.success() {
2215 return Err(PoKeysError::InternalError(
2216 "system_profiler command failed".to_string(),
2217 ));
2218 }
2219
2220 let output_str = String::from_utf8_lossy(&output.stdout);
2221
2222 let lines: Vec<&str> = output_str.lines().collect();
2225 let mut i = 0;
2226
2227 while i < lines.len() {
2228 let line = lines[i].trim();
2229
2230 if line.contains("<key>vendor_id</key>") && i + 1 < lines.len() {
2232 let next_line = lines[i + 1].trim();
2233
2234 if next_line.contains("<integer>7619</integer>")
2236 || next_line.contains("<string>0x1dc3</string>")
2237 {
2238 let mut product_id = 0u16;
2240 let mut serial_number = None;
2241 let mut location_id = String::new();
2242
2243 for j in (i + 2)..(i + 50).min(lines.len()) {
2245 let search_line = lines[j].trim();
2246
2247 if search_line.contains("<key>product_id</key>") && j + 1 < lines.len() {
2248 let product_line = lines[j + 1].trim();
2249 if let Some(start) = product_line.find("<integer>") {
2250 if let Some(end) = product_line.find("</integer>") {
2251 if let Ok(pid) = product_line[start + 9..end].parse::<u16>() {
2252 product_id = pid;
2253 }
2254 }
2255 }
2256 }
2257
2258 if search_line.contains("<key>serial_num</key>") && j + 1 < lines.len() {
2259 let serial_line = lines[j + 1].trim();
2260 if let Some(start) = serial_line.find("<string>") {
2261 if let Some(end) = serial_line.find("</string>") {
2262 serial_number = Some(serial_line[start + 8..end].to_string());
2263 }
2264 }
2265 }
2266
2267 if search_line.contains("<key>location_id</key>") && j + 1 < lines.len() {
2268 let location_line = lines[j + 1].trim();
2269 if let Some(start) = location_line.find("<string>") {
2270 if let Some(end) = location_line.find("</string>") {
2271 location_id = location_line[start + 8..end].to_string();
2272 }
2273 }
2274 }
2275
2276 if search_line.contains("<key>vendor_id</key>") && j > i + 2 {
2278 break;
2279 }
2280 }
2281
2282 device_list.push(UsbDeviceInfo {
2284 vendor_id: 0x1DC3,
2285 product_id,
2286 serial_number,
2287 path: format!("macos_usb_{}", location_id),
2288 });
2289 }
2290 }
2291 i += 1;
2292 }
2293
2294 Ok(device_list.len() as i32)
2295}
2296
2297#[cfg(target_os = "linux")]
2298fn enumerate_usb_devices_linux(device_list: &mut Vec<UsbDeviceInfo>) -> Result<i32> {
2299 Ok(device_list.len() as i32)
2303}
2304
2305#[cfg(target_os = "windows")]
2306fn enumerate_usb_devices_windows(device_list: &mut Vec<UsbDeviceInfo>) -> Result<i32> {
2307 Ok(device_list.len() as i32)
2311}
2312
2313#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
2315fn connect_network_device(
2316 device_summary: &NetworkDeviceSummary,
2317) -> Result<Box<dyn NetworkInterface>> {
2318 use crate::network::{TcpNetworkInterface, UdpNetworkInterface};
2319
2320 if device_summary.use_udp != 0 {
2321 let interface = UdpNetworkInterface::new(device_summary.ip_address, 20055)?;
2323 Ok(Box::new(interface))
2324 } else {
2325 let interface = TcpNetworkInterface::new(device_summary.ip_address, 20055)?;
2327 Ok(Box::new(interface))
2328 }
2329}
2330
2331#[cfg(target_os = "macos")]
2333fn connect_usb_device_macos(_path: &str) -> Result<StubUsbInterface> {
2334 Err(PoKeysError::NotSupported)
2335}
2336
2337#[cfg(target_os = "linux")]
2338fn connect_usb_device_linux(_path: &str) -> Result<StubUsbInterface> {
2339 Err(PoKeysError::NotSupported)
2340}
2341
2342#[cfg(target_os = "windows")]
2343fn connect_usb_device_windows(_path: &str) -> Result<StubUsbInterface> {
2344 Err(PoKeysError::NotSupported)
2345}
2346
2347fn parse_system_load_response(response: &[u8]) -> u8 {
2351 response[2]
2352}
2353
2354#[cfg(test)]
2355mod tests {
2356 use super::*;
2357
2358 #[test]
2359 fn test_device_creation() {
2360 let device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2361 assert_eq!(device.connection_type, DeviceConnectionType::UsbDevice);
2362 assert_eq!(device.pins.len(), 0); }
2364
2365 #[test]
2366 fn test_parse_system_load_response() {
2367 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
2368 response[1] = 0x05; response[2] = 42; assert_eq!(parse_system_load_response(&response), 42);
2371
2372 response[2] = 0;
2373 assert_eq!(parse_system_load_response(&response), 0);
2374
2375 response[2] = 100;
2376 assert_eq!(parse_system_load_response(&response), 100);
2377 }
2378
2379 #[test]
2380 fn test_encode_matrix_kb_config_payload_layout_enabled() {
2381 let row_pins: Vec<u8> = (1..=16).collect();
2383 let col_pins = vec![21u8, 22, 23, 24];
2384 let data = encode_matrix_kb_config_payload(true, 4, 16, &col_pins, &row_pins);
2385
2386 assert_eq!(data[0], 1);
2388 assert_eq!(data[1], 0x3F);
2390
2391 for i in 0..8 {
2393 assert_eq!(data[2 + i], i as u8, "row {} (low half)", i);
2394 }
2395 for i in 0..4 {
2397 assert_eq!(data[10 + i], 20 + i as u8, "column {}", i);
2398 }
2399
2400 assert!(
2402 data[18..34].iter().all(|&b| b == 0),
2403 "direct/macro bitmap must be zero by default"
2404 );
2405
2406 for i in 0..8 {
2408 assert_eq!(
2409 data[34 + i],
2410 (8 + i) as u8,
2411 "row {} (extended; high half)",
2412 8 + i
2413 );
2414 }
2415
2416 assert_eq!(data[42], 0);
2418 }
2419
2420 #[test]
2421 fn test_encode_matrix_kb_config_payload_layout_disabled() {
2422 let row_pins: Vec<u8> = (1..=16).collect();
2426 let col_pins = vec![21u8, 22, 23, 24];
2427 let data = encode_matrix_kb_config_payload(false, 4, 16, &col_pins, &row_pins);
2428
2429 assert_eq!(data[0], 0, "enable bit must be cleared");
2430 assert_eq!(data[1], 0x3F, "size byte unchanged from activated payload");
2431 for i in 0..8 {
2433 assert_eq!(data[2 + i], i as u8);
2434 }
2435 for i in 0..8 {
2436 assert_eq!(data[34 + i], (8 + i) as u8);
2437 }
2438 }
2439
2440 #[test]
2441 fn test_encode_matrix_kb_config_payload_extended_rows_unconditional() {
2442 let row_pins = vec![1u8, 2, 3, 4]; let col_pins = vec![21u8, 22, 23, 24];
2447 let data = encode_matrix_kb_config_payload(true, 4, 4, &col_pins, &row_pins);
2448
2449 assert!(data[34..42].iter().all(|&b| b == 0));
2453
2454 let long_rows: Vec<u8> = (1..=16).collect();
2457 let data2 = encode_matrix_kb_config_payload(true, 4, 4, &col_pins, &long_rows);
2458 for i in 0..8 {
2460 assert_eq!(
2461 data2[34 + i],
2462 (8 + i) as u8,
2463 "extended row pin {i} should be written verbatim"
2464 );
2465 }
2466 }
2467
2468 #[test]
2469 fn test_encode_matrix_kb_mapping_chunk_writes_codes_modifiers_and_triggered_bitmap() {
2470 let codes: Vec<u8> = (0..128).map(|i| i as u8).collect();
2471 let modifiers: Vec<u8> = (0..128).map(|i| (i as u8).wrapping_mul(2)).collect();
2472 let mut triggered = vec![0u8; 128];
2473 triggered[33] = 1;
2475 triggered[47] = 99;
2476
2477 let chunk = encode_matrix_kb_mapping_chunk(&codes, &modifiers, Some(&triggered), 32);
2478
2479 for i in 0..16 {
2481 assert_eq!(chunk[i], (32 + i) as u8, "code {i}");
2482 assert_eq!(chunk[16 + i], (32 + i) as u8 * 2, "modifier {i}");
2483 }
2484 let bitmap = u16::from_le_bytes([chunk[32], chunk[33]]);
2486 assert_eq!(bitmap, (1 << 1) | (1 << 15));
2487 }
2488
2489 #[test]
2490 fn test_encode_matrix_kb_mapping_chunk_omits_bitmap_when_no_triggered() {
2491 let codes = vec![0u8; 128];
2492 let modifiers = vec![0u8; 128];
2493 let chunk = encode_matrix_kb_mapping_chunk(&codes, &modifiers, None, 0);
2494 assert_eq!(chunk[32], 0);
2495 assert_eq!(chunk[33], 0);
2496 }
2497
2498 #[test]
2499 fn test_decode_matrix_kb_mapping_chunk_roundtrip() {
2500 let codes_in: Vec<u8> = (0..128).map(|i| (i as u8).wrapping_add(7)).collect();
2501 let modifiers_in: Vec<u8> = (0..128).map(|i| (i as u8).wrapping_mul(3)).collect();
2502 let mut triggered_in = vec![0u8; 128];
2503 triggered_in[5] = 1;
2504 triggered_in[20] = 1;
2505
2506 let chunk =
2508 encode_matrix_kb_mapping_chunk(&codes_in, &modifiers_in, Some(&triggered_in), 16);
2509 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
2510 response[1] = 0xCA;
2511 response[8..8 + 16].copy_from_slice(&chunk[0..16]); response[24..24 + 16].copy_from_slice(&chunk[16..32]); response[40] = chunk[32];
2514 response[41] = chunk[33];
2515
2516 let mut codes_out = vec![0u8; 128];
2517 let mut modifiers_out = vec![0u8; 128];
2518 let mut triggered_out = vec![0u8; 128];
2519 decode_matrix_kb_mapping_chunk(
2520 &response,
2521 &mut codes_out,
2522 &mut modifiers_out,
2523 Some(&mut triggered_out),
2524 16,
2525 );
2526
2527 for i in 16..32 {
2529 assert_eq!(codes_out[i], codes_in[i], "code {i}");
2530 assert_eq!(modifiers_out[i], modifiers_in[i], "modifier {i}");
2531 }
2532 assert_eq!(triggered_out[20], 1);
2533 assert_eq!(triggered_out[16], 0);
2534 assert_eq!(codes_out[0], 0);
2536 assert_eq!(codes_out[40], 0);
2537 }
2538
2539 #[test]
2540 fn test_validate_matrix_kb_mapping_response_rejects_wrong_command() {
2541 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
2542 response[1] = 0xC9; assert!(validate_matrix_kb_mapping_response(&response, 12).is_err());
2544 }
2545
2546 #[test]
2547 fn test_validate_matrix_kb_mapping_response_rejects_short_response() {
2548 let mut response = [0u8; 30]; response[1] = 0xCA;
2550 assert!(validate_matrix_kb_mapping_response(&response, 12).is_err());
2551 }
2552
2553 #[test]
2554 fn test_validate_matrix_kb_mapping_response_accepts_valid() {
2555 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
2556 response[1] = 0xCA;
2557 assert!(validate_matrix_kb_mapping_response(&response, 12).is_ok());
2558 }
2559
2560 #[test]
2561 fn test_configure_matrix_keyboard_rejects_pin_in_both_row_and_col() {
2562 let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2563 let cols = [5u8, 6, 7, 8];
2564 let rows = [1u8, 2, 3, 5]; let err = device
2566 .configure_matrix_keyboard(4, 4, &cols, &rows)
2567 .expect_err("must reject overlapping pin assignment");
2568 match err {
2569 PoKeysError::Parameter(msg) => {
2570 assert!(
2571 msg.contains("Pin 5"),
2572 "error must name the offending pin, got: {msg}"
2573 );
2574 }
2575 other => panic!("expected Parameter error, got {other:?}"),
2576 }
2577 }
2578
2579 #[test]
2580 fn test_configure_matrix_keyboard_rejects_pin_zero() {
2581 let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2582 let cols = [5u8, 6, 7, 8];
2583 let rows = [0u8, 2, 3, 4]; let err = device
2585 .configure_matrix_keyboard(4, 4, &cols, &rows)
2586 .expect_err("must reject pin 0");
2587 assert!(matches!(err, PoKeysError::Parameter(_)));
2588 }
2589
2590 #[test]
2591 fn test_configure_matrix_keyboard_rejects_pin_above_55() {
2592 let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2593 let cols = [5u8, 6, 7, 8];
2594 let rows = [1u8, 2, 3, 56]; let err = device
2596 .configure_matrix_keyboard(4, 4, &cols, &rows)
2597 .expect_err("must reject pin 56");
2598 assert!(matches!(err, PoKeysError::Parameter(_)));
2599 }
2600
2601 #[test]
2602 fn test_configure_matrix_keyboard_only_validates_used_pins() {
2603 let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2606 let cols = [5u8, 6, 7, 8, 0, 99, 99, 99]; let rows = [1u8, 2, 3, 4, 0, 99, 99, 99];
2608 let err = device
2612 .configure_matrix_keyboard(4, 4, &cols, &rows)
2613 .expect_err("expected NotConnected, not Parameter");
2614 assert!(
2615 !matches!(err, PoKeysError::Parameter(_)),
2616 "validation should have accepted these pins; got Parameter error: {err:?}"
2617 );
2618 }
2619
2620 fn make_matrix_kb_readback(
2624 enabled: bool,
2625 width: u8,
2626 height: u8,
2627 rows: &[u8],
2628 cols: &[u8],
2629 ) -> [u8; RESPONSE_BUFFER_SIZE] {
2630 let mut r = [0u8; RESPONSE_BUFFER_SIZE];
2631 r[1] = 0xCA;
2632 r[8] = if enabled { 1 } else { 0 };
2633 r[9] = ((width.saturating_sub(1)) << 4) | (height.saturating_sub(1));
2634 for (i, &p) in rows.iter().enumerate().take(8) {
2635 r[10 + i] = p.saturating_sub(1);
2636 }
2637 for (i, &p) in cols.iter().enumerate().take(8) {
2638 r[18 + i] = p.saturating_sub(1);
2639 }
2640 for (offset, &p) in rows.iter().enumerate().skip(8).take(8) {
2641 r[42 + (offset - 8)] = p.saturating_sub(1);
2642 }
2643 r
2644 }
2645
2646 #[test]
2647 fn test_parse_matrix_kb_config_readback_full() {
2648 let rows: Vec<u8> = (1..=16).collect();
2649 let cols = [17u8, 18, 19, 20, 21, 22, 23, 24];
2650 let mut r = make_matrix_kb_readback(true, 8, 16, &rows, &cols);
2651 r[26] = 0b0000_0101; r[50] = 7; r[51] = 25; let cfg = parse_matrix_kb_config_readback(&r).expect("parse");
2656 assert!(cfg.enabled);
2657 assert_eq!(cfg.width, 8);
2658 assert_eq!(cfg.height, 16);
2659 for i in 0..16 {
2660 assert_eq!(cfg.row_pins[i], (i + 1) as u8);
2661 }
2662 for i in 0..8 {
2663 assert_eq!(cfg.column_pins[i], (17 + i) as u8);
2664 }
2665 assert_eq!(cfg.direct_macro_bitmap[0], 0b0000_0101);
2666 assert_eq!(cfg.alternate_function_pin, 7);
2667 assert_eq!(cfg.scanning_decimation, 25);
2668 assert_eq!(cfg.size_byte(), 0x7F);
2669 }
2670
2671 #[test]
2672 fn test_parse_matrix_kb_config_readback_rejects_short_response() {
2673 let mut r = [0u8; 30];
2674 r[1] = 0xCA;
2675 let err = parse_matrix_kb_config_readback(&r).expect_err("must reject");
2676 assert!(matches!(err, PoKeysError::Protocol(msg) if msg.contains("readback too short")));
2677 }
2678
2679 #[test]
2680 fn test_parse_matrix_kb_config_readback_rejects_wrong_command_echo() {
2681 let mut r = [0u8; RESPONSE_BUFFER_SIZE];
2682 r[1] = 0xC9;
2683 let err = parse_matrix_kb_config_readback(&r).expect_err("must reject");
2684 assert!(matches!(err, PoKeysError::Protocol(msg) if msg.contains("echo mismatch")));
2685 }
2686
2687 #[test]
2688 fn test_verify_matches_accepts_matching_4x4() {
2689 let rows = [1u8, 2, 3, 4];
2690 let cols = [5u8, 6, 7, 8];
2691 let r = make_matrix_kb_readback(true, 4, 4, &rows, &cols);
2692 let cfg = parse_matrix_kb_config_readback(&r).unwrap();
2693 verify_matrix_kb_config_matches(&cfg, 4, 4, &cols, &rows, true).expect("must accept");
2694 }
2695
2696 #[test]
2697 fn test_verify_matches_rejects_disabled_when_expecting_enabled() {
2698 let rows = [1u8, 2, 3, 4];
2701 let cols = [5u8, 6, 7, 8];
2702 let r = make_matrix_kb_readback(false, 4, 4, &rows, &cols);
2703 let cfg = parse_matrix_kb_config_readback(&r).unwrap();
2704 let err = verify_matrix_kb_config_matches(&cfg, 4, 4, &cols, &rows, true)
2705 .expect_err("must reject silent rejection");
2706 match err {
2707 PoKeysError::Protocol(msg) => assert!(
2708 msg.contains("enabled=false") && msg.contains("locked"),
2709 "error must hint at locked state, got: {msg}"
2710 ),
2711 other => panic!("expected Protocol error, got {other:?}"),
2712 }
2713 }
2714
2715 #[test]
2716 fn test_verify_matches_disabled_path_skips_size_and_pin_checks() {
2717 let mut r = [0u8; RESPONSE_BUFFER_SIZE];
2718 r[1] = 0xCA;
2719 r[8] = 0; r[9] = 0xFF; let cfg = parse_matrix_kb_config_readback(&r).unwrap();
2722 verify_matrix_kb_config_matches(&cfg, 0, 0, &[], &[], false).expect("must accept");
2723 }
2724
2725 #[test]
2726 fn test_verify_matches_rejects_wrong_size() {
2727 let rows = [1u8, 2, 3, 4];
2728 let cols = [5u8, 6, 7, 8];
2729 let r = make_matrix_kb_readback(true, 4, 4, &rows, &cols);
2730 let cfg = parse_matrix_kb_config_readback(&r).unwrap();
2731 let err = verify_matrix_kb_config_matches(&cfg, 8, 4, &cols, &rows, true)
2732 .expect_err("must reject size mismatch");
2733 assert!(matches!(err, PoKeysError::Protocol(msg) if msg.contains("width=4")));
2734 }
2735
2736 #[test]
2737 fn test_verify_matches_rejects_wrong_row_pin() {
2738 let rows = [1u8, 2, 3, 4];
2739 let cols = [5u8, 6, 7, 8];
2740 let mut r = make_matrix_kb_readback(true, 4, 4, &rows, &cols);
2741 r[12] = 99; let cfg = parse_matrix_kb_config_readback(&r).unwrap();
2743 let err = verify_matrix_kb_config_matches(&cfg, 4, 4, &cols, &rows, true)
2744 .expect_err("must reject pin mismatch");
2745 assert!(matches!(err, PoKeysError::Protocol(msg) if msg.contains("row pin 2")));
2746 }
2747
2748 #[test]
2749 fn test_verify_matches_rejects_wrong_extended_row_pin() {
2750 let rows: Vec<u8> = (1..=12).collect();
2751 let cols = [13u8, 14, 15, 16];
2752 let mut r = make_matrix_kb_readback(true, 4, 12, &rows, &cols);
2753 r[44] = 99; let cfg = parse_matrix_kb_config_readback(&r).unwrap();
2755 let err = verify_matrix_kb_config_matches(&cfg, 4, 12, &cols, &rows, true)
2756 .expect_err("must reject extended row pin mismatch");
2757 assert!(matches!(err, PoKeysError::Protocol(msg) if msg.contains("extended row pin 10")));
2758 }
2759
2760 #[test]
2761 fn test_parse_device_data_populates_lock_status() {
2762 let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2763 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
2764 response[8..12].copy_from_slice(b"PK58");
2765 response[4] = 0x12; response[5] = 3;
2767 response[58] = 1; device
2769 .parse_device_data_response(&response)
2770 .expect("parse must succeed");
2771 assert_eq!(device.device_data.device_lock_status, 1);
2772 assert!(device.device_data.device_locked());
2773 }
2774
2775 #[test]
2776 fn test_parse_device_data_lock_status_zero_means_unlocked() {
2777 let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
2778 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
2779 response[8..12].copy_from_slice(b"PKEx");
2780 response[4] = 0x10;
2781 response[5] = 1;
2782 response[58] = 0;
2783 device
2784 .parse_device_data_response(&response)
2785 .expect("parse must succeed");
2786 assert!(!device.device_data.device_locked());
2787 }
2788
2789 #[test]
2790 fn test_pin_capability_checking() {
2791 assert!(check_pokeys57cnc_pin_capability(
2793 1,
2794 crate::io::PinCapability::DigitalInput
2795 ));
2796 assert!(check_pokeys57cnc_pin_capability(
2797 1,
2798 crate::io::PinCapability::DigitalOutput
2799 ));
2800 assert!(!check_pokeys57cnc_pin_capability(
2801 14,
2802 crate::io::PinCapability::DigitalOutput
2803 )); assert!(!check_pokeys57cnc_pin_capability(
2805 100,
2806 crate::io::PinCapability::DigitalInput
2807 )); }
2809
2810 #[cfg(feature = "hardware-tests")]
2812 mod hardware_tests {
2813 use super::*;
2814 use std::thread;
2815 use std::time::Duration;
2816
2817 #[test]
2818 fn test_hardware_device_enumeration() {
2819 println!("Testing hardware device enumeration...");
2820
2821 match enumerate_usb_devices() {
2822 Ok(count) => {
2823 println!("Found {} USB PoKeys devices", count);
2824 if count == 0 {
2825 println!(
2826 "WARNING: No PoKeys devices found. Connect a device to run hardware tests."
2827 );
2828 }
2829 }
2830 Err(e) => {
2831 panic!("Failed to enumerate USB devices: {}", e);
2832 }
2833 }
2834 }
2835
2836 #[test]
2837 fn test_hardware_device_connection() {
2838 println!("Testing hardware device connection...");
2839
2840 let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
2841
2842 if device_count == 0 {
2843 println!("SKIP: No PoKeys devices found for hardware test");
2844 return;
2845 }
2846
2847 match connect_to_device(0) {
2848 Ok(mut device) => {
2849 println!("Successfully connected to device");
2850
2851 match device.get_device_data() {
2853 Ok(_) => {
2854 println!("Device Serial: {}", device.device_data.serial_number);
2855 println!(
2856 "Firmware: {}.{}",
2857 device.device_data.firmware_version_major,
2858 device.device_data.firmware_version_minor
2859 );
2860 println!("Pin Count: {}", device.info.pin_count);
2861 }
2862 Err(e) => {
2863 println!("WARNING: Could not get device data: {}", e);
2864 }
2865 }
2866 }
2867 Err(e) => {
2868 panic!("Failed to connect to device: {}", e);
2869 }
2870 }
2871 }
2872
2873 #[test]
2874 fn test_hardware_digital_io() {
2875 println!("Testing hardware digital I/O...");
2876
2877 let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
2878 if device_count == 0 {
2879 println!("SKIP: No PoKeys devices found for hardware test");
2880 return;
2881 }
2882
2883 let mut device = connect_to_device(0).expect("Failed to connect to device");
2884 device.get_device_data().expect("Failed to get device data");
2885
2886 match device.set_pin_function(1, crate::io::PinFunction::DigitalOutput) {
2888 Ok(_) => {
2889 println!("Successfully configured pin 1 as digital output");
2890
2891 for state in [true, false, true, false] {
2893 match device.set_digital_output(1, state) {
2894 Ok(_) => {
2895 println!("Set pin 1 to {}", if state { "HIGH" } else { "LOW" })
2896 }
2897 Err(e) => println!("WARNING: Failed to set digital output: {}", e),
2898 }
2899 thread::sleep(Duration::from_millis(100));
2900 }
2901 }
2902 Err(e) => {
2903 println!("WARNING: Could not configure pin function: {}", e);
2904 }
2905 }
2906 }
2907
2908 #[test]
2909 fn test_hardware_analog_input() {
2910 println!("Testing hardware analog input...");
2911
2912 let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
2913 if device_count == 0 {
2914 println!("SKIP: No PoKeys devices found for hardware test");
2915 return;
2916 }
2917
2918 let mut device = connect_to_device(0).expect("Failed to connect to device");
2919 device.get_device_data().expect("Failed to get device data");
2920
2921 match device.get_analog_input(1) {
2923 Ok(value) => {
2924 println!("Analog input 1 value: {}", value);
2925 assert!(value <= 4095, "Analog value out of range for 12-bit ADC");
2927 }
2928 Err(e) => {
2929 println!("WARNING: Could not read analog input: {}", e);
2930 }
2931 }
2932 }
2933
2934 #[test]
2935 fn test_hardware_pwm_output() {
2936 println!("Testing hardware PWM output...");
2937
2938 let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
2939 if device_count == 0 {
2940 println!("SKIP: No PoKeys devices found for hardware test");
2941 return;
2942 }
2943
2944 let mut device = connect_to_device(0).expect("Failed to connect to device");
2945 device.get_device_data().expect("Failed to get device data");
2946
2947 match device.set_pwm_period(25000) {
2949 Ok(_) => {
2950 println!("Set PWM period to 25000 cycles");
2951
2952 for duty in [25.0, 50.0, 75.0, 0.0] {
2954 match device.set_pwm_duty_cycle_percent_for_pin(17, duty) {
2955 Ok(_) => {
2956 println!("Set PWM duty cycle to {}%", duty);
2957 thread::sleep(Duration::from_millis(200));
2958 }
2959 Err(e) => {
2960 println!("WARNING: Could not set PWM duty cycle: {}", e);
2961 }
2962 }
2963 }
2964 }
2965 Err(e) => {
2966 println!("WARNING: Could not configure PWM: {}", e);
2967 }
2968 }
2969 }
2970
2971 #[test]
2972 fn test_hardware_encoder_reading() {
2973 println!("Testing hardware encoder reading...");
2974
2975 let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
2976 if device_count == 0 {
2977 println!("SKIP: No PoKeys devices found for hardware test");
2978 return;
2979 }
2980
2981 let mut device = connect_to_device(0).expect("Failed to connect to device");
2982 device.get_device_data().expect("Failed to get device data");
2983
2984 let mut options = crate::encoders::EncoderOptions::new();
2986 options.enabled = true;
2987 options.sampling_4x = true;
2988
2989 match device.configure_encoder(0, 1, 2, options) {
2990 Ok(_) => {
2991 println!("Configured encoder 0 on pins 1 and 2");
2992
2993 for i in 0..5 {
2995 match device.get_encoder_value(0) {
2996 Ok(value) => {
2997 println!("Encoder 0 reading {}: {}", i + 1, value);
2998 }
2999 Err(e) => {
3000 println!("WARNING: Could not read encoder: {}", e);
3001 }
3002 }
3003 thread::sleep(Duration::from_millis(100));
3004 }
3005 }
3006 Err(e) => {
3007 println!("WARNING: Could not configure encoder: {}", e);
3008 }
3009 }
3010 }
3011
3012 #[test]
3013 fn test_hardware_network_discovery() {
3014 println!("Testing hardware network device discovery...");
3015
3016 match enumerate_network_devices(2000) {
3017 Ok(devices) => {
3018 println!("Found {} network PoKeys devices", devices.len());
3019
3020 for (i, device) in devices.iter().enumerate() {
3021 println!(
3022 "Network device {}: Serial {}, IP {}.{}.{}.{}",
3023 i + 1,
3024 device.serial_number,
3025 device.ip_address[0],
3026 device.ip_address[1],
3027 device.ip_address[2],
3028 device.ip_address[3]
3029 );
3030 }
3031
3032 if devices.is_empty() {
3033 println!(
3034 "No network devices found - this is normal if no network PoKeys devices are present"
3035 );
3036 }
3037 }
3038 Err(e) => {
3039 println!("WARNING: Network discovery failed: {}", e);
3040 }
3041 }
3042 }
3043
3044 #[test]
3045 fn test_hardware_device_info_validation() {
3046 println!("Testing hardware device info validation...");
3047
3048 let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
3049 if device_count == 0 {
3050 println!("SKIP: No PoKeys devices found for hardware test");
3051 return;
3052 }
3053
3054 let mut device = connect_to_device(0).expect("Failed to connect to device");
3055 device.get_device_data().expect("Failed to get device data");
3056
3057 assert!(
3059 device.device_data.serial_number > 0,
3060 "Serial number should be non-zero"
3061 );
3062 assert!(device.info.pin_count > 0, "Pin count should be non-zero");
3063 assert!(
3064 device.info.pin_count <= 100,
3065 "Pin count should be reasonable"
3066 );
3067
3068 println!("Device validation passed:");
3069 println!(" Serial: {}", device.device_data.serial_number);
3070 println!(" Pins: {}", device.info.pin_count);
3071 println!(" PWM Channels: {}", device.info.pwm_count);
3072 println!(" Encoders: {}", device.info.encoders_count);
3073 println!(" Analog Inputs: {}", device.info.analog_inputs);
3074 }
3075 }
3076}