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}