gpsd_proto/lib.rs
1//! The `gpsd_proto` module contains types and functions to connect to
2//! [gpsd](http://catb.org/gpsd/) to get GPS coordinates and satellite
3//! information.
4//!
5//! `gpsd_proto` uses a plain TCP socket to connect to `gpsd`, reads
6//! and writes JSON messages. The main motivation to create this crate
7//! was independence from C libraries, like `libgps` (provided by
8//! `gpsd`) to ease cross compiling.
9//!
10//! A example demo application is provided in the `example` sub
11//! directory. Check the repository for up to date sample code.
12//!
13//! # Testing
14//!
15//! `gpsd_proto` has been tested against `gpsd` version 3.17 on macOS
16//! with a GPS mice (Adopt SkyTraQ Venus 8) and the iOS app
17//! [GPS2IP](http://www.capsicumdreams.com/iphone/gps2ip/).
18//!
19//! Feel free to report any other supported GPS by opening a GitHub
20//! issue.
21//!
22//! # Reference documentation
23//!
24//! Important reference documentation of `gpsd` are the [JSON
25//! protocol](http://www.catb.org/gpsd/gpsd_json.html) and the [client
26//! HOWTO](http://catb.org/gpsd/client-howto.html).
27//!
28//! # Development notes
29//!
30//! Start `gpsd` with a real GPS device:
31//!
32//! ```sh
33//! /usr/local/sbin/gpsd -N -D4 /dev/tty.SLAB_USBtoUART
34//! ```
35//!
36//! Or start [gpsd](http://catb.org/gpsd/gpsd.html) with a TCP stream to a remote GPS:
37//!
38//! ```sh
39//! /usr/local/sbin/gpsd -N -D2 tcp://192.168.177.147:11123
40//! ```
41//!
42//! Test the connection to `gpsd` with `telnet localhost 2947` and send the string:
43//!
44//! ```text
45//! ?WATCH={"enable":true,"json":true};
46//! ```
47
48#[macro_use]
49extern crate log;
50
51#[macro_use]
52extern crate serde_derive;
53
54use serde::de::*;
55use serde::Deserializer;
56use std::fmt;
57use std::io;
58
59/// Minimum supported version of `gpsd`.
60pub const PROTO_MAJOR_MIN: u8 = 3;
61
62/// Command to enable watch.
63pub const ENABLE_WATCH_CMD: &str = "?WATCH={\"enable\":true,\"json\":true};\r\n";
64
65/// `gpsd` ships a VERSION response to each client when the client
66/// first connects to it.
67#[derive(Debug, Deserialize, Clone)]
68#[cfg_attr(feature = "serialize", derive(Serialize))]
69pub struct Version {
70 /// Public release level.
71 pub release: String,
72 /// Internal revision-control level.
73 pub rev: String,
74 /// API major revision level.
75 pub proto_major: u8,
76 /// API minor revision level.
77 pub proto_minor: u8,
78 /// URL of the remote daemon reporting this version. If empty,
79 /// this is the version of the local daemon.
80 pub remote: Option<String>,
81}
82
83/// Device information (i.e. device enumeration).
84#[derive(Debug, Deserialize, Clone)]
85#[cfg_attr(feature = "serialize", derive(Serialize))]
86pub struct Devices {
87 pub devices: Vec<DeviceInfo>,
88}
89
90/// Single device information as reported by `gpsd`.
91#[derive(Debug, Deserialize, Clone)]
92#[cfg_attr(feature = "serialize", derive(Serialize))]
93pub struct DeviceInfo {
94 /// Name the device for which the control bits are being reported,
95 /// or for which they are to be applied. This attribute may be
96 /// omitted only when there is exactly one subscribed channel.
97 pub path: Option<String>,
98 /// Time the device was activated as an ISO8601 timestamp. If the
99 /// device is inactive this attribute is absent.
100 pub activated: Option<String>,
101}
102
103/// Watch response. Elicits a report of per-subscriber policy.
104#[derive(Debug, Deserialize, Clone)]
105#[cfg_attr(feature = "serialize", derive(Serialize))]
106pub struct Watch {
107 /// Enable (true) or disable (false) watcher mode. Default is
108 /// true.
109 pub enable: Option<bool>,
110 /// Enable (true) or disable (false) dumping of JSON reports.
111 /// Default is false.
112 pub json: Option<bool>,
113 /// Enable (true) or disable (false) dumping of binary packets
114 /// as pseudo-NMEA. Default is false.
115 pub nmea: Option<bool>,
116 /// Controls 'raw' mode. When this attribute is set to 1 for a
117 /// channel, gpsd reports the unprocessed NMEA or AIVDM data
118 /// stream from whatever device is attached. Binary GPS
119 /// packets are hex-dumped. RTCM2 and RTCM3 packets are not
120 /// dumped in raw mode. When this attribute is set to 2 for a
121 /// channel that processes binary data, gpsd reports the
122 /// received data verbatim without hex-dumping.
123 pub raw: Option<u8>,
124 /// If true, apply scaling divisors to output before dumping;
125 /// default is false.
126 pub scaled: Option<bool>,
127 /// undocumented
128 pub timing: Option<bool>,
129 /// If true, aggregate AIS type24 sentence parts. If false,
130 /// report each part as a separate JSON object, leaving the
131 /// client to match MMSIs and aggregate. Default is false.
132 /// Applies only to AIS reports.
133 pub split24: Option<bool>,
134 /// If true, emit the TOFF JSON message on each cycle and a
135 /// PPS JSON message when the device issues 1PPS. Default is
136 /// false.
137 pub pps: Option<bool>,
138}
139
140/// Responses from `gpsd` during handshake..
141#[derive(Debug, Deserialize, Clone)]
142#[cfg_attr(feature = "serialize", derive(Serialize))]
143#[serde(tag = "class")]
144#[serde(rename_all = "UPPERCASE")]
145pub enum ResponseHandshake {
146 Version(Version),
147 Devices(Devices),
148 Watch(Watch),
149}
150
151/// Device information.
152#[derive(Debug, Deserialize, Clone)]
153#[cfg_attr(feature = "serialize", derive(Serialize))]
154pub struct Device {
155 /// Name the device for which the control bits are being
156 /// reported, or for which they are to be applied. This
157 /// attribute may be omitted only when there is exactly one
158 /// subscribed channel.
159 pub path: Option<String>,
160 /// Time the device was activated as an ISO8601 timestamp. If
161 /// the device is inactive this attribute is absent.
162 pub activated: Option<String>,
163 /// Bit vector of property flags. Currently defined flags are:
164 /// describe packet types seen so far (GPS, RTCM2, RTCM3,
165 /// AIS). Won't be reported if empty, e.g. before gpsd has
166 /// seen identifiable packets from the device.
167 pub flags: Option<i32>,
168 /// GPSD's name for the device driver type. Won't be reported
169 /// before gpsd has seen identifiable packets from the device.
170 pub driver: Option<String>,
171 /// Whatever version information the device returned.
172 pub subtype: Option<String>,
173 /// Device speed in bits per second.
174 pub bps: Option<u16>,
175 /// N, O or E for no parity, odd, or even.
176 pub parity: Option<String>,
177 /// Stop bits (1 or 2).
178 pub stopbits: Option<u8>,
179 /// 0 means NMEA mode and 1 means alternate mode (binary if it
180 /// has one, for SiRF and Evermore chipsets in particular).
181 /// Attempting to set this mode on a non-GPS device will yield
182 /// an error.
183 pub native: Option<u8>,
184 /// Device cycle time in seconds.
185 pub cycle: Option<f32>,
186 /// Device minimum cycle time in seconds. Reported from
187 /// ?DEVICE when (and only when) the rate is switchable. It is
188 /// read-only and not settable.
189 pub mincycle: Option<f32>,
190}
191
192/// Type of GPS fix.
193#[derive(Debug, Copy, Clone)]
194pub enum Mode {
195 /// No fix at all.
196 NoFix,
197 /// Two dimensional fix, 2D.
198 Fix2d,
199 /// Three dimensional fix, 3D (i.e. with altitude).
200 Fix3d,
201}
202
203impl fmt::Display for Mode {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 match self {
206 Mode::NoFix => write!(f, "NoFix"),
207 Mode::Fix2d => write!(f, "2d"),
208 Mode::Fix3d => write!(f, "3d"),
209 }
210 }
211}
212
213fn mode_from_str<'de, D>(deserializer: D) -> Result<Mode, D::Error>
214where
215 D: Deserializer<'de>,
216{
217 let s = u8::deserialize(deserializer)?;
218 match s {
219 2 => Ok(Mode::Fix2d),
220 3 => Ok(Mode::Fix3d),
221 _ => Ok(Mode::NoFix),
222 }
223}
224
225/// GPS position.
226///
227/// A TPV object is a time-position-velocity report. The "mode"
228/// field will be emitted before optional fields that may be
229/// absent when there is no fix. Error estimates will be emitted
230/// after the fix components they're associated with. Others may
231/// be reported or not depending on the fix quality.
232#[derive(Debug, Deserialize, Clone)]
233#[cfg_attr(feature = "serialize", derive(Serialize))]
234pub struct Tpv {
235 /// Name of the originating device.
236 pub device: Option<String>,
237 /// GPS fix status.
238 pub status: Option<i32>,
239 /// NMEA mode, see `Mode` enum.
240 #[serde(deserialize_with = "mode_from_str")]
241 pub mode: Mode,
242 /// Time/date stamp in ISO8601 format, UTC. May have a
243 /// fractional part of up to .001sec precision. May be absent
244 /// if mode is not 2 or 3.
245 pub time: Option<String>,
246 /// Estimated timestamp error (%f, seconds, 95% confidence).
247 /// Present if time is present.
248 pub ept: Option<f32>,
249 pub leapseconds: Option<i32>,
250 /// MSL altitude in meters.
251 #[serde(rename = "altMSL")]
252 pub alt_msl: Option<f32>,
253 /// Altitude height above ellipsoid (elipsoid is unspecified, but probably WGS48)
254 #[serde(rename = "altHAE")]
255 pub alt_hae: Option<f32>,
256 /// Geoid separation between whatever geoid the device uses and WGS84, in metres
257 #[serde(rename = "geoidSep")]
258 pub geoid_sep: Option<f32>,
259 /// Latitude in degrees: +/- signifies North/South. Present
260 /// when mode is 2 or 3.
261 pub lat: Option<f64>,
262 /// Longitude in degrees: +/- signifies East/West. Present
263 /// when mode is 2 or 3.
264 pub lon: Option<f64>,
265 /// Altitude in meters. Present if mode is 3.
266 pub alt: Option<f32>,
267 /// Longitude error estimate in meters, 95% confidence.
268 /// Present if mode is 2 or 3 and DOPs can be calculated from
269 /// the satellite view.
270 pub epx: Option<f32>,
271 /// Latitude error estimate in meters, 95% confidence. Present
272 /// if mode is 2 or 3 and DOPs can be calculated from the
273 /// satellite view.
274 pub epy: Option<f32>,
275 /// Estimated vertical error in meters, 95% confidence.
276 /// Present if mode is 3 and DOPs can be calculated from the
277 /// satellite view.
278 pub epv: Option<f32>,
279 /// Course over ground, degrees from true north.
280 pub track: Option<f32>,
281 /// Speed over ground, meters per second.
282 pub speed: Option<f32>,
283 /// Climb (positive) or sink (negative) rate, meters per
284 /// second.
285 pub climb: Option<f32>,
286 /// Direction error estimate in degrees, 95% confidence.
287 pub epd: Option<f32>,
288 /// Speed error estinmate in meters/sec, 95% confidence.
289 pub eps: Option<f32>,
290 /// Climb/sink error estimate in meters/sec, 95% confidence.
291 pub epc: Option<f32>,
292 /// Horizontal 2D position error in meters.
293 pub eph: Option<f32>,
294}
295
296/// Detailed satellite information.
297#[derive(Debug, Deserialize, Clone)]
298#[cfg_attr(feature = "serialize", derive(Serialize))]
299pub struct Satellite {
300 /// PRN ID of the satellite. 1-63 are GNSS satellites, 64-96 are
301 /// GLONASS satellites, 100-164 are SBAS satellites.
302 #[serde(rename = "PRN")]
303 pub prn: i16,
304 /// Elevation in degrees.
305 pub el: Option<f32>,
306 /// Azimuth, degrees from true north.
307 pub az: Option<f32>,
308 /// Signal strength in dB.
309 pub ss: Option<f32>,
310 /// Used in current solution? (SBAS/WAAS/EGNOS satellites may be
311 /// flagged used if the solution has corrections from them, but
312 /// not all drivers make this information available.).
313 pub used: bool,
314 pub gnssid: Option<u8>,
315 pub svid: Option<u16>,
316 pub health: Option<u8>,
317}
318
319/// Satellites information.
320///
321/// A SKY object reports a sky view of the GPS satellite
322/// positions. If there is no GPS device available, or no skyview
323/// has been reported yet.
324///
325/// Many devices compute dilution of precision factors but do not
326/// include them in their reports. Many that do report DOPs report
327/// only HDOP, two-dimensional circular error. gpsd always passes
328/// through whatever the device actually reports, then attempts to
329/// fill in other DOPs by calculating the appropriate determinants
330/// in a covariance matrix based on the satellite view. DOPs may
331/// be missing if some of these determinants are singular. It can
332/// even happen that the device reports an error estimate in
333/// meters when the corresponding DOP is unavailable; some devices
334/// use more sophisticated error modeling than the covariance
335/// calculation.
336#[derive(Debug, Deserialize, Clone)]
337#[cfg_attr(feature = "serialize", derive(Serialize))]
338pub struct Sky {
339 /// Name of originating device.
340 pub device: Option<String>,
341 /// Longitudinal dilution of precision, a dimensionless factor
342 /// which should be multiplied by a base UERE to get an error
343 /// estimate.
344 pub xdop: Option<f32>,
345 /// Latitudinal dilution of precision, a dimensionless factor
346 /// which should be multiplied by a base UERE to get an error
347 /// estimate.
348 pub ydop: Option<f32>,
349 /// Altitude dilution of precision, a dimensionless factor
350 /// which should be multiplied by a base UERE to get an error
351 /// estimate.
352 pub vdop: Option<f32>,
353 /// Time dilution of precision, a dimensionless factor which
354 /// should be multiplied by a base UERE to get an error
355 /// estimate.
356 pub tdop: Option<f32>,
357 /// Horizontal dilution of precision, a dimensionless factor
358 /// which should be multiplied by a base UERE to get a
359 /// circular error estimate.
360 pub hdop: Option<f32>,
361 /// Hyperspherical dilution of precision, a dimensionless
362 /// factor which should be multiplied by a base UERE to get an
363 /// error estimate.
364 pub gdop: Option<f32>,
365 /// Spherical dilution of precision, a dimensionless factor
366 /// which should be multiplied by a base UERE to get an error
367 /// estimate.
368 pub pdop: Option<f32>,
369 /// List of satellite objects in skyview.
370 pub satellites: Option<Vec<Satellite>>,
371}
372
373/// This message is emitted each time the daemon sees a valid PPS (Pulse Per
374/// Second) strobe from a device.
375///
376/// This message exactly mirrors the TOFF message except for two details.
377///
378/// PPS emits the NTP precision. See the NTP documentation for their definition
379/// of precision.
380///
381/// The TOFF message reports the GPS time as derived from the GPS serial data
382/// stream. The PPS message reports the GPS time as derived from the GPS PPS
383/// pulse.
384///
385/// There are various sources of error in the reported clock times. The speed of
386/// the serial connection between the GPS and the system adds a delay to start
387/// of cycle detection. An even bigger error is added by the variable
388/// computation time inside the GPS. Taken together the time derived from the
389/// start of the GPS cycle can have offsets of 10 millisecond to 700
390/// milliseconds and combined jitter and wander of 100 to 300 millisecond.
391///
392/// This message is emitted once per second to watchers of a device emitting
393/// PPS, and reports the time of the start of the GPS second (when the 1PPS
394/// arrives) and seconds as reported by the system clock (which may be
395/// NTP-corrected) at that moment.
396///
397/// The message contains two second/nanosecond pairs: real_sec and real_nsec
398/// contain the time the GPS thinks it was at the PPS edge; clock_sec and
399/// clock_nsec contain the time the system clock thinks it was at the PPS edge.
400/// real_nsec is always to nanosecond precision. clock_nsec is nanosecond
401/// precision on most systems.
402///
403/// There are various sources of error in the reported clock times. For PPS
404/// delivered via a real serial-line strobe, serial-interrupt latency plus
405/// processing time to the timer call should be bounded above by about 10
406/// microseconds; that can be reduced to less than 1 microsecond if your kernel
407/// supports RFC 2783. USB1.1-to-serial control-line emulation is limited to
408/// about 1 millisecond.
409#[derive(Debug, Deserialize, Clone)]
410#[cfg_attr(feature = "serialize", derive(Serialize))]
411pub struct Pps {
412 /// Name of originating device.
413 pub device: String,
414 /// Seconds from the PPS source.
415 pub real_sec: f32,
416 /// Nanoseconds from the PPS source.
417 pub real_nsec: f32,
418 /// Seconds from the system clock.
419 pub clock_sec: f32,
420 /// Nanoseconds from the system clock.
421 pub clock_nsec: f32,
422 /// NTP style estimate of PPS precision.
423 pub precision: f32,
424}
425
426/// Pseudorange noise report.
427#[derive(Debug, Deserialize, Clone)]
428#[cfg_attr(feature = "serialize", derive(Serialize))]
429pub struct Gst {
430 /// Name of originating device.
431 pub device: Option<String>,
432 /// Time/date stamp in ISO8601 format, UTC. May have a fractional part of up
433 /// to .001 sec precision.
434 pub time: Option<String>,
435 /// Value of the standard deviation of the range inputs to the navigation
436 /// process (range inputs include pseudoranges and DGPS corrections).
437 pub rms: Option<f32>,
438 /// Standard deviation of semi-major axis of error ellipse, in meters.
439 pub major: Option<f32>,
440 /// Standard deviation of semi-minor axis of error ellipse, in meters.
441 pub minor: Option<f32>,
442 /// Orientation of semi-major axis of error ellipse, in degrees from true
443 /// north.
444 pub orient: Option<f32>,
445 /// Standard deviation of latitude error, in meters.
446 pub lat: Option<f32>,
447 /// Standard deviation of longitude error, in meters.
448 pub lon: Option<f32>,
449 /// Standard deviation of altitude error, in meters.
450 pub alt: Option<f32>,
451}
452
453/// Responses from `gpsd` after handshake (i.e. the payload)
454#[derive(Debug, Deserialize, Clone)]
455#[cfg_attr(feature = "serialize", derive(Serialize))]
456#[serde(tag = "class")]
457#[serde(rename_all = "UPPERCASE")]
458pub enum ResponseData {
459 Device(Device),
460 Tpv(Tpv),
461 Sky(Sky),
462 Pps(Pps),
463 Gst(Gst),
464}
465
466/// All known `gpsd` responses (handshake + normal operation).
467#[derive(Debug, Deserialize, Clone)]
468#[cfg_attr(feature = "serialize", derive(Serialize))]
469#[serde(tag = "class")]
470#[serde(rename_all = "UPPERCASE")]
471pub enum UnifiedResponse {
472 Version(Version),
473 Devices(Devices),
474 Watch(Watch),
475 Device(Device),
476 Tpv(Tpv),
477 Sky(Sky),
478 Pps(Pps),
479 Gst(Gst),
480}
481
482/// Errors during handshake or data acquisition.
483#[derive(Debug)]
484pub enum GpsdError {
485 /// Generic I/O error.
486 IoError(io::Error),
487 /// JSON error.
488 JsonError(serde_json::Error),
489 /// The protocol version reported by `gpsd` is smaller `PROTO_MAJOR_MIN`.
490 UnsupportedGpsdProtocolVersion,
491 /// Unexpected reply of `gpsd`.
492 UnexpectedGpsdReply(String),
493 /// Failed to enable watch.
494 WatchFail(String),
495}
496
497impl From<io::Error> for GpsdError {
498 fn from(err: io::Error) -> GpsdError {
499 GpsdError::IoError(err)
500 }
501}
502
503impl From<serde_json::Error> for GpsdError {
504 fn from(err: serde_json::Error) -> GpsdError {
505 GpsdError::JsonError(err)
506 }
507}
508
509impl fmt::Display for GpsdError {
510 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
511 match self {
512 GpsdError::IoError(e) => write!(f, "IoError: {}", e),
513 GpsdError::JsonError(e) => write!(f, "JsonError: {}", e),
514 GpsdError::UnsupportedGpsdProtocolVersion => {
515 write!(f, "UnsupportedGpsdProtocolVersion")
516 }
517 GpsdError::UnexpectedGpsdReply(e) => write!(f, "UnexpectedGpsdReply: {}", e),
518 GpsdError::WatchFail(e) => write!(f, "WatchFail: {}", e),
519 }
520 }
521}
522
523/// Performs the initial handshake with `gpsd`.
524///
525/// The following sequence of messages is expected: get VERSION, set
526/// WATCH, get DEVICES, get WATCH.
527///
528/// # Arguments
529///
530/// * `debug` - enable debug printing of raw JSON data received
531/// * `reader` - reader to fetch data from `gpsd`
532/// * `writer` - write to send data to `gpsd`
533///
534/// # Errors
535///
536/// If the handshake fails, this functions returns an error that
537/// indicates the type of error.
538pub fn handshake(
539 reader: &mut dyn io::BufRead,
540 writer: &mut dyn io::Write,
541) -> Result<(), GpsdError> {
542 // Get VERSION
543 let mut data = Vec::new();
544 reader.read_until(b'\n', &mut data)?;
545 trace!("{}", String::from_utf8(data.clone()).unwrap());
546 let msg: ResponseHandshake = serde_json::from_slice(&data)?;
547 match msg {
548 ResponseHandshake::Version(v) => {
549 if v.proto_major < PROTO_MAJOR_MIN {
550 return Err(GpsdError::UnsupportedGpsdProtocolVersion);
551 }
552 }
553 _ => {
554 return Err(GpsdError::UnexpectedGpsdReply(
555 String::from_utf8(data).unwrap(),
556 ))
557 }
558 }
559
560 // Enable WATCH
561 writer.write_all(ENABLE_WATCH_CMD.as_bytes())?;
562 writer.flush()?;
563
564 // Get DEVICES
565 let mut data = Vec::new();
566 reader.read_until(b'\n', &mut data)?;
567 trace!("{}", String::from_utf8(data.clone()).unwrap());
568 let msg: ResponseHandshake = serde_json::from_slice(&data)?;
569 match msg {
570 ResponseHandshake::Devices(_) => {}
571 _ => {
572 return Err(GpsdError::UnexpectedGpsdReply(
573 String::from_utf8(data).unwrap(),
574 ))
575 }
576 }
577
578 // Get WATCH
579 let mut data = Vec::new();
580 reader.read_until(b'\n', &mut data)?;
581 trace!("{}", String::from_utf8(data.clone()).unwrap());
582 let msg: ResponseHandshake = serde_json::from_slice(&data)?;
583 match msg {
584 ResponseHandshake::Watch(w) => {
585 if let (false, false, true) = (
586 w.enable.unwrap_or(false),
587 w.json.unwrap_or(false),
588 w.nmea.unwrap_or(false),
589 ) {
590 return Err(GpsdError::WatchFail(
591 String::from_utf8(data).unwrap(),
592 ));
593 }
594 }
595 _ => {
596 return Err(GpsdError::UnexpectedGpsdReply(
597 String::from_utf8(data).unwrap(),
598 ))
599 }
600 }
601
602 Ok(())
603}
604
605/// Get one payload entry from `gpsd`.
606///
607/// # Arguments
608///
609/// * `reader` - reader to fetch data from `gpsd`
610/// * `writer` - write to send data to `gpsd`
611pub fn get_data(reader: &mut dyn io::BufRead) -> Result<ResponseData, GpsdError> {
612 let mut data = Vec::new();
613 reader.read_until(b'\n', &mut data)?;
614 trace!("{}", String::from_utf8(data.clone()).unwrap());
615 let msg: ResponseData = serde_json::from_slice(&data)?;
616 Ok(msg)
617}
618
619#[cfg(test)]
620mod tests {
621 use super::{get_data, handshake, GpsdError, Mode, ResponseData, ENABLE_WATCH_CMD};
622 use std::io::BufWriter;
623
624 #[test]
625 fn handshake_ok() {
626 // Note: linefeeds (0x0a) are added implicit; each line ends with 0x0d 0x0a.
627 let mut reader: &[u8] = b"{\"class\":\"VERSION\",\"release\":\"blah\",\"rev\":\"blurp\",\"proto_major\":3,\"proto_minor\":12}\x0d
628{\"class\":\"DEVICES\",\"devices\":[{\"path\":\"/dev/gps\",\"activated\":\"true\"}]}
629{\"class\":\"WATCH\",\"enable\":true,\"json\":true,\"nmea\":false}
630";
631 let mut writer = BufWriter::new(Vec::<u8>::new());
632 let r = handshake(&mut reader, &mut writer);
633 assert!(r.is_ok());
634 assert_eq!(writer.get_mut().as_slice(), ENABLE_WATCH_CMD.as_bytes());
635 }
636
637 #[test]
638 fn handshake_unsupported_protocol_version() {
639 let mut reader: &[u8] = b"{\"class\":\"VERSION\",\"release\":\"blah\",\"rev\":\"blurp\",\"proto_major\":2,\"proto_minor\":17}\x0d
640";
641 let mut writer = BufWriter::new(Vec::<u8>::new());
642 let err = match handshake(&mut reader, &mut writer) {
643 Err(GpsdError::UnsupportedGpsdProtocolVersion) => Ok(()),
644 _ => Err(()),
645 };
646 assert_eq!(err, Ok(()));
647 let empty: &[u8] = &[];
648 assert_eq!(writer.get_mut().as_slice(), empty);
649 }
650
651 #[test]
652 fn handshake_unexpected_gpsd_reply() {
653 // A possible response, but in the wrong order; At the begin
654 // of the handshake, a VERSION reply is expected.
655 let mut reader: &[u8] =
656 b"{\"class\":\"DEVICES\",\"devices\":[{\"path\":\"/dev/gps\",\"activated\":\"true\"}]}
657";
658 let mut writer = BufWriter::new(Vec::<u8>::new());
659 let err = match handshake(&mut reader, &mut writer) {
660 Err(GpsdError::UnexpectedGpsdReply(_)) => Ok(()),
661 _ => Err(()),
662 };
663 assert_eq!(err, Ok(()));
664 let empty: &[u8] = &[];
665 assert_eq!(writer.get_mut().as_slice(), empty);
666 }
667
668 #[test]
669 fn handshake_json_error() {
670 let mut reader: &[u8] = b"{\"class\":broken";
671 let mut writer = BufWriter::new(Vec::<u8>::new());
672 let err = match handshake(&mut reader, &mut writer) {
673 Err(GpsdError::JsonError(_)) => Ok(()),
674 _ => Err(()),
675 };
676 assert_eq!(err, Ok(()));
677 let empty: &[u8] = &[];
678 assert_eq!(writer.get_mut().as_slice(), empty);
679 }
680
681 #[test]
682 fn get_data_tpv() {
683 let mut reader: &[u8] = b"{\"class\":\"TPV\",\"mode\":3,\"lat\":66.123}\x0d\x0a";
684 let r = get_data(&mut reader).unwrap();
685 let test = match r {
686 ResponseData::Tpv(tpv) => {
687 assert!(match tpv.mode {
688 Mode::Fix3d => true,
689 _ => false,
690 });
691 assert_eq!(tpv.lat.unwrap(), 66.123);
692 Ok(())
693 }
694 _ => Err(()),
695 };
696 assert_eq!(test, Ok(()));
697 }
698
699 #[test]
700 fn get_data_sky() {
701 let mut reader: &[u8] = b"{\"class\":\"SKY\",\"device\":\"aDevice\",\"satellites\":[{\"PRN\":123,\"el\":1.0,\"az\":2.0,\"ss\":3.0,\"used\":true,\"gnssid\":1,\"svid\":271,\"health\":1}]}\x0d\x0a";
702
703 let r = get_data(&mut reader).unwrap();
704 let test = match r {
705 ResponseData::Sky(sky) => {
706 assert_eq!(sky.device.unwrap(), "aDevice");
707 let actual = &sky.satellites.unwrap()[0];
708 assert_eq!(actual.prn, 123);
709 assert_eq!(actual.el, Some(1.));
710 assert_eq!(actual.az, Some(2.));
711 assert_eq!(actual.ss, Some(3.));
712 assert_eq!(actual.used, true);
713 assert_eq!(actual.gnssid, Some(1));
714 assert_eq!(actual.svid, Some(271));
715 assert_eq!(actual.health, Some(1));
716 Ok(())
717 }
718 _ => Err(()),
719 };
720 assert_eq!(test, Ok(()));
721 }
722
723 #[test]
724 fn mode_to_string() {
725 assert_eq!("NoFix", Mode::NoFix.to_string());
726 assert_eq!("2d", Mode::Fix2d.to_string());
727 assert_eq!("3d", Mode::Fix3d.to_string());
728 }
729}