kenwood_thd75/protocol/
aprs.rs

1//! APRS-related commands: AS (TNC baud), AE (serial info), PT (beacon type), MS (position source).
2//!
3//! Provides parsing of responses for the 4 APRS-related CAT protocol
4//! commands. Serialization is handled inline by the main dispatcher.
5
6use crate::error::ProtocolError;
7use crate::types::radio_params::{BeaconMode, TncBaud};
8
9use super::Response;
10
11/// Parse a `u8` from a string field.
12fn parse_u8_field(s: &str, cmd: &str, field: &str) -> Result<u8, ProtocolError> {
13    s.parse::<u8>().map_err(|_| ProtocolError::FieldParse {
14        command: cmd.to_owned(),
15        field: field.to_owned(),
16        detail: format!("invalid u8: {s:?}"),
17    })
18}
19
20/// Parse AE (serial info): `serial,model_code`.
21///
22/// Despite the AE mnemonic, this returns the radio serial number and model code.
23/// Example: `C3C10368,K01`.
24fn parse_ae(payload: &str) -> Response {
25    if let Some((serial, model_code)) = payload.split_once(',') {
26        Response::SerialInfo {
27            serial: serial.to_owned(),
28            model_code: model_code.to_owned(),
29        }
30    } else {
31        // Fallback: treat whole payload as serial with empty model_code
32        Response::SerialInfo {
33            serial: payload.to_owned(),
34            model_code: String::new(),
35        }
36    }
37}
38
39/// Parse an APRS command response from mnemonic and payload.
40///
41/// Returns `None` if the mnemonic is not an APRS command.
42pub(crate) fn parse_aprs(mnemonic: &str, payload: &str) -> Option<Result<Response, ProtocolError>> {
43    match mnemonic {
44        "AS" => Some(parse_u8_field(payload, "AS", "rate").and_then(|raw| {
45            TncBaud::try_from(raw)
46                .map(|rate| Response::TncBaud { rate })
47                .map_err(|e| ProtocolError::FieldParse {
48                    command: "AS".into(),
49                    field: "rate".into(),
50                    detail: e.to_string(),
51                })
52        })),
53        "AE" => Some(Ok(parse_ae(payload))),
54        "PT" => Some(parse_u8_field(payload, "PT", "mode").and_then(|raw| {
55            BeaconMode::try_from(raw)
56                .map(|mode| Response::BeaconType { mode })
57                .map_err(|e| ProtocolError::FieldParse {
58                    command: "PT".into(),
59                    field: "mode".into(),
60                    detail: e.to_string(),
61                })
62        })),
63        "MS" => Some(
64            parse_u8_field(payload, "MS", "source")
65                .map(|source| Response::PositionSource { source }),
66        ),
67        _ => None,
68    }
69}