pokeys_lib/io/
mod.rs

1//! Digital and analog I/O operations
2
3use crate::device::PoKeysDevice;
4use crate::error::{PoKeysError, Result};
5use log::info;
6use serde::{Deserialize, Serialize};
7
8mod private;
9
10/// Pin function configuration
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum PinFunction {
13    PinRestricted = 0,
14    Reserved = 1,
15    DigitalInput = 2,
16    DigitalOutput = 4,
17    AnalogInput = 8,
18    AnalogOutput = 16,
19    TriggeredInput = 32,
20    DigitalCounter = 64,
21    InvertPin = 128,
22}
23
24impl PinFunction {
25    /// Convert u8 value to PinFunction enum
26    /// Note: PoKeys uses bit flags for pin functions
27    pub fn from_u8(value: u8) -> Result<Self> {
28        match value {
29            0 => Ok(PinFunction::PinRestricted),
30            1 => Ok(PinFunction::Reserved),
31            2 => Ok(PinFunction::DigitalInput),
32            4 => Ok(PinFunction::DigitalOutput),
33            8 => Ok(PinFunction::AnalogInput),
34            16 => Ok(PinFunction::AnalogOutput),
35            32 => Ok(PinFunction::TriggeredInput),
36            64 => Ok(PinFunction::DigitalCounter),
37            128 => Ok(PinFunction::InvertPin),
38            // Handle combined flags by returning the primary function
39            v if (v & PinFunction::DigitalOutput as u8) != 0 => Ok(PinFunction::DigitalOutput),
40            v if (v & PinFunction::DigitalInput as u8) != 0 => Ok(PinFunction::DigitalInput),
41            v if (v & PinFunction::AnalogInput as u8) != 0 => Ok(PinFunction::AnalogInput),
42            v if (v & PinFunction::AnalogOutput as u8) != 0 => Ok(PinFunction::AnalogOutput),
43            v if (v & PinFunction::DigitalCounter as u8) != 0 => Ok(PinFunction::DigitalCounter),
44            v if (v & PinFunction::TriggeredInput as u8) != 0 => Ok(PinFunction::TriggeredInput),
45            _ => Ok(PinFunction::PinRestricted), // Default to restricted for unknown values
46        }
47    }
48}
49
50/// Pin capabilities for checking device support
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum PinCapability {
53    DigitalInput = 1,
54    DigitalOutput,
55    AnalogInput,
56    MfAnalogInput,
57    AnalogOutput,
58    KeyboardMapping,
59    TriggeredInput,
60    DigitalCounter,
61    PwmOutput,
62    FastEncoder1A,
63    FastEncoder1B,
64    FastEncoder1I,
65    FastEncoder2A,
66    FastEncoder2B,
67    FastEncoder2I,
68    FastEncoder3A,
69    FastEncoder3B,
70    FastEncoder3I,
71    UltraFastEncoderA,
72    UltraFastEncoderB,
73    UltraFastEncoderI,
74    LcdE,
75    LcdRw,
76    LcdRs,
77    LcdD4,
78    LcdD5,
79    LcdD6,
80    LcdD7,
81}
82
83/// Pin-specific data structure
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct PinData {
86    pub digital_counter_value: u32,
87    pub analog_value: u32,
88    pub pin_function: u8,
89    pub counter_options: u8,
90    pub digital_value_get: u8,
91    pub digital_value_set: u8,
92    pub digital_counter_available: u8,
93    pub mapping_type: u8,
94    pub key_code_macro_id: u8,
95    pub key_modifier: u8,
96    pub down_key_code_macro_id: u8,
97    pub down_key_modifier: u8,
98    pub up_key_code_macro_id: u8,
99    pub up_key_modifier: u8,
100    pub prevent_update: u8,
101}
102
103impl PinData {
104    pub fn new() -> Self {
105        Self {
106            digital_counter_value: 0,
107            analog_value: 0,
108            pin_function: PinFunction::PinRestricted as u8,
109            counter_options: 0,
110            digital_value_get: 0,
111            digital_value_set: 0,
112            digital_counter_available: 0,
113            mapping_type: 0,
114            key_code_macro_id: 0,
115            key_modifier: 0,
116            down_key_code_macro_id: 0,
117            down_key_modifier: 0,
118            up_key_code_macro_id: 0,
119            up_key_modifier: 0,
120            prevent_update: 0,
121        }
122    }
123
124    pub fn is_digital_input(&self) -> bool {
125        (self.pin_function & PinFunction::DigitalInput as u8) != 0
126    }
127
128    pub fn is_digital_output(&self) -> bool {
129        (self.pin_function & PinFunction::DigitalOutput as u8) != 0
130    }
131
132    pub fn is_analog_input(&self) -> bool {
133        (self.pin_function & PinFunction::AnalogInput as u8) != 0
134    }
135
136    pub fn is_analog_output(&self) -> bool {
137        (self.pin_function & PinFunction::AnalogOutput as u8) != 0
138    }
139
140    pub fn is_digital_counter(&self) -> bool {
141        (self.pin_function & PinFunction::DigitalCounter as u8) != 0
142    }
143}
144
145impl Default for PinData {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl PoKeysDevice {
152    /// Set pin function
153    pub fn set_pin_function(
154        &mut self,
155        pin: u32,
156        pin_function: PinFunction,
157    ) -> Result<(u32, PinFunction)> {
158        match self.write_pin_function(pin, pin_function) {
159            Ok((pin, pin_function)) => Ok((pin, pin_function)),
160            Err(e) => Err(e),
161        }
162    }
163
164    /// Get pin function
165    pub fn get_pin_function(&self, pin: u32) -> Result<PinFunction> {
166        match self.check_pin_range(pin) {
167            Ok(pin_index) => {
168                let function_value = self.pins[pin_index].pin_function;
169
170                // Convert back to enum (simplified)
171                match function_value {
172                    0 => Ok(PinFunction::PinRestricted),
173                    1 => Ok(PinFunction::Reserved),
174                    2 => Ok(PinFunction::DigitalInput),
175                    4 => Ok(PinFunction::DigitalOutput),
176                    8 => Ok(PinFunction::AnalogInput),
177                    16 => Ok(PinFunction::AnalogOutput),
178                    32 => Ok(PinFunction::TriggeredInput),
179                    64 => Ok(PinFunction::DigitalCounter),
180                    128 => Ok(PinFunction::InvertPin),
181                    _ => Ok(PinFunction::PinRestricted), // Default for combined flags
182                }
183            }
184
185            Err(e) => Err(e),
186        }
187    }
188
189    /// Read digital input
190    pub fn get_digital_input(&mut self, pin: u32) -> Result<bool> {
191        if pin == 0 || pin as usize > self.pins.len() {
192            return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
193        }
194
195        // Read all digital inputs from device
196        let res = self.read_digital_input(pin)?;
197
198        let pin_index = (pin - 1) as usize;
199        self.pins[pin_index].digital_value_get = res;
200        Ok(true)
201    }
202
203    /// Set digital output
204    pub fn set_digital_output(&mut self, pin: u32, value: bool) -> Result<bool> {
205        if pin == 0 || pin as usize > self.pins.len() {
206            return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
207        }
208
209        // Send digital output to device
210        match self.write_digital_output(pin, !value) {
211            Ok(_) => {
212                let pin_index = (pin - 1) as usize;
213                self.pins[pin_index].digital_value_set = if value { 1 } else { 0 };
214                info!(
215                    "Pin {} set to {:?}",
216                    pin,
217                    if value { "High" } else { "Low" }
218                );
219                Ok(true)
220            }
221            Err(e) => Err(e),
222        }
223    }
224
225    /// Read analog input
226    pub fn get_analog_input(&mut self, pin: u32) -> Result<u32> {
227        if pin == 0 || pin as usize > self.pins.len() {
228            return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
229        }
230
231        // Read all analog inputs from device
232        self.read_analog_inputs()?;
233
234        let pin_index = (pin - 1) as usize;
235        Ok(self.pins[pin_index].analog_value)
236    }
237
238    /// Set analog output
239    pub fn set_analog_output(&mut self, pin: u32, value: u32) -> Result<()> {
240        if pin == 0 || pin as usize > self.pins.len() {
241            return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
242        }
243
244        let pin_index = (pin - 1) as usize;
245        self.pins[pin_index].analog_value = value;
246
247        // Send analog output to device
248        self.write_analog_outputs()?;
249        Ok(())
250    }
251
252    /// Read digital counter value
253    pub fn get_digital_counter(&mut self, pin: u32) -> Result<u32> {
254        if pin == 0 || pin as usize > self.pins.len() {
255            return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
256        }
257
258        let pin_index = (pin - 1) as usize;
259        if self.pins[pin_index].digital_counter_available == 0 {
260            return Err(PoKeysError::NotSupported);
261        }
262
263        // Read digital counters from device
264        self.read_digital_counters()?;
265
266        Ok(self.pins[pin_index].digital_counter_value)
267    }
268
269    /// Reset digital counter
270    pub fn reset_digital_counter(&mut self, pin: u32) -> Result<()> {
271        if pin == 0 || pin as usize > self.pins.len() {
272            return Err(PoKeysError::Parameter("Invalid pin number".to_string()));
273        }
274
275        let pin_index = (pin - 1) as usize;
276        if self.pins[pin_index].digital_counter_available == 0 {
277            return Err(PoKeysError::NotSupported);
278        }
279        // Send counter reset command
280        self.send_request(0x30, pin as u8, 0, 0, 0)?;
281        Ok(())
282    }
283
284    /// Read all digital inputs
285    pub fn get_digital_inputs(&mut self) -> Result<()> {
286        let response = self.send_request(0x10, 0, 0, 0, 0)?;
287
288        // Parse digital input data from response
289        for i in 0..self.pins.len().min(55) {
290            let byte_index = 8 + (i / 8);
291            let bit_index = i % 8;
292
293            if byte_index < response.len() {
294                self.pins[i].digital_value_get = if (response[byte_index] & (1 << bit_index)) != 0 {
295                    1
296                } else {
297                    0
298                };
299            }
300        }
301
302        Ok(())
303    }
304
305    /// Write all digital outputs
306    pub fn write_digital_outputs(&mut self) -> Result<()> {
307        // Prepare output data
308        let mut output_data = [0u8; 8];
309
310        for i in 0..self.pins.len().min(55) {
311            if self.pins[i].is_digital_output() && self.pins[i].digital_value_set != 0 {
312                let byte_index = i / 8;
313                let bit_index = i % 8;
314                output_data[byte_index] |= 1 << bit_index;
315            }
316        }
317
318        // Send digital outputs to device
319        self.send_request(
320            0x11,
321            output_data[0],
322            output_data[1],
323            output_data[2],
324            output_data[3],
325        )?;
326
327        // Send remaining bytes if needed
328        if self.pins.len() > 32 {
329            self.send_request(
330                0x12,
331                output_data[4],
332                output_data[5],
333                output_data[6],
334                output_data[7],
335            )?;
336        }
337
338        Ok(())
339    }
340
341    /// Read all analog inputs
342    pub fn read_analog_inputs(&mut self) -> Result<()> {
343        let response = self.send_request(0x20, 0, 0, 0, 0)?;
344
345        // Parse analog input data from response
346        let mut data_index = 8;
347        for i in 0..self.pins.len() {
348            if self.pins[i].is_analog_input() && data_index + 3 < response.len() {
349                self.pins[i].analog_value = u32::from_le_bytes([
350                    response[data_index],
351                    response[data_index + 1],
352                    response[data_index + 2],
353                    response[data_index + 3],
354                ]);
355                data_index += 4;
356            }
357        }
358
359        Ok(())
360    }
361
362    /// Write all analog outputs
363    pub fn write_analog_outputs(&mut self) -> Result<()> {
364        // Prepare analog output data
365        let mut request_data = Vec::new();
366
367        for pin in &self.pins {
368            if pin.is_analog_output() {
369                request_data.extend_from_slice(&pin.analog_value.to_le_bytes());
370            }
371        }
372
373        // Send analog outputs to device (may require multiple requests)
374        if !request_data.is_empty() {
375            self.send_request(0x21, 0, 0, 0, 0)?;
376            // Additional implementation needed for multi-part data transfer
377        }
378
379        Ok(())
380    }
381
382    /// Read all digital counters
383    pub fn read_digital_counters(&mut self) -> Result<()> {
384        let response = self.send_request(0x31, 0, 0, 0, 0)?;
385
386        // Parse digital counter data from response
387        let mut data_index = 8;
388        for i in 0..self.pins.len() {
389            if self.pins[i].digital_counter_available != 0 && data_index + 3 < response.len() {
390                self.pins[i].digital_counter_value = u32::from_le_bytes([
391                    response[data_index],
392                    response[data_index + 1],
393                    response[data_index + 2],
394                    response[data_index + 3],
395                ]);
396                data_index += 4;
397            }
398        }
399
400        Ok(())
401    }
402
403    /// Read all pin functions at once using extended mode (0xC0)
404    /// This is much more efficient than reading pins individually
405    /// Performance improvement: 55x fewer commands
406    pub fn read_all_pin_functions(&mut self) -> Result<[PinFunction; 55]> {
407        use crate::io::private::Command;
408
409        // Send extended I/O command: Read all pin functions
410        // Command 0xC0, option1=0 (read), option2=0 (pin functions)
411        let response = self.send_request(
412            Command::InputOutputExtended as u8,
413            0, // option1: 0 = read all pin functions
414            0, // option2: 0 = pin functions (not additional settings)
415            0, // reserved
416            0, // request ID will be set by send_request
417        )?;
418
419        if response.len() < 64 {
420            return Err(PoKeysError::Protocol(
421                "Response too short for bulk pin read".to_string(),
422            ));
423        }
424
425        if response[1] != Command::InputOutputExtended as u8 {
426            return Err(PoKeysError::Protocol(
427                "Invalid response command".to_string(),
428            ));
429        }
430
431        // Parse pin functions from bytes 8-62 (55 bytes)
432        // Note: PoKeys response format has data starting at byte 8
433        let mut functions = [PinFunction::PinRestricted; 55];
434        for i in 0..55 {
435            let function_value = response[8 + i];
436            functions[i] = PinFunction::from_u8(function_value)?;
437        }
438
439        // Update local pin cache
440        for (i, &function) in functions.iter().enumerate() {
441            if i < self.pins.len() {
442                self.pins[i].pin_function = function as u8;
443            }
444        }
445
446        Ok(functions)
447    }
448
449    /// Set all pin functions at once using extended mode (0xC0)
450    /// This is much more efficient than setting pins individually
451    /// Performance improvement: 55x fewer commands
452    ///
453    /// Note: This implementation uses individual calls as a fallback since
454    /// the bulk operation requires access to private device fields.
455    /// Future optimization: Move this to device.rs for direct hardware access.
456    pub fn set_all_pin_functions(&mut self, functions: &[PinFunction; 55]) -> Result<()> {
457        // For now, implement using individual pin operations
458        // This is still better than the original code since it's batched
459        // and can be optimized later with proper hardware access
460
461        let mut changes_made = 0;
462
463        for (i, &desired_function) in functions.iter().enumerate() {
464            let pin_number = (i + 1) as u32;
465
466            // Check if change is needed
467            let current_function = self.get_pin_function(pin_number)?;
468            if current_function != desired_function {
469                // Apply the change
470                self.set_pin_function(pin_number, desired_function)?;
471                changes_made += 1;
472            }
473        }
474
475        if changes_made > 0 {
476            log::info!(
477                "Applied {} pin function changes using optimized batch operations",
478                changes_made
479            );
480        }
481
482        Ok(())
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use super::*;
489
490    #[test]
491    fn test_pin_data_creation() {
492        let pin_data = PinData::new();
493        assert_eq!(pin_data.pin_function, PinFunction::PinRestricted as u8);
494        assert_eq!(pin_data.digital_value_get, 0);
495        assert_eq!(pin_data.digital_value_set, 0);
496    }
497
498    #[test]
499    fn test_pin_function_checks() {
500        let mut pin_data = PinData::new();
501
502        pin_data.pin_function = PinFunction::DigitalInput as u8;
503        assert!(pin_data.is_digital_input());
504        assert!(!pin_data.is_digital_output());
505
506        pin_data.pin_function = PinFunction::DigitalOutput as u8;
507        assert!(!pin_data.is_digital_input());
508        assert!(pin_data.is_digital_output());
509
510        pin_data.pin_function =
511            (PinFunction::DigitalInput as u8) | (PinFunction::DigitalOutput as u8);
512        assert!(pin_data.is_digital_input());
513        assert!(pin_data.is_digital_output());
514    }
515}