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::debug!(
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::debug!("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::debug!("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::debug!(
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::debug!("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::debug!(
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::debug!("Bulk read group {} returned {} values", group, values.len());
866        Ok(values)
867    }
868
869    /// Set encoder long RAW values (bulk operation).
870    ///
871    /// Protocol `0xCD` with `option = 10` for encoders 1-13 or `11` for
872    /// encoders 14-26. Payload at protocol bytes 9-60 (0-based 8-59) carries
873    /// the 32-bit LE RAW values.
874    pub fn set_encoder_long_values(&mut self, group: u8, values: &[i32]) -> Result<()> {
875        if group > 1 {
876            return Err(PoKeysError::Parameter(
877                "Group must be 0 (encoders 1-13) or 1 (encoders 14-26)".to_string(),
878            ));
879        }
880
881        let expected_count = if group == 0 { 13 } else { 12 }; // Group 1 has 12 regular + ultra-fast
882        if values.len() < expected_count {
883            return Err(PoKeysError::Parameter(format!(
884                "Need {} values for group {}",
885                expected_count, group
886            )));
887        }
888
889        // Pack the 32-bit LE values into the data payload. `send_request_with_data`
890        // copies this to request[8..], which the firmware reads as protocol
891        // bytes 9..
892        let count = values.len().min(13);
893        let mut payload = [0u8; 52];
894        for (i, &value) in values.iter().take(count).enumerate() {
895            let offset = i * 4;
896            payload[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
897        }
898
899        let _response = self.send_request_with_data(
900            0xCD,
901            group + 10, // option: 10 = set group 0, 11 = set group 1
902            0,
903            0,
904            0,
905            &payload[..count * 4],
906        )?;
907
908        // Update local cache
909        let start_encoder = if group == 0 { 1 } else { 14 };
910        for (i, &value) in values.iter().enumerate() {
911            let encoder_index = start_encoder + i;
912            if encoder_index < self.encoders.len() {
913                self.encoders[encoder_index].encoder_value = value;
914            }
915        }
916
917        Ok(())
918    }
919
920    /// Read all encoder values using bulk operations (more efficient)
921    pub fn read_all_encoder_values(&mut self) -> Result<Vec<i32>> {
922        let mut all_values = Vec::new();
923
924        // Read encoders 1-13
925        let group1_values = self.read_encoder_long_values(0)?;
926        all_values.extend(group1_values);
927
928        // Read encoders 14-25 + ultra-fast
929        let group2_values = self.read_encoder_long_values(1)?;
930        all_values.extend(group2_values);
931
932        Ok(all_values)
933    }
934
935    /// Configure encoder options (bulk operation).
936    ///
937    /// Protocol `0xC4` with `option = 1` (set). The 25 option bytes are
938    /// placed at protocol bytes 9-33 (0-based 8-32).
939    pub fn configure_encoder_options_bulk(&mut self, options: &[u8]) -> Result<Vec<u8>> {
940        if options.len() != 25 {
941            return Err(PoKeysError::Parameter(
942                "Need exactly 25 encoder options".to_string(),
943            ));
944        }
945
946        let response = self.send_request_with_data(0xC4, 1, 0, 0, 0, options)?;
947
948        // Parse returned options from response bytes 9-33 (0-based 8-32).
949        let mut returned_options = Vec::with_capacity(25);
950        if response.len() >= 33 {
951            for i in 0..25 {
952                returned_options.push(response[8 + i]);
953                if i < self.encoders.len() {
954                    self.encoders[i].encoder_options = response[8 + i];
955                }
956            }
957        }
958
959        Ok(returned_options)
960    }
961
962    /// Read encoder options (bulk operation).
963    ///
964    /// Protocol `0xC4` with `option = 0` (get). Response carries 25 option
965    /// bytes at protocol bytes 9-33 (0-based 8-32).
966    pub fn read_encoder_options_bulk(&mut self) -> Result<Vec<u8>> {
967        let response = self.send_request(0xC4, 0, 0, 0, 0)?;
968
969        let mut options = Vec::with_capacity(25);
970        if response.len() >= 33 {
971            for i in 0..25 {
972                options.push(response[8 + i]);
973                if i < self.encoders.len() {
974                    self.encoders[i].encoder_options = response[8 + i];
975                }
976            }
977        }
978
979        Ok(options)
980    }
981
982    /// Configure the fast encoders via protocol command `0xCE`.
983    ///
984    /// `0xCE` uses two bytes: the **configuration selector** (which pin
985    /// configuration / enable state to use) and the **options byte**
986    /// (per-encoder direction invert + 4x-sampling control). They are passed
987    /// as typed values so callers don't have to know the bit layout.
988    ///
989    /// Per PoKeysLib C reference (`PoKeysLibEncoders.c:154`):
990    /// `req[2] = FastEncodersConfiguration`, `req[3] = FastEncodersOptions`.
991    pub fn configure_fast_encoders(
992        &mut self,
993        config: FastEncoderConfiguration,
994        options: FastEncoderOptions,
995    ) -> Result<()> {
996        let config_byte = config.to_byte();
997        let options_byte = options.to_byte();
998
999        self.fast_encoders_configuration = config_byte;
1000        self.fast_encoders_options = options_byte;
1001
1002        let response = self.send_request(
1003            0xCE,         // Command: Enable/disable fast encoders
1004            config_byte,  // param1: FastEncodersConfiguration
1005            options_byte, // param2: FastEncodersOptions
1006            0,            // param3: reserved
1007            0,            // param4: reserved
1008        )?;
1009
1010        // Response byte 3 (0-based index 2) is status.
1011        if response.len() > 2 {
1012            let status = response[2];
1013            if status != 0 {
1014                return Err(PoKeysError::Protocol(format!(
1015                    "Fast encoder configuration failed: status {}",
1016                    status
1017                )));
1018            }
1019        }
1020
1021        Ok(())
1022    }
1023
1024    /// Read fast encoder values
1025    pub fn read_fast_encoder_values(&mut self) -> Result<[i32; 3]> {
1026        // Fast encoders use the bulk read operation for encoders 0, 1, 2
1027        let values = self.read_encoder_long_values(0)?;
1028
1029        let mut fast_values = [0i32; 3];
1030        let copy_len = 3.min(values.len());
1031        fast_values[..copy_len].copy_from_slice(&values[..copy_len]);
1032
1033        Ok(fast_values)
1034    }
1035
1036    /// Configure the ultra-fast encoder via protocol command `0x1C`
1037    /// (PoKeys56E and later).
1038    ///
1039    /// The ultra-fast encoder uses fixed pins: Pin 8 (Phase A), Pin 12 (Phase B),
1040    /// Pin 13 (Index).
1041    ///
1042    /// Bit layout of the options byte matches PoKeysLib C reference
1043    /// (`ePK_UltraFastEncoderOptions`): bit 0 = invert direction, bit 1 = signal
1044    /// mode, bit 2 = enable 4x sampling. See [`UltraFastEncoderOptions`].
1045    pub fn configure_ultra_fast_encoder(
1046        &mut self,
1047        enable: bool,
1048        options: UltraFastEncoderOptions,
1049        reset_on_index: bool,
1050        filter_delay: u32,
1051    ) -> Result<()> {
1052        let options_byte = options.to_byte();
1053
1054        self.ultra_fast_encoder_configuration = if enable { 1 } else { 0 };
1055        self.ultra_fast_encoder_options = options_byte;
1056        self.ultra_fast_encoder_filter = filter_delay;
1057
1058        // Packet layout per spec / PoKeysLib:
1059        //   byte 2 (param1) = enable flag (1 = enable, 0 = disable, 0xFF = read)
1060        //   byte 3 (param2) = options byte
1061        //   byte 4 (param3) = reset-on-index flag
1062        //   byte 5 (param4) = reserved (0)
1063        //   bytes 8..12     = filter delay (u32 LE) — placed via `with_data`
1064        let filter_bytes = filter_delay.to_le_bytes();
1065        let response = self.send_request_with_data(
1066            0x1C,
1067            if enable { 1 } else { 0 },
1068            options_byte,
1069            if reset_on_index { 1 } else { 0 },
1070            0,
1071            &filter_bytes,
1072        )?;
1073
1074        // Response byte 3 (0-based index 2) is status.
1075        if response.len() > 2 {
1076            let status = response[2];
1077            if status != 0 {
1078                return Err(PoKeysError::Protocol(format!(
1079                    "Ultra-fast encoder configuration failed: status {}",
1080                    status
1081                )));
1082            }
1083        }
1084
1085        Ok(())
1086    }
1087
1088    /// Read the ultra-fast encoder's current configuration.
1089    ///
1090    /// Protocol: `0x1C` with `param1 = 0xFF` to request a read. Returns the
1091    /// enable flag, decoded [`UltraFastEncoderOptions`], and the filter delay
1092    /// (u32 LE at response bytes 8..12).
1093    pub fn read_ultra_fast_encoder_config(
1094        &mut self,
1095    ) -> Result<(bool, UltraFastEncoderOptions, u32)> {
1096        let response = self.send_request(0x1C, 0xFF, 0, 0, 0)?;
1097
1098        if response.len() < 12 {
1099            return Err(PoKeysError::Protocol("Invalid response length".to_string()));
1100        }
1101
1102        // Spec: byte 3 (index 2) = enable flag echo, byte 4 (index 3) = options.
1103        let enabled = response[2] != 0;
1104        let options = UltraFastEncoderOptions::from_byte(response[3]);
1105        let filter_delay =
1106            u32::from_le_bytes([response[8], response[9], response[10], response[11]]);
1107
1108        Ok((enabled, options, filter_delay))
1109    }
1110
1111    /// Read ultra-fast encoder value
1112    pub fn read_ultra_fast_encoder_value(&mut self) -> Result<i32> {
1113        // Ultra-fast encoder is included in bulk read group 1
1114        let values = self.read_encoder_long_values(1)?;
1115
1116        // Ultra-fast encoder is the last value in group 1
1117        if let Some(&value) = values.last() {
1118            Ok(value)
1119        } else {
1120            Err(PoKeysError::Protocol(
1121                "No ultra-fast encoder value in response".to_string(),
1122            ))
1123        }
1124    }
1125
1126    /// Set ultra-fast encoder value
1127    pub fn set_ultra_fast_encoder_value(&mut self, value: i32) -> Result<()> {
1128        // Use bulk set operation for group 1 with only the ultra-fast encoder value
1129        // We need to read current values first, then set only the ultra-fast one
1130        let mut values = self.read_encoder_long_values(1)?;
1131
1132        // Set the ultra-fast encoder value (last in the array)
1133        if let Some(last) = values.last_mut() {
1134            *last = value;
1135        } else {
1136            return Err(PoKeysError::Protocol(
1137                "Cannot set ultra-fast encoder value".to_string(),
1138            ));
1139        }
1140
1141        self.set_encoder_long_values(1, &values)
1142    }
1143    /// Configure encoder with keyboard mapping (convenience method)
1144    #[allow(clippy::too_many_arguments)]
1145    pub fn configure_encoder_with_keys(
1146        &mut self,
1147        encoder_id: u8,
1148        channel_a_pin: u8,
1149        channel_b_pin: u8,
1150        sampling_4x: bool,
1151        sampling_2x: bool,
1152        dir_a_key_code: u8,
1153        dir_a_key_modifier: u8,
1154        dir_b_key_code: u8,
1155        dir_b_key_modifier: u8,
1156    ) -> Result<()> {
1157        // Configure basic encoder settings
1158        let mut options = EncoderOptions::new();
1159        options.enabled = true;
1160        options.sampling_4x = sampling_4x;
1161        options.sampling_2x = sampling_2x;
1162        options.direct_key_mapping_a = true;
1163        options.direct_key_mapping_b = true;
1164
1165        self.configure_encoder(encoder_id, channel_a_pin, channel_b_pin, options)?;
1166
1167        // Configure key mappings
1168        self.configure_encoder_key_mapping_a(encoder_id, dir_a_key_code, dir_a_key_modifier)?;
1169        self.configure_encoder_key_mapping_b(encoder_id, dir_b_key_code, dir_b_key_modifier)?;
1170
1171        Ok(())
1172    }
1173
1174    /// Get encoder sampling mode as string (for debugging/display)
1175    pub fn get_encoder_sampling_mode(&self, encoder_id: u8) -> Result<String> {
1176        if encoder_id as usize >= self.encoders.len() {
1177            return Err(PoKeysError::Parameter(format!(
1178                "Invalid encoder ID: {}",
1179                encoder_id
1180            )));
1181        }
1182
1183        let encoder = &self.encoders[encoder_id as usize];
1184        Ok(encoder.sampling_mode_str().to_string())
1185    }
1186
1187    /// Check if encoder is configured for 4x sampling
1188    pub fn is_encoder_4x_sampling(&self, encoder_id: u8) -> Result<bool> {
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        Ok(self.encoders[encoder_id as usize].is_4x_sampling())
1197    }
1198
1199    /// Check if encoder is configured for 2x sampling
1200    pub fn is_encoder_2x_sampling(&self, encoder_id: u8) -> Result<bool> {
1201        if encoder_id as usize >= self.encoders.len() {
1202            return Err(PoKeysError::Parameter(format!(
1203                "Invalid encoder ID: {}",
1204                encoder_id
1205            )));
1206        }
1207
1208        Ok(self.encoders[encoder_id as usize].is_2x_sampling())
1209    }
1210
1211    /// Get all enabled encoders
1212    pub fn get_enabled_encoders(&self) -> Vec<u8> {
1213        self.encoders
1214            .iter()
1215            .enumerate()
1216            .filter_map(|(i, encoder)| {
1217                if encoder.is_enabled() {
1218                    Some(i as u8)
1219                } else {
1220                    None
1221                }
1222            })
1223            .collect()
1224    }
1225}
1226
1227#[cfg(test)]
1228mod tests {
1229    use super::*;
1230
1231    #[test]
1232    fn test_encoder_options_4x_sampling() {
1233        let options = EncoderOptions::with_4x_sampling();
1234        assert!(options.enabled);
1235        assert!(options.sampling_4x);
1236        assert!(!options.sampling_2x);
1237
1238        let byte = options.to_byte();
1239        assert_eq!(byte & 0b00000011, 0b00000011); // enabled + 4x sampling
1240
1241        let options_from_byte = EncoderOptions::from_byte(byte);
1242        assert!(options_from_byte.enabled);
1243        assert!(options_from_byte.sampling_4x);
1244        assert!(!options_from_byte.sampling_2x);
1245    }
1246
1247    #[test]
1248    fn test_encoder_options_2x_sampling() {
1249        let options = EncoderOptions::with_2x_sampling();
1250        assert!(options.enabled);
1251        assert!(!options.sampling_4x);
1252        assert!(options.sampling_2x);
1253
1254        let byte = options.to_byte();
1255        assert_eq!(byte & 0b00000111, 0b00000101); // enabled + 2x sampling
1256
1257        let options_from_byte = EncoderOptions::from_byte(byte);
1258        assert!(options_from_byte.enabled);
1259        assert!(!options_from_byte.sampling_4x);
1260        assert!(options_from_byte.sampling_2x);
1261    }
1262
1263    #[test]
1264    fn test_encoder_options_key_mapping() {
1265        let mut options = EncoderOptions::new();
1266        options.enabled = true;
1267        options.direct_key_mapping_a = true;
1268        options.macro_mapping_b = true;
1269
1270        let byte = options.to_byte();
1271        assert_eq!(byte & 0b11110001, 0b10010001); // enabled + key_a + macro_b
1272
1273        let options_from_byte = EncoderOptions::from_byte(byte);
1274        assert!(options_from_byte.enabled);
1275        assert!(options_from_byte.direct_key_mapping_a);
1276        assert!(options_from_byte.macro_mapping_b);
1277        assert!(!options_from_byte.direct_key_mapping_b);
1278        assert!(!options_from_byte.macro_mapping_a);
1279    }
1280
1281    #[test]
1282    fn test_encoder_data_sampling_modes() {
1283        let mut encoder = EncoderData::new();
1284
1285        // Test 4x sampling
1286        let options_4x = EncoderOptions::with_4x_sampling();
1287        encoder.set_options(options_4x);
1288        assert!(encoder.is_4x_sampling());
1289        assert!(!encoder.is_2x_sampling());
1290        assert_eq!(encoder.sampling_mode_str(), "4x (both edges)");
1291
1292        // Test 2x sampling
1293        let options_2x = EncoderOptions::with_2x_sampling();
1294        encoder.set_options(options_2x);
1295        assert!(!encoder.is_4x_sampling());
1296        assert!(encoder.is_2x_sampling());
1297        assert_eq!(encoder.sampling_mode_str(), "2x (A edges only)");
1298
1299        // Test disabled
1300        let options_disabled = EncoderOptions::new();
1301        encoder.set_options(options_disabled);
1302        assert!(!encoder.is_4x_sampling());
1303        assert!(!encoder.is_2x_sampling());
1304        assert_eq!(encoder.sampling_mode_str(), "1x (disabled)");
1305    }
1306
1307    #[test]
1308    fn test_encoder_constants() {
1309        assert_eq!(MAX_ENCODERS, 25);
1310        assert_eq!(MAX_FAST_ENCODERS, 3);
1311        assert_eq!(ULTRA_FAST_ENCODER_INDEX, 25);
1312    }
1313
1314    // ---- Fast encoder (0xCE) bit-layout tests ----------------------------
1315    //
1316    // These bit positions are authoritative per the PoLabs PoKeysLib C
1317    // reference (`ePK_FastEncoderOptions` in LinuxCnc_PokeysLibComp/
1318    // pokeys_uspace/PoKeysComp.h, also matching PoLabsEE/PoKeysLib usage).
1319
1320    #[test]
1321    fn test_fast_encoder_configuration_to_byte() {
1322        assert_eq!(FastEncoderConfiguration::Disabled.to_byte(), 0x00);
1323        assert_eq!(FastEncoderConfiguration::Config1.to_byte(), 0x01);
1324        assert_eq!(FastEncoderConfiguration::Config2.to_byte(), 0x10);
1325    }
1326
1327    #[test]
1328    fn test_fast_encoder_configuration_round_trip() {
1329        for cfg in [
1330            FastEncoderConfiguration::Disabled,
1331            FastEncoderConfiguration::Config1,
1332            FastEncoderConfiguration::Config2,
1333        ] {
1334            assert_eq!(FastEncoderConfiguration::from_byte(cfg.to_byte()), cfg);
1335        }
1336    }
1337
1338    #[test]
1339    fn test_fast_encoder_options_bit_masks() {
1340        // Each flag alone must land on the exact bit the firmware expects.
1341        assert_eq!(
1342            FastEncoderOptions {
1343                disable_4x_sampling: true,
1344                ..Default::default()
1345            }
1346            .to_byte(),
1347            0x10
1348        );
1349        assert_eq!(
1350            FastEncoderOptions {
1351                invert_direction_1: true,
1352                ..Default::default()
1353            }
1354            .to_byte(),
1355            0x20
1356        );
1357        assert_eq!(
1358            FastEncoderOptions {
1359                invert_direction_2: true,
1360                ..Default::default()
1361            }
1362            .to_byte(),
1363            0x40
1364        );
1365        assert_eq!(
1366            FastEncoderOptions {
1367                invert_direction_3: true,
1368                ..Default::default()
1369            }
1370            .to_byte(),
1371            0x80
1372        );
1373    }
1374
1375    #[test]
1376    fn test_fast_encoder_options_combined() {
1377        // All four flags set → 0x10 | 0x20 | 0x40 | 0x80 = 0xF0.
1378        let all = FastEncoderOptions {
1379            disable_4x_sampling: true,
1380            invert_direction_1: true,
1381            invert_direction_2: true,
1382            invert_direction_3: true,
1383        };
1384        assert_eq!(all.to_byte(), 0xF0);
1385    }
1386
1387    #[test]
1388    fn test_fast_encoder_options_round_trip() {
1389        let o = FastEncoderOptions {
1390            disable_4x_sampling: false,
1391            invert_direction_1: true,
1392            invert_direction_2: false,
1393            invert_direction_3: true,
1394        };
1395        assert_eq!(FastEncoderOptions::from_byte(o.to_byte()), o);
1396    }
1397
1398    #[test]
1399    fn test_fast_encoder_options_ignores_reserved_low_nibble() {
1400        // Low nibble must not leak into any field.
1401        let decoded = FastEncoderOptions::from_byte(0x0F);
1402        assert_eq!(decoded, FastEncoderOptions::default());
1403    }
1404
1405    // ---- Ultra-fast encoder (0x1C) bit-layout tests ---------------------
1406    //
1407    // Per `ePK_UltraFastEncoderOptions`: bit 0 = invert direction,
1408    // bit 1 = signal mode, bit 2 = enable 4x sampling.
1409
1410    #[test]
1411    fn test_ultra_fast_encoder_options_bit_masks() {
1412        assert_eq!(
1413            UltraFastEncoderOptions {
1414                invert_direction: true,
1415                ..Default::default()
1416            }
1417            .to_byte(),
1418            0x01
1419        );
1420        assert_eq!(
1421            UltraFastEncoderOptions {
1422                signal_mode_direction_clock: true,
1423                ..Default::default()
1424            }
1425            .to_byte(),
1426            0x02
1427        );
1428        assert_eq!(
1429            UltraFastEncoderOptions {
1430                enable_4x_sampling: true,
1431                ..Default::default()
1432            }
1433            .to_byte(),
1434            0x04
1435        );
1436    }
1437
1438    #[test]
1439    fn test_ultra_fast_encoder_options_combined() {
1440        // invert + signal mode + 4x sampling → 0x07.
1441        let all = UltraFastEncoderOptions {
1442            invert_direction: true,
1443            signal_mode_direction_clock: true,
1444            enable_4x_sampling: true,
1445        };
1446        assert_eq!(all.to_byte(), 0x07);
1447    }
1448
1449    #[test]
1450    fn test_ultra_fast_encoder_options_round_trip() {
1451        let o = UltraFastEncoderOptions {
1452            invert_direction: true,
1453            signal_mode_direction_clock: false,
1454            enable_4x_sampling: true,
1455        };
1456        assert_eq!(UltraFastEncoderOptions::from_byte(o.to_byte()), o);
1457    }
1458
1459    #[test]
1460    fn test_ultra_fast_encoder_options_ignores_reserved_high_bits() {
1461        // Bits 3-7 are reserved; must not leak into any field.
1462        let decoded = UltraFastEncoderOptions::from_byte(0xF8);
1463        assert_eq!(decoded, UltraFastEncoderOptions::default());
1464    }
1465}