Initial commit
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
use crate::dsp408::client::constants::{METERS_HIGH_STEP, METERS_LOW_STEP, METERS_SPLIT, METERS_SPLIT_LEVEL};
|
||||
use crate::dsp408::client::types::{
|
||||
Acknowledgement, AuthenticationResult, ConfigChunk, CurrentPreset, DeviceFlags, DeviceInfo,
|
||||
DeviceResponseType, HandshakeAck, Meters, ModifiedPreset, NotImplemented, PresetName,
|
||||
UnknownResponse,
|
||||
};
|
||||
use crate::common::errors::ProtocolError;
|
||||
use crate::dsp408::protocol::frame::Frame;
|
||||
|
||||
use super::encoding::{decode_ascii, decode_float16};
|
||||
|
||||
pub fn map_response(frame: &Frame) -> Result<DeviceResponseType, ProtocolError> {
|
||||
let cmd = frame.command;
|
||||
let payload = &frame.payload;
|
||||
|
||||
// Command `0x01`
|
||||
if cmd == 0x01 {
|
||||
return Ok(DeviceResponseType::Acknowledgement(Acknowledgement {
|
||||
success: true,
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x02`
|
||||
if cmd == 0x02 {
|
||||
return Ok(DeviceResponseType::NotImplemented(NotImplemented));
|
||||
}
|
||||
|
||||
// Command `0x10` (Start)
|
||||
if cmd == 0x10 {
|
||||
if payload.len() != 1 {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid Start response payload length: {}",
|
||||
payload.len()
|
||||
)));
|
||||
}
|
||||
|
||||
return Ok(DeviceResponseType::HandshakeAck(HandshakeAck {
|
||||
ack: payload.first().copied(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x13` (Device Name)
|
||||
if cmd == 0x13 {
|
||||
if payload.is_empty() {
|
||||
return Err(ProtocolError::new("Missing device name payload"));
|
||||
}
|
||||
|
||||
let name = decode_ascii(payload, false)
|
||||
.map_err(|e| ProtocolError::new(e.0))?;
|
||||
|
||||
return Ok(DeviceResponseType::DeviceInfo(DeviceInfo { name }));
|
||||
}
|
||||
|
||||
// Command `0x14` (Current Preset Index)
|
||||
if cmd == 0x14 {
|
||||
if payload.len() != 1 {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid Current Preset payload length: {}",
|
||||
payload.len()
|
||||
)));
|
||||
}
|
||||
|
||||
return Ok(DeviceResponseType::CurrentPreset(CurrentPreset {
|
||||
index: payload[0],
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x22` (Get Preset Modification Status)
|
||||
if cmd == 0x22 {
|
||||
if payload.len() != 20 {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid Modified Preset payload length: {}",
|
||||
payload.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let flags: Vec<bool> = payload.iter().map(|&b| b == 0xFF).collect();
|
||||
|
||||
return Ok(DeviceResponseType::ModifiedPreset(ModifiedPreset {
|
||||
modified: flags,
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x24` (Get Config Chunk)
|
||||
if cmd == 0x24 {
|
||||
// Mirrors Python's `int.from_bytes(payload[:1], "little")`, which
|
||||
// is `0` for an empty payload rather than an error — Python slices
|
||||
// never panic. `payload.get(1..)` below mirrors the same
|
||||
// forgiving-slice behavior for the remainder.
|
||||
let index = payload.first().copied().unwrap_or(0);
|
||||
|
||||
if index > 0x1c {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid Config Chunk Index: {}",
|
||||
index
|
||||
)));
|
||||
}
|
||||
|
||||
let data = payload.get(1..).unwrap_or(&[]).to_vec();
|
||||
|
||||
return Ok(DeviceResponseType::ConfigChunk(ConfigChunk {
|
||||
sub_index: index,
|
||||
data,
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x29` (Preset Name)
|
||||
if cmd == 0x29 {
|
||||
if payload.is_empty() {
|
||||
return Err(ProtocolError::new("Missing preset index"));
|
||||
}
|
||||
|
||||
let name = decode_ascii(&payload[1..], false)
|
||||
.map_err(|e| ProtocolError::new(e.0))?;
|
||||
|
||||
return Ok(DeviceResponseType::PresetName(PresetName {
|
||||
index: payload[0],
|
||||
name,
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x2C` (Device Flags)
|
||||
if cmd == 0x2C {
|
||||
if payload.len() < 6 {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid Device Flags payload length: {}",
|
||||
payload.len()
|
||||
)));
|
||||
}
|
||||
|
||||
if payload[5] != 0x00 && payload[5] != 0x01 {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid lock flag: {:#04x}",
|
||||
payload[5]
|
||||
)));
|
||||
}
|
||||
|
||||
return Ok(DeviceResponseType::DeviceFlags(DeviceFlags {
|
||||
is_locked: payload[5] == 0x01,
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x2D` (Unlock Device)
|
||||
if cmd == 0x2D {
|
||||
if payload.len() < 2 {
|
||||
return Err(ProtocolError::new("Missing authentication result"));
|
||||
}
|
||||
|
||||
if payload[1] != 0x00 && payload[1] != 0x01 {
|
||||
return Err(ProtocolError::new(format!(
|
||||
"Invalid authentication flag: {:#04x}",
|
||||
payload[1]
|
||||
)));
|
||||
}
|
||||
|
||||
return Ok(DeviceResponseType::AuthenticationResult(AuthenticationResult {
|
||||
success: payload[1] == 0x01,
|
||||
}));
|
||||
}
|
||||
|
||||
// Command `0x40` (Get Level Meters)
|
||||
if cmd == 0x40 {
|
||||
if payload.len() != 38 {
|
||||
return Err(ProtocolError::new("Not enough bytes for meter levels"));
|
||||
}
|
||||
|
||||
let floats: Vec<f32> = (0..36)
|
||||
.step_by(3)
|
||||
.map(|i| decode_float16(payload[i], payload[i + 1]).unwrap_or_default())
|
||||
.collect();
|
||||
|
||||
let meters: Vec<f32> = floats
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
if f < METERS_SPLIT {
|
||||
METERS_SPLIT_LEVEL + (f - METERS_SPLIT) / METERS_LOW_STEP
|
||||
} else {
|
||||
METERS_SPLIT_LEVEL + (f - METERS_SPLIT) / METERS_HIGH_STEP
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
return Ok(DeviceResponseType::Meters(Meters { levels: meters }));
|
||||
}
|
||||
|
||||
Ok(DeviceResponseType::UnknownResponse(UnknownResponse {
|
||||
cmd: format!("{:#04x}", cmd),
|
||||
raw: payload.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn acknowledgement() {
|
||||
let frame = Frame { command: 0x01, payload: vec![] };
|
||||
assert_eq!(
|
||||
map_response(&frame).unwrap(),
|
||||
DeviceResponseType::Acknowledgement(Acknowledgement { success: true })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_preset_wrong_length_errors() {
|
||||
let frame = Frame { command: 0x14, payload: vec![1, 2] };
|
||||
assert!(map_response(&frame).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_command_falls_through() {
|
||||
let frame = Frame { command: 0x99, payload: vec![0xAA] };
|
||||
match map_response(&frame).unwrap() {
|
||||
DeviceResponseType::UnknownResponse(r) => {
|
||||
assert_eq!(r.cmd, "0x99");
|
||||
assert_eq!(r.raw, vec![0xAA]);
|
||||
}
|
||||
other => panic!("expected UnknownResponse, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_chunk_empty_payload_defaults_to_zero() {
|
||||
let frame = Frame { command: 0x24, payload: vec![] };
|
||||
match map_response(&frame).unwrap() {
|
||||
DeviceResponseType::ConfigChunk(c) => {
|
||||
assert_eq!(c.sub_index, 0);
|
||||
assert_eq!(c.data, Vec::<u8>::new());
|
||||
}
|
||||
other => panic!("expected ConfigChunk, got {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user