pokeys_lib/protocols/
i2c.rs

1//! I2C protocol implementation
2//!
3//! This module provides I2C communication functionality for PoKeys devices.
4//! The implementation follows the PoKeys protocol specification for I2C operations.
5
6use crate::device::PoKeysDevice;
7use crate::error::{PoKeysError, Result};
8use crate::types::{I2cStatus, RetryConfig};
9use std::time::Duration;
10
11/// I2C protocol implementation
12impl PoKeysDevice {
13    /// Initialize I2C bus with default settings
14    ///
15    /// This initializes the I2C bus. Note: I2C bus is always activated on PoKeys devices.
16    pub fn i2c_init(&mut self) -> Result<()> {
17        // I2C bus is always activated on PoKeys devices, so just return success
18        // We can optionally check the activation status
19        let response = self.send_request(0xDB, 0x02, 0, 0, 0)?;
20
21        // Check if I2C is activated (should always be successful)
22        if response.len() > 3 && response[3] == 1 {
23            Ok(())
24        } else {
25            Err(PoKeysError::Protocol("I2C bus not available".to_string()))
26        }
27    }
28
29    /// Configure I2C bus with specific settings
30    ///
31    /// # Arguments
32    /// * `speed_khz` - I2C bus speed in kHz (typically 100 or 400)
33    /// * `options` - Additional I2C configuration options
34    pub fn i2c_configure(&mut self, _speed_khz: u16, _options: u8) -> Result<()> {
35        // I2C configuration is handled automatically by the device
36        // Just ensure I2C is available
37        self.i2c_init()
38    }
39
40    /// Write data to I2C device with enhanced error handling
41    ///
42    /// # Arguments
43    /// * `address` - 7-bit I2C device address
44    /// * `data` - Data buffer to write (maximum 32 bytes)
45    ///
46    /// # Returns
47    /// I2C operation status
48    pub fn i2c_write(&mut self, address: u8, data: &[u8]) -> Result<I2cStatus> {
49        if data.is_empty() {
50            return Err(PoKeysError::Parameter(
51                "I2C data cannot be empty".to_string(),
52            ));
53        }
54
55        if data.len() > 32 {
56            return Err(PoKeysError::I2cPacketTooLarge {
57                size: data.len(),
58                max_size: 32,
59                suggestion: "Use i2c_write_fragmented() for large packets or split data manually"
60                    .to_string(),
61            });
62        }
63
64        // Start I2C write operation
65        // Command 0xDB, operation 0x10 - Write to I2C - start
66        let response = self.send_request_with_data(
67            0xDB,             // Command
68            0x10,             // Operation: Write to I2C - start
69            address,          // I2C device address
70            data.len() as u8, // Length of data packet
71            0,                // Number of bytes to read after write (0 for write-only)
72            data,             // Data payload (bytes 9-40)
73        )?;
74
75        // Check initial response
76        let initial_status = self.parse_i2c_status(&response)?;
77
78        // If operation is in progress, get the result
79        if initial_status == I2cStatus::InProgress {
80            // Wait a bit for the operation to complete
81            std::thread::sleep(std::time::Duration::from_millis(10));
82
83            // Get the result with operation 0x11 - Write to I2C - get result
84            let result_response = self.send_request(0xDB, 0x11, 0, 0, 0)?;
85            self.parse_i2c_status(&result_response)
86        } else {
87            Ok(initial_status)
88        }
89    }
90
91    /// Read data from I2C device
92    ///
93    /// # Arguments
94    /// * `address` - 7-bit I2C device address
95    /// * `length` - Number of bytes to read (maximum 32 bytes)
96    ///
97    /// # Returns
98    /// Tuple of (status, data) where data contains the read bytes
99    pub fn i2c_read(&mut self, address: u8, length: u8) -> Result<(I2cStatus, Vec<u8>)> {
100        if length == 0 {
101            return Err(PoKeysError::Parameter(
102                "I2C read length cannot be zero".to_string(),
103            ));
104        }
105
106        if length > 32 {
107            return Err(PoKeysError::Parameter(
108                "I2C read length too long (maximum 32 bytes)".to_string(),
109            ));
110        }
111
112        // Start I2C read operation
113        // Command 0xDB, operation 0x20 - Read from I2C - start
114        let response = self.send_request(
115            0xDB,    // Command
116            0x20,    // Operation: Read from I2C - start
117            address, // I2C device address
118            length,  // Length of data packet to read
119            0,       // Reserved
120        )?;
121
122        let initial_status = self.parse_i2c_status(&response)?;
123
124        // If operation is in progress, get the result
125        if initial_status == I2cStatus::InProgress {
126            // Wait a bit for the operation to complete
127            std::thread::sleep(std::time::Duration::from_millis(10));
128
129            // Get the result with operation 0x21 - Read from I2C - get result
130            let result_response = self.send_request(0xDB, 0x21, 0, 0, 0)?;
131            let status = self.parse_i2c_status(&result_response)?;
132
133            let mut data = Vec::new();
134            if status == I2cStatus::Ok && result_response.len() > 10 {
135                // Byte 10: data length, Bytes 11-42: data bytes
136                let data_length = result_response[9] as usize; // Byte 10 (0-indexed as 9)
137                if result_response.len() >= 10 + data_length {
138                    data.extend_from_slice(&result_response[10..10 + data_length]);
139                }
140            }
141
142            Ok((status, data))
143        } else {
144            Ok((initial_status, Vec::new()))
145        }
146    }
147
148    /// Write to I2C device register
149    ///
150    /// This is a convenience method for writing to a specific register in an I2C device.
151    ///
152    /// # Arguments
153    /// * `address` - 7-bit I2C device address
154    /// * `register` - Register address
155    /// * `data` - Data to write to the register
156    pub fn i2c_write_register(
157        &mut self,
158        address: u8,
159        register: u8,
160        data: &[u8],
161    ) -> Result<I2cStatus> {
162        if data.len() > 31 {
163            return Err(PoKeysError::Parameter(
164                "I2C register data too long (maximum 31 bytes)".to_string(),
165            ));
166        }
167
168        let mut write_data = Vec::with_capacity(1 + data.len());
169        write_data.push(register);
170        write_data.extend_from_slice(data);
171
172        self.i2c_write(address, &write_data)
173    }
174
175    /// Read from I2C device register
176    ///
177    /// This is a convenience method for reading from a specific register in an I2C device.
178    ///
179    /// # Arguments
180    /// * `address` - 7-bit I2C device address
181    /// * `register` - Register address
182    /// * `length` - Number of bytes to read
183    pub fn i2c_read_register(
184        &mut self,
185        address: u8,
186        register: u8,
187        length: u8,
188    ) -> Result<(I2cStatus, Vec<u8>)> {
189        // First write the register address
190        let status = self.i2c_write(address, &[register])?;
191        if status != I2cStatus::Ok {
192            return Ok((status, Vec::new()));
193        }
194
195        // Small delay between write and read
196        std::thread::sleep(std::time::Duration::from_millis(1));
197
198        // Then read the data
199        self.i2c_read(address, length)
200    }
201
202    /// Scan I2C bus for devices
203    ///
204    /// This method scans the I2C bus for responding devices.
205    ///
206    /// # Returns
207    /// Vector of addresses that responded to the scan
208    pub fn i2c_scan(&mut self) -> Result<Vec<u8>> {
209        // Start I2C scan operation
210        // Command 0xDB, operation 0x30 - Scan I2C - start
211        let response = self.send_request(0xDB, 0x30, 0, 0, 0)?;
212
213        let initial_status = self.parse_i2c_status(&response)?;
214
215        // If operation is in progress, get the result
216        if initial_status == I2cStatus::InProgress {
217            // Wait for scan to complete
218            std::thread::sleep(std::time::Duration::from_millis(100));
219
220            // Get the result with operation 0x31 - Scan I2C - get result
221            let result_response = self.send_request(0xDB, 0x31, 0, 0, 0)?;
222            let status = self.parse_i2c_status(&result_response)?;
223
224            let mut found_devices = Vec::new();
225            if status == I2cStatus::Ok && result_response.len() >= 25 {
226                // Bytes 10-25: bit encoded result (16 bytes = 128 bits for addresses 0x00-0x7F)
227                for byte_idx in 0..16 {
228                    if result_response.len() > 9 + byte_idx {
229                        let byte_val = result_response[9 + byte_idx];
230                        for bit_idx in 0..8 {
231                            if (byte_val & (1 << bit_idx)) != 0 {
232                                let address = (byte_idx * 8 + bit_idx) as u8;
233                                // Only include valid 7-bit I2C addresses (0x08-0x77)
234                                if (0x08..=0x77).contains(&address) {
235                                    found_devices.push(address);
236                                }
237                            }
238                        }
239                    }
240                }
241            }
242
243            Ok(found_devices)
244        } else {
245            Ok(Vec::new())
246        }
247    }
248
249    /// Parse I2C status from response
250    fn parse_i2c_status(&self, response: &[u8]) -> Result<I2cStatus> {
251        if response.len() < 4 {
252            return Err(PoKeysError::Protocol(
253                "Invalid I2C response length".to_string(),
254            ));
255        }
256
257        let status = match response[3] {
258            0 => I2cStatus::Error,
259            1 => I2cStatus::Ok,
260            0x10 => I2cStatus::InProgress,
261            _ => I2cStatus::Error,
262        };
263
264        Ok(status)
265    }
266
267    /// Write data to I2C device with automatic packet fragmentation
268    ///
269    /// This method automatically fragments large I2C packets into smaller chunks
270    /// that fit within the 32-byte limit.
271    ///
272    /// # Arguments
273    /// * `address` - 7-bit I2C device address
274    /// * `data` - Data buffer to write (any size)
275    ///
276    /// # Returns
277    /// I2C operation status
278    pub fn i2c_write_fragmented(&mut self, address: u8, data: &[u8]) -> Result<I2cStatus> {
279        const MAX_PACKET_SIZE: usize = 32;
280
281        if data.len() <= MAX_PACKET_SIZE {
282            return self.i2c_write(address, data);
283        }
284
285        // Fragment into multiple packets with sequence numbers
286        for (seq, chunk) in data.chunks(MAX_PACKET_SIZE - 2).enumerate() {
287            let mut packet = vec![0xF0 | (seq as u8 & 0x0F)]; // Fragment header
288            packet.extend_from_slice(chunk);
289
290            let status = self.i2c_write(address, &packet)?;
291            if status != I2cStatus::Ok {
292                return Ok(status);
293            }
294
295            // Wait for acknowledgment before sending next fragment
296            std::thread::sleep(Duration::from_millis(10));
297        }
298
299        // Send end-of-transmission marker
300        self.i2c_write(address, &[0xFF])
301    }
302
303    /// Write data to I2C device with retry logic
304    ///
305    /// # Arguments
306    /// * `address` - 7-bit I2C device address
307    /// * `data` - Data buffer to write
308    /// * `config` - Retry configuration
309    ///
310    /// # Returns
311    /// I2C operation status
312    pub fn i2c_write_with_retry(
313        &mut self,
314        address: u8,
315        data: &[u8],
316        config: &RetryConfig,
317    ) -> Result<I2cStatus> {
318        let mut delay = config.base_delay_ms;
319
320        for attempt in 0..config.max_attempts {
321            match self.i2c_write(address, data) {
322                Ok(status) => return Ok(status),
323                Err(e) if e.is_recoverable() => {
324                    if attempt < config.max_attempts - 1 {
325                        let actual_delay = if config.jitter {
326                            delay + (fastrand::u64(0..delay / 4))
327                        } else {
328                            delay
329                        };
330
331                        std::thread::sleep(Duration::from_millis(actual_delay));
332                        delay = std::cmp::min(
333                            (delay as f64 * config.backoff_multiplier) as u64,
334                            config.max_delay_ms,
335                        );
336                    }
337                }
338                Err(e) => return Err(e), // Non-recoverable error
339            }
340        }
341
342        Err(PoKeysError::MaxRetriesExceeded)
343    }
344
345    /// Check I2C bus status
346    ///
347    /// This method checks the current status of the I2C bus.
348    pub fn i2c_get_status(&mut self) -> Result<I2cStatus> {
349        // Check I2C activation status
350        let response = self.send_request(0xDB, 0x02, 0, 0, 0)?;
351        self.parse_i2c_status(&response)
352    }
353}