kenwood_thd75/protocol/
control.rs1use crate::error::ProtocolError;
9use crate::types::Band;
10use crate::types::radio_params::{BatteryLevel, DetectOutputMode, VoxDelay, VoxGain};
11
12use super::Response;
13
14pub(crate) fn parse_control(
18 mnemonic: &str,
19 payload: &str,
20) -> Option<Result<Response, ProtocolError>> {
21 match mnemonic {
22 "AI" => Some(parse_bool(payload, "AI").map(|enabled| Response::AutoInfo { enabled })),
23 "BY" => Some(parse_by(payload)),
24 "DL" => Some(parse_bool(payload, "DL").map(|enabled| Response::DualBand { enabled })),
25 "DW" => Some(Ok(Response::FrequencyDown)),
26 "BE" => Some(parse_bool(payload, "BE").map(|enabled| Response::Beep { enabled })),
27 "RX" | "TX" => Some(Ok(Response::Ok)),
28 "LC" => Some(parse_bool(payload, "LC").map(|locked| Response::Lock { locked })),
29 "IO" => Some(parse_u8_field(payload, "IO", "value").and_then(|raw| {
30 DetectOutputMode::try_from(raw)
31 .map(|value| Response::IoPort { value })
32 .map_err(|e| ProtocolError::FieldParse {
33 command: "IO".into(),
34 field: "value".into(),
35 detail: e.to_string(),
36 })
37 })),
38 "BL" => Some(parse_bl(payload)),
39 "VD" => Some(parse_u8_field(payload, "VD", "delay").and_then(|raw| {
40 VoxDelay::try_from(raw)
41 .map(|delay| Response::VoxDelay { delay })
42 .map_err(|e| ProtocolError::FieldParse {
43 command: "VD".into(),
44 field: "delay".into(),
45 detail: e.to_string(),
46 })
47 })),
48 "VG" => Some(parse_u8_field(payload, "VG", "gain").and_then(|raw| {
49 VoxGain::try_from(raw)
50 .map(|gain| Response::VoxGain { gain })
51 .map_err(|e| ProtocolError::FieldParse {
52 command: "VG".into(),
53 field: "gain".into(),
54 detail: e.to_string(),
55 })
56 })),
57 "VX" => Some(parse_bool(payload, "VX").map(|enabled| Response::Vox { enabled })),
58 _ => None,
59 }
60}
61
62fn parse_bool(payload: &str, cmd: &str) -> Result<bool, ProtocolError> {
66 match payload.trim() {
67 "" | "0" => Ok(false),
68 "1" => Ok(true),
69 _ => Err(ProtocolError::FieldParse {
70 command: cmd.to_owned(),
71 field: "value".to_owned(),
72 detail: format!("expected 0 or 1, got {payload:?}"),
73 }),
74 }
75}
76
77fn parse_u8_field(s: &str, cmd: &str, field: &str) -> Result<u8, ProtocolError> {
79 s.parse::<u8>().map_err(|_| ProtocolError::FieldParse {
80 command: cmd.to_owned(),
81 field: field.to_owned(),
82 detail: format!("invalid u8: {s:?}"),
83 })
84}
85
86fn split_band_value<'a>(payload: &'a str, cmd: &str) -> Result<(Band, &'a str), ProtocolError> {
88 let parts: Vec<&str> = payload.splitn(2, ',').collect();
89 if parts.len() != 2 {
90 return Err(ProtocolError::FieldParse {
91 command: cmd.to_owned(),
92 field: "all".to_owned(),
93 detail: format!("expected band,value, got {payload:?}"),
94 });
95 }
96 let band_val = parse_u8_field(parts[0], cmd, "band")?;
97 let band = Band::try_from(band_val).map_err(|e| ProtocolError::FieldParse {
98 command: cmd.to_owned(),
99 field: "band".to_owned(),
100 detail: e.to_string(),
101 })?;
102 Ok((band, parts[1]))
103}
104
105fn parse_bl(payload: &str) -> Result<Response, ProtocolError> {
114 let level_str = if let Some((_prefix, level)) = payload.split_once(',') {
115 level
116 } else {
117 payload
118 };
119 let raw = parse_u8_field(level_str.trim(), "BL", "level")?;
120 let level = BatteryLevel::try_from(raw).map_err(|e| ProtocolError::FieldParse {
121 command: "BL".to_owned(),
122 field: "level".to_owned(),
123 detail: e.to_string(),
124 })?;
125 Ok(Response::BatteryLevel { level })
126}
127
128fn parse_by(payload: &str) -> Result<Response, ProtocolError> {
130 let (band, val_str) = split_band_value(payload, "BY")?;
131 let val = parse_u8_field(val_str, "BY", "busy")?;
132 Ok(Response::Busy {
133 band,
134 busy: val != 0,
135 })
136}