use crate::device::PoKeysDevice;
use crate::error::{PoKeysError, Result};
pub const OEM_PARAMETER_MAX_INDEX: u8 = 61;
pub const LOCATION_PARAMETER_INDEX: u8 = 0;
const OEM_PARAM_CMD: u8 = 0xFD;
const OEM_PARAM_READ: u8 = 0x00;
const OEM_PARAM_WRITE: u8 = 0x01;
const OEM_PARAM_CLEAR: u8 = 0x02;
const RESP_SUBCMD: usize = 2; const RESP_STATUS: usize = 5; const RESP_VALUE_START: usize = 8;
impl PoKeysDevice {
pub fn read_oem_parameter(&mut self, index: u8) -> Result<Option<i32>> {
validate_index(index)?;
let response = self.send_request(OEM_PARAM_CMD, OEM_PARAM_READ, index, 1, 0)?;
if response[RESP_SUBCMD] != OEM_PARAM_READ {
return Err(PoKeysError::Protocol(format!(
"OEM read: unexpected response sub-command 0x{:02X}",
response[RESP_SUBCMD]
)));
}
if response[RESP_STATUS] & 0x01 == 0 {
return Ok(None);
}
let value = i32::from_le_bytes([
response[RESP_VALUE_START],
response[RESP_VALUE_START + 1],
response[RESP_VALUE_START + 2],
response[RESP_VALUE_START + 3],
]);
Ok(Some(value))
}
pub fn write_oem_parameter(&mut self, index: u8, value: i32) -> Result<()> {
validate_index(index)?;
let value_bytes = value.to_le_bytes();
let response =
self.send_request_with_data(OEM_PARAM_CMD, OEM_PARAM_WRITE, index, 0, 0, &value_bytes)?;
if response[RESP_SUBCMD] != OEM_PARAM_WRITE {
return Err(PoKeysError::Protocol(format!(
"OEM write: unexpected response sub-command 0x{:02X}",
response[RESP_SUBCMD]
)));
}
let echoed = i32::from_le_bytes([
response[RESP_VALUE_START],
response[RESP_VALUE_START + 1],
response[RESP_VALUE_START + 2],
response[RESP_VALUE_START + 3],
]);
if echoed != value {
return Err(PoKeysError::Protocol(format!(
"OEM write: echoed value {} does not match written value {}",
echoed, value
)));
}
Ok(())
}
pub fn get_location(&mut self) -> Result<Option<i32>> {
self.read_oem_parameter(LOCATION_PARAMETER_INDEX)
}
pub fn set_location(&mut self, location: i32) -> Result<()> {
self.write_oem_parameter(LOCATION_PARAMETER_INDEX, location)
}
pub fn clear_location(&mut self) -> Result<()> {
self.clear_oem_parameter(LOCATION_PARAMETER_INDEX)
}
pub fn clear_oem_parameter(&mut self, index: u8) -> Result<()> {
validate_index(index)?;
let response = self.send_request(OEM_PARAM_CMD, OEM_PARAM_CLEAR, index, 0, 0)?;
if response[RESP_SUBCMD] != OEM_PARAM_CLEAR {
return Err(PoKeysError::Protocol(format!(
"OEM clear: unexpected response sub-command 0x{:02X}",
response[RESP_SUBCMD]
)));
}
Ok(())
}
}
fn validate_index(index: u8) -> Result<()> {
if index > OEM_PARAMETER_MAX_INDEX {
return Err(PoKeysError::Parameter(format!(
"OEM parameter index {} out of range (valid range 0–{})",
index, OEM_PARAMETER_MAX_INDEX
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::communication::Protocol;
use crate::types::*;
#[test]
fn validate_index_accepts_boundary_values() {
assert!(validate_index(0).is_ok());
assert!(validate_index(61).is_ok());
}
#[test]
fn validate_index_rejects_out_of_range() {
let err = validate_index(62).unwrap_err();
assert!(matches!(err, PoKeysError::Parameter(_)));
assert!(err.to_string().contains("62"));
}
#[test]
fn read_request_packet_layout() {
let mut protocol = Protocol::new();
let pkt = protocol.prepare_request(OEM_PARAM_CMD, OEM_PARAM_READ, 5, 1, 0, None);
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));
}
#[test]
fn write_request_packet_layout() {
let mut protocol = Protocol::new();
let mut pkt = protocol.prepare_request(OEM_PARAM_CMD, OEM_PARAM_WRITE, 3, 0, 0, None);
let value: i32 = 0x0102_0304;
let value_bytes = value.to_le_bytes();
pkt[8..12].copy_from_slice(&value_bytes);
pkt[7] = Protocol::calculate_checksum(&pkt);
assert_eq!(pkt[1], OEM_PARAM_CMD);
assert_eq!(pkt[2], OEM_PARAM_WRITE);
assert_eq!(pkt[3], 3); assert_eq!(&pkt[8..12], &value_bytes);
assert_eq!(pkt[7], Protocol::calculate_checksum(&pkt));
}
#[test]
fn read_response_parses_set_parameter() {
let mut response = [0u8; RESPONSE_BUFFER_SIZE];
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;
response[8..12].copy_from_slice(&value.to_le_bytes());
response[7] = Protocol::calculate_checksum(&response);
assert_eq!(response[RESP_SUBCMD], OEM_PARAM_READ);
assert_ne!(response[RESP_STATUS] & 0x01, 0);
let parsed = i32::from_le_bytes([
response[RESP_VALUE_START],
response[RESP_VALUE_START + 1],
response[RESP_VALUE_START + 2],
response[RESP_VALUE_START + 3],
]);
assert_eq!(parsed, 42);
}
#[test]
fn read_response_detects_unset_parameter() {
let mut response = [0u8; RESPONSE_BUFFER_SIZE];
response[0] = RESPONSE_HEADER;
response[1] = OEM_PARAM_CMD;
response[2] = OEM_PARAM_READ;
response[5] = 0x00; response[6] = 1;
response[7] = Protocol::calculate_checksum(&response);
assert_eq!(response[RESP_STATUS] & 0x01, 0);
}
#[test]
fn write_response_echo_check() {
let value: i32 = -99;
let mut response = [0u8; RESPONSE_BUFFER_SIZE];
response[0] = RESPONSE_HEADER;
response[1] = OEM_PARAM_CMD;
response[2] = OEM_PARAM_WRITE;
response[3] = LOCATION_PARAMETER_INDEX;
response[6] = 1;
response[8..12].copy_from_slice(&value.to_le_bytes());
response[7] = Protocol::calculate_checksum(&response);
let echoed = i32::from_le_bytes([
response[RESP_VALUE_START],
response[RESP_VALUE_START + 1],
response[RESP_VALUE_START + 2],
response[RESP_VALUE_START + 3],
]);
assert_eq!(echoed, value);
}
#[test]
fn clear_request_packet_layout() {
let mut protocol = Protocol::new();
let pkt = protocol.prepare_request(OEM_PARAM_CMD, OEM_PARAM_CLEAR, 7, 0, 0, None);
assert_eq!(pkt[0], REQUEST_HEADER);
assert_eq!(pkt[1], OEM_PARAM_CMD);
assert_eq!(pkt[2], OEM_PARAM_CLEAR);
assert_eq!(pkt[3], 7); assert_eq!(pkt[4], 0); assert_eq!(pkt[5], 0); assert_eq!(pkt[7], Protocol::calculate_checksum(&pkt));
}
#[test]
fn clear_response_subcmd_check() {
let mut response = [0u8; RESPONSE_BUFFER_SIZE];
response[0] = RESPONSE_HEADER;
response[1] = OEM_PARAM_CMD;
response[2] = OEM_PARAM_CLEAR;
response[3] = 7; response[6] = 1;
response[7] = Protocol::calculate_checksum(&response);
assert_eq!(response[RESP_SUBCMD], OEM_PARAM_CLEAR);
}
}