pokeys_lib/
oem_parameters.rs1use crate::device::PoKeysDevice;
9use crate::error::{PoKeysError, Result};
10
11pub const OEM_PARAMETER_MAX_INDEX: u8 = 61;
13
14pub const LOCATION_PARAMETER_INDEX: u8 = 0;
16
17const OEM_PARAM_CMD: u8 = 0xFD;
19const OEM_PARAM_READ: u8 = 0x00;
20const OEM_PARAM_WRITE: u8 = 0x01;
21const OEM_PARAM_CLEAR: u8 = 0x02;
22
23const RESP_SUBCMD: usize = 2; const RESP_STATUS: usize = 5; const RESP_VALUE_START: usize = 8; impl PoKeysDevice {
29 pub fn read_oem_parameter(&mut self, index: u8) -> Result<Option<i32>> {
39 validate_index(index)?;
40
41 let response = self.send_request(OEM_PARAM_CMD, OEM_PARAM_READ, index, 1, 0)?;
44
45 if response[RESP_SUBCMD] != OEM_PARAM_READ {
46 return Err(PoKeysError::Protocol(format!(
47 "OEM read: unexpected response sub-command 0x{:02X}",
48 response[RESP_SUBCMD]
49 )));
50 }
51
52 if response[RESP_STATUS] & 0x01 == 0 {
54 return Ok(None);
55 }
56
57 let value = i32::from_le_bytes([
58 response[RESP_VALUE_START],
59 response[RESP_VALUE_START + 1],
60 response[RESP_VALUE_START + 2],
61 response[RESP_VALUE_START + 3],
62 ]);
63 Ok(Some(value))
64 }
65
66 pub fn write_oem_parameter(&mut self, index: u8, value: i32) -> Result<()> {
73 validate_index(index)?;
74
75 let value_bytes = value.to_le_bytes();
79 let response =
80 self.send_request_with_data(OEM_PARAM_CMD, OEM_PARAM_WRITE, index, 0, 0, &value_bytes)?;
81
82 if response[RESP_SUBCMD] != OEM_PARAM_WRITE {
83 return Err(PoKeysError::Protocol(format!(
84 "OEM write: unexpected response sub-command 0x{:02X}",
85 response[RESP_SUBCMD]
86 )));
87 }
88
89 let echoed = i32::from_le_bytes([
91 response[RESP_VALUE_START],
92 response[RESP_VALUE_START + 1],
93 response[RESP_VALUE_START + 2],
94 response[RESP_VALUE_START + 3],
95 ]);
96 if echoed != value {
97 return Err(PoKeysError::Protocol(format!(
98 "OEM write: echoed value {} does not match written value {}",
99 echoed, value
100 )));
101 }
102
103 Ok(())
104 }
105
106 pub fn get_location(&mut self) -> Result<Option<i32>> {
111 self.read_oem_parameter(LOCATION_PARAMETER_INDEX)
112 }
113
114 pub fn set_location(&mut self, location: i32) -> Result<()> {
119 self.write_oem_parameter(LOCATION_PARAMETER_INDEX, location)
120 }
121
122 pub fn clear_location(&mut self) -> Result<()> {
126 self.clear_oem_parameter(LOCATION_PARAMETER_INDEX)
127 }
128
129 pub fn clear_oem_parameter(&mut self, index: u8) -> Result<()> {
139 validate_index(index)?;
140
141 let response = self.send_request(OEM_PARAM_CMD, OEM_PARAM_CLEAR, index, 0, 0)?;
144
145 if response[RESP_SUBCMD] != OEM_PARAM_CLEAR {
146 return Err(PoKeysError::Protocol(format!(
147 "OEM clear: unexpected response sub-command 0x{:02X}",
148 response[RESP_SUBCMD]
149 )));
150 }
151
152 Ok(())
153 }
154}
155
156fn validate_index(index: u8) -> Result<()> {
157 if index > OEM_PARAMETER_MAX_INDEX {
158 return Err(PoKeysError::Parameter(format!(
159 "OEM parameter index {} out of range (valid range 0–{})",
160 index, OEM_PARAMETER_MAX_INDEX
161 )));
162 }
163 Ok(())
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::communication::Protocol;
170 use crate::types::*;
171
172 #[test]
175 fn validate_index_accepts_boundary_values() {
176 assert!(validate_index(0).is_ok());
177 assert!(validate_index(61).is_ok());
178 }
179
180 #[test]
181 fn validate_index_rejects_out_of_range() {
182 let err = validate_index(62).unwrap_err();
183 assert!(matches!(err, PoKeysError::Parameter(_)));
184 assert!(err.to_string().contains("62"));
185 }
186
187 #[test]
190 fn read_request_packet_layout() {
191 let mut protocol = Protocol::new();
192 let pkt = protocol.prepare_request(OEM_PARAM_CMD, OEM_PARAM_READ, 5, 1, 0, None);
194
195 assert_eq!(pkt[0], REQUEST_HEADER); assert_eq!(pkt[1], OEM_PARAM_CMD); assert_eq!(pkt[2], OEM_PARAM_READ); assert_eq!(pkt[3], 5); assert_eq!(pkt[4], 1); assert_eq!(pkt[5], 0); assert_eq!(pkt[7], Protocol::calculate_checksum(&pkt));
203 }
204
205 #[test]
208 fn write_request_packet_layout() {
209 let mut protocol = Protocol::new();
210 let mut pkt = protocol.prepare_request(OEM_PARAM_CMD, OEM_PARAM_WRITE, 3, 0, 0, None);
212
213 let value: i32 = 0x0102_0304;
215 let value_bytes = value.to_le_bytes();
216 pkt[8..12].copy_from_slice(&value_bytes);
217 pkt[7] = Protocol::calculate_checksum(&pkt);
218
219 assert_eq!(pkt[1], OEM_PARAM_CMD);
220 assert_eq!(pkt[2], OEM_PARAM_WRITE);
221 assert_eq!(pkt[3], 3); assert_eq!(&pkt[8..12], &value_bytes);
223 assert_eq!(pkt[7], Protocol::calculate_checksum(&pkt));
225 }
226
227 #[test]
230 fn read_response_parses_set_parameter() {
231 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
233 response[0] = RESPONSE_HEADER; response[1] = OEM_PARAM_CMD; response[2] = OEM_PARAM_READ; response[3] = LOCATION_PARAMETER_INDEX; response[4] = 1; response[5] = 0x01; response[6] = 1; let value: i32 = 42;
241 response[8..12].copy_from_slice(&value.to_le_bytes());
242 response[7] = Protocol::calculate_checksum(&response);
243
244 assert_eq!(response[RESP_SUBCMD], OEM_PARAM_READ);
246 assert_ne!(response[RESP_STATUS] & 0x01, 0);
247 let parsed = i32::from_le_bytes([
248 response[RESP_VALUE_START],
249 response[RESP_VALUE_START + 1],
250 response[RESP_VALUE_START + 2],
251 response[RESP_VALUE_START + 3],
252 ]);
253 assert_eq!(parsed, 42);
254 }
255
256 #[test]
257 fn read_response_detects_unset_parameter() {
258 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
259 response[0] = RESPONSE_HEADER;
260 response[1] = OEM_PARAM_CMD;
261 response[2] = OEM_PARAM_READ;
262 response[5] = 0x00; response[6] = 1;
264 response[7] = Protocol::calculate_checksum(&response);
265
266 assert_eq!(response[RESP_STATUS] & 0x01, 0);
267 }
268
269 #[test]
270 fn write_response_echo_check() {
271 let value: i32 = -99;
272 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
273 response[0] = RESPONSE_HEADER;
274 response[1] = OEM_PARAM_CMD;
275 response[2] = OEM_PARAM_WRITE;
276 response[3] = LOCATION_PARAMETER_INDEX;
277 response[6] = 1;
278 response[8..12].copy_from_slice(&value.to_le_bytes());
279 response[7] = Protocol::calculate_checksum(&response);
280
281 let echoed = i32::from_le_bytes([
282 response[RESP_VALUE_START],
283 response[RESP_VALUE_START + 1],
284 response[RESP_VALUE_START + 2],
285 response[RESP_VALUE_START + 3],
286 ]);
287 assert_eq!(echoed, value);
288 }
289
290 #[test]
293 fn clear_request_packet_layout() {
294 let mut protocol = Protocol::new();
295 let pkt = protocol.prepare_request(OEM_PARAM_CMD, OEM_PARAM_CLEAR, 7, 0, 0, None);
296
297 assert_eq!(pkt[0], REQUEST_HEADER);
298 assert_eq!(pkt[1], OEM_PARAM_CMD);
299 assert_eq!(pkt[2], OEM_PARAM_CLEAR);
300 assert_eq!(pkt[3], 7); assert_eq!(pkt[4], 0); assert_eq!(pkt[5], 0); assert_eq!(pkt[7], Protocol::calculate_checksum(&pkt));
304 }
305
306 #[test]
307 fn clear_response_subcmd_check() {
308 let mut response = [0u8; RESPONSE_BUFFER_SIZE];
309 response[0] = RESPONSE_HEADER;
310 response[1] = OEM_PARAM_CMD;
311 response[2] = OEM_PARAM_CLEAR;
312 response[3] = 7; response[6] = 1;
314 response[7] = Protocol::calculate_checksum(&response);
315
316 assert_eq!(response[RESP_SUBCMD], OEM_PARAM_CLEAR);
317 }
318}