kenwood_thd75/radio/gps.rs
1//! GPS subsystem methods.
2//!
3//! The TH-D75 has a built-in GPS receiver that provides position data for APRS beaconing,
4//! waypoint navigation, and time synchronization. The GPS integrates directly with the APRS
5//! TNC — when APRS beaconing is enabled and the GPS has a fix, position reports are
6//! automatically included in transmitted beacons.
7//!
8//! The `pc_output` flag in the GPS configuration controls whether raw NMEA sentences are
9//! forwarded over the serial (USB/BT) connection. This is useful for feeding GPS data to
10//! mapping software, but **competes with CAT command I/O** on the same serial channel.
11//!
12//! # Related commands
13//!
14//! - **GP**: GPS enable and PC output configuration
15//! - **GS**: NMEA sentence selection (which sentence types to output)
16//! - **GM**: GPS/Radio mode (bare read only — `GM 1` reboots the radio into GPS-only mode)
17
18use crate::error::{Error, ProtocolError};
19use crate::protocol::{Command, Response};
20use crate::transport::Transport;
21use crate::types::GpsRadioMode;
22
23use super::Radio;
24
25impl<T: Transport> Radio<T> {
26 /// Get GPS configuration (GP read).
27 ///
28 /// Returns `(gps_enabled, pc_output)`.
29 ///
30 /// # Errors
31 ///
32 /// Returns an error if the command fails or the response is unexpected.
33 pub async fn get_gps_config(&mut self) -> Result<(bool, bool), Error> {
34 tracing::debug!("reading GPS config");
35 let response = self.execute(Command::GetGpsConfig).await?;
36 match response {
37 Response::GpsConfig {
38 gps_enabled,
39 pc_output,
40 } => Ok((gps_enabled, pc_output)),
41 other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
42 expected: "GpsConfig".into(),
43 actual: format!("{other:?}").into_bytes(),
44 })),
45 }
46 }
47
48 /// Get GPS NMEA sentence enable flags (GS read).
49 ///
50 /// Returns `(gga, gll, gsa, gsv, rmc, vtg)` — six booleans indicating which NMEA 0183
51 /// sentence types are enabled for output when `pc_output` is active.
52 ///
53 /// # Sentence types
54 ///
55 /// - **GGA** (Global Positioning System Fix Data): time, position, fix quality, number of
56 /// satellites, HDOP, altitude. The primary fix sentence.
57 /// - **GLL** (Geographic Position - Latitude/Longitude): position and time, simpler than GGA.
58 /// - **GSA** (GNSS DOP and Active Satellites): fix type (2D/3D), satellite IDs in use,
59 /// PDOP/HDOP/VDOP dilution of precision values.
60 /// - **GSV** (GNSS Satellites in View): satellite count, PRN numbers, elevation, azimuth,
61 /// and SNR for each satellite. Multiple sentences for all visible satellites.
62 /// - **RMC** (Recommended Minimum Navigation Information): time, position, speed over
63 /// ground, course, date, magnetic variation. The most commonly used sentence.
64 /// - **VTG** (Course Over Ground and Ground Speed): track (true and magnetic) and speed
65 /// (knots and km/h).
66 ///
67 /// # Errors
68 ///
69 /// Returns an error if the command fails or the response is unexpected.
70 pub async fn get_gps_sentences(
71 &mut self,
72 ) -> Result<(bool, bool, bool, bool, bool, bool), Error> {
73 tracing::debug!("reading GPS NMEA sentence flags");
74 let response = self.execute(Command::GetGpsSentences).await?;
75 match response {
76 Response::GpsSentences {
77 gga,
78 gll,
79 gsa,
80 gsv,
81 rmc,
82 vtg,
83 } => Ok((gga, gll, gsa, gsv, rmc, vtg)),
84 other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
85 expected: "GpsSentences".into(),
86 actual: format!("{other:?}").into_bytes(),
87 })),
88 }
89 }
90
91 /// Set GPS configuration (GP write).
92 ///
93 /// - `gps_enabled`: turns the GPS receiver on or off. When off, no position fix is
94 /// available for APRS beaconing or display.
95 /// - `pc_output`: when `true`, the radio outputs raw NMEA sentences over the serial
96 /// connection (USB or Bluetooth SPP). **This competes with CAT command I/O** — NMEA
97 /// data will be interleaved with CAT responses on the same serial channel, which can
98 /// confuse the protocol parser. Only enable this if you are prepared to handle mixed
99 /// NMEA/CAT traffic, or if you are using the serial port exclusively for GPS data.
100 ///
101 /// # Errors
102 ///
103 /// Returns an error if the command fails or the response is unexpected.
104 pub async fn set_gps_config(
105 &mut self,
106 gps_enabled: bool,
107 pc_output: bool,
108 ) -> Result<(), Error> {
109 tracing::info!(gps_enabled, pc_output, "setting GPS config");
110 let response = self
111 .execute(Command::SetGpsConfig {
112 gps_enabled,
113 pc_output,
114 })
115 .await?;
116 match response {
117 Response::GpsConfig { .. } => Ok(()),
118 other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
119 expected: "GpsConfig".into(),
120 actual: format!("{other:?}").into_bytes(),
121 })),
122 }
123 }
124
125 /// Set GPS NMEA sentence enable flags (GS write).
126 ///
127 /// Sets 6 boolean flags controlling which NMEA sentences are output:
128 /// GGA, GLL, GSA, GSV, RMC, VTG.
129 ///
130 /// # Errors
131 ///
132 /// Returns an error if the command fails or the response is unexpected.
133 #[allow(clippy::fn_params_excessive_bools, clippy::similar_names)]
134 pub async fn set_gps_sentences(
135 &mut self,
136 gga: bool,
137 gll: bool,
138 gsa: bool,
139 gsv: bool,
140 rmc: bool,
141 vtg: bool,
142 ) -> Result<(), Error> {
143 tracing::info!(gga, gll, gsa, gsv, rmc, vtg, "setting GPS NMEA sentences");
144 let response = self
145 .execute(Command::SetGpsSentences {
146 gga,
147 gll,
148 gsa,
149 gsv,
150 rmc,
151 vtg,
152 })
153 .await?;
154 match response {
155 Response::GpsSentences { .. } => Ok(()),
156 other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
157 expected: "GpsSentences".into(),
158 actual: format!("{other:?}").into_bytes(),
159 })),
160 }
161 }
162
163 /// Get GPS/Radio mode status (GM bare read).
164 ///
165 /// Returns the current GPS/Radio operating mode. `Normal` (0) means
166 /// standard transceiver operation. `GpsReceiver` (1) means GPS-only mode.
167 ///
168 /// # Warning
169 /// Only the bare `GM\r` read is safe. Sending `GM 1\r` would reboot
170 /// the radio into GPS-only mode. This method only sends the bare read.
171 ///
172 /// # Errors
173 ///
174 /// Returns an error if the command fails or the response is unexpected.
175 pub async fn get_gps_mode(&mut self) -> Result<GpsRadioMode, Error> {
176 tracing::debug!("reading GPS/Radio mode");
177 let response = self.execute(Command::GetGpsMode).await?;
178 match response {
179 Response::GpsMode { mode } => Ok(mode),
180 other => Err(Error::Protocol(ProtocolError::UnexpectedResponse {
181 expected: "GpsMode".into(),
182 actual: format!("{other:?}").into_bytes(),
183 })),
184 }
185 }
186}