pokeys-lib 1.0.4

Pure Rust core library for PoKeys device control - USB/Network connectivity, I/O, PWM, encoders, SPI/I2C protocols
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
//! Core types and structures for PoKeys library

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};

/// Device connection type
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceConnectionType {
    UsbDevice = 0,
    NetworkDevice = 1,
    FastUsbDevice = 2,
}

/// Device connection parameters
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionParam {
    Tcp = 0,
    Udp = 1,
}

/// Device type IDs
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceTypeId {
    Bootloader55 = 3,
    Bootloader56U = 15,
    Bootloader56E = 16,
    Bootloader58 = 41,

    Device55v1 = 0,
    Device55v2 = 1,
    Device55v3 = 2,

    Device56U = 10,
    Device56E = 11,
    Device27U = 20,
    Device27E = 21,

    Device57U = 30,
    Device57E = 31,
    PoKeys57CNC = 32,
    PoKeys57CNCpro4x25 = 33,
    PoKeys57CNCdb25 = 38,
    PoKeys57Utest = 39,

    LiniTester = 43,

    Device57Uv0 = 28,
    Device57Ev0 = 29,

    Device58EU = 40,
    PoPLC58 = 50,

    OEM1 = 100,
    SerialReader = 101,
    X15_02_24 = 102,
}

/// Device type masks for capability checking
#[derive(Debug, Clone, Copy)]
pub struct DeviceTypeMask(pub u64);

impl DeviceTypeMask {
    pub const BOOTLOADER: Self = Self(1 << 0);
    pub const BOOTLOADER55: Self = Self(1 << 1);
    pub const BOOTLOADER56: Self = Self(1 << 2);
    pub const BOOTLOADER56U: Self = Self(1 << 3);
    pub const BOOTLOADER56E: Self = Self(1 << 4);
    pub const BOOTLOADER58: Self = Self(1 << 5);

    pub const DEVICE55: Self = Self(1 << 10);
    pub const DEVICE55V1: Self = Self(1 << 11);
    pub const DEVICE55V2: Self = Self(1 << 12);
    pub const DEVICE55V3: Self = Self(1 << 13);

    pub const DEVICE56: Self = Self(1 << 14);
    pub const DEVICE56U: Self = Self(1 << 15);
    pub const DEVICE56E: Self = Self(1 << 16);
    pub const DEVICE27: Self = Self(1 << 17);
    pub const DEVICE27U: Self = Self(1 << 18);
    pub const DEVICE27E: Self = Self(1 << 19);

    pub const DEVICE57: Self = Self(1 << 20);
    pub const DEVICE57U: Self = Self(1 << 24);
    pub const DEVICE57E: Self = Self(1 << 25);
    pub const DEVICE57CNC: Self = Self(1 << 26);
    pub const DEVICE57CNCDB25: Self = Self(1 << 27);
    pub const DEVICE57UTEST: Self = Self(1 << 28);
    pub const DEVICE57CNCPRO4X25: Self = Self(1 << 29);

    pub const DEVICE58: Self = Self(1 << 21);
    pub const POPLC58: Self = Self(1 << 22);

    pub const POKEYS16RF: Self = Self(1 << 23);
}

/// Pulse engine state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PulseEngineState {
    Stopped = 0,
    Internal = 1,
    Buffer = 2,
    Running = 3,

    Jogging = 10,
    Stopping = 11,

    Home = 20,
    Homing = 21,

    ProbeComplete = 30,
    Probe = 31,
    ProbeError = 32,

    HybridProbeStopping = 40,
    HybridProbeComplete = 41,

    StopLimit = 100,
    StopEmergency = 101,
}

/// Pulse engine axis state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PulseEngineAxisState {
    Stopped = 0,
    Ready = 1,
    Running = 2,
    Accelerating = 3,
    Decelerating = 4,

    HomingResetting = 8,
    HomingBackingOff = 9,
    Home = 10,
    HomingStart = 11,
    HomingSearch = 12,
    HomingBack = 13,

    Probed = 14,
    ProbeStart = 15,
    ProbeSearch = 16,

    Error = 20,
    Limit = 30,
}

/// I2C status codes - unified across PoKeys ecosystem
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum I2cStatus {
    Error = 0,
    Ok = 1,
    Complete = 2,
    InProgress = 0x10,
    Timeout = 0x20,
    ChecksumError = 0x30,  // For protocol validation
    DeviceNotFound = 0x40, // Device scan specific
    PacketTooLarge = 0x50, // Fragmentation needed
}

/// I2C configuration options - standardized interface
#[derive(Debug, Clone)]
pub struct I2cConfig {
    // Common fields
    pub max_packet_size: usize,
    pub timeout_ms: u64,
    pub retry_attempts: u32,

