Skip to main content

pokeys_lib/
device.rs

1//! Device enumeration, connection, and management
2
3use crate::communication::{CommunicationManager, NetworkInterface, UsbHidInterface};
4use crate::encoders::EncoderData;
5use crate::error::{PoKeysError, Result};
6use crate::io::PinData;
7use crate::keyboard_matrix::MatrixKeyboard;
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
17/// Main PoKeys device structure
18pub struct PoKeysDevice {
19    // Connection information
20    connection_type: DeviceConnectionType,
21    connection_param: ConnectionParam,
22
23    // Device information
24    pub info: DeviceInfo,
25    pub device_data: DeviceData,
26    pub network_device_data: Option<NetworkDeviceInfo>,
27
28    // Device model
29    pub model: Option<crate::models::DeviceModel>,
30
31    // Pin and I/O data
32    pub pins: Vec<PinData>,
33    pub encoders: Vec<EncoderData>,
34    pub pwm: PwmData,
35
36    // Peripheral data
37    pub matrix_keyboard: MatrixKeyboard,
38    pub matrix_led: Vec<MatrixLed>,
39    pub lcd: LcdData,
40    pub pulse_engine_v2: PulseEngineV2,
41    pub easy_sensors: Vec<EasySensor>,
42    pub rtc: RealTimeClock,
43
44    // Communication
45    pub(crate) communication: CommunicationManager,
46    usb_interface: Option<Box<dyn UsbHidInterface>>,
47    network_interface: Option<Box<dyn NetworkInterface>>,
48
49    // Configuration
50    pub fast_encoders_configuration: u8,
51    pub fast_encoders_options: u8,
52    pub ultra_fast_encoder_configuration: u8,
53    pub ultra_fast_encoder_options: u8,
54    pub ultra_fast_encoder_filter: u32,
55    pub po_ext_bus_data: Vec<u8>,
56
57    // I2C configuration and metrics
58    pub i2c_config: I2cConfig,
59    pub i2c_metrics: I2cMetrics,
60    pub validation_level: ValidationLevel,
61
62    // Internal state
63    #[allow(dead_code)]
64    request_buffer: [u8; REQUEST_BUFFER_SIZE],
65    #[allow(dead_code)]
66    response_buffer: [u8; RESPONSE_BUFFER_SIZE],
67    #[allow(dead_code)]
68    multipart_buffer: Vec<u8>,
69}
70
71impl PoKeysDevice {
72    /// Create a new device instance
73    fn new(connection_type: DeviceConnectionType) -> Self {
74        Self {
75            connection_type,
76            connection_param: ConnectionParam::Tcp,
77            info: DeviceInfo::default(),
78            device_data: DeviceData::default(),
79            network_device_data: None,
80            model: None,
81            pins: Vec::new(),
82            encoders: Vec::new(),
83            pwm: PwmData::new(),
84            matrix_keyboard: MatrixKeyboard::new(),
85            matrix_led: Vec::new(),
86            lcd: LcdData::new(),
87            pulse_engine_v2: PulseEngineV2::new(),
88            easy_sensors: Vec::new(),
89            rtc: RealTimeClock::default(),
90            communication: CommunicationManager::new(connection_type),
91            usb_interface: None,
92            network_interface: None,
93            fast_encoders_configuration: 0,
94            fast_encoders_options: 0,
95            ultra_fast_encoder_configuration: 0,
96            ultra_fast_encoder_options: 0,
97            ultra_fast_encoder_filter: 0,
98            po_ext_bus_data: Vec::new(),
99            i2c_config: I2cConfig::default(),
100            i2c_metrics: I2cMetrics::default(),
101            validation_level: ValidationLevel::None,
102            request_buffer: [0; REQUEST_BUFFER_SIZE],
103            response_buffer: [0; RESPONSE_BUFFER_SIZE],
104            multipart_buffer: Vec::new(),
105        }
106    }
107
108    /// Get device information from the connected device
109    pub fn get_device_data(&mut self) -> Result<()> {
110        // Use the comprehensive read device data command instead of the old method
111        self.read_device_data()?;
112        self.initialize_device_structures()?;
113
114        // Try to load the device model
115        self.load_device_model()?;
116
117        Ok(())
118    }
119
120    /// Load the device model based on the device type
121    fn load_device_model(&mut self) -> Result<()> {
122        use crate::models::load_model;
123
124        // Determine the model name based on the device type
125        let device_type_name = self.device_data.device_type_name();
126        let model_name = match device_type_name.as_str() {
127            "PoKeys56U" => "PoKeys56U",
128            "PoKeys56E" => "PoKeys56E",
129            "PoKeys57U" => "PoKeys57U",
130            "PoKeys57E" => "PoKeys57E",
131            "PoKeys57Ev1.1" => "PoKeys57E", // Map v1.1 to base model
132            "PoKeys57CNC" => "PoKeys57CNC",
133            _ => return Ok(()), // No model for other device types
134        };
135
136        // Try to load the model
137        match load_model(model_name, None) {
138            Ok(model) => {
139                log::info!("Loaded device model: {}", model_name);
140                self.model = Some(model);
141                Ok(())
142            }
143            Err(e) => {
144                log::warn!("Failed to load device model {}: {}", model_name, e);
145                Ok(()) // Continue without a model
146            }
147        }
148    }
149
150    /// Check if a pin supports a specific capability
151    ///
152    /// # Arguments
153    ///
154    /// * `pin` - The pin number to check
155    /// * `capability` - The capability to check for
156    ///
157    /// # Returns
158    ///
159    /// * `bool` - True if the pin supports the capability, false otherwise
160    pub fn is_pin_capability_supported(&self, pin: u32, capability: &str) -> bool {
161        if let Some(model) = &self.model {
162            model.is_pin_capability_supported(pin as u8, capability)
163        } else {
164            // If no model is loaded, assume all capabilities are supported
165            true
166        }
167    }
168
169    /// Get all capabilities for a pin
170    ///
171    /// # Arguments
172    ///
173    /// * `pin` - The pin number to get capabilities for
174    ///
175    /// # Returns
176    ///
177    /// * `Vec<String>` - List of capabilities supported by the pin
178    pub fn get_pin_capabilities(&self, pin: u32) -> Vec<String> {
179        if let Some(model) = &self.model {
180            model.get_pin_capabilities(pin as u8)
181        } else {
182            // If no model is loaded, return an empty list
183            Vec::new()
184        }
185    }
186
187    /// Validate that a pin can be configured with a specific capability
188    ///
189    /// # Arguments
190    ///
191    /// * `pin` - The pin number to check
192    /// * `capability` - The capability to check for
193    ///
194    /// # Returns
195    ///
196    /// * `Result<()>` - Ok if the capability is valid, an error otherwise
197    pub fn validate_pin_capability(&self, pin: u32, capability: &str) -> Result<()> {
198        if let Some(model) = &self.model {
199            model.validate_pin_capability(pin as u8, capability)
200        } else {
201            // If no model is loaded, assume all capabilities are valid
202            Ok(())
203        }
204    }
205
206    /// Get related capabilities for a specific capability
207    ///
208    /// # Arguments
209    ///
210    /// * `pin` - The pin number with the capability
211    /// * `capability` - The capability to find related capabilities for
212    ///
213    /// # Returns
214    ///
215    /// * `Vec<(String, u8)>` - List of related capabilities and their pin numbers
216    pub fn get_related_capabilities(&self, pin: u32, capability: &str) -> Vec<(String, u8)> {
217        if let Some(model) = &self.model {
218            model.get_related_capabilities(pin as u8, capability)
219        } else {
220            // If no model is loaded, return an empty list
221            Vec::new()
222        }
223    }
224
225    /// Save current configuration to device
226    pub fn save_configuration(&mut self) -> Result<()> {
227        // Based on official PoKeysLib source code:
228        // CreateRequest(device->request, 0x50, 0xAA, 0x55, 0, 0);
229        // Command 0x50 with parameters 0xAA, 0x55, 0, 0
230        self.send_request(0x50, 0xAA, 0x55, 0, 0)?;
231        Ok(())
232    }
233
234    /// Get the device's current system load as a percentage (0–100).
235    ///
236    /// Sends the "Get system load status" command (`0x05`) defined in the
237    /// PoKeys protocol specification. The device replies with the current
238    /// CPU/system load in byte 3 of the response.
239    pub fn get_system_load(&mut self) -> Result<u8> {
240        let response = self.send_request(0x05, 0, 0, 0, 0)?;
241        Ok(parse_system_load_response(&response))
242    }
243
244    /// Set the device name (up to 20 bytes for the long device name).
245    ///
246    /// Sends protocol command `0x06` with "write long device name" flags set.
247    /// After the write succeeds, [`save_configuration`](PoKeysDevice::save_configuration)
248    /// is called to persist the value to non-volatile storage — `0x06` alone
249    /// does not auto-save.
250    ///
251    /// # Side effect
252    ///
253    /// The request packet includes fields for the joystick device name
254    /// (spec bytes 19–34) and the product-ID offset (byte 35) that this
255    /// implementation always sends as zero. If the target device was using
256    /// those fields, they will be cleared as a side effect of this call.
257    pub fn set_device_name(&mut self, name: &str) -> Result<()> {
258        // Based on official documentation:
259        // - byte 2: 0x06
260        // - byte 3: Bit 0 for writing device name (0x01)
261        // - byte 4: use long device name (1)
262        // - byte 5-6: 0
263        // - byte 7: request ID
264        // - bytes 36-55: long device name string (20 bytes)
265
266        // Prepare long device name (20 bytes for long name)
267        let mut name_bytes = [0u8; 20];
268        let name_str = if name.len() > 20 { &name[..20] } else { name };
269        let name_bytes_slice = name_str.as_bytes();
270        name_bytes[..name_bytes_slice.len()].copy_from_slice(name_bytes_slice);
271
272        // Create request manually according to documentation
273        let mut request = [0u8; 64];
274        request[0] = 0xBB; // header
275        request[1] = 0x06; // command (byte 2 in doc)
276        request[2] = 0x01; // bit 0 set for writing device name (byte 3 in doc)
277        request[3] = 0x01; // use long device name (byte 4 in doc)
278        request[4] = 0x00; // byte 5 in doc
279        request[5] = 0x00; // byte 6 in doc
280        request[6] = self.communication.get_next_request_id();
281
282        // Long device name at bytes 36-55 in doc = bytes 35-54 in 0-based array
283        request[35..55].copy_from_slice(&name_bytes);
284
285        // Calculate checksum exactly like official PoKeysLib getChecksum function
286        // Sum bytes 0-6 (not including byte 7 where checksum goes)
287        let mut checksum: u8 = 0;
288        for i in 0..7 {
289            checksum = checksum.wrapping_add(request[i]);
290        }
291        request[7] = checksum;
292
293        // Send the request using the appropriate interface
294        match self.connection_type {
295            DeviceConnectionType::UsbDevice | DeviceConnectionType::FastUsbDevice => {
296                if let Some(ref mut interface) = self.usb_interface {
297                    let mut hid_packet = [0u8; 65];
298                    hid_packet[0] = 0; // Report ID
299                    hid_packet[1..65].copy_from_slice(&request);
300
301                    interface.write(&hid_packet)?;
302
303                    let mut response = [0u8; 65];
304                    interface.read(&mut response)?;
305                } else {
306                    return Err(PoKeysError::NotConnected);
307                }
308            }
309            DeviceConnectionType::NetworkDevice => {
310                if let Some(ref mut interface) = self.network_interface {
311                    interface.send(&request)?;
312
313                    // Use timeout for network response to avoid hanging
314                    let mut response = [0u8; 64];
315                    let _ = interface
316                        .receive_timeout(&mut response, std::time::Duration::from_millis(2000));
317                } else {
318                    return Err(PoKeysError::NotConnected);
319                }
320            }
321        }
322
323        self.save_configuration()
324    }
325
326    /// Reset device configuration to defaults (protocol command `0x52`
327    /// "Disable lock and reset configuration"). Requires the magic bytes
328    /// `0xAA, 0x55` to match the spec and the upstream PoKeysLib behaviour.
329    pub fn clear_configuration(&mut self) -> Result<()> {
330        self.send_request(0x52, 0xAA, 0x55, 0, 0)?;
331        Ok(())
332    }
333
334    /// Reboot the device (command `0xF3`).
335    ///
336    /// Sends the "Reboot system" command defined in the PoKeys protocol
337    /// specification. The device may reboot before a response reaches the
338    /// host; transfer-level failures caused by the interrupted response are
339    /// treated as success, as the request was already delivered.
340    ///
341    /// After a successful reboot the active connection is effectively
342    /// invalidated — callers should re-enumerate and reconnect before issuing
343    /// further commands.
344    pub fn reboot_device(&mut self) -> Result<()> {
345        match self.send_request(0xF3, 0, 0, 0, 0) {
346            Ok(_) => Ok(()),
347            // The device typically reboots before responding, so a transfer
348            // error after the request was sent is expected and not fatal.
349            Err(PoKeysError::Transfer(_)) => Ok(()),
350            Err(e) => Err(e),
351        }
352    }
353
354    /// Send custom request to device
355    pub fn custom_request(
356        &mut self,
357        request_type: u8,
358        param1: u8,
359        param2: u8,
360        param3: u8,
361        param4: u8,
362    ) -> Result<[u8; RESPONSE_BUFFER_SIZE]> {
363        self.send_request(request_type, param1, param2, param3, param4)
364    }
365
366    /// Set ethernet retry count and timeout
367    pub fn set_ethernet_retry_count_and_timeout(
368        &mut self,
369        send_retries: u32,
370        read_retries: u32,
371        timeout_ms: u32,
372    ) {
373        self.communication.set_retries_and_timeout(
374            send_retries,
375            read_retries,
376            Duration::from_millis(timeout_ms as u64),
377        );
378    }
379
380    /// Get connection type
381    pub fn get_connection_type(&self) -> DeviceConnectionType {
382        self.connection_type
383    }
384
385    /// Set I2C configuration
386    pub fn set_i2c_config(&mut self, config: I2cConfig) {
387        self.i2c_config = config;
388    }
389
390    /// Get I2C configuration
391    pub fn get_i2c_config(&self) -> &I2cConfig {
392        &self.i2c_config
393    }
394
395    /// Set validation level
396    pub fn set_validation_level(&mut self, level: ValidationLevel) {
397        self.validation_level = level;
398    }
399
400    /// Get I2C metrics
401    pub fn get_i2c_metrics(&self) -> &I2cMetrics {
402        &self.i2c_metrics
403    }
404
405    /// Reset I2C metrics
406    pub fn reset_i2c_metrics(&mut self) {
407        self.i2c_metrics = I2cMetrics::default();
408    }
409
410    /// Perform device health check
411    pub fn health_check(&mut self) -> HealthStatus {
412        HealthStatus {
413            connectivity: self.test_connectivity(),
414            i2c_health: self.test_i2c_health(),
415            error_rate: self.calculate_error_rate(),
416            performance: self.get_performance_summary(),
417        }
418    }
419
420    /// Test basic connectivity
421    fn test_connectivity(&mut self) -> ConnectivityStatus {
422        match self.get_device_data() {
423            Ok(_) => ConnectivityStatus::Healthy,
424            Err(e) => ConnectivityStatus::Degraded(e.to_string()),
425        }
426    }
427
428    /// Test I2C bus health
429    fn test_i2c_health(&mut self) -> I2cHealthStatus {
430        match self.i2c_get_status() {
431            Ok(I2cStatus::Ok) => I2cHealthStatus::Healthy,
432            Ok(status) => I2cHealthStatus::Degraded(format!("I2C status: {:?}", status)),
433            Err(e) => I2cHealthStatus::Failed(e.to_string()),
434        }
435    }
436
437    /// Calculate current error rate
438    fn calculate_error_rate(&self) -> f64 {
439        if self.i2c_metrics.total_commands == 0 {
440            0.0
441        } else {
442            self.i2c_metrics.failed_commands as f64 / self.i2c_metrics.total_commands as f64
443        }
444    }
445
446    /// Get performance summary
447    fn get_performance_summary(&self) -> PerformanceSummary {
448        let success_rate = if self.i2c_metrics.total_commands == 0 {
449            1.0
450        } else {
451            self.i2c_metrics.successful_commands as f64 / self.i2c_metrics.total_commands as f64
452        };
453
454        PerformanceSummary {
455            avg_response_time_ms: self.i2c_metrics.average_response_time.as_millis() as f64,
456            success_rate,
457            throughput_commands_per_sec: 0.0, // Would need timing data to calculate
458        }
459    }
460
461    /// Validate packet according to current validation level
462    #[allow(dead_code)]
463    fn validate_packet(&self, data: &[u8]) -> Result<()> {
464        match &self.validation_level {
465            ValidationLevel::None => Ok(()),
466            ValidationLevel::Basic => self.validate_basic_structure(data),
467            ValidationLevel::Strict => self.validate_strict_protocol(data),
468            ValidationLevel::Custom(config) => self.validate_custom(data, config),
469        }
470    }
471
472    /// Validate basic packet structure
473    #[allow(dead_code)]
474    fn validate_basic_structure(&self, data: &[u8]) -> Result<()> {
475        if data.len() < 3 {
476            return Err(PoKeysError::InvalidPacketStructure(
477                "Minimum packet size is 3 bytes".to_string(),
478            ));
479        }
480        Ok(())
481    }
482
483    /// Validate strict protocol compliance
484    #[allow(dead_code)]
485    fn validate_strict_protocol(&self, data: &[u8]) -> Result<()> {
486        if data.len() < 3 {
487            return Err(PoKeysError::InvalidPacketStructure(
488                "Minimum packet size is 3 bytes".to_string(),
489            ));
490        }
491
492        let command = data[0];
493        let device_id = data[1];
494        let checksum = data[data.len() - 1];
495
496        // Validate command ID (uSPIBridge command range)
497        if !matches!(command, 0x11..=0x42) {
498            return Err(PoKeysError::InvalidCommand(command));
499        }
500
501        // Validate device ID
502        if device_id >= 16 {
503            // Reasonable max device count
504            return Err(PoKeysError::InvalidDeviceId(device_id));
505        }
506
507        // Validate checksum
508        let calculated_checksum = self.calculate_checksum(&data[..data.len() - 1]);
509        if checksum != calculated_checksum {
510            return Err(PoKeysError::InvalidChecksumDetailed {
511                expected: calculated_checksum,
512                received: checksum,
513            });
514        }
515
516        Ok(())
517    }
518
519    /// Validate with custom configuration
520    #[allow(dead_code)]
521    fn validate_custom(&self, data: &[u8], config: &crate::types::ValidationConfig) -> Result<()> {
522        if config.validate_packet_structure && data.len() < 3 {
523            return Err(PoKeysError::InvalidPacketStructure(
524                "Minimum packet size is 3 bytes".to_string(),
525            ));
526        }
527
528        if data.len() >= 3 {
529            let command = data[0];
530            let device_id = data[1];
531            let checksum = data[data.len() - 1];
532
533            if config.validate_command_ids
534                && !config.valid_commands.is_empty()
535                && !config.valid_commands.contains(&command)
536            {
537                return Err(PoKeysError::InvalidCommand(command));
538            }
539
540            if config.validate_device_ids && device_id > config.max_device_id {
541                return Err(PoKeysError::InvalidDeviceId(device_id));
542            }
543
544            if config.validate_checksums {
545                let calculated_checksum = self.calculate_checksum(&data[..data.len() - 1]);
546                if checksum != calculated_checksum {
547                    return Err(PoKeysError::InvalidChecksumDetailed {
548                        expected: calculated_checksum,
549                        received: checksum,
550                    });
551                }
552            }
553        }
554
555        Ok(())
556    }
557
558    /// Calculate XOR checksum
559    #[allow(dead_code)]
560    fn calculate_checksum(&self, data: &[u8]) -> u8 {
561        data.iter().fold(0, |acc, &byte| acc ^ byte)
562    }
563
564    /// Check if device supports a specific capability
565    pub fn check_pin_capability(&self, pin: u32, capability: crate::io::PinCapability) -> bool {
566        if pin as usize >= self.pins.len() {
567            return false;
568        }
569
570        // Implementation would check device-specific pin capabilities
571        // This is a simplified version
572        match self.device_data.device_type_id {
573            32 => check_pokeys57cnc_pin_capability(pin, capability), // PoKeys57CNC
574            _ => false, // Add other device types as needed
575        }
576    }
577
578    /// Get complete network configuration including discovery info
579    pub fn get_network_configuration(
580        &mut self,
581        timeout_ms: u32,
582    ) -> Result<(Option<NetworkDeviceSummary>, NetworkDeviceInfo)> {
583        // Get discovery info
584        let discovery_info = enumerate_network_devices(timeout_ms)?
585            .into_iter()
586            .find(|d| d.serial_number == self.device_data.serial_number);
587
588        // Get detailed config using 0xBB request
589        let response = self.send_request(0xE0, 0x00, 0x00, 0, 0)?;
590
591        // Parse network configuration from response
592        let config = NetworkDeviceInfo {
593            dhcp: response.get(8).copied().unwrap_or(0),
594            ip_address_setup: [
595                response.get(9).copied().unwrap_or(0),
596                response.get(10).copied().unwrap_or(0),
597                response.get(11).copied().unwrap_or(0),
598                response.get(12).copied().unwrap_or(0),
599            ],
600            ip_address_current: [
601                response.get(13).copied().unwrap_or(0),
602                response.get(14).copied().unwrap_or(0),
603                response.get(15).copied().unwrap_or(0),
604                response.get(16).copied().unwrap_or(0),
605            ],
606            tcp_timeout: u16::from_le_bytes([
607                response.get(17).copied().unwrap_or(0),
608                response.get(18).copied().unwrap_or(0),
609            ])
610            .saturating_mul(100),
611            gateway_ip: [
612                response.get(19).copied().unwrap_or(0),
613                response.get(20).copied().unwrap_or(0),
614                response.get(21).copied().unwrap_or(0),
615                response.get(22).copied().unwrap_or(0),
616            ],
617            subnet_mask: [
618                response.get(23).copied().unwrap_or(0),
619                response.get(24).copied().unwrap_or(0),
620                response.get(25).copied().unwrap_or(0),
621                response.get(26).copied().unwrap_or(0),
622            ],
623            additional_network_options: response.get(27).copied().unwrap_or(0),
624        };
625
626        Ok((discovery_info, config))
627    }
628
629    /// Set network configuration on the device.
630    ///
631    /// Sends command `0xE0` with option `10`, which writes **and saves** the
632    /// configuration to non-volatile storage in one operation — no separate
633    /// [`save_configuration`](PoKeysDevice::save_configuration) call is needed.
634    ///
635    /// # Field mapping
636    ///
637    /// | `NetworkDeviceInfo` field      | Protocol byte (doc) | Notes |
638    /// |-------------------------------|---------------------|-------|
639    /// | `dhcp`                        | 9                   | 0 = fixed IP, 1 = DHCP |
640    /// | `ip_address_setup`            | 10–13               | Applied when `dhcp == 0` |
641    /// | `tcp_timeout` (ms)            | 18–19               | Stored in units of 100 ms |
642    /// | `gateway_ip`                  | 20–23               | Applied when non-zero |
643    /// | `subnet_mask`                 | 24–27               | Applied when non-zero |
644    /// | `additional_network_options`  | 29                  | Upper nibble forced to `0xA` |
645    ///
646    /// `ip_address_current` is read-only (assigned by DHCP) and is ignored here.
647    pub fn set_network_configuration(&mut self, config: &NetworkDeviceInfo) -> Result<()> {
648        // TCP timeout is stored in units of 100 ms; NetworkDeviceInfo holds ms.
649        let timeout_units = (config.tcp_timeout / 100).max(1);
650        let timeout_bytes = timeout_units.to_le_bytes();
651
652        // Set gateway/subnet flag: tell the device to apply those fields too.
653        let gateway_subnet_set: u8 =
654            if config.gateway_ip != [0, 0, 0, 0] || config.subnet_mask != [0, 0, 0, 0] {
655                1
656            } else {
657                0
658            };
659
660        // Upper nibble of the options byte must always be 0xA (protocol requirement).
661        let options = (config.additional_network_options & 0x0F) | 0xA0;
662
663        // Build the data payload (doc bytes 9–29, 0-based bytes 8–28).
664        let mut data = [0u8; 21];
665        data[0] = config.dhcp; // doc byte  9: IP setup
666        data[1..5].copy_from_slice(&config.ip_address_setup); // doc bytes 10-13: fixed IP
667        // data[5..9] = reserved (zeros)               // doc bytes 14-17
668        data[9] = timeout_bytes[0]; // doc bytes 18-19: TCP timeout (LE)
669        data[10] = timeout_bytes[1];
670        data[11..15].copy_from_slice(&config.gateway_ip); // doc bytes 20-23
671        data[15..19].copy_from_slice(&config.subnet_mask); // doc bytes 24-27
672        data[19] = gateway_subnet_set; // doc byte  28: apply gateway/subnet
673        data[20] = options; // doc byte  29: additional options
674
675        // option = 10 triggers write + save (per spec footnote 17 and 18).
676        self.send_request_with_data(0xE0, 10, 0, 0, 0, &data)?;
677        Ok(())
678    }
679
680    // Internal methods
681
682    pub fn send_request(
683        &mut self,
684        request_type: u8,
685        param1: u8,
686        param2: u8,
687        param3: u8,
688        param4: u8,
689    ) -> Result<[u8; RESPONSE_BUFFER_SIZE]> {
690        match self.connection_type {
691            DeviceConnectionType::UsbDevice | DeviceConnectionType::FastUsbDevice => {
692                if let Some(ref mut interface) = self.usb_interface {
693                    self.communication.send_usb_request(
694                        interface,
695                        request_type,
696                        param1,
697                        param2,
698                        param3,
699                        param4,
700                    )
701                } else {
702                    Err(PoKeysError::NotConnected)
703                }
704            }
705            DeviceConnectionType::NetworkDevice => {
706                if let Some(ref mut interface) = self.network_interface {
707                    self.communication.send_network_request(
708                        interface,
709                        request_type,
710                        param1,
711                        param2,
712                        param3,
713                        param4,
714                    )
715                } else {
716                    Err(PoKeysError::NotConnected)
717                }
718            }
719        }
720    }
721
722    /// Send request with data payload
723    pub fn send_request_with_data(
724        &mut self,
725        request_type: u8,
726        param1: u8,
727        param2: u8,
728        param3: u8,
729        param4: u8,
730        data: &[u8],
731    ) -> Result<[u8; RESPONSE_BUFFER_SIZE]> {
732        let request = self.communication.prepare_request_with_data(
733            request_type,
734            param1,
735            param2,
736            param3,
737            param4,
738            Some(data),
739        );
740
741        match self.connection_type {
742            DeviceConnectionType::UsbDevice | DeviceConnectionType::FastUsbDevice => {
743                if let Some(ref mut interface) = self.usb_interface {
744                    self.communication.send_usb_request_raw(interface, &request)
745                } else {
746                    Err(PoKeysError::NotConnected)
747                }
748            }
749            DeviceConnectionType::NetworkDevice => {
750                if let Some(ref mut interface) = self.network_interface {
751                    self.communication
752                        .send_network_request_raw(interface, &request)
753                } else {
754                    Err(PoKeysError::NotConnected)
755                }
756            }
757        }
758    }
759
760    /// Read comprehensive device data using command 0x00
761    /// This provides accurate device information including proper firmware version and device type
762    pub fn read_device_data(&mut self) -> Result<()> {
763        // Send read device data command (byte 2: 0x00, bytes 3-6: 0)
764        let response = self.send_request(0x00, 0, 0, 0, 0)?;
765
766        if response.len() < 64 {
767            return Err(PoKeysError::Protocol(
768                "Read device data response too short".to_string(),
769            ));
770        }
771
772        self.parse_device_data_response(&response)?;
773        Ok(())
774    }
775
776    /// Parse the comprehensive device data response according to PoKeys protocol specification
777    fn parse_device_data_response(&mut self, response: &[u8]) -> Result<()> {
778        // Check for extended device signature (PK58/PKEx) at bytes 9-12 (doc says 9-12, 0-based = 8-11)
779        if response.len() >= 64 && (&response[8..12] == b"PK58" || &response[8..12] == b"PKEx") {
780            // Extended device parsing (based on official documentation)
781
782            // Basic firmware info (bytes 5-6)
783            let software_version_encoded = response[4]; // Byte 5 in doc = byte 4 in 0-based
784            let revision_number = response[5]; // Byte 6 in doc = byte 5 in 0-based
785
786            // Decode software version: v(1+[bits 4-7]).(bits [0-3])
787            let major_bits = (software_version_encoded >> 4) & 0x0F; // Extract bits 4-7
788            let minor_bits = software_version_encoded & 0x0F; // Extract bits 0-3
789            let decoded_major = 1 + major_bits;
790            let decoded_minor = minor_bits;
791
792            // 32-bit serial number (bytes 13-16 in doc = bytes 12-15 in 0-based)
793            let serial_32bit =
794                u32::from_le_bytes([response[12], response[13], response[14], response[15]]);
795
796            // Extended firmware info (bytes 17-18 in doc = bytes 16-17 in 0-based)
797            let firmware_version = response[16];
798            let firmware_revision = response[17];
799
800            // Hardware ID (byte 19 in doc = byte 18 in 0-based)
801            let hw_id = response[18];
802
803            // User ID (byte 20 in doc = byte 19 in 0-based)
804            let user_id = response[19];
805
806            // Build date (bytes 21-31 in doc = bytes 20-30 in 0-based)
807            let build_date_bytes = &response[20..31];
808
809            // Device name (bytes 32-41 in doc = bytes 31-40 in 0-based)
810            let device_name_bytes = &response[31..41];
811
812            // Firmware type (byte 42 in doc = byte 41 in 0-based)
813            let firmware_type = response[41];
814
815            // Application firmware version (bytes 43-44 in doc = bytes 42-43 in 0-based)
816            let _app_firmware_major = response[42];
817            let _app_firmware_minor = response[43];
818
819            // Product ID offset (byte 58 in doc = byte 57 in 0-based)
820            let product_id = response[57];
821
822            // Update device data structure
823            self.device_data.serial_number = serial_32bit;
824
825            // Use the decoded firmware version for display
826            self.device_data.firmware_version_major = decoded_major;
827            self.device_data.firmware_version_minor = decoded_minor;
828            self.device_data.firmware_revision = revision_number;
829
830            // Store the extended firmware info in secondary fields
831            self.device_data.secondary_firmware_version_major = firmware_version;
832            self.device_data.secondary_firmware_version_minor = firmware_revision;
833
834            self.device_data.hw_type = hw_id;
835            self.device_data.user_id = user_id;
836            self.device_data.fw_type = firmware_type;
837            self.device_data.product_id = product_id;
838
839            // Store device name (copy raw bytes)
840            self.device_data.device_name.fill(0);
841            let name_len =
842                std::cmp::min(device_name_bytes.len(), self.device_data.device_name.len());
843            self.device_data.device_name[..name_len]
844                .copy_from_slice(&device_name_bytes[..name_len]);
845
846            // Store build date (copy raw bytes)
847            self.device_data.build_date.fill(0);
848            let date_len = std::cmp::min(build_date_bytes.len(), self.device_data.build_date.len());
849            self.device_data.build_date[..date_len].copy_from_slice(&build_date_bytes[..date_len]);
850
851            log::debug!("Extended device info parsed:");
852            log::debug!("  Serial: {}", serial_32bit);
853            log::debug!(
854                "  Decoded firmware: {}.{}.{}",
855                decoded_major,
856                decoded_minor,
857                revision_number
858            );
859            log::debug!(
860                "  Raw software version byte: 0x{:02X}",
861                software_version_encoded
862            );
863            log::debug!(
864                "  Extended firmware: {}.{}",
865                firmware_version,
866                firmware_revision
867            );
868            log::debug!("  Hardware type: {}", hw_id);
869        } else {
870            // Legacy device parsing
871
872            // Serial number (bytes 3-4 in doc = bytes 2-3 in 0-based)
873            let serial_16bit = ((response[2] as u32) << 8) | (response[3] as u32);
874
875            // Software version (byte 5 in doc = byte 4 in 0-based)
876            let software_version_encoded = response[4];
877            let revision_number = response[5];
878
879            // Decode software version: v(1+[bits 4-7]).(bits [0-3])
880            let major_bits = (software_version_encoded >> 4) & 0x0F;
881            let minor_bits = software_version_encoded & 0x0F;
882            let decoded_major = 1 + major_bits;
883            let decoded_minor = minor_bits;
884
885            self.device_data.serial_number = serial_16bit;
886            self.device_data.firmware_version_major = decoded_major;
887            self.device_data.firmware_version_minor = decoded_minor;
888            self.device_data.firmware_revision = revision_number;
889            self.device_data.hw_type = 0; // Unknown for legacy
890            self.device_data.device_name.fill(0);
891            self.device_data.build_date.fill(0);
892
893            log::debug!("Legacy device info parsed:");
894            log::debug!("  Serial: {}", serial_16bit);
895            log::debug!(
896                "  Decoded firmware: {}.{}.{}",
897                decoded_major,
898                decoded_minor,
899                revision_number
900            );
901        }
902
903        Ok(())
904    }
905
906    fn initialize_device_structures(&mut self) -> Result<()> {
907        // Initialize device-specific structures based on device type
908        match self.device_data.device_type_id {
909            32 => self.initialize_pokeys57cnc(), // PoKeys57CNC
910            _ => self.initialize_generic_device(),
911        }
912    }
913
914    fn initialize_pokeys57cnc(&mut self) -> Result<()> {
915        // PoKeys57CNC specific initialization
916        self.info.pin_count = 55;
917        self.info.pwm_count = 6;
918        self.info.encoders_count = 25;
919        self.info.fast_encoders = 3;
920        self.info.ultra_fast_encoders = 1;
921        self.info.analog_inputs = 8;
922        self.info.pulse_engine_v2 = 1;
923
924        // Initialize pin array
925        self.pins = vec![PinData::new(); self.info.pin_count as usize];
926        self.encoders = vec![EncoderData::new(); self.info.encoders_count as usize];
927        // PWM data is already initialized in new()
928        self.po_ext_bus_data = vec![0; 10]; // PoKeys57CNC has 10 PoExtBus outputs
929
930        Ok(())
931    }
932
933    fn initialize_generic_device(&mut self) -> Result<()> {
934        // Generic device initialization
935        self.info.pin_count = 55; // Default
936        self.pins = vec![PinData::new(); self.info.pin_count as usize];
937        self.encoders = vec![EncoderData::new(); 25]; // Default encoder count
938        Ok(())
939    }
940
941    // LED Matrix Protocol Implementation
942
943    /// Command 0xD5: Get/Set Matrix LED Configuration
944    pub fn configure_led_matrix(
945        &mut self,
946        config: &crate::matrix::MatrixLedProtocolConfig,
947    ) -> Result<()> {
948        let enabled_flags = self.encode_matrix_enabled(config);
949        let display1_size = self.encode_display_size(
950            config.display1_characters,
951            crate::matrix::SEVEN_SEGMENT_COLUMNS,
952        );
953        let display2_size = self.encode_display_size(
954            config.display2_characters,
955            crate::matrix::SEVEN_SEGMENT_COLUMNS,
956        );
957
958        let _response =
959            self.send_request(0xD5, 0x00, enabled_flags, display1_size, display2_size)?;
960        Ok(())
961    }
962
963    /// Read Matrix LED Configuration
964    pub fn read_led_matrix_config(&mut self) -> Result<crate::matrix::MatrixLedProtocolConfig> {
965        // Read the configuration using command 0xD5 with parameter 0x01
966        let response = self.send_request(0xD5, 0x01, 0, 0, 0)?;
967
968        // Parse the response
969        let enabled_flags = response[2];
970        let display1_size = response[3];
971        let display2_size = response[4];
972
973        // Decode enabled flags
974        let display1_enabled = (enabled_flags & 0x01) != 0;
975        let display2_enabled = (enabled_flags & 0x02) != 0;
976
977        // Decode character counts (lower 4 bits)
978        // Note: The protocol response has display parameters swapped
979        let display1_characters = display2_size & 0x0F; // Use display2_size for display1
980        let display2_characters = display1_size & 0x0F; // Use display1_size for display2
981
982        Ok(crate::matrix::MatrixLedProtocolConfig {
983            display1_enabled,
984            display2_enabled,
985            display1_characters,
986            display2_characters,
987        })
988    }
989
990    /// Command 0xD6: Update Matrix Display
991    pub fn update_led_matrix(
992        &mut self,
993        matrix_id: u8,
994        action: crate::matrix::MatrixAction,
995        row: u8,
996        column: u8,
997        data: &[u8],
998    ) -> Result<()> {
999        let action_code = match (matrix_id, action) {
1000            (1, crate::matrix::MatrixAction::UpdateWhole) => 1,
1001            (1, crate::matrix::MatrixAction::SetPixel) => 5,
1002            (1, crate::matrix::MatrixAction::ClearPixel) => 6,
1003            (2, crate::matrix::MatrixAction::UpdateWhole) => 11,
1004            (2, crate::matrix::MatrixAction::SetPixel) => 15,
1005            (2, crate::matrix::MatrixAction::ClearPixel) => 16,
1006            _ => {
1007                return Err(PoKeysError::Parameter(format!(
1008                    "Invalid matrix ID: {}",
1009                    matrix_id
1010                )));
1011            }
1012        };
1013
1014        // Prepare the request with data payload
1015        let request = self.communication.prepare_request_with_data(
1016            0xD6,
1017            action_code,
1018            row,
1019            column,
1020            0,
1021            Some(data),
1022        );
1023
1024        // Send the full request
1025        match self.connection_type {
1026            DeviceConnectionType::NetworkDevice => {
1027                if let Some(ref mut interface) = self.network_interface {
1028                    let request_id = request[6];
1029
1030                    // Send request
1031                    interface.send(&request[..64])?;
1032
1033                    // Receive response
1034                    let mut response = [0u8; RESPONSE_BUFFER_SIZE];
1035                    interface.receive(&mut response)?;
1036
1037                    // Validate response
1038                    self.communication
1039                        .validate_response(&response, request_id)?;
1040                }
1041            }
1042            _ => {
1043                return Err(PoKeysError::NotSupported);
1044            }
1045        }
1046
1047        Ok(())
1048    }
1049
1050    /// Command 0xCA: Configure Matrix Keyboard
1051    ///
1052    /// Configures a matrix keyboard using the official PoKeys protocol.
1053    /// Uses command 0xCA with option 16 for configuration.
1054    ///
1055    /// # Arguments
1056    /// * `width` - Number of columns (1-8)
1057    /// * `height` - Number of rows (1-16)
1058    /// * `column_pins` - Pin numbers for columns (1-based)
1059    /// * `row_pins` - Pin numbers for rows (1-based)
1060    ///
1061    /// # Protocol Details
1062    /// - Pins are converted to 0-based indexing for the device
1063    /// - Matrix uses 8-column internal layout regardless of configured width
1064    /// - Supports extended row pins for matrices with height > 8
1065    pub fn configure_matrix_keyboard(
1066        &mut self,
1067        width: u8,
1068        height: u8,
1069        column_pins: &[u8],
1070        row_pins: &[u8],
1071    ) -> Result<()> {
1072        if width > 8 || height > 16 {
1073            return Err(PoKeysError::Parameter("Matrix size too large".to_string()));
1074        }
1075
1076        // Prepare configuration data
1077        let mut data = [0u8; 55];
1078        data[0] = 1; // Enable matrix keyboard (bit 0)
1079        data[1] = ((width - 1) << 4) | (height - 1); // Size: width-1 in upper 4 bits, height-1 in lower 4 bits
1080
1081        // Row pins (bytes 2-9, 0-based indexing)
1082        for (i, &pin) in row_pins.iter().enumerate().take(8) {
1083            data[2 + i] = if pin > 0 { pin - 1 } else { 0 };
1084        }
1085
1086        // Column pins (bytes 10-17, 0-based indexing)
1087        for (i, &pin) in column_pins.iter().enumerate().take(8) {
1088            data[10 + i] = if pin > 0 { pin - 1 } else { 0 };
1089        }
1090
1091        // Extended row pins for height > 8 (bytes 34-41)
1092        if height > 8 {
1093            for (i, &pin) in row_pins.iter().enumerate().skip(8).take(8) {
1094                data[34 + i] = if pin > 0 { pin - 1 } else { 0 };
1095            }
1096        }
1097
1098        // Send configuration
1099        self.send_request_with_data(0xCA, 16, 0, 0, 0, &data)?;
1100
1101        // Update local state
1102        self.matrix_keyboard.configuration = 1;
1103        self.matrix_keyboard.width = width;
1104        self.matrix_keyboard.height = height;
1105
1106        // Copy pin assignments
1107        for (i, &pin) in column_pins.iter().enumerate().take(8) {
1108            self.matrix_keyboard.column_pins[i] = pin;
1109        }
1110        for (i, &pin) in row_pins.iter().enumerate().take(16) {
1111            self.matrix_keyboard.row_pins[i] = pin;
1112        }
1113
1114        Ok(())
1115    }
1116
1117    /// Command 0xCA: Read Matrix Keyboard State
1118    ///
1119    /// Reads the current state of all keys in the matrix keyboard.
1120    /// Uses command 0xCA with option 20 to read the 16x8 matrix status.
1121    ///
1122    /// # Protocol Details
1123    /// - Returns 16 bytes representing the full 16x8 matrix
1124    /// - Each byte represents 8 keys in a row (bit 0 = column 0, bit 7 = column 7)
1125    /// - Key indexing: row * 8 + column (e.g., key at row 1, col 2 = index 10)
1126    /// - Updates the internal key_values array with current state
1127    pub fn read_matrix_keyboard(&mut self) -> Result<()> {
1128        let response = self.send_request(0xCA, 20, 0, 0, 0)?;
1129
1130        // Parse keyboard state from response (bytes 8-23 contain 16x8 matrix)
1131        let data_start = 8;
1132
1133        if response.len() >= data_start + 16 {
1134            // Clear existing state
1135            self.matrix_keyboard.key_values.fill(0);
1136
1137            // Copy matrix state (16 bytes for 16x8 matrix)
1138            // Each byte represents 8 keys in a row
1139            for (row, &byte_val) in response[data_start..data_start + 16].iter().enumerate() {
1140                for col in 0..8 {
1141                    let key_index = row * 8 + col;
1142                    if key_index < 128 {
1143                        self.matrix_keyboard.key_values[key_index] =
1144                            if (byte_val & (1 << col)) != 0 { 1 } else { 0 };
1145                    }
1146                }
1147            }
1148        }
1149
1150        Ok(())
1151    }
1152
1153    /// Helper to encode display size (characters as rows, always 8 columns for 7-segment)
1154    fn encode_display_size(&self, characters: u8, columns: u8) -> u8 {
1155        // bits 0-3: number of rows (characters)
1156        // bits 4-7: number of columns (always 8 for 7-segment)
1157        (characters & 0x0F) | ((columns & 0x0F) << 4)
1158    }
1159
1160    /// Helper to encode matrix enabled flags
1161    fn encode_matrix_enabled(&self, config: &crate::matrix::MatrixLedProtocolConfig) -> u8 {
1162        let mut enabled = 0u8;
1163        if config.display1_enabled {
1164            enabled |= 1 << 0;
1165        }
1166        if config.display2_enabled {
1167            enabled |= 1 << 1;
1168        }
1169        enabled
1170    }
1171
1172    /// Configure multiple LED matrices with device model validation
1173    ///
1174    /// This method validates all configurations against the device model first,
1175    /// then applies the configurations and reserves the necessary pins.
1176    ///
1177    /// # Arguments
1178    ///
1179    /// * `configs` - Array of LED matrix configurations
1180    ///
1181    /// # Returns
1182    ///
1183    /// * `Result<()>` - Ok if all configurations were applied successfully
1184    pub fn configure_led_matrices(
1185        &mut self,
1186        configs: &[crate::matrix::LedMatrixConfig],
1187    ) -> Result<()> {
1188        // Basic validation (always performed)
1189        for config in configs {
1190            // Validate matrix ID
1191            if config.matrix_id < 1 || config.matrix_id > 2 {
1192                return Err(PoKeysError::InvalidConfiguration(
1193                    "Matrix ID must be 1 or 2".to_string(),
1194                ));
1195            }
1196
1197            // Validate character count (1-8 characters for 7-segment displays)
1198            if config.characters < 1 || config.characters > 8 {
1199                return Err(PoKeysError::InvalidConfiguration(
1200                    "Character count must be between 1 and 8 for 7-segment displays".to_string(),
1201                ));
1202            }
1203        }
1204
1205        // Device model validation (if available)
1206        for config in configs {
1207            if let Some(model) = &self.model {
1208                model.validate_led_matrix_config(config)?;
1209            }
1210        }
1211
1212        // Apply configurations
1213        let mut protocol_config = crate::matrix::MatrixLedProtocolConfig {
1214            display1_enabled: false,
1215            display2_enabled: false,
1216            display1_characters: 1,
1217            display2_characters: 1,
1218        };
1219
1220        for config in configs {
1221            match config.matrix_id {
1222                1 => {
1223                    protocol_config.display1_enabled = config.enabled;
1224                    protocol_config.display1_characters = config.characters;
1225                }
1226                2 => {
1227                    protocol_config.display2_enabled = config.enabled;
1228                    protocol_config.display2_characters = config.characters;
1229                }
1230                _ => {
1231                    return Err(PoKeysError::InvalidConfiguration(
1232                        "Invalid matrix ID".to_string(),
1233                    ));
1234                }
1235            }
1236        }
1237
1238        self.configure_led_matrix(&protocol_config)?;
1239
1240        // Reserve pins in model
1241        if let Some(model) = &mut self.model {
1242            for config in configs {
1243                if config.enabled {
1244                    model.reserve_led_matrix_pins(config.matrix_id)?;
1245                }
1246            }
1247        }
1248
1249        Ok(())
1250    }
1251
1252    /// Configure and control a servo motor
1253    pub fn configure_servo(&mut self, config: crate::pwm::ServoConfig) -> Result<()> {
1254        // Ensure PWM period is set for servo control (20ms = 500,000 cycles)
1255        if self.pwm.pwm_period == 0 {
1256            self.set_pwm_period(500000)?; // 20ms period for servo control
1257        }
1258
1259        // Enable PWM for the servo pin
1260        self.enable_pwm_for_pin(config.pin, true)?;
1261
1262        Ok(())
1263    }
1264
1265    /// Set servo angle (for position servos)
1266    pub fn set_servo_angle(&mut self, config: &crate::pwm::ServoConfig, angle: f32) -> Result<()> {
1267        config.set_angle(self, angle)
1268    }
1269
1270    /// Set servo speed (for speed servos)
1271    pub fn set_servo_speed(&mut self, config: &crate::pwm::ServoConfig, speed: f32) -> Result<()> {
1272        config.set_speed(self, speed)
1273    }
1274
1275    /// Stop servo (for speed servos)
1276    pub fn stop_servo(&mut self, config: &crate::pwm::ServoConfig) -> Result<()> {
1277        config.stop(self)
1278    }
1279}
1280
1281// Default implementations for device structures
1282impl Default for DeviceInfo {
1283    fn default() -> Self {
1284        Self {
1285            pin_count: 0,
1286            pwm_count: 0,
1287            basic_encoder_count: 0,
1288            encoders_count: 0,
1289            fast_encoders: 0,
1290            ultra_fast_encoders: 0,
1291            pwm_internal_frequency: 0,
1292            analog_inputs: 0,
1293            key_mapping: 0,
1294            triggered_key_mapping: 0,
1295            key_repeat_delay: 0,
1296            digital_counters: 0,
1297            joystick_button_axis_mapping: 0,
1298            joystick_analog_to_digital_mapping: 0,
1299            macros: 0,
1300            matrix_keyboard: 0,
1301            matrix_keyboard_triggered_mapping: 0,
1302            lcd: 0,
1303            matrix_led: 0,
1304            connection_signal: 0,
1305            po_ext_bus: 0,
1306            po_net: 0,
1307            analog_filtering: 0,
1308            init_outputs_start: 0,
1309            prot_i2c: 0,
1310            prot_1wire: 0,
1311            additional_options: 0,
1312            load_status: 0,
1313            custom_device_name: 0,
1314            po_tlog27_support: 0,
1315            sensor_list: 0,
1316            web_interface: 0,
1317            fail_safe_settings: 0,
1318            joystick_hat_switch: 0,
1319            pulse_engine: 0,
1320            pulse_engine_v2: 0,
1321            easy_sensors: 0,
1322        }
1323    }
1324}
1325
1326impl Default for RealTimeClock {
1327    fn default() -> Self {
1328        Self {
1329            sec: 0,
1330            min: 0,
1331            hour: 0,
1332            dow: 0,
1333            dom: 0,
1334            tmp: 0,
1335            doy: 0,
1336            month: 0,
1337            year: 0,
1338        }
1339    }
1340}
1341
1342// Device capability checking functions
1343fn check_pokeys57cnc_pin_capability(pin: u32, capability: crate::io::PinCapability) -> bool {
1344    use crate::io::PinCapability;
1345
1346    match pin {
1347        1..=2 => matches!(
1348            capability,
1349            PinCapability::DigitalInput
1350                | PinCapability::DigitalOutput
1351                | PinCapability::DigitalCounter
1352                | PinCapability::FastEncoder1A
1353        ),
1354        3..=6 => matches!(
1355            capability,
1356            PinCapability::DigitalInput
1357                | PinCapability::DigitalCounter
1358                | PinCapability::FastEncoder1A
1359        ),
1360        8 | 12..=13 => matches!(
1361            capability,
1362            PinCapability::DigitalInput | PinCapability::DigitalOutput
1363        ),
1364        9..=11 | 15..=16 => matches!(
1365            capability,
1366            PinCapability::DigitalInput | PinCapability::DigitalCounter
1367        ),
1368        14 => matches!(capability, PinCapability::DigitalInput),
1369        17..=18 | 20..=21 => matches!(
1370            capability,
1371            PinCapability::DigitalOutput | PinCapability::PwmOutput
1372        ),
1373        19 => matches!(
1374            capability,
1375            PinCapability::DigitalInput | PinCapability::DigitalCounter
1376        ),
1377        _ => false,
1378    }
1379}
1380
1381// Global device enumeration and connection functions
1382
1383static USB_DEVICE_LIST: LazyLock<Mutex<Vec<UsbDeviceInfo>>> =
1384    LazyLock::new(|| Mutex::new(Vec::new()));
1385#[allow(dead_code)]
1386static NETWORK_DEVICE_LIST: LazyLock<Mutex<Vec<NetworkDeviceSummary>>> =
1387    LazyLock::new(|| Mutex::new(Vec::new()));
1388
1389#[derive(Debug, Clone)]
1390#[allow(dead_code)]
1391struct UsbDeviceInfo {
1392    vendor_id: u16,
1393    product_id: u16,
1394    serial_number: Option<String>,
1395    path: String,
1396}
1397
1398/// Enumerate USB PoKeys devices
1399pub fn enumerate_usb_devices() -> Result<i32> {
1400    let mut device_list = USB_DEVICE_LIST
1401        .lock()
1402        .map_err(|_| PoKeysError::InternalError("Failed to lock USB device list".to_string()))?;
1403    device_list.clear();
1404
1405    // Platform-specific USB enumeration
1406    #[cfg(target_os = "macos")]
1407    {
1408        enumerate_usb_devices_macos(&mut device_list)
1409    }
1410    #[cfg(target_os = "linux")]
1411    {
1412        enumerate_usb_devices_linux(&mut device_list)
1413    }
1414    #[cfg(target_os = "windows")]
1415    {
1416        enumerate_usb_devices_windows(&mut device_list)
1417    }
1418    #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
1419    {
1420        Err(PoKeysError::NotSupported)
1421    }
1422}
1423
1424/// Enumerate network PoKeys devices
1425pub fn enumerate_network_devices(timeout_ms: u32) -> Result<Vec<NetworkDeviceSummary>> {
1426    use crate::network::discover_all_devices;
1427
1428    log::info!("Enumerating network devices with timeout {timeout_ms}ms");
1429
1430    // Use the network discovery implementation
1431    discover_all_devices(timeout_ms)
1432}
1433
1434/// Connect to USB device by index
1435pub fn connect_to_device(device_index: u32) -> Result<PoKeysDevice> {
1436    let device_list = USB_DEVICE_LIST
1437        .lock()
1438        .map_err(|_| PoKeysError::InternalError("Failed to lock USB device list".to_string()))?;
1439
1440    if device_index as usize >= device_list.len() {
1441        return Err(PoKeysError::Parameter("Invalid device index".to_string()));
1442    }
1443
1444    let device_info = device_list[device_index as usize].clone();
1445    drop(device_list); // Release the lock early
1446
1447    // Create device instance
1448    let mut device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
1449
1450    // Platform-specific USB connection
1451    #[cfg(target_os = "macos")]
1452    {
1453        device.usb_interface = Some(Box::new(connect_usb_device_macos(&device_info.path)?));
1454    }
1455    #[cfg(target_os = "linux")]
1456    {
1457        device.usb_interface = Some(Box::new(connect_usb_device_linux(&device_info.path)?));
1458    }
1459    #[cfg(target_os = "windows")]
1460    {
1461        device.usb_interface = Some(Box::new(connect_usb_device_windows(&device_info.path)?));
1462    }
1463
1464    // Get device data
1465    device.get_device_data()?;
1466
1467    Ok(device)
1468}
1469
1470/// Connect to device by serial number
1471pub fn connect_to_device_with_serial(
1472    serial_number: u32,
1473    check_network: bool,
1474    timeout_ms: u32,
1475) -> Result<PoKeysDevice> {
1476    if check_network {
1477        let network_devices = enumerate_network_devices(timeout_ms)?;
1478        for network_device in network_devices {
1479            if network_device.serial_number == serial_number {
1480                return connect_to_network_device(&network_device);
1481            }
1482        }
1483    }
1484
1485    // First try USB devices
1486    let usb_count = enumerate_usb_devices()?;
1487
1488    for i in 0..usb_count {
1489        if let Ok(device) = connect_to_device(i as u32) {
1490            if device.device_data.serial_number == serial_number {
1491                return Ok(device);
1492            }
1493        }
1494    }
1495
1496    Err(PoKeysError::CannotConnect)
1497}
1498
1499/// Connect to network device
1500pub fn connect_to_network_device(device_summary: &NetworkDeviceSummary) -> Result<PoKeysDevice> {
1501    let mut device = PoKeysDevice::new(DeviceConnectionType::NetworkDevice);
1502
1503    // Create network interface based on device settings
1504    if device_summary.use_udp != 0 {
1505        device.connection_param = ConnectionParam::Udp;
1506    } else {
1507        device.connection_param = ConnectionParam::Tcp;
1508    }
1509
1510    // Platform-specific network connection
1511    #[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
1512    {
1513        device.network_interface = Some(connect_network_device(device_summary)?);
1514    }
1515
1516    // Get device data
1517    device.get_device_data()?;
1518
1519    Ok(device)
1520}
1521
1522// Stub implementations for compilation
1523struct StubUsbInterface;
1524
1525impl UsbHidInterface for StubUsbInterface {
1526    fn write(&mut self, _data: &[u8]) -> Result<usize> {
1527        Err(PoKeysError::NotSupported)
1528    }
1529
1530    fn read(&mut self, _buffer: &mut [u8]) -> Result<usize> {
1531        Err(PoKeysError::NotSupported)
1532    }
1533
1534    fn read_timeout(&mut self, _buffer: &mut [u8], _timeout: Duration) -> Result<usize> {
1535        Err(PoKeysError::NotSupported)
1536    }
1537}
1538
1539#[cfg(target_os = "macos")]
1540fn enumerate_usb_devices_macos(device_list: &mut Vec<UsbDeviceInfo>) -> Result<i32> {
1541    use std::process::Command;
1542
1543    // Use system_profiler to get USB device information
1544    let output = Command::new("system_profiler")
1545        .args(["SPUSBDataType", "-xml"])
1546        .output()
1547        .map_err(|e| PoKeysError::InternalError(format!("Failed to run system_profiler: {}", e)))?;
1548
1549    if !output.status.success() {
1550        return Err(PoKeysError::InternalError(
1551            "system_profiler command failed".to_string(),
1552        ));
1553    }
1554
1555    let output_str = String::from_utf8_lossy(&output.stdout);
1556
1557    // Look for PoKeys devices (vendor ID 0x1DC3)
1558    // This is a simple text-based search since we don't want to add XML parsing dependencies
1559    let lines: Vec<&str> = output_str.lines().collect();
1560    let mut i = 0;
1561
1562    while i < lines.len() {
1563        let line = lines[i].trim();
1564
1565        // Look for vendor_id entries
1566        if line.contains("<key>vendor_id</key>") && i + 1 < lines.len() {
1567            let next_line = lines[i + 1].trim();
1568
1569            // Check if it's a PoKeys device (vendor ID 0x1DC3 = 7619 decimal)
1570            if next_line.contains("<integer>7619</integer>")
1571                || next_line.contains("<string>0x1dc3</string>")
1572            {
1573                // Found a PoKeys device, now look for product_id and serial
1574                let mut product_id = 0u16;
1575                let mut serial_number = None;
1576                let mut location_id = String::new();
1577
1578                // Search forward for product_id and serial_number
1579                for j in (i + 2)..(i + 50).min(lines.len()) {
1580                    let search_line = lines[j].trim();
1581
1582                    if search_line.contains("<key>product_id</key>") && j + 1 < lines.len() {
1583                        let product_line = lines[j + 1].trim();
1584                        if let Some(start) = product_line.find("<integer>") {
1585                            if let Some(end) = product_line.find("</integer>") {
1586                                if let Ok(pid) = product_line[start + 9..end].parse::<u16>() {
1587                                    product_id = pid;
1588                                }
1589                            }
1590                        }
1591                    }
1592
1593                    if search_line.contains("<key>serial_num</key>") && j + 1 < lines.len() {
1594                        let serial_line = lines[j + 1].trim();
1595                        if let Some(start) = serial_line.find("<string>") {
1596                            if let Some(end) = serial_line.find("</string>") {
1597                                serial_number = Some(serial_line[start + 8..end].to_string());
1598                            }
1599                        }
1600                    }
1601
1602                    if search_line.contains("<key>location_id</key>") && j + 1 < lines.len() {
1603                        let location_line = lines[j + 1].trim();
1604                        if let Some(start) = location_line.find("<string>") {
1605                            if let Some(end) = location_line.find("</string>") {
1606                                location_id = location_line[start + 8..end].to_string();
1607                            }
1608                        }
1609                    }
1610
1611                    // Stop searching when we hit the next device or end of this device
1612                    if search_line.contains("<key>vendor_id</key>") && j > i + 2 {
1613                        break;
1614                    }
1615                }
1616
1617                // Add the device to our list
1618                device_list.push(UsbDeviceInfo {
1619                    vendor_id: 0x1DC3,
1620                    product_id,
1621                    serial_number,
1622                    path: format!("macos_usb_{}", location_id),
1623                });
1624            }
1625        }
1626        i += 1;
1627    }
1628
1629    Ok(device_list.len() as i32)
1630}
1631
1632#[cfg(target_os = "linux")]
1633fn enumerate_usb_devices_linux(device_list: &mut Vec<UsbDeviceInfo>) -> Result<i32> {
1634    // Linux-specific USB enumeration using libudev
1635    // This is a placeholder implementation
1636    // When implemented, add devices to device_list
1637    Ok(device_list.len() as i32)
1638}
1639
1640#[cfg(target_os = "windows")]
1641fn enumerate_usb_devices_windows(device_list: &mut Vec<UsbDeviceInfo>) -> Result<i32> {
1642    // Windows-specific USB enumeration using WinAPI
1643    // This is a placeholder implementation
1644    // When implemented, add devices to device_list
1645    Ok(device_list.len() as i32)
1646}
1647
1648// Network connection function
1649#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
1650fn connect_network_device(
1651    device_summary: &NetworkDeviceSummary,
1652) -> Result<Box<dyn NetworkInterface>> {
1653    use crate::network::{TcpNetworkInterface, UdpNetworkInterface};
1654
1655    if device_summary.use_udp != 0 {
1656        // Use UDP connection
1657        let interface = UdpNetworkInterface::new(device_summary.ip_address, 20055)?;
1658        Ok(Box::new(interface))
1659    } else {
1660        // Use TCP connection
1661        let interface = TcpNetworkInterface::new(device_summary.ip_address, 20055)?;
1662        Ok(Box::new(interface))
1663    }
1664}
1665
1666// Placeholder USB connection functions
1667#[cfg(target_os = "macos")]
1668fn connect_usb_device_macos(_path: &str) -> Result<StubUsbInterface> {
1669    Err(PoKeysError::NotSupported)
1670}
1671
1672#[cfg(target_os = "linux")]
1673fn connect_usb_device_linux(_path: &str) -> Result<StubUsbInterface> {
1674    Err(PoKeysError::NotSupported)
1675}
1676
1677#[cfg(target_os = "windows")]
1678fn connect_usb_device_windows(_path: &str) -> Result<StubUsbInterface> {
1679    Err(PoKeysError::NotSupported)
1680}
1681
1682/// Parse the "Get system load status" (0x05) response payload.
1683///
1684/// Protocol byte 3 (0-based index 2) holds the system load percentage.
1685fn parse_system_load_response(response: &[u8]) -> u8 {
1686    response[2]
1687}
1688
1689#[cfg(test)]
1690mod tests {
1691    use super::*;
1692
1693    #[test]
1694    fn test_device_creation() {
1695        let device = PoKeysDevice::new(DeviceConnectionType::UsbDevice);
1696        assert_eq!(device.connection_type, DeviceConnectionType::UsbDevice);
1697        assert_eq!(device.pins.len(), 0); // Not initialized yet
1698    }
1699
1700    #[test]
1701    fn test_parse_system_load_response() {
1702        let mut response = [0u8; RESPONSE_BUFFER_SIZE];
1703        response[1] = 0x05; // echo of command ID in byte 2
1704        response[2] = 42; // system load % in byte 3
1705        assert_eq!(parse_system_load_response(&response), 42);
1706
1707        response[2] = 0;
1708        assert_eq!(parse_system_load_response(&response), 0);
1709
1710        response[2] = 100;
1711        assert_eq!(parse_system_load_response(&response), 100);
1712    }
1713
1714    #[test]
1715    fn test_pin_capability_checking() {
1716        // Test PoKeys57CNC pin capabilities
1717        assert!(check_pokeys57cnc_pin_capability(
1718            1,
1719            crate::io::PinCapability::DigitalInput
1720        ));
1721        assert!(check_pokeys57cnc_pin_capability(
1722            1,
1723            crate::io::PinCapability::DigitalOutput
1724        ));
1725        assert!(!check_pokeys57cnc_pin_capability(
1726            14,
1727            crate::io::PinCapability::DigitalOutput
1728        )); // Input only
1729        assert!(!check_pokeys57cnc_pin_capability(
1730            100,
1731            crate::io::PinCapability::DigitalInput
1732        )); // Invalid pin
1733    }
1734
1735    // Hardware tests - only run when hardware-tests feature is enabled
1736    #[cfg(feature = "hardware-tests")]
1737    mod hardware_tests {
1738        use super::*;
1739        use std::thread;
1740        use std::time::Duration;
1741
1742        #[test]
1743        fn test_hardware_device_enumeration() {
1744            println!("Testing hardware device enumeration...");
1745
1746            match enumerate_usb_devices() {
1747                Ok(count) => {
1748                    println!("Found {} USB PoKeys devices", count);
1749                    if count == 0 {
1750                        println!(
1751                            "WARNING: No PoKeys devices found. Connect a device to run hardware tests."
1752                        );
1753                    }
1754                }
1755                Err(e) => {
1756                    panic!("Failed to enumerate USB devices: {}", e);
1757                }
1758            }
1759        }
1760
1761        #[test]
1762        fn test_hardware_device_connection() {
1763            println!("Testing hardware device connection...");
1764
1765            let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
1766
1767            if device_count == 0 {
1768                println!("SKIP: No PoKeys devices found for hardware test");
1769                return;
1770            }
1771
1772            match connect_to_device(0) {
1773                Ok(mut device) => {
1774                    println!("Successfully connected to device");
1775
1776                    // Test getting device data
1777                    match device.get_device_data() {
1778                        Ok(_) => {
1779                            println!("Device Serial: {}", device.device_data.serial_number);
1780                            println!(
1781                                "Firmware: {}.{}",
1782                                device.device_data.firmware_version_major,
1783                                device.device_data.firmware_version_minor
1784                            );
1785                            println!("Pin Count: {}", device.info.pin_count);
1786                        }
1787                        Err(e) => {
1788                            println!("WARNING: Could not get device data: {}", e);
1789                        }
1790                    }
1791                }
1792                Err(e) => {
1793                    panic!("Failed to connect to device: {}", e);
1794                }
1795            }
1796        }
1797
1798        #[test]
1799        fn test_hardware_digital_io() {
1800            println!("Testing hardware digital I/O...");
1801
1802            let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
1803            if device_count == 0 {
1804                println!("SKIP: No PoKeys devices found for hardware test");
1805                return;
1806            }
1807
1808            let mut device = connect_to_device(0).expect("Failed to connect to device");
1809            device.get_device_data().expect("Failed to get device data");
1810
1811            // Test setting pin as digital output
1812            match device.set_pin_function(1, crate::io::PinFunction::DigitalOutput) {
1813                Ok(_) => {
1814                    println!("Successfully configured pin 1 as digital output");
1815
1816                    // Test setting output high and low
1817                    for state in [true, false, true, false] {
1818                        match device.set_digital_output(1, state) {
1819                            Ok(_) => {
1820                                println!("Set pin 1 to {}", if state { "HIGH" } else { "LOW" })
1821                            }
1822                            Err(e) => println!("WARNING: Failed to set digital output: {}", e),
1823                        }
1824                        thread::sleep(Duration::from_millis(100));
1825                    }
1826                }
1827                Err(e) => {
1828                    println!("WARNING: Could not configure pin function: {}", e);
1829                }
1830            }
1831        }
1832
1833        #[test]
1834        fn test_hardware_analog_input() {
1835            println!("Testing hardware analog input...");
1836
1837            let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
1838            if device_count == 0 {
1839                println!("SKIP: No PoKeys devices found for hardware test");
1840                return;
1841            }
1842
1843            let mut device = connect_to_device(0).expect("Failed to connect to device");
1844            device.get_device_data().expect("Failed to get device data");
1845
1846            // Test reading analog input
1847            match device.get_analog_input(1) {
1848                Ok(value) => {
1849                    println!("Analog input 1 value: {}", value);
1850                    // Basic sanity check - value should be within ADC range
1851                    assert!(value <= 4095, "Analog value out of range for 12-bit ADC");
1852                }
1853                Err(e) => {
1854                    println!("WARNING: Could not read analog input: {}", e);
1855                }
1856            }
1857        }
1858
1859        #[test]
1860        fn test_hardware_pwm_output() {
1861            println!("Testing hardware PWM output...");
1862
1863            let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
1864            if device_count == 0 {
1865                println!("SKIP: No PoKeys devices found for hardware test");
1866                return;
1867            }
1868
1869            let mut device = connect_to_device(0).expect("Failed to connect to device");
1870            device.get_device_data().expect("Failed to get device data");
1871
1872            // Test PWM configuration
1873            match device.set_pwm_period(25000) {
1874                Ok(_) => {
1875                    println!("Set PWM period to 25000 cycles");
1876
1877                    // Test different duty cycles
1878                    for duty in [25.0, 50.0, 75.0, 0.0] {
1879                        match device.set_pwm_duty_cycle_percent_for_pin(17, duty) {
1880                            Ok(_) => {
1881                                println!("Set PWM duty cycle to {}%", duty);
1882                                thread::sleep(Duration::from_millis(200));
1883                            }
1884                            Err(e) => {
1885                                println!("WARNING: Could not set PWM duty cycle: {}", e);
1886                            }
1887                        }
1888                    }
1889                }
1890                Err(e) => {
1891                    println!("WARNING: Could not configure PWM: {}", e);
1892                }
1893            }
1894        }
1895
1896        #[test]
1897        fn test_hardware_encoder_reading() {
1898            println!("Testing hardware encoder reading...");
1899
1900            let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
1901            if device_count == 0 {
1902                println!("SKIP: No PoKeys devices found for hardware test");
1903                return;
1904            }
1905
1906            let mut device = connect_to_device(0).expect("Failed to connect to device");
1907            device.get_device_data().expect("Failed to get device data");
1908
1909            // Test encoder configuration
1910            let mut options = crate::encoders::EncoderOptions::new();
1911            options.enabled = true;
1912            options.sampling_4x = true;
1913
1914            match device.configure_encoder(0, 1, 2, options) {
1915                Ok(_) => {
1916                    println!("Configured encoder 0 on pins 1 and 2");
1917
1918                    // Read encoder value multiple times
1919                    for i in 0..5 {
1920                        match device.get_encoder_value(0) {
1921                            Ok(value) => {
1922                                println!("Encoder 0 reading {}: {}", i + 1, value);
1923                            }
1924                            Err(e) => {
1925                                println!("WARNING: Could not read encoder: {}", e);
1926                            }
1927                        }
1928                        thread::sleep(Duration::from_millis(100));
1929                    }
1930                }
1931                Err(e) => {
1932                    println!("WARNING: Could not configure encoder: {}", e);
1933                }
1934            }
1935        }
1936
1937        #[test]
1938        fn test_hardware_network_discovery() {
1939            println!("Testing hardware network device discovery...");
1940
1941            match enumerate_network_devices(2000) {
1942                Ok(devices) => {
1943                    println!("Found {} network PoKeys devices", devices.len());
1944
1945                    for (i, device) in devices.iter().enumerate() {
1946                        println!(
1947                            "Network device {}: Serial {}, IP {}.{}.{}.{}",
1948                            i + 1,
1949                            device.serial_number,
1950                            device.ip_address[0],
1951                            device.ip_address[1],
1952                            device.ip_address[2],
1953                            device.ip_address[3]
1954                        );
1955                    }
1956
1957                    if devices.is_empty() {
1958                        println!(
1959                            "No network devices found - this is normal if no network PoKeys devices are present"
1960                        );
1961                    }
1962                }
1963                Err(e) => {
1964                    println!("WARNING: Network discovery failed: {}", e);
1965                }
1966            }
1967        }
1968
1969        #[test]
1970        fn test_hardware_device_info_validation() {
1971            println!("Testing hardware device info validation...");
1972
1973            let device_count = enumerate_usb_devices().expect("Failed to enumerate devices");
1974            if device_count == 0 {
1975                println!("SKIP: No PoKeys devices found for hardware test");
1976                return;
1977            }
1978
1979            let mut device = connect_to_device(0).expect("Failed to connect to device");
1980            device.get_device_data().expect("Failed to get device data");
1981
1982            // Validate device information makes sense
1983            assert!(
1984                device.device_data.serial_number > 0,
1985                "Serial number should be non-zero"
1986            );
1987            assert!(device.info.pin_count > 0, "Pin count should be non-zero");
1988            assert!(
1989                device.info.pin_count <= 100,
1990                "Pin count should be reasonable"
1991            );
1992
1993            println!("Device validation passed:");
1994            println!("  Serial: {}", device.device_data.serial_number);
1995            println!("  Pins: {}", device.info.pin_count);
1996            println!("  PWM Channels: {}", device.info.pwm_count);
1997            println!("  Encoders: {}", device.info.encoders_count);
1998            println!("  Analog Inputs: {}", device.info.analog_inputs);
1999        }
2000    }
2001}