Skip to main content

pokeys_lib/
encoders.rs

1//! Encoder support for PoKeys devices
2//!
3//! This module implements the complete PoKeys encoder protocol specification,
4//! supporting up to 25 normal encoders, 3 fast encoders, and 1 ultra-fast encoder.
5//!
6//! Features:
7//! - 4x and 2x sampling modes for precise position tracking
8//! - Key mapping for encoder directions
9//! - Bulk operations for efficient multi-encoder management
10//! - Fast and ultra-fast encoder support for high-speed applications
11
12use crate::device::PoKeysDevice;
13use crate::error::{PoKeysError, Result};
14use serde::{Deserialize, Serialize};
15
16/// Maximum number of normal encoders supported
17pub const MAX_ENCODERS: usize = 25;
18
19/// Maximum number of fast encoders supported  
20pub const MAX_FAST_ENCODERS: usize = 3;
21
22/// Ultra-fast encoder index (encoder 25)
23pub const ULTRA_FAST_ENCODER_INDEX: u8 = 25;
24
25/// Encoder configuration options
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct EncoderOptions {
28    /// Enable encoder
29    pub enabled: bool,
30    /// 4x sampling mode (both A and B edges counted)
31    pub sampling_4x: bool,
32    /// 2x sampling mode (only A edges counted)
33    pub sampling_2x: bool,
34    /// Direct key mapping for direction A
35    pub direct_key_mapping_a: bool,
36    /// Macro mapping for direction A
37    pub macro_mapping_a: bool,
38    /// Direct key mapping for direction B
39    pub direct_key_mapping_b: bool,
40    /// Macro mapping for direction B
41    pub macro_mapping_b: bool,
42}
43
44impl Default for EncoderOptions {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl EncoderOptions {
51    /// Create new encoder options with all features disabled
52    pub fn new() -> Self {
53        Self {
54            enabled: false,
55            sampling_4x: false,
56            sampling_2x: false,
57            direct_key_mapping_a: false,
58            macro_mapping_a: false,
59            direct_key_mapping_b: false,
60            macro_mapping_b: false,
61        }
62    }
63
64    /// Create encoder options with 4x sampling enabled
65    pub fn with_4x_sampling() -> Self {
66        Self {
67            enabled: true,
68            sampling_4x: true,
69            sampling_2x: false,
70            direct_key_mapping_a: false,
71            macro_mapping_a: false,
72            direct_key_mapping_b: false,
73            macro_mapping_b: false,
74        }
75    }
76
77    /// Create encoder options with 2x sampling enabled
78    pub fn with_2x_sampling() -> Self {
79        Self {
80            enabled: true,
81            sampling_4x: false,
82            sampling_2x: true,
83            direct_key_mapping_a: false,
84            macro_mapping_a: false,
85            direct_key_mapping_b: false,
86            macro_mapping_b: false,
87        }
88    }
89
90    /// Convert options to protocol byte format
91    /// Bit layout: [macro_b][key_b][macro_a][key_a][reserved][2x][4x][enable]
92    pub fn to_byte(&self) -> u8 {
93        let mut options = 0u8;
94        if self.enabled {
95            options |= 1 << 0;
96        }
97        if self.sampling_4x {
98            options |= 1 << 1;
99        }
100        if self.sampling_2x {
101            options |= 1 << 2;
102        }
103        // bit 3 is reserved
104        if self.direct_key_mapping_a {
105            options |= 1 << 4;
106        }
107        if self.macro_mapping_a {
108            options |= 1 << 5;
109        }
110        if self.direct_key_mapping_b {
111            options |= 1 << 6;
112        }
113        if self.macro_mapping_b {
114            options |= 1 << 7;
115        }
116        options
117    }
118
119    /// Create options from protocol byte format
120    pub fn from_byte(byte: u8) -> Self {
121        Self {
122            enabled: (byte & (1 << 0)) != 0,
123            sampling_4x: (byte & (1 << 1)) != 0,
124            sampling_2x: (byte & (1 << 2)) != 0,
125            direct_key_mapping_a: (byte & (1 << 4)) != 0,
126            macro_mapping_a: (byte & (1 << 5)) != 0,
127            direct_key_mapping_b: (byte & (1 << 6)) != 0,
128            macro_mapping_b: (byte & (1 << 7)) != 0,
129        }
130    }
131}
132
133/// Fast-encoder hardware configuration selector (protocol command `0xCE`
134/// byte 3 = "FastEncodersConfiguration").
135///
136/// Only one configuration may be active at a time. Newer PoKeys56/57 devices
137/// support only [`Config2`](Self::Config2); older PoKeys55 hardware supports
138/// both.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum FastEncoderConfiguration {
141    /// Fast encoders disabled.
142    Disabled,
143    /// Config 1 — encoder 1 on pins 1-2, encoder 2 on pins 3-4, encoder 3 on pins 15-16.
144    /// (PoKeys55-era devices only.)
145    Config1,
146    /// Config 2 — encoder 1 on pins 1-2, encoder 2 on pins 5-6, encoder 3 on pins 15-16.
147    /// Supported by all devices with fast encoder hardware.
148    Config2,
149}
150
151impl FastEncoderConfiguration {
152    /// Byte value for the "FastEncodersConfiguration" field of command `0xCE`.
153    /// Matches the PoKeysLib C reference constants:
154    /// `PK_FASTENCODER_CONF_CFG1 = 0x01`, `PK_FASTENCODER_CONF_CFG2 = 0x10`.
155    pub fn to_byte(self) -> u8 {
156        match self {
157            FastEncoderConfiguration::Disabled => 0x00,
158            FastEncoderConfiguration::Config1 => 0x01,
159            FastEncoderConfiguration::Config2 => 0x10,
160        }
161    }
162
163    /// Decode the byte field back to a configuration. Unknown values map to
164    /// [`Disabled`](Self::Disabled).
165    pub fn from_byte(byte: u8) -> Self {
166        match byte {
167            0x01 => FastEncoderConfiguration::Config1,
168            0x10 => FastEncoderConfiguration::Config2,
169            _ => FastEncoderConfiguration::Disabled,
170        }
171    }
172}
173
174/// Per-encoder options for the fast encoders (protocol command `0xCE`
175/// byte 4 = "FastEncodersOptions").
176///
177/// Bit layout matches the PoKeysLib C reference (`ePK_FastEncoderOptions`):
178///
179/// | Bit | Mask | Meaning                           |
180/// |-----|------|-----------------------------------|
181/// | 4   | 0x10 | Disable 4x sampling (default is 4x on). |
182/// | 5   | 0x20 | Invert direction of fast encoder 1. |
183/// | 6   | 0x40 | Invert direction of fast encoder 2. |
184/// | 7   | 0x80 | Invert direction of fast encoder 3. |
185///
186/// Bits 0-3 are reserved (the low nibble carries nothing — the configuration
187/// selector lives in a separate byte, see [`FastEncoderConfiguration`]).
188#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
189pub struct FastEncoderOptions {
190    /// Disable 4x sampling for the fast encoders (default is 4x enabled).
191    pub disable_4x_sampling: bool,
192    /// Invert direction of fast encoder 1.
193    pub invert_direction_1: bool,
194    /// Invert direction of fast encoder 2.
195    pub invert_direction_2: bool,
196    /// Invert direction of fast encoder 3.
197    pub invert_direction_3: bool,
198}
199
200/// Bit 4 (`0x10`) of the fast-encoders options byte — disable 4x sampling.
201pub(crate) const FAST_ENCODER_DISABLE_4X_SAMPLING: u8 = 0x10;
202/// Bit 5 (`0x20`) — invert direction of fast encoder 1.
203pub(crate) const FAST_ENCODER_INVERT_E1: u8 = 0x20;
204/// Bit 6 (`0x40`) — invert direction of fast encoder 2.
205pub(crate) const FAST_ENCODER_INVERT_E2: u8 = 0x40;
206/// Bit 7 (`0x80`) — invert direction of fast encoder 3.
207pub(crate) const FAST_ENCODER_INVERT_E3: u8 = 0x80;
208
209impl FastEncoderOptions {
210    /// Pack the options into the protocol byte (byte 4 of command `0xCE`).
211    pub fn to_byte(self) -> u8 {
212        let mut b = 0u8;
213        if self.disable_4x_sampling {
214            b |= FAST_ENCODER_DISABLE_4X_SAMPLING;
215        }
216        if self.invert_direction_1 {
217            b |= FAST_ENCODER_INVERT_E1;
218        }
219        if self.invert_direction_2 {
220            b |= FAST_ENCODER_INVERT_E2;
221        }
222        if self.invert_direction_3 {
223            b |= FAST_ENCODER_INVERT_E3;
224        }
225        b
226    }
227
228    /// Decode an options byte back into the struct (reserved bits ignored).
229    pub fn from_byte(byte: u8) -> Self {
230        Self {
231            disable_4x_sampling: (byte & FAST_ENCODER_DISABLE_4X_SAMPLING) != 0,
232            invert_direction_1: (byte & FAST_ENCODER_INVERT_E1) != 0,
233            invert_direction_2: (byte & FAST_ENCODER_INVERT_E2) != 0,
234            invert_direction_3: (byte & FAST_ENCODER_INVERT_E3) != 0,
235        }
236    }
237}
238
239/// Ultra-fast encoder options (protocol command `0x1C` byte 4 = "additional options").
240///
241/// Bit layout matches PoKeysLib C reference (`ePK_UltraFastEncoderOptions`):
242///
243/// | Bit | Mask | Meaning                        |
244/// |-----|------|--------------------------------|
245/// | 0   | 0x01 | Invert direction.              |
246/// | 1   | 0x02 | Signal mode: `false` = quadrature (A/B), `true` = direction + clock. |
247/// | 2   | 0x04 | Enable 4x sampling.            |
248///
249/// Bits 3-7 are reserved.
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
251pub struct UltraFastEncoderOptions {
252    /// Invert direction (bit 0).
253    pub invert_direction: bool,
254    /// Signal mode. `false` = A/B are quadrature inputs; `true` = A is the
255    /// direction signal and B is the clock signal.
256    pub signal_mode_direction_clock: bool,
257    /// Enable 4x sampling (both edges of A and B counted).
258    pub enable_4x_sampling: bool,
259}
260
261/// Bit 0 — invert direction (ultra-fast).
262pub(crate) const UFENC_INVERT_DIRECTION: u8 = 0x01;
263/// Bit 1 — signal mode (ultra-fast).
264pub(crate) const UFENC_SIGNAL_MODE: u8 = 0x02;
265/// Bit 2 — enable 4x sampling (ultra-fast).
266pub(crate) const UFENC_ENABLE_4X_SAMPLING: u8 = 0x04;
267
268impl UltraFastEncoderOptions {
269    /// Pack into the options byte sent to the device.
270    pub fn to_byte(self) -> u8 {
271        let mut b = 0u8;
272        if self.invert_direction {
273            b |= UFENC_INVERT_DIRECTION;
274        }
275        if self.signal_mode_direction_clock {
276            b |= UFENC_SIGNAL_MODE;
277        }
278        if self.enable_4x_sampling {
279            b |= UFENC_ENABLE_4X_SAMPLING;
280        }
281        b
282    }
283
284    /// Decode from a device-reported options byte.
285    pub fn from_byte(byte: u8) -> Self {
286        Self {
287            invert_direction: (byte & UFENC_INVERT_DIRECTION) != 0,
288            signal_mode_direction_clock: (byte & UFENC_SIGNAL_MODE) != 0,
289            enable_4x_sampling: (byte & UFENC_ENABLE_4X_SAMPLING) != 0,
290        }
291    }
292}
293
294/// Encoder data structure containing all encoder state and configuration
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct EncoderData {
297    /// Current encoder value (32-bit signed)
298    pub encoder_value: i32,
299    /// Encoder configuration options
300    pub encoder_options: u8,
301    /// Channel A input pin (0-54)
302    pub channel_a_pin: u8,
303    /// Channel B input pin (0-54)
304    pub channel_b_pin: u8,
305    /// Direction A key code for keyboard mapping
306    pub dir_a_key_code: u8,
307    /// Direction A key modifier for keyboard mapping
308    pub dir_a_key_modifier: u8,
309    /// Direction B key code for keyboard mapping
310    pub dir_b_key_code: u8,
311    /// Direction B key modifier for keyboard mapping
312    pub dir_b_key_modifier: u8,
313}
314
315impl EncoderData {
316    /// Create new encoder data with default values
317    pub fn new() -> Self {
318        Self {
319            encoder_value: 0,
320            encoder_options: 0,
321            channel_a_pin: 0,
322            channel_b_pin: 0,
323            dir_a_key_code: 0,
324            dir_a_key_modifier: 0,
325            dir_b_key_code: 0,
326            dir_b_key_modifier: 0,
327        }
328    }
329
330    /// Get encoder options as structured data
331    pub fn get_options(&self) -> EncoderOptions {
332        EncoderOptions::from_byte(self.encoder_options)
333    }
334
335    /// Set encoder options from structured data
336    pub fn set_options(&mut self, options: EncoderOptions) {
337        self.encoder_options = options.to_byte();
338    }
339
340    /// Check if encoder is enabled
341    pub fn is_enabled(&self) -> bool {
342        (self.encoder_options & 1) != 0
343    }
344
345    /// Check if 4x sampling is enabled
346    pub fn is_4x_sampling(&self) -> bool {
347        (self.encoder_options & (1 << 1)) != 0
348    }
349
350    /// Check if 2x sampling is enabled
351    pub fn is_2x_sampling(&self) -> bool {
352        (self.encoder_options & (1 << 2)) != 0
353    }
354
355    /// Get sampling mode as string for debugging
356    pub fn sampling_mode_str(&self) -> &'static str {
357        if self.is_4x_sampling() {
358            "4x (both edges)"
359        } else if self.is_2x_sampling() {
360            "2x (A edges only)"
361        } else {
362            "1x (disabled)"
363        }
364    }
365}
366
367impl Default for EncoderData {
368    fn default() -> Self {
369        Self::new()
370    }
371}
372
373impl PoKeysDevice {
374    /// Configure encoder with pins and options
375    /// Protocol: 0x11 - Individual encoder settings (per protocol spec)
376    pub fn configure_encoder(
377        &mut self,
378        encoder_id: u8,
379        channel_a_pin: u8,
380        channel_b_pin: u8,
381        options: EncoderOptions,
382    ) -> Result<()> {
383        if encoder_id as usize >= self.encoders.len() {
384            return Err(PoKeysError::Parameter(format!(
385                "Invalid encoder ID: {}",
386                encoder_id
387            )));
388        }
389
390        // Convert 1-based pin numbers to 0-based for protocol
391        // The calling code uses 1-based pin numbers, but the protocol expects 0-based
392        let protocol_pin_a = if channel_a_pin > 0 {
393            channel_a_pin - 1
394        } else {
395            0
396        };
397        let protocol_pin_b = if channel_b_pin > 0 {
398            channel_b_pin - 1
399        } else {
400            0
401        };
402
403        let encoder = &mut self.encoders[encoder_id as usize];
404        encoder.channel_a_pin = protocol_pin_a; // Store 0-based internally
405        encoder.channel_b_pin = protocol_pin_b; // Store 0-based internally
406        encoder.set_options(options);
407
408        log::info!(
409            "Configuring encoder {} with pins A={}, B={} (1-based: A={}, B={}), options={:08b} using protocol 0x11",
410            encoder_id,
411            protocol_pin_a,
412            protocol_pin_b,
413            channel_a_pin,
414            channel_b_pin,
415            options.to_byte()
416        );
417
418        // Use protocol command 0x11 for individual encoder configuration
419        // Per spec: byte 2: 0x11, byte 3: encoder ID (0-25), byte 4: option, byte 5: channel A, byte 6: channel B
420        let response = self.send_request(
421            0x11,              // Command: Encoder settings (per protocol spec)
422            encoder_id,        // Encoder ID (0-25)
423            options.to_byte(), // Options byte
424            protocol_pin_a,    // Channel A input pin (0-based)
425            protocol_pin_b,    // Channel B input pin (0-based)
426        )?;
427
428        log::info!("Encoder configuration response: {:?}", &response[0..8]);
429
430        // Check response status per spec: byte 3 (index 2) = 0 = OK, 1 = encoder ID out of range or configuration locked
431        if response.len() > 2 {
432            match response[2] {
433                // Status is at index 2 (spec byte 3)
434                0 => {
435                    log::info!("Encoder {} configuration successful", encoder_id);
436                }
437                1 => {
438                    return Err(PoKeysError::Protocol(format!(
439                        "Encoder {} configuration failed: encoder ID out of range or configuration locked",
440                        encoder_id
441                    )));
442                }
443                other => {
444                    return Err(PoKeysError::Protocol(format!(
445                        "Encoder {} configuration failed with status: {} (0x{:02X})",
446                        encoder_id, other, other
447                    )));
448                }
449            }
450        }
451
452        Ok(())
453    }
454
455    /// Read encoder settings using protocol 0x16
456    pub fn read_encoder_settings(&mut self, encoder_id: u8) -> Result<EncoderData> {
457        if encoder_id >= MAX_ENCODERS as u8 {
458            return Err(PoKeysError::Parameter(format!(
459                "Encoder ID {} exceeds maximum {}",
460                encoder_id, MAX_ENCODERS
461            )));
462        }
463
464        log::info!(
465            "Reading encoder {} settings using protocol 0x16",
466            encoder_id
467        );
468
469        // Use protocol command 0x16 for reading individual encoder settings
470        // Per spec: byte 2: 0x16, byte 3: encoder (0-25), byte 4-6: 0
471        let response = self.send_request(
472            0x16,       // Command: Read encoder settings
473            encoder_id, // Encoder ID (0-25)
474            0,          // Reserved
475            0,          // Reserved
476            0,          // Reserved
477        )?;
478
479        log::info!("Read encoder settings response: {:?}", &response[0..8]);
480
481        // Parse response per spec, accounting for header byte:
482        // Index 0: header (0xAA), Index 1: command (0x16), Index 2: encoder, Index 3: options, Index 4: channel A, Index 5: channel B
483        if response.len() >= 6 {
484            let returned_encoder_id = response[2]; // Encoder ID at index 2
485            let options_byte = response[3]; // Options at index 3
486            let channel_a_pin = response[4]; // Channel A pin at index 4
487            let channel_b_pin = response[5]; // Channel B pin at index 5
488
489            if returned_encoder_id != encoder_id {
490                log::warn!(
491                    "Response encoder ID {} doesn't match requested {}",
492                    returned_encoder_id,
493                    encoder_id
494                );
495            }
496
497            // Convert 0-based protocol pins to 1-based for display/API consistency
498            let display_pin_a = channel_a_pin + 1;
499            let display_pin_b = channel_b_pin + 1;
500
501            let settings = EncoderData {
502                channel_a_pin: display_pin_a, // Return 1-based pin numbers
503                channel_b_pin: display_pin_b, // Return 1-based pin numbers
504                encoder_options: options_byte,
505                ..Default::default()
506            };
507
508            log::info!(
509                "Encoder {} settings: A={}, B={} (protocol: A={}, B={}), options={:08b}",
510                encoder_id,
511                display_pin_a,
512                display_pin_b,
513                channel_a_pin,
514                channel_b_pin,
515                options_byte
516            );
517
518            Ok(settings)
519        } else {
520            Err(PoKeysError::Protocol(
521                "Invalid encoder settings response length".to_string(),
522            ))
523        }
524    }
525
526    /// Enable or disable encoder
527    pub fn enable_encoder(&mut self, encoder_id: u8, enable: bool) -> Result<()> {
528        if encoder_id as usize >= self.encoders.len() {
529            return Err(PoKeysError::Parameter(format!(
530                "Invalid encoder ID: {}",
531                encoder_id
532            )));
533        }
534
535        // Get current pin assignments
536        let (channel_a_pin, channel_b_pin) = {
537            let encoder = &self.encoders[encoder_id as usize];
538            (encoder.channel_a_pin, encoder.channel_b_pin)
539        };
540
541        // Update options
542        let mut options = {
543            let encoder = &self.encoders[encoder_id as usize];
544            encoder.get_options()
545        };
546        options.enabled = enable;
547
548        self.configure_encoder(encoder_id, channel_a_pin, channel_b_pin, options)
549    }
550
551    /// Set encoder sampling mode (4x or 2x)
552    pub fn set_encoder_sampling(
553        &mut self,
554        encoder_id: u8,
555        sampling_4x: bool,
556        sampling_2x: bool,
557    ) -> Result<()> {
558        if encoder_id as usize >= self.encoders.len() {
559            return Err(PoKeysError::Parameter(format!(
560                "Invalid encoder ID: {}",
561                encoder_id
562            )));
563        }
564
565        // Validate sampling mode - only one can be active
566        if sampling_4x && sampling_2x {
567            return Err(PoKeysError::Parameter(
568                "Cannot enable both 4x and 2x sampling simultaneously".to_string(),
569            ));
570        }
571
572        // Get current pin assignments
573        let (channel_a_pin, channel_b_pin) = {
574            let encoder = &self.encoders[encoder_id as usize];
575            (encoder.channel_a_pin, encoder.channel_b_pin)
576        };
577
578        // Update options
579        let mut options = {
580            let encoder = &self.encoders[encoder_id as usize];
581            encoder.get_options()
582        };
583        options.sampling_4x = sampling_4x;
584        options.sampling_2x = sampling_2x;
585
586        self.configure_encoder(encoder_id, channel_a_pin, channel_b_pin, options)
587    }
588
589    /// Configure encoder key mapping for direction A
590    /// Protocol: 0x12 - Encoder key mapping for direction A
591    pub fn configure_encoder_key_mapping_a(
592        &mut self,
593        encoder_id: u8,
594        key_code: u8,
595        key_modifier: u8,
596    ) -> Result<()> {
597        if encoder_id as usize >= self.encoders.len() {
598            return Err(PoKeysError::Parameter(format!(
599                "Invalid encoder ID: {}",
600                encoder_id
601            )));
602        }
603
604        let encoder = &mut self.encoders[encoder_id as usize];
605        encoder.dir_a_key_code = key_code;
606        encoder.dir_a_key_modifier = key_modifier;
607
608        // Send key mapping configuration using protocol command 0x12
609        let response = self.send_request(
610            0x12,         // Command: Encoder key mapping for direction A
611            encoder_id,   // Encoder ID (0-25)
612            0,            // Reserved
613            key_code,     // Key code or macro ID
614            key_modifier, // Key modifier
615        )?;
616
617        // Check response status
618        if response.len() > 3 && response[3] != 0 {
619            return Err(PoKeysError::Protocol(format!(
620                "Encoder key mapping A failed for encoder {}: status {}",
621                encoder_id, response[3]
622            )));
623        }
624
625        Ok(())
626    }
627
628    /// Configure encoder key mapping for direction B
629    /// Protocol: 0x13 - Encoder key mapping for direction B
630    pub fn configure_encoder_key_mapping_b(
631        &mut self,
632        encoder_id: u8,
633        key_code: u8,
634        key_modifier: u8,
635    ) -> Result<()> {
636        if encoder_id as usize >= self.encoders.len() {
637            return Err(PoKeysError::Parameter(format!(
638                "Invalid encoder ID: {}",
639                encoder_id
640            )));
641        }
642
643        let encoder = &mut self.encoders[encoder_id as usize];
644        encoder.dir_b_key_code = key_code;
645        encoder.dir_b_key_modifier = key_modifier;
646
647        // Send key mapping configuration using protocol command 0x13
648        let response = self.send_request(
649            0x13,         // Command: Encoder key mapping for direction B
650            encoder_id,   // Encoder ID (0-25)
651            0,            // Reserved
652            key_code,     // Key code or macro ID
653            key_modifier, // Key modifier
654        )?;
655
656        // Check response status
657        if response.len() > 3 && response[3] != 0 {
658            return Err(PoKeysError::Protocol(format!(
659                "Encoder key mapping B failed for encoder {}: status {}",
660                encoder_id, response[3]
661            )));
662        }
663
664        Ok(())
665    }
666
667    /// Read encoder key mapping for direction A
668    /// Protocol: 0x17 - Read encoder key mapping for direction A
669    pub fn read_encoder_key_mapping_a(&mut self, encoder_id: u8) -> Result<(u8, u8)> {
670        if encoder_id as usize >= self.encoders.len() {
671            return Err(PoKeysError::Parameter(format!(
672                "Invalid encoder ID: {}",
673                encoder_id
674            )));
675        }
676
677        let response = self.send_request(
678            0x17,       // Command: Read encoder key mapping for direction A
679            encoder_id, // Encoder ID (0-25)
680            0,          // Reserved
681            0,          // Reserved
682            0,          // Reserved
683        )?;
684
685        if response.len() < 8 {
686            return Err(PoKeysError::Protocol("Invalid response length".to_string()));
687        }
688
689        // Parse response: byte 5 = key code, byte 6 = key modifier
690        let key_code = response[5];
691        let key_modifier = response[6];
692
693        // Update local cache
694        self.encoders[encoder_id as usize].dir_a_key_code = key_code;
695        self.encoders[encoder_id as usize].dir_a_key_modifier = key_modifier;
696
697        Ok((key_code, key_modifier))
698    }
699
700    /// Read encoder key mapping for direction B
701    /// Protocol: 0x18 - Read encoder key mapping for direction B
702    pub fn read_encoder_key_mapping_b(&mut self, encoder_id: u8) -> Result<(u8, u8)> {
703        if encoder_id as usize >= self.encoders.len() {
704            return Err(PoKeysError::Parameter(format!(
705                "Invalid encoder ID: {}",
706                encoder_id
707            )));
708        }
709
710        let response = self.send_request(
711            0x18,       // Command: Read encoder key mapping for direction B
712            encoder_id, // Encoder ID (0-25)
713            0,          // Reserved
714            0,          // Reserved
715            0,          // Reserved
716        )?;
717
718        if response.len() < 8 {
719            return Err(PoKeysError::Protocol("Invalid response length".to_string()));
720        }
721
722        // Parse response: byte 5 = key code, byte 6 = key modifier
723        let key_code = response[5];
724        let key_modifier = response[6];
725
726        // Update local cache
727        self.encoders[encoder_id as usize].dir_b_key_code = key_code;
728        self.encoders[encoder_id as usize].dir_b_key_modifier = key_modifier;
729
730        Ok((key_code, key_modifier))
731    }
732
733    /// Read encoder RAW value
734    /// Protocol: 0x19 - Read encoder RAW value
735    pub fn read_encoder_raw_value(&mut self, encoder_id: u8) -> Result<i32> {
736        if encoder_id as usize >= self.encoders.len() {
737            return Err(PoKeysError::Parameter(format!(
738                "Invalid encoder ID: {}",
739                encoder_id
740            )));
741        }
742
743        let response = self.send_request(
744            0x19,       // Command: Read encoder RAW value
745            encoder_id, // Encoder ID (0-25)
746            0,          // Reserved
747            0,          // Reserved
748            0,          // Reserved
749        )?;
750
751        if response.len() < 8 {
752            return Err(PoKeysError::Protocol("Invalid response length".to_string()));
753        }
754
755        // Parse response: byte 4 = RAW value (8-bit for individual read)
756        // Note: For full 32-bit values, use bulk read operations
757        let raw_value = response[4] as i8 as i32; // Sign-extend 8-bit to 32-bit
758
759        // Update local cache
760        self.encoders[encoder_id as usize].encoder_value = raw_value;
761
762        Ok(raw_value)
763    }
764
765    /// Reset encoder RAW value to zero
766    /// Protocol: 0x1A - Reset encoder RAW value
767    pub fn reset_encoder_raw_value(&mut self, encoder_id: u8) -> Result<()> {
768        if encoder_id as usize >= self.encoders.len() {
769            return Err(PoKeysError::Parameter(format!(
770                "Invalid encoder ID: {}",
771                encoder_id
772            )));
773        }
774
775        let response = self.send_request(
776            0x1A,       // Command: Reset encoder RAW value
777            encoder_id, // Encoder ID (0-25)
778            0,          // Reserved
779            0,          // Reserved
780            0,          // Reserved
781        )?;
782
783        // Check if command was successful (no specific error checking in protocol)
784        if response.len() < 8 {
785            return Err(PoKeysError::Protocol("Invalid response length".to_string()));
786        }
787
788        // Update local cache
789        self.encoders[encoder_id as usize].encoder_value = 0;
790
791        Ok(())
792    }
793
794    /// Get encoder value (convenience method)
795    pub fn get_encoder_value(&mut self, encoder_id: u8) -> Result<i32> {
796        self.read_encoder_raw_value(encoder_id)
797    }
798
799    /// Reset encoder value (convenience method)
800    pub fn reset_encoder(&mut self, encoder_id: u8) -> Result<()> {
801        self.reset_encoder_raw_value(encoder_id)
802    }
803
804    /// Get encoder long RAW values (bulk operation)
805    /// Protocol: 0xCD - Get encoder long RAW values
806    /// option: 0 = encoders 1-13, 1 = encoders 14-26
807    pub fn read_encoder_long_values(&mut self, group: u8) -> Result<Vec<i32>> {
808        if group > 1 {
809            return Err(PoKeysError::Parameter(
810                "Group must be 0 (encoders 1-13) or 1 (encoders 14-26)".to_string(),
811            ));
812        }
813
814        let response = self.send_request(
815            0xCD,  // Command: Get encoder long RAW values
816            group, // Option: 0 or 1
817            0,     // Reserved
818            0,     // Reserved
819            0,     // Reserved
820        )?;
821
822        if response.len() < 64 {
823            return Err(PoKeysError::Protocol(
824                "Invalid response length for bulk encoder read".to_string(),
825            ));
826        }
827
828        let mut values = Vec::new();
829
830        // Parse 32-bit values starting from byte 8 (protocol spec says byte 9, but that's 1-based)
831        // Group 0: encoders 1-13, Group 1: encoders 14-26
832        for i in 0..13 {
833            let byte_offset = 8 + (i * 4); // Start at byte 8 in 0-based array, 4 bytes per encoder
834            if byte_offset + 3 < response.len() {
835                let value = i32::from_le_bytes([
836                    response[byte_offset],
837                    response[byte_offset + 1],
838                    response[byte_offset + 2],
839                    response[byte_offset + 3],
840                ]);
841                values.push(value);
842
843                // Update local cache - map to 0-based encoder IDs
844                let encoder_index = if group == 0 { i } else { 13 + i };
845                if encoder_index < self.encoders.len() {
846                    self.encoders[encoder_index].encoder_value = value;
847                }
848            }
849        }
850
851        // Handle ultra-fast encoder for group 1 (bytes 56-59 in 0-based array)
852        if group == 1 && response.len() >= 60 {
853            let ultra_fast_value = i32::from_le_bytes([
854                response[56], // Protocol spec byte 57 = array index 56
855                response[57], // Protocol spec byte 58 = array index 57
856                response[58], // Protocol spec byte 59 = array index 58
857                response[59], // Protocol spec byte 60 = array index 59
858            ]);
859            // Ultra-fast encoder is at index 25 (encoder 26 in 1-based numbering)
860            if self.encoders.len() > 25 {
861                self.encoders[25].encoder_value = ultra_fast_value;
862            }
863        }
864
865        log::info!("Bulk read group {} returned {} values", group, values.len());
866        Ok(values)
867    }
868
869    /// Set encoder long RAW values (bulk operation)
870    /// Protocol: 0xCD - Set encoder long RAW values
871    /// option: 10 = encoders 1-13, 11 = encoders 14-26
872    pub fn set_encoder_long_values(&mut self, group: u8, values: &[i32]) -> Result<()> {
873        if group > 1 {
874            return Err(PoKeysError::Parameter(
875                "Group must be 0 (encoders 1-13) or 1 (encoders 14-26)".to_string(),
876            ));
877        }
878
879        let expected_count = if group == 0 { 13 } else { 12 }; // Group 1 has 12 regular + ultra-fast
880        if values.len() < expected_count {
881            return Err(PoKeysError::Parameter(format!(
882                "Need {} values for group {}",
883                expected_count, group
884            )));
885        }
886
887        // Prepare request with values
888        let mut request = vec![0u8; 64];
889        request[2] = 0xCD; // Command
890        request[3] = group + 10; // Option: 10 or 11 for set operation
891        request[7] = self.get_next_request_id(); // Request ID
892
893        // Pack 32-bit values starting from byte 9
894        for (i, &value) in values.iter().enumerate() {
895            let byte_offset = 9 + (i * 4);
896            if byte_offset + 3 < request.len() {
897                let bytes = value.to_le_bytes();
898                request[byte_offset] = bytes[0];
899                request[byte_offset + 1] = bytes[1];
900                request[byte_offset + 2] = bytes[2];
901                request[byte_offset + 3] = bytes[3];
902            }
903        }
904
905        let _response = self.send_raw_request(&request)?;
906
907        // Update local cache
908        let start_encoder = if group == 0 { 1 } else { 14 };
909        for (i, &value) in values.iter().enumerate() {
910            let encoder_index = start_encoder + i;
911            if encoder_index < self.encoders.len() {
912                self.encoders[encoder_index].encoder_value = value;
913            }
914        }
915
916        Ok(())
917    }
918
919    /// Read all encoder values using bulk operations (more efficient)
920    pub fn read_all_encoder_values(&mut self) -> Result<Vec<i32>> {
921        let mut all_values = Vec::new();
922
923        // Read encoders 1-13
924        let group1_values = self.read_encoder_long_values(0)?;
925        all_values.extend(group1_values);
926
927        // Read encoders 14-25 + ultra-fast
928        let group2_values = self.read_encoder_long_values(1)?;
929        all_values.extend(group2_values);
930
931        Ok(all_values)
932    }
933
934    /// Configure encoder options (bulk operation)
935    /// Protocol: 0xC4 - Encoder option
936    pub fn configure_encoder_options_bulk(&mut self, options: &[u8]) -> Result<Vec<u8>> {
937        if options.len() != 25 {
938            return Err(PoKeysError::Parameter(
939                "Need exactly 25 encoder options".to_string(),
940            ));
941        }
942
943        let mut request = vec![0u8; 64];
944        request[2] = 0xC4; // Command
945        request[3] = 1; // Option: 1 = set
946        request[7] = self.get_next_request_id(); // Request ID
947
948        // Copy encoder options to bytes 9-33
949        for (i, &option) in options.iter().enumerate() {
950            request[9 + i] = option;
951        }
952
953        let response = self.send_raw_request(&request)?;
954
955        // Parse returned options from bytes 9-33
956        let mut returned_options = Vec::new();
957        if response.len() >= 34 {
958            for i in 0..25 {
959                returned_options.push(response[9 + i]);
960                // Update local cache
961                if i < self.encoders.len() {
962                    self.encoders[i].encoder_options = response[9 + i];
963                }
964            }
965        }
966
967        Ok(returned_options)
968    }
969
970    /// Read encoder options (bulk operation)
971    /// Protocol: 0xC4 - Encoder option
972    pub fn read_encoder_options_bulk(&mut self) -> Result<Vec<u8>> {
973        let response = self.send_request(
974            0xC4, // Command: Encoder option
975            0,    // Option: 0 = get
976            0,    // Reserved
977            0,    // Reserved
978            0,    // Reserved
979        )?;
980
981        let mut options = Vec::new();
982        if response.len() >= 34 {
983            for i in 0..25 {
984                options.push(response[9 + i]);
985                // Update local cache
986                if i < self.encoders.len() {
987                    self.encoders[i].encoder_options = response[9 + i];
988                }
989            }
990        }
991
992        Ok(options)
993    }
994
995    /// Configure the fast encoders via protocol command `0xCE`.
996    ///
997    /// `0xCE` uses two bytes: the **configuration selector** (which pin
998    /// configuration / enable state to use) and the **options byte**
999    /// (per-encoder direction invert + 4x-sampling control). They are passed
1000    /// as typed values so callers don't have to know the bit layout.
1001    ///
1002    /// Per PoKeysLib C reference (`PoKeysLibEncoders.c:154`):
1003    /// `req[2] = FastEncodersConfiguration`, `req[3] = FastEncodersOptions`.
1004    pub fn configure_fast_encoders(
1005        &mut self,
1006        config: FastEncoderConfiguration,
1007        options: FastEncoderOptions,
1008    ) -> Result<()> {
1009        let config_byte = config.to_byte();
1010        let options_byte = options.to_byte();
1011
1012        self.fast_encoders_configuration = config_byte;
1013        self.fast_encoders_options = options_byte;
1014
1015        let response = self.send_request(
1016            0xCE,         // Command: Enable/disable fast encoders
1017            config_byte,  // param1: FastEncodersConfiguration
1018            options_byte, // param2: FastEncodersOptions
1019            0,            // param3: reserved
1020            0,            // param4: reserved
1021        )?;
1022
1023        // Response byte 3 (0-based index 2) is status.
1024        if response.len() > 2 {
1025            let status = response[2];
1026            if status != 0 {
1027                return Err(PoKeysError::Protocol(format!(
1028                    "Fast encoder configuration failed: status {}",
1029                    status
1030                )));
1031            }
1032        }
1033
1034        Ok(())
1035    }
1036
1037    /// Read fast encoder values
1038    pub fn read_fast_encoder_values(&mut self) -> Result<[i32; 3]> {
1039        // Fast encoders use the bulk read operation for encoders 0, 1, 2
1040        let values = self.read_encoder_long_values(0)?;
1041
1042        let mut fast_values = [0i32; 3];
1043        let copy_len = 3.min(values.len());
1044        fast_values[..copy_len].copy_from_slice(&values[..copy_len]);
1045
1046        Ok(fast_values)
1047    }
1048
1049    /// Configure the ultra-fast encoder via protocol command `0x1C`
1050    /// (PoKeys56E and later).
1051    ///
1052    /// The ultra-fast encoder uses fixed pins: Pin 8 (Phase A), Pin 12 (Phase B),
1053    /// Pin 13 (Index).
1054    ///
1055    /// Bit layout of the options byte matches PoKeysLib C reference
1056    /// (`ePK_UltraFastEncoderOptions`): bit 0 = invert direction, bit 1 = signal
1057    /// mode, bit 2 = enable 4x sampling. See [`UltraFastEncoderOptions`].
1058    pub fn configure_ultra_fast_encoder(
1059        &mut self,
1060        enable: bool,
1061        options: UltraFastEncoderOptions,
1062        reset_on_index: bool,
1063        filter_delay: u32,
1064    ) -> Result<()> {
1065        let options_byte = options.to_byte();
1066
1067        self.ultra_fast_encoder_configuration = if enable { 1 } else { 0 };
1068        self.ultra_fast_encoder_options = options_byte;
1069        self.ultra_fast_encoder_filter = filter_delay;
1070
1071        // Packet layout per spec / PoKeysLib:
1072        //   byte 2 (param1) = enable flag (1 = enable, 0 = disable, 0xFF = read)
1073        //   byte 3 (param2) = options byte
1074        //   byte 4 (param3) = reset-on-index flag
1075        //   byte 5 (param4) = reserved (0)
1076        //   bytes 8..12     = filter delay (u32 LE) — placed via `with_data`
1077        let filter_bytes = filter_delay.to_le_bytes();
1078        let response = self.send_request_with_data(
1079            0x1C,
1080            if enable { 1 } else { 0 },
1081            options_byte,
1082            if reset_on_index { 1 } else { 0 },
1083            0,
1084            &filter_bytes,
1085        )?;
1086
1087        // Response byte 3 (0-based index 2) is status.
1088        if response.len() > 2 {
1089            let status = response[2];
1090            if status != 0 {
1091                return Err(PoKeysError::Protocol(format!(
1092                    "Ultra-fast encoder configuration failed: status {}",
1093                    status
1094                )));
1095            }
1096        }
1097
1098        Ok(())
1099    }
1100
1101    /// Read the ultra-fast encoder's current configuration.
1102    ///
1103    /// Protocol: `0x1C` with `param1 = 0xFF` to request a read. Returns the
1104    /// enable flag, decoded [`UltraFastEncoderOptions`], and the filter delay
1105    /// (u32 LE at response bytes 8..12).
1106    pub fn read_ultra_fast_encoder_config(
1107        &mut self,
1108    ) -> Result<(bool, UltraFastEncoderOptions, u32)> {
1109        let response = self.send_request(0x1C, 0xFF, 0, 0, 0)?;
1110
1111        if response.len() < 12 {
1112            return Err(PoKeysError::Protocol("Invalid response length".to_string()));
1113        }
1114
1115        // Spec: byte 3 (index 2) = enable flag echo, byte 4 (index 3) = options.
1116        let enabled = response[2] != 0;
1117        let options = UltraFastEncoderOptions::from_byte(response[3]);
1118        let filter_delay =
1119            u32::from_le_bytes([response[8], response[9], response[10], response[11]]);
1120
1121        Ok((enabled, options, filter_delay))
1122    }
1123
1124    /// Read ultra-fast encoder value
1125    pub fn read_ultra_fast_encoder_value(&mut self) -> Result<i32> {
1126        // Ultra-fast encoder is included in bulk read group 1
1127        let values = self.read_encoder_long_values(1)?;
1128
1129        // Ultra-fast encoder is the last value in group 1
1130        if let Some(&value) = values.last() {
1131            Ok(value)
1132        } else {
1133            Err(PoKeysError::Protocol(
1134                "No ultra-fast encoder value in response".to_string(),
1135            ))
1136        }
1137    }
1138
1139    /// Set ultra-fast encoder value
1140    pub fn set_ultra_fast_encoder_value(&mut self, value: i32) -> Result<()> {
1141        // Use bulk set operation for group 1 with only the ultra-fast encoder value
1142        // We need to read current values first, then set only the ultra-fast one
1143        let mut values = self.read_encoder_long_values(1)?;
1144
1145        // Set the ultra-fast encoder value (last in the array)
1146        if let Some(last) = values.last_mut() {
1147            *last = value;
1148        } else {
1149            return Err(PoKeysError::Protocol(
1150                "Cannot set ultra-fast encoder value".to_string(),
1151            ));
1152        }
1153
1154        self.set_encoder_long_values(1, &values)
1155    }
1156    /// Configure encoder with keyboard mapping (convenience method)
1157    #[allow(clippy::too_many_arguments)]
1158    pub fn configure_encoder_with_keys(
1159        &mut self,
1160        encoder_id: u8,
1161        channel_a_pin: u8,
1162        channel_b_pin: u8,
1163        sampling_4x: bool,
1164        sampling_2x: bool,
1165        dir_a_key_code: u8,
1166        dir_a_key_modifier: u8,
1167        dir_b_key_code: u8,
1168        dir_b_key_modifier: u8,
1169    ) -> Result<()> {
1170        // Configure basic encoder settings
1171        let mut options = EncoderOptions::new();
1172        options.enabled = true;
1173        options.sampling_4x = sampling_4x;
1174        options.sampling_2x = sampling_2x;
1175        options.direct_key_mapping_a = true;
1176        options.direct_key_mapping_b = true;
1177
1178        self.configure_encoder(encoder_id, channel_a_pin, channel_b_pin, options)?;
1179
1180        // Configure key mappings
1181        self.configure_encoder_key_mapping_a(encoder_id, dir_a_key_code, dir_a_key_modifier)?;
1182        self.configure_encoder_key_mapping_b(encoder_id, dir_b_key_code, dir_b_key_modifier)?;
1183
1184        Ok(())
1185    }
1186
1187    /// Get encoder sampling mode as string (for debugging/display)
1188    pub fn get_encoder_sampling_mode(&self, encoder_id: u8) -> Result<String> {
1189        if encoder_id as usize >= self.encoders.len() {
1190            return Err(PoKeysError::Parameter(format!(
1191                "Invalid encoder ID: {}",
1192                encoder_id
1193            )));
1194        }
1195
1196        let encoder = &self.encoders[encoder_id as usize];
1197        Ok(encoder.sampling_mode_str().to_string())
1198    }
1199
1200    /// Check if encoder is configured for 4x sampling
1201    pub fn is_encoder_4x_sampling(&self, encoder_id: u8) -> Result<bool> {
1202        if encoder_id as usize >= self.encoders.len() {
1203            return Err(PoKeysError::Parameter(format!(
1204                "Invalid encoder ID: {}",
1205                encoder_id
1206            )));
1207        }
1208
1209        Ok(self.encoders[encoder_id as usize].is_4x_sampling())
1210    }
1211
1212    /// Check if encoder is configured for 2x sampling
1213    pub fn is_encoder_2x_sampling(&self, encoder_id: u8) -> Result<bool> {
1214        if encoder_id as usize >= self.encoders.len() {
1215            return Err(PoKeysError::Parameter(format!(
1216                "Invalid encoder ID: {}",
1217                encoder_id
1218            )));
1219        }
1220
1221        Ok(self.encoders[encoder_id as usize].is_2x_sampling())
1222    }
1223
1224    /// Get all enabled encoders
1225    pub fn get_enabled_encoders(&self) -> Vec<u8> {
1226        self.encoders
1227            .iter()
1228            .enumerate()
1229            .filter_map(|(i, encoder)| {
1230                if encoder.is_enabled() {
1231                    Some(i as u8)
1232                } else {
1233                    None
1234                }
1235            })
1236            .collect()
1237    }
1238
1239    /// Helper method to get next request ID (implement in device)
1240    fn get_next_request_id(&mut self) -> u8 {
1241        // This should be implemented in the device structure
1242        // For now, return a simple incrementing counter
1243        static mut REQUEST_ID: u8 = 0;
1244        unsafe {
1245            REQUEST_ID = REQUEST_ID.wrapping_add(1);
1246            REQUEST_ID
1247        }
1248    }
1249
1250    /// Helper method to send raw request (implement in device)
1251    fn send_raw_request(&mut self, request: &[u8]) -> Result<Vec<u8>> {
1252        // This should use the actual communication interface
1253        // For now, delegate to the existing send_request method and convert array to Vec
1254        if request.len() >= 8 {
1255            let response_array =
1256                self.send_request(request[2], request[3], request[4], request[5], request[6])?;
1257            Ok(response_array.to_vec())
1258        } else {
1259            Err(PoKeysError::Protocol("Invalid request format".to_string()))
1260        }
1261    }
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266    use super::*;
1267
1268    #[test]
1269    fn test_encoder_options_4x_sampling() {
1270        let options = EncoderOptions::with_4x_sampling();
1271        assert!(options.enabled);
1272        assert!(options.sampling_4x);
1273        assert!(!options.sampling_2x);
1274
1275        let byte = options.to_byte();
1276        assert_eq!(byte & 0b00000011, 0b00000011); // enabled + 4x sampling
1277
1278        let options_from_byte = EncoderOptions::from_byte(byte);
1279        assert!(options_from_byte.enabled);
1280        assert!(options_from_byte.sampling_4x);
1281        assert!(!options_from_byte.sampling_2x);
1282    }
1283
1284    #[test]
1285    fn test_encoder_options_2x_sampling() {
1286        let options = EncoderOptions::with_2x_sampling();
1287        assert!(options.enabled);
1288        assert!(!options.sampling_4x);
1289        assert!(options.sampling_2x);
1290
1291        let byte = options.to_byte();
1292        assert_eq!(byte & 0b00000111, 0b00000101); // enabled + 2x sampling
1293
1294        let options_from_byte = EncoderOptions::from_byte(byte);
1295        assert!(options_from_byte.enabled);
1296        assert!(!options_from_byte.sampling_4x);
1297        assert!(options_from_byte.sampling_2x);
1298    }
1299
1300    #[test]
1301    fn test_encoder_options_key_mapping() {
1302        let mut options = EncoderOptions::new();
1303        options.enabled = true;
1304        options.direct_key_mapping_a = true;
1305        options.macro_mapping_b = true;
1306
1307        let byte = options.to_byte();
1308        assert_eq!(byte & 0b11110001, 0b10010001); // enabled + key_a + macro_b
1309
1310        let options_from_byte = EncoderOptions::from_byte(byte);
1311        assert!(options_from_byte.enabled);
1312        assert!(options_from_byte.direct_key_mapping_a);
1313        assert!(options_from_byte.macro_mapping_b);
1314        assert!(!options_from_byte.direct_key_mapping_b);
1315        assert!(!options_from_byte.macro_mapping_a);
1316    }
1317
1318    #[test]
1319    fn test_encoder_data_sampling_modes() {
1320        let mut encoder = EncoderData::new();
1321
1322        // Test 4x sampling
1323        let options_4x = EncoderOptions::with_4x_sampling();
1324        encoder.set_options(options_4x);
1325        assert!(encoder.is_4x_sampling());
1326        assert!(!encoder.is_2x_sampling());
1327        assert_eq!(encoder.sampling_mode_str(), "4x (both edges)");
1328
1329        // Test 2x sampling
1330        let options_2x = EncoderOptions::with_2x_sampling();
1331        encoder.set_options(options_2x);
1332        assert!(!encoder.is_4x_sampling());
1333        assert!(encoder.is_2x_sampling());
1334        assert_eq!(encoder.sampling_mode_str(), "2x (A edges only)");
1335
1336        // Test disabled
1337        let options_disabled = EncoderOptions::new();
1338        encoder.set_options(options_disabled);
1339        assert!(!encoder.is_4x_sampling());
1340        assert!(!encoder.is_2x_sampling());
1341        assert_eq!(encoder.sampling_mode_str(), "1x (disabled)");
1342    }
1343
1344    #[test]
1345    fn test_encoder_constants() {
1346        assert_eq!(MAX_ENCODERS, 25);
1347        assert_eq!(MAX_FAST_ENCODERS, 3);
1348        assert_eq!(ULTRA_FAST_ENCODER_INDEX, 25);
1349    }
1350
1351    // ---- Fast encoder (0xCE) bit-layout tests ----------------------------
1352    //
1353    // These bit positions are authoritative per the PoLabs PoKeysLib C
1354    // reference (`ePK_FastEncoderOptions` in LinuxCnc_PokeysLibComp/
1355    // pokeys_uspace/PoKeysComp.h, also matching PoLabsEE/PoKeysLib usage).
1356
1357    #[test]
1358    fn test_fast_encoder_configuration_to_byte() {
1359        assert_eq!(FastEncoderConfiguration::Disabled.to_byte(), 0x00);
1360        assert_eq!(FastEncoderConfiguration::Config1.to_byte(), 0x01);
1361        assert_eq!(FastEncoderConfiguration::Config2.to_byte(), 0x10);
1362    }
1363
1364    #[test]
1365    fn test_fast_encoder_configuration_round_trip() {
1366        for cfg in [
1367            FastEncoderConfiguration::Disabled,
1368            FastEncoderConfiguration::Config1,
1369            FastEncoderConfiguration::Config2,
1370        ] {
1371            assert_eq!(FastEncoderConfiguration::from_byte(cfg.to_byte()), cfg);
1372        }
1373    }
1374
1375    #[test]
1376    fn test_fast_encoder_options_bit_masks() {
1377        // Each flag alone must land on the exact bit the firmware expects.
1378        assert_eq!(
1379            FastEncoderOptions {
1380                disable_4x_sampling: true,
1381                ..Default::default()
1382            }
1383            .to_byte(),
1384            0x10
1385        );
1386        assert_eq!(
1387            FastEncoderOptions {
1388                invert_direction_1: true,
1389                ..Default::default()
1390            }
1391            .to_byte(),
1392            0x20
1393        );
1394        assert_eq!(
1395            FastEncoderOptions {
1396                invert_direction_2: true,
1397                ..Default::default()
1398            }
1399            .to_byte(),
1400            0x40
1401        );
1402        assert_eq!(
1403            FastEncoderOptions {
1404                invert_direction_3: true,
1405                ..Default::default()
1406            }
1407            .to_byte(),
1408            0x80
1409        );
1410    }
1411
1412    #[test]
1413    fn test_fast_encoder_options_combined() {
1414        // All four flags set → 0x10 | 0x20 | 0x40 | 0x80 = 0xF0.
1415        let all = FastEncoderOptions {
1416            disable_4x_sampling: true,
1417            invert_direction_1: true,
1418            invert_direction_2: true,
1419            invert_direction_3: true,
1420        };
1421        assert_eq!(all.to_byte(), 0xF0);
1422    }
1423
1424    #[test]
1425    fn test_fast_encoder_options_round_trip() {
1426        let o = FastEncoderOptions {
1427            disable_4x_sampling: false,
1428            invert_direction_1: true,
1429            invert_direction_2: false,
1430            invert_direction_3: true,
1431        };
1432        assert_eq!(FastEncoderOptions::from_byte(o.to_byte()), o);
1433    }
1434
1435    #[test]
1436    fn test_fast_encoder_options_ignores_reserved_low_nibble() {
1437        // Low nibble must not leak into any field.
1438        let decoded = FastEncoderOptions::from_byte(0x0F);
1439        assert_eq!(decoded, FastEncoderOptions::default());
1440    }
1441
1442    // ---- Ultra-fast encoder (0x1C) bit-layout tests ---------------------
1443    //
1444    // Per `ePK_UltraFastEncoderOptions`: bit 0 = invert direction,
1445    // bit 1 = signal mode, bit 2 = enable 4x sampling.
1446
1447    #[test]
1448    fn test_ultra_fast_encoder_options_bit_masks() {
1449        assert_eq!(
1450            UltraFastEncoderOptions {
1451                invert_direction: true,
1452                ..Default::default()
1453            }
1454            .to_byte(),
1455            0x01
1456        );
1457        assert_eq!(
1458            UltraFastEncoderOptions {
1459                signal_mode_direction_clock: true,
1460                ..Default::default()
1461            }
1462            .to_byte(),
1463            0x02
1464        );
1465        assert_eq!(
1466            UltraFastEncoderOptions {
1467                enable_4x_sampling: true,
1468                ..Default::default()
1469            }
1470            .to_byte(),
1471            0x04
1472        );
1473    }
1474
1475    #[test]
1476    fn test_ultra_fast_encoder_options_combined() {
1477        // invert + signal mode + 4x sampling → 0x07.
1478        let all = UltraFastEncoderOptions {
1479            invert_direction: true,
1480            signal_mode_direction_clock: true,
1481            enable_4x_sampling: true,
1482        };
1483        assert_eq!(all.to_byte(), 0x07);
1484    }
1485
1486    #[test]
1487    fn test_ultra_fast_encoder_options_round_trip() {
1488        let o = UltraFastEncoderOptions {
1489            invert_direction: true,
1490            signal_mode_direction_clock: false,
1491            enable_4x_sampling: true,
1492        };
1493        assert_eq!(UltraFastEncoderOptions::from_byte(o.to_byte()), o);
1494    }
1495
1496    #[test]
1497    fn test_ultra_fast_encoder_options_ignores_reserved_high_bits() {
1498        // Bits 3-7 are reserved; must not leak into any field.
1499        let decoded = UltraFastEncoderOptions::from_byte(0xF8);
1500        assert_eq!(decoded, UltraFastEncoderOptions::default());
1501    }
1502}