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}