    // PoKeys specific features
    pub auto_fragment: bool,
    pub fragment_delay_ms: u64,
    pub validation_level: ValidationLevel,
    pub performance_monitoring: bool,
}

impl Default for I2cConfig {
    fn default() -> Self {
        Self {
            max_packet_size: 32,
            timeout_ms: 1000,
            retry_attempts: 3,
            auto_fragment: false,
            fragment_delay_ms: 10,
            validation_level: ValidationLevel::None,
            performance_monitoring: false,
        }
    }
}

/// Retry configuration for error recovery
#[derive(Debug, Clone)]
pub struct RetryConfig {
    pub max_attempts: u32,
    pub base_delay_ms: u64,
    pub max_delay_ms: u64,
    pub backoff_multiplier: f64,
    pub jitter: bool,
}

impl Default for RetryConfig {
    fn default() -> Self {
        Self {
            max_attempts: 3,
            base_delay_ms: 100,
            max_delay_ms: 2000,
            backoff_multiplier: 2.0,
            jitter: true,
        }
    }
}

/// Validation levels for protocol validation
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationLevel {
    None,   // Current behavior - pass everything through
    Basic,  // Validate packet structure only
    Strict, // Full protocol validation
    Custom(ValidationConfig),
}

/// Custom validation configuration
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationConfig {
    pub validate_checksums: bool,
    pub validate_command_ids: bool,
    pub validate_device_ids: bool,
    pub validate_packet_structure: bool,
    pub max_device_id: u8,
    pub valid_commands: HashSet<u8>,
}

impl Default for ValidationConfig {
    fn default() -> Self {
        Self {
            validate_checksums: true,
            validate_command_ids: true,
            validate_device_ids: true,
            validate_packet_structure: true,
            max_device_id: 255,
            valid_commands: HashSet::new(),
        }
    }
}

/// I2C performance metrics
#[derive(Debug, Clone, Default)]
pub struct I2cMetrics {
    pub total_commands: u64,
    pub successful_commands: u64,
    pub failed_commands: u64,
    pub average_response_time: std::time::Duration,
    pub max_response_time: std::time::Duration,
    pub min_response_time: std::time::Duration,
    pub error_counts: HashMap<String, u32>,
}

/// Health status for device diagnostics
#[derive(Debug, Clone)]
pub struct HealthStatus {
    pub connectivity: ConnectivityStatus,
    pub i2c_health: I2cHealthStatus,
    pub error_rate: f64,
    pub performance: PerformanceSummary,
}

/// Connectivity status
#[derive(Debug, Clone)]
pub enum ConnectivityStatus {
    Healthy,
    Degraded(String),
    Failed(String),
}

/// I2C health status
#[derive(Debug, Clone)]
pub enum I2cHealthStatus {
    Healthy,
    Degraded(String),
    Failed(String),
}

/// Performance summary
#[derive(Debug, Clone, Default)]
pub struct PerformanceSummary {
    pub avg_response_time_ms: f64,
    pub success_rate: f64,
    pub throughput_commands_per_sec: f64,
}

/// LCD mode
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LcdMode {
    Direct = 0,
    Buffered = 1,
}

/// Device information structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceInfo {
    pub pin_count: u32,
    pub pwm_count: u32,
    pub basic_encoder_count: u32,
    pub encoders_count: u32,
    pub fast_encoders: u32,
    pub ultra_fast_encoders: u32,
    pub pwm_internal_frequency: u32,
    pub analog_inputs: u32,

    // Feature flags
    pub key_mapping: u32,
    pub triggered_key_mapping: u32,
    pub key_repeat_delay: u32,
    pub digital_counters: u32,
    pub joystick_button_axis_mapping: u32,
    pub joystick_analog_to_digital_mapping: u32,
    pub macros: u32,
    pub matrix_keyboard: u32,
    pub matrix_keyboard_triggered_mapping: u32,
    pub lcd: u32,
    pub matrix_led: u32,
    pub connection_signal: u32,
    pub po_ext_bus: u32,
    pub po_net: u32,
    pub analog_filtering: u32,
    pub init_outputs_start: u32,
    pub prot_i2c: u32,
    pub prot_1wire: u32,
    pub additional_options: u32,
    pub load_status: u32,
    pub custom_device_name: u32,
    pub po_tlog27_support: u32,
    pub sensor_list: u32,
    pub web_interface: u32,
    pub fail_safe_settings: u32,
    pub joystick_hat_switch: u32,
    pub pulse_engine: u32,
    pub pulse_engine_v2: u32,
    pub easy_sensors: u32,
}

/// Device-specific data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceData {
    pub device_type_id: u32,
    pub serial_number: u32,
    pub device_name: [u8; 30],
    pub device_type_name: [u8; 30],
    pub build_date: [u8; 12],
    pub activation_code: [u8; 8],
    pub firmware_version_major: u8,
    pub firmware_version_minor: u8,
    pub firmware_revision: u8,
    pub user_id: u8,
    pub device_type: u8,
    pub activated_options: u8,
    pub device_lock_status: u8,
    pub hw_type: u8,
    pub fw_type: u8,
    pub product_id: u8,
    pub secondary_firmware_version_major: u8,
    pub secondary_firmware_version_minor: u8,
    pub device_is_bootloader: u8,
}

/// Network device summary for enumeration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkDeviceSummary {
    pub serial_number: u32,
    pub ip_address: [u8; 4],
    pub host_ip: [u8; 4],
    pub firmware_version_major: u8,
    pub firmware_version_minor: u8,
    pub firmware_revision: u8,
    pub user_id: u8,
    pub dhcp: u8,
    pub hw_type: u8,
    pub use_udp: u8,
}

/// Network device information
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NetworkDeviceInfo {
    pub ip_address_current: [u8; 4],
    pub ip_address_setup: [u8; 4],
    pub subnet_mask: [u8; 4],
    pub gateway_ip: [u8; 4],
    pub tcp_timeout: u16,
    pub additional_network_options: u8,
    pub dhcp: u8,
}

/// Real-time clock structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealTimeClock {
    pub sec: u8,
    pub min: u8,
    pub hour: u8,
    pub dow: u8,
    pub dom: u8,
    pub tmp: u8,
    pub doy: u16,
    pub month: u16,
    pub year: u16,
}

/// CAN message structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CanMessage {
    pub id: u32,
    pub data: [u8; 8],
    pub len: u8,
    pub format: u8,
    pub msg_type: u8,
}

/// Communication buffer size constants
pub const REQUEST_BUFFER_SIZE: usize = 68;
pub const RESPONSE_BUFFER_SIZE: usize = 68;
pub const MULTIPART_BUFFER_SIZE: usize = 448;

/// Default implementations for common types
impl Default for DeviceData {
    fn default() -> Self {
        Self {
            device_type_id: 0,
            serial_number: 0,
            device_name: [0; 30],
            device_type_name: [0; 30],
            build_date: [0; 12],
            activation_code: [0; 8],
            firmware_version_major: 0,
            firmware_version_minor: 0,
            firmware_revision: 0,
            user_id: 0,
            device_type: 0,
            activated_options: 0,
            device_lock_status: 0,
            hw_type: 0,
            fw_type: 0,
            product_id: 0,
            secondary_firmware_version_major: 0,
            secondary_firmware_version_minor: 0,
            device_is_bootloader: 0,
        }
    }
}

/// Additional types for testing compatibility
impl DeviceData {
    pub fn device_locked(&self) -> bool {
        self.device_lock_status != 0
    }

    pub fn device_features(&self) -> u8 {
        self.activated_options
    }

    /// Parse and format the software version from bytes 5 and 6 of device data response
    /// Byte 5: bits 4-7 = major-1, bits 0-3 = minor
    /// Byte 6: revision number
    /// Returns formatted version string like "4.7.15"
    pub fn software_version_string(&self) -> String {
        // For now, using firmware_version_major as byte 5 and firmware_version_minor as byte 6
        // This will be updated when we implement the proper read device data command
        let software_version_byte = self.firmware_version_major;
        let revision_byte = self.firmware_version_minor;

        // Extract major version: 1 + bits 4-7
        let major = 1 + ((software_version_byte >> 4) & 0x0F);

        // Extract minor version: bits 0-3
        let minor = software_version_byte & 0x0F;

        // Revision is the full byte 6 value
        let revision = revision_byte;

        format!("{}.{}.{}", major, minor, revision)
    }

    pub fn device_name(&self) -> String {
        String::from_utf8_lossy(&self.device_name)
            .trim_end_matches('\0')
            .to_string()
    }

    /// Get the device type name based on hardware ID, device_type_id, or extracted from device_name
    pub fn device_type_name(&self) -> String {
        // First check for device signature in device_type_name field (from read device data command)

        // Try to use the hardware ID (hw_type) from read device data command (byte 19)
        match self.hw_type {
            1 => "PoKeys55".to_string(),
            2 => "PoKeys55".to_string(),
            3 => "PoKeys55".to_string(),
            10 => "Pokeys56U".to_string(),
            11 => "Pokeys56E".to_string(),
            28 => "Pokeys57U".to_string(),
            29 => "Pokeys57E".to_string(),
            30 => "PoKeys57Uv1.1".to_string(),
            31 => "PoKeys57Ev1.1".to_string(),
            32 => "PoKeys57CNC".to_string(),
            35 => "PoKeys57U OEM".to_string(),
            36 => "PoKeys57U OEM".to_string(),
            37 => "PoPLC57NG".to_string(),
            38 => "PoKeys57CNCdb25 ".to_string(),
            39 => "PoKeys57Utest ".to_string(),
            40 => "PoKeys58EU".to_string(),
            41 => "PoBootload (series 58)".to_string(),
            43 => "LiniTester programmer".to_string(),
            44 => "LiniTester calibrator  ".to_string(),
            45 => "PoKeys57Industrial1 ".to_string(),
            50 => "PoPLC v1.0".to_string(),
            60 => "PoKeys16".to_string(),
            // Add more HW ID mappings as needed
            _ => {
                // Fallback to showing the unknown device ID and hardware ID
                format!(
                    "Unknown Device (ID: {}, HW: {})",
                    self.device_type_id, self.hw_type
                )
            }
        }
    }

    /// Get the build date as a string
    pub fn build_date_string(&self) -> String {
        String::from_utf8_lossy(&self.build_date)
            .trim_end_matches('\0')
            .to_string()
    }
}

impl NetworkDeviceInfo {
    pub fn ip_address(&self) -> [u8; 4] {
        self.ip_address_current
    }

    pub fn gateway(&self) -> [u8; 4] {
        self.gateway_ip
    }

    pub fn dns_server(&self) -> [u8; 4] {
        [0; 4] // Not stored in this structure
    }

    pub fn mac_address(&self) -> [u8; 6] {
        [0; 6] // Not stored in this structure
    }

    pub fn device_name(&self) -> String {
        "".to_string() // Not stored in this structure
    }

    pub fn http_port(&self) -> u16 {
        80 // Default HTTP port
    }

    pub fn tcp_port(&self) -> u16 {
        20055 // Default PoKeys TCP port
    }

    pub fn udp_port(&self) -> u16 {
        20055 // Default PoKeys UDP port
    }

    pub fn dhcp_enabled(&self) -> bool {
        self.dhcp != 0
    }
}

/// USB vendor and product IDs
pub const USB_VENDOR_ID: u16 = 0x1DC3;
pub const USB_PRODUCT_ID_1: u16 = 0x1001;
pub const USB_PRODUCT_ID_2: u16 = 0x1002;

/// Protocol constants
pub const REQUEST_HEADER: u8 = 0xBB;
pub const RESPONSE_HEADER: u8 = 0xAA;
pub const CHECKSUM_LENGTH: usize = 7;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_device_type_name() {
        // Test known device types by hw_type
        let mut device_data = DeviceData {
            hw_type: 10,
            ..Default::default()
        };
        assert_eq!(device_data.device_type_name(), "Pokeys56U");

        device_data.hw_type = 11;
        assert_eq!(device_data.device_type_name(), "Pokeys56E");

        device_data.hw_type = 30;
        assert_eq!(device_data.device_type_name(), "PoKeys57Uv1.1");

        device_data.hw_type = 31;
        assert_eq!(device_data.device_type_name(), "PoKeys57Ev1.1");

        device_data.hw_type = 32;
        assert_eq!(device_data.device_type_name(), "PoKeys57CNC");

        // Test device type extraction from device_name field
        device_data.hw_type = 0; // Unknown hw_type
        device_data.device_type_id = 999999; // Unknown ID
        device_data.device_name = [0; 30];
        let test_name = b"Dec 11 2024PoKeys57E\0\0\0\0\0\0\0\0\0\0";
        device_data.device_name[..test_name.len()].copy_from_slice(test_name);
        assert_eq!(
            device_data.device_type_name(),
            "Unknown Device (ID: 999999, HW: 0)"
        );

        // Test another device type in name
        let test_name2 = b"Build PoKeys56U info\0\0\0\0\0\0\0\0\0";
        device_data.device_name[..test_name2.len()].copy_from_slice(test_name2);
        assert_eq!(
            device_data.device_type_name(),
            "Unknown Device (ID: 999999, HW: 0)"
        );

        // Test unknown device type
        device_data.device_type_id = 99;
        device_data.hw_type = 0;
        device_data.device_name = [0; 30];
        let test_name3 = b"Some other text\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
        device_data.device_name[..test_name3.len()].copy_from_slice(test_name3);
        assert_eq!(
            device_data.device_type_name(),
            "Unknown Device (ID: 99, HW: 0)"
        );
    }
}