1 3
//! 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]
49
extern crate log;
50

51
#[macro_use]
52
extern crate serde_derive;
53

54
use serde::de::*;
55
use serde::Deserializer;
56
use std::fmt;
57
use std::io;
58
use std::io::Write;
59

60
/// Minimum supported version of `gpsd`.
61
pub const PROTO_MAJOR_MIN: u8 = 3;
62

63
/// Command to enable watch.
64
pub const ENABLE_WATCH_CMD: &str = "?WATCH={\"enable\":true,\"json\":true};\r\n";
65

66
/// `gpsd` ships a VERSION response to each client when the client
67 3
/// first connects to it.
68 3
#[derive(Debug, Deserialize)]
69
#[cfg_attr(feature = "serialize", derive(Serialize))]
70
pub struct Version {
71
    /// Public release level.
72
    pub release: String,
73
    /// Internal revision-control level.
74
    pub rev: String,
75
    /// API major revision level.
76
    pub proto_major: u8,
77
    /// API minor revision level.
78
    pub proto_minor: u8,
79
    /// URL of the remote daemon reporting this version. If empty,
80
    /// this is the version of the local daemon.
81
    pub remote: Option<String>,
82
}
83

84 3
/// Device information (i.e. device enumeration).
85 3
#[derive(Debug, Deserialize)]
86
#[cfg_attr(feature = "serialize", derive(Serialize))]
87
pub struct Devices {
88
    devices: Vec<DeviceInfo>,
89
}
90

91 3
/// Single device information as reported by `gpsd`.
92 3
#[derive(Debug, Deserialize)]
93
#[cfg_attr(feature = "serialize", derive(Serialize))]
94
pub struct DeviceInfo {
95
    /// Name the device for which the control bits are being reported,
96
    /// or for which they are to be applied. This attribute may be
97
    /// omitted only when there is exactly one subscribed channel.
98
    pub path: Option<String>,
99
    /// Time the device was activated as an ISO8601 timestamp. If the
100
    /// device is inactive this attribute is absent.
101
    pub activated: Option<String>,
102
}
103

104 3
/// Watch response. Elicits a report of per-subscriber policy.
105 3
#[derive(Debug, Deserialize)]
106
#[cfg_attr(feature = "serialize", derive(Serialize))]
107
pub struct Watch {
108
    /// Enable (true) or disable (false) watcher mode. Default is
109
    /// true.
110
    pub enable: Option<bool>,
111
    /// Enable (true) or disable (false) dumping of JSON reports.
112
    /// Default is false.
113
    pub json: Option<bool>,
114
    /// Enable (true) or disable (false) dumping of binary packets
115
    /// as pseudo-NMEA. Default is false.
116
    pub nmea: Option<bool>,
117
    /// Controls 'raw' mode. When this attribute is set to 1 for a
118
    /// channel, gpsd reports the unprocessed NMEA or AIVDM data
119
    /// stream from whatever device is attached. Binary GPS
120
    /// packets are hex-dumped. RTCM2 and RTCM3 packets are not
121
    /// dumped in raw mode. When this attribute is set to 2 for a
122
    /// channel that processes binary data, gpsd reports the
123
    /// received data verbatim without hex-dumping.
124
    pub raw: Option<u8>,
125
    /// If true, apply scaling divisors to output before dumping;
126
    /// default is false.
127
    pub scaled: Option<bool>,
128
    /// undocumented
129
    pub timing: Option<bool>,
130
    /// If true, aggregate AIS type24 sentence parts. If false,
131
    /// report each part as a separate JSON object, leaving the
132
    /// client to match MMSIs and aggregate. Default is false.
133
    /// Applies only to AIS reports.
134
    pub split24: Option<bool>,
135
    /// If true, emit the TOFF JSON message on each cycle and a
136
    /// PPS JSON message when the device issues 1PPS. Default is
137
    /// false.
138
    pub pps: Option<bool>,
139
}
140

141 3
/// Responses from `gpsd` during handshake..
142 3
#[derive(Debug, Deserialize)]
143
#[cfg_attr(feature = "serialize", derive(Serialize))]
144
#[serde(tag = "class")]
145
#[serde(rename_all = "UPPERCASE")]
146 3
pub enum ResponseHandshake {
147 3
    Version(Version),
148 3
    Devices(Devices),
149 3
    Watch(Watch),
150
}
151

152 0
/// Device information.
153 0
#[derive(Debug, Deserialize)]
154
#[cfg_attr(feature = "serialize", derive(Serialize))]
155
pub struct Device {
156
    /// Name the device for which the control bits are being
157
    /// reported, or for which they are to be applied. This
158
    /// attribute may be omitted only when there is exactly one
159
    /// subscribed channel.
160
    pub path: Option<String>,
161
    /// Time the device was activated as an ISO8601 timestamp. If
162
    /// the device is inactive this attribute is absent.
163
    pub activated: Option<String>,
164
    /// Bit vector of property flags. Currently defined flags are:
165
    /// describe packet types seen so far (GPS, RTCM2, RTCM3,
166
    /// AIS). Won't be reported if empty, e.g. before gpsd has
167
    /// seen identifiable packets from the device.
168
    pub flags: Option<i32>,
169
    /// GPSD's name for the device driver type. Won't be reported
170
    /// before gpsd has seen identifiable packets from the device.
171
    pub driver: Option<String>,
172
    /// Whatever version information the device returned.
173
    pub subtype: Option<String>,
174
    /// Device speed in bits per second.
175
    pub bps: Option<u16>,
176
    /// N, O or E for no parity, odd, or even.
177
    pub parity: Option<String>,
178
    /// Stop bits (1 or 2).
179
    pub stopbits: Option<u8>,
180
    /// 0 means NMEA mode and 1 means alternate mode (binary if it
181
    /// has one, for SiRF and Evermore chipsets in particular).
182
    /// Attempting to set this mode on a non-GPS device will yield
183
    /// an error.
184
    pub native: Option<u8>,
185
    /// Device cycle time in seconds.
186
    pub cycle: Option<f32>,
187
    /// Device minimum cycle time in seconds. Reported from
188
    /// ?DEVICE when (and only when) the rate is switchable. It is
189
    /// read-only and not settable.
190
    pub mincycle: Option<f32>,
191
}
192

193
/// Type of GPS fix.
194
#[derive(Debug, Copy, Clone)]
195
pub enum Mode {
196
    /// No fix at all.
197
    NoFix,
198
    /// Two dimensional fix, 2D.
199
    Fix2d,
200
    /// Three dimensional fix, 3D (i.e. with altitude).
201
    Fix3d,
202
}
203

204 3
impl fmt::Display for Mode {
205 3
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
206 3
        match self {
207 3
            Mode::NoFix => write!(f, "NoFix"),
208 3
            Mode::Fix2d => write!(f, "2d"),
209 3
            Mode::Fix3d => write!(f, "3d"),
210 3
        }
211 3
    }
212
}
213 3

214 3
fn mode_from_str<'de, D>(deserializer: D) -> Result<Mode, D::Error>
215
where
216
    D: Deserializer<'de>,
217 3
{
218 3
    let s = u8::deserialize(deserializer)?;
219 3
    match s {
220 3
        2 => Ok(Mode::Fix2d),
221 3
        3 => Ok(Mode::Fix3d),
222 0
        _ => Ok(Mode::NoFix),
223 3
    }
224 3
}
225

226
/// GPS position.
227
///
228
/// A TPV object is a time-position-velocity report. The "mode"
229
/// field will be emitted before optional fields that may be
230
/// absent when there is no fix. Error estimates will be emitted
231
/// after the fix components they're associated with. Others may
232 3
/// be reported or not depending on the fix quality.
233 3
#[derive(Debug, Deserialize)]
234
#[cfg_attr(feature = "serialize", derive(Serialize))]
235
pub struct Tpv {
236
    /// Name of the originating device.
237
    pub device: Option<String>,
238
    /// GPS fix status.
239
    pub status: Option<i32>,
240
    /// NMEA mode, see `Mode` enum.
241
    #[serde(deserialize_with = "mode_from_str")]
242
    pub mode: Mode,
243
    /// Time/date stamp in ISO8601 format, UTC. May have a
244
    /// fractional part of up to .001sec precision. May be absent
245
    /// if mode is not 2 or 3.
246
    pub time: Option<String>,
247
    /// Estimated timestamp error (%f, seconds, 95% confidence).
248
    /// Present if time is present.
249
    pub ept: Option<f32>,
250
    /// Latitude in degrees: +/- signifies North/South. Present
251
    /// when mode is 2 or 3.
252
    pub lat: Option<f64>,
253
    /// Longitude in degrees: +/- signifies East/West. Present
254
    /// when mode is 2 or 3.
255
    pub lon: Option<f64>,
256
    /// Altitude in meters. Present if mode is 3.
257
    pub alt: Option<f32>,
258
    /// Longitude error estimate in meters, 95% confidence.
259
    /// Present if mode is 2 or 3 and DOPs can be calculated from
260
    /// the satellite view.
261
    pub epx: Option<f32>,
262
    /// Latitude error estimate in meters, 95% confidence. Present
263
    /// if mode is 2 or 3 and DOPs can be calculated from the
264
    /// satellite view.
265
    pub epy: Option<f32>,
266
    /// Estimated vertical error in meters, 95% confidence.
267
    /// Present if mode is 3 and DOPs can be calculated from the
268
    /// satellite view.
269
    pub epv: Option<f32>,
270
    /// Course over ground, degrees from true north.
271
    pub track: Option<f32>,
272
    /// Speed over ground, meters per second.
273
    pub speed: Option<f32>,
274
    /// Climb (positive) or sink (negative) rate, meters per
275
    /// second.
276
    pub climb: Option<f32>,
277
    /// Direction error estimate in degrees, 95% confidence.
278
    pub epd: Option<f32>,
279
    /// Speed error estinmate in meters/sec, 95% confidence.
280
    pub eps: Option<f32>,
281
    /// Climb/sink error estimate in meters/sec, 95% confidence.
282
    pub epc: Option<f32>,
283
}
284

285 3
/// Detailed satellite information.
286 3
#[derive(Debug, Deserialize)]
287
#[cfg_attr(feature = "serialize", derive(Serialize))]
288
pub struct Satellite {
289
    /// PRN ID of the satellite. 1-63 are GNSS satellites, 64-96 are
290
    /// GLONASS satellites, 100-164 are SBAS satellites.
291
    #[serde(rename = "PRN")]
292
    pub prn: i16,
293
    /// Elevation in degrees.
294
    pub el: i16,
295
    /// Azimuth, degrees from true north.
296
    pub az: i16,
297
    /// Signal strength in dB.
298
    pub ss: i16,
299
    /// Used in current solution? (SBAS/WAAS/EGNOS satellites may be
300
    /// flagged used if the solution has corrections from them, but
301
    /// not all drivers make this information available.).
302
    pub used: bool,
303
}
304

305
/// Satellites information.
306
///
307
/// A SKY object reports a sky view of the GPS satellite
308
/// positions. If there is no GPS device available, or no skyview
309
/// has been reported yet.
310
///
311
/// Many devices compute dilution of precision factors but do not
312
/// include them in their reports. Many that do report DOPs report
313
/// only HDOP, two-dimensional circular error. gpsd always passes
314
/// through whatever the device actually reports, then attempts to
315
/// fill in other DOPs by calculating the appropriate determinants
316
/// in a covariance matrix based on the satellite view. DOPs may
317
/// be missing if some of these determinants are singular. It can
318
/// even happen that the device reports an error estimate in
319
/// meters when the corresponding DOP is unavailable; some devices
320
/// use more sophisticated error modeling than the covariance
321 3
/// calculation.
322 3
#[derive(Debug, Deserialize)]
323
#[cfg_attr(feature = "serialize", derive(Serialize))]
324
pub struct Sky {
325
    /// Name of originating device.
326
    pub device: Option<String>,
327
    /// Longitudinal dilution of precision, a dimensionless factor
328
    /// which should be multiplied by a base UERE to get an error
329
    /// estimate.
330
    pub xdop: Option<f32>,
331
    /// Latitudinal dilution of precision, a dimensionless factor
332
    /// which should be multiplied by a base UERE to get an error
333
    /// estimate.
334
    pub ydop: Option<f32>,
335
    /// Altitude dilution of precision, a dimensionless factor
336
    /// which should be multiplied by a base UERE to get an error
337
    /// estimate.
338
    pub vdop: Option<f32>,
339
    /// Time dilution of precision, a dimensionless factor which
340
    /// should be multiplied by a base UERE to get an error
341
    /// estimate.
342
    pub tdop: Option<f32>,
343
    /// Horizontal dilution of precision, a dimensionless factor
344
    /// which should be multiplied by a base UERE to get a
345
    /// circular error estimate.
346
    pub hdop: Option<f32>,
347
    /// Hyperspherical dilution of precision, a dimensionless
348
    /// factor which should be multiplied by a base UERE to get an
349
    /// error estimate.
350
    pub gdop: Option<f32>,
351
    /// Spherical dilution of precision, a dimensionless factor
352
    /// which should be multiplied by a base UERE to get an error
353
    /// estimate.
354
    pub pdop: Option<f32>,
355
    /// List of satellite objects in skyview.
356
    pub satellites: Vec<Satellite>,
357
}
358

359
/// This message is emitted each time the daemon sees a valid PPS (Pulse Per
360
/// Second) strobe from a device.
361
///
362
/// This message exactly mirrors the TOFF message except for two details.
363
///
364
/// PPS emits the NTP precision. See the NTP documentation for their definition
365
/// of precision.
366
///
367
/// The TOFF message reports the GPS time as derived from the GPS serial data
368
/// stream. The PPS message reports the GPS time as derived from the GPS PPS
369
/// pulse.
370
///
371
/// There are various sources of error in the reported clock times. The speed of
372
/// the serial connection between the GPS and the system adds a delay to start
373
/// of cycle detection. An even bigger error is added by the variable
374
/// computation time inside the GPS. Taken together the time derived from the
375
/// start of the GPS cycle can have offsets of 10 millisecond to 700
376
/// milliseconds and combined jitter and wander of 100 to 300 millisecond.
377
///
378
/// This message is emitted once per second to watchers of a device emitting
379
/// PPS, and reports the time of the start of the GPS second (when the 1PPS
380
/// arrives) and seconds as reported by the system clock (which may be
381
/// NTP-corrected) at that moment.
382
///
383
/// The message contains two second/nanosecond pairs: real_sec and real_nsec
384
/// contain the time the GPS thinks it was at the PPS edge; clock_sec and
385
/// clock_nsec contain the time the system clock thinks it was at the PPS edge.
386
/// real_nsec is always to nanosecond precision. clock_nsec is nanosecond
387
/// precision on most systems.
388
///
389
/// There are various sources of error in the reported clock times. For PPS
390
/// delivered via a real serial-line strobe, serial-interrupt latency plus
391
/// processing time to the timer call should be bounded above by about 10
392
/// microseconds; that can be reduced to less than 1 microsecond if your kernel
393
/// supports RFC 2783. USB1.1-to-serial control-line emulation is limited to
394 0
/// about 1 millisecond.
395 0
#[derive(Debug, Deserialize)]
396
#[cfg_attr(feature = "serialize", derive(Serialize))]
397
pub struct Pps {
398
    /// Name of originating device.
399
    pub device: String,
400
    /// Seconds from the PPS source.
401
    pub real_sec: f32,
402
    /// Nanoseconds from the PPS source.
403
    pub real_nsec: f32,
404
    /// Seconds from the system clock.
405
    pub clock_sec: f32,
406
    /// Nanoseconds from the system clock.
407
    pub clock_nsec: f32,
408
    /// NTP style estimate of PPS precision.
409
    pub precision: f32,
410
}
411

412 0
/// Pseudorange noise report.
413 0
#[derive(Debug, Deserialize)]
414
#[cfg_attr(feature = "serialize", derive(Serialize))]
415
pub struct Gst {
416
    /// Name of originating device.
417
    pub device: Option<String>,
418
    /// Time/date stamp in ISO8601 format, UTC. May have a fractional part of up
419
    /// to .001 sec precision.
420
    pub time: Option<String>,
421
    /// Value of the standard deviation of the range inputs to the navigation
422
    /// process (range inputs include pseudoranges and DGPS corrections).
423
    pub rms: Option<f32>,
424
    /// Standard deviation of semi-major axis of error ellipse, in meters.
425
    pub major: Option<f32>,
426
    /// Standard deviation of semi-minor axis of error ellipse, in meters.
427
    pub minor: Option<f32>,
428
    /// Orientation of semi-major axis of error ellipse, in degrees from true
429
    /// north.
430
    pub orient: Option<f32>,
431
    /// Standard deviation of latitude error, in meters.
432
    pub lat: Option<f32>,
433
    /// Standard deviation of longitude error, in meters.
434
    pub lon: Option<f32>,
435
    /// Standard deviation of altitude error, in meters.
436
    pub alt: Option<f32>,
437
}
438

439 3
/// Responses from `gpsd` after handshake (i.e. the payload)
440 3
#[derive(Debug, Deserialize)]
441
#[cfg_attr(feature = "serialize", derive(Serialize))]
442
#[serde(tag = "class")]
443
#[serde(rename_all = "UPPERCASE")]
444 0
pub enum ResponseData {
445 3
    Device(Device),
446 3
    Tpv(Tpv),
447 3
    Sky(Sky),
448 0
    Pps(Pps),
449 0
    Gst(Gst),
450
}
451

452
/// All known `gpsd` responses (handshake + normal operation).
453
#[derive(Debug, Deserialize)]
454
#[cfg_attr(feature = "serialize", derive(Serialize))]
455
#[serde(tag = "class")]
456
#[serde(rename_all = "UPPERCASE")]
457
pub enum UnifiedResponse {
458
    Version(Version),
459
    Devices(Devices),
460
    Watch(Watch),
461
    Device(Device),
462
    Tpv(Tpv),
463
    Sky(Sky),
464
    Pps(Pps),
465
    Gst(Gst),
466
}
467

468 0
/// Errors during handshake or data acquisition.
469 0
#[derive(Debug)]
470
pub enum GpsdError {
471 0
    /// Generic I/O error.
472 0
    IoError(io::Error),
473 0
    /// JSON error.
474 0
    JsonError(serde_json::Error),
475
    /// The protocol version reported by `gpsd` is smaller `PROTO_MAJOR_MIN`.
476
    UnsupportedGpsdProtocolVersion,
477 0
    /// Unexpected reply of `gpsd`.
478 0
    UnexpectedGpsdReply(String),
479 0
    /// Failed to enable watch.
480 0
    WatchFail(String),
481
}
482

483 0
impl From<io::Error> for GpsdError {
484 0
    fn from(err: io::Error) -> GpsdError {
485 0
        GpsdError::IoError(err)
486 0
    }
487
}
488

489 3
impl From<serde_json::Error> for GpsdError {
490 3
    fn from(err: serde_json::Error) -> GpsdError {
491 3
        GpsdError::JsonError(err)
492 3
    }
493
}
494

495
impl fmt::Display for GpsdError {
496
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
497
        match self {
498
            GpsdError::IoError(e) => write!(f, "IoError: {}", e),
499
            GpsdError::JsonError(e) => write!(f, "JsonError: {}", e),
500
            GpsdError::UnsupportedGpsdProtocolVersion => {
501
                write!(f, "UnsupportedGpsdProtocolVersion")
502
            }
503
            GpsdError::UnexpectedGpsdReply(e) => write!(f, "UnexpectedGpsdReply: {}", e),
504
            GpsdError::WatchFail(e) => write!(f, "WatchFail: {}", e),
505
        }
506
    }
507
}
508

509
/// Performs the initial handshake with `gpsd`.
510
///
511
/// The following sequence of messages is expected: get VERSION, set
512
/// WATCH, get DEVICES, get WATCH.
513
///
514
/// # Arguments
515
///
516
/// * `debug` - enable debug printing of raw JSON data received
517
/// * `reader` - reader to fetch data from `gpsd`
518
/// * `writer` - write to send data to `gpsd`
519
///
520
/// # Errors
521
///
522
/// If the handshake fails, this functions returns an error that
523 3
/// indicates the type of error.
524 3
pub fn handshake<R>(
525
    reader: &mut io::BufRead,
526
    writer: &mut io::BufWriter<R>,
527
) -> Result<(), GpsdError>
528 3
where
529 3
    R: std::io::Write,
530 3
{
531 3
    // Get VERSION
532 3
    let mut data = Vec::new();
533 3
    reader.read_until(b'\n', &mut data)?;
534 3
    trace!("{}", String::from_utf8(data.clone()).unwrap());
535 3
    let msg: ResponseHandshake = serde_json::from_slice(&data)?;
536
    match msg {
537 3
        ResponseHandshake::Version(v) => {
538 3
            if v.proto_major < PROTO_MAJOR_MIN {
539 3
                return Err(GpsdError::UnsupportedGpsdProtocolVersion);
540 3
            }
541
        }
542 3
        _ => {
543 3
            return Err(GpsdError::UnexpectedGpsdReply(
544 3
                String::from_utf8(data.clone()).unwrap(),
545
            ))
546 3
        }
547 3
    }
548

549
    // Enable WATCH
550 3
    writer.write_all(ENABLE_WATCH_CMD.as_bytes())?;
551 3
    writer.flush()?;
552 3

553 3
    // Get DEVICES
554 3
    let mut data = Vec::new();
555 3
    reader.read_until(b'\n', &mut data)?;
556 3
    trace!("{}", String::from_utf8(data.clone()).unwrap());
557 3
    let msg: ResponseHandshake = serde_json::from_slice(&data)?;
558 0
    match msg {
559 3
        ResponseHandshake::Devices(_) => {}
560
        _ => {
561 0
            return Err(GpsdError::UnexpectedGpsdReply(
562 0
                String::from_utf8(data.clone()).unwrap(),
563
            ))
564 3
        }
565 3
    }
566 3

567 3
    // Get WATCH
568 3
    let mut data = Vec::new();
569 3
    reader.read_until(b'\n', &mut data)?;
570 3
    trace!("{}", String::from_utf8(data.clone()).unwrap());
571 3
    let msg: ResponseHandshake = serde_json::from_slice(&data)?;
572 3
    match msg {
573 3
        ResponseHandshake::Watch(w) => {
574 3
            if let (false, false, true) = (
575 3
                w.enable.unwrap_or(false),
576 3
                w.json.unwrap_or(false),
577 3
                w.nmea.unwrap_or(false),
578
            ) {
579 0
                return Err(GpsdError::WatchFail(
580 0
                    String::from_utf8(data.clone()).unwrap(),
581 0
                ));
582 0
            }
583
        }
584
        _ => {
585 0
            return Err(GpsdError::UnexpectedGpsdReply(
586 0
                String::from_utf8(data.clone()).unwrap(),
587 3
            ))
588 3
        }
589
    }
590

591 3
    Ok(())
592 3
}
593

594
/// Get one payload entry from `gpsd`.
595
///
596 3
/// # Arguments
597 3
///
598 3
/// * `reader` - reader to fetch data from `gpsd`
599 3
/// * `writer` - write to send data to `gpsd`
600 3
pub fn get_data(reader: &mut io::BufRead) -> Result<ResponseData, GpsdError> {
601 3
    let mut data = Vec::new();
602 3
    reader.read_until(b'\n', &mut data)?;
603 3
    trace!("{}", String::from_utf8(data.clone()).unwrap());
604 3
    let msg: ResponseData = serde_json::from_slice(&data)?;
605 3
    Ok(msg)
606 3
}
607

608
#[cfg(test)]
609
mod tests {
610 3
    use super::{get_data, handshake, GpsdError, Mode, ResponseData, Satellite, ENABLE_WATCH_CMD};
611
    use std::io::BufWriter;
612 3

613
    #[test]
614 3
    fn handshake_ok() {
615
        // Note: linefeeds (0x0a) are added implicit; each line ends with 0x0d 0x0a.
616 3
        let mut reader: &[u8] = b"{\"class\":\"VERSION\",\"release\":\"blah\",\"rev\":\"blurp\",\"proto_major\":3,\"proto_minor\":12}\x0d
617 3
{\"class\":\"DEVICES\",\"devices\":[{\"path\":\"/dev/gps\",\"activated\":\"true\"}]}
618 3
{\"class\":\"WATCH\",\"enable\":true,\"json\":true,\"nmea\":false}
619 3
";
620 3
        let mut writer = BufWriter::new(Vec::<u8>::new());
621 3
        let r = handshake(&mut reader, &mut writer);
622 3
        assert!(r.is_ok());
623 3
        assert_eq!(writer.get_mut().as_slice(), ENABLE_WATCH_CMD.as_bytes());
624 3
    }
625

626 3
    #[test]
627 3
    fn handshake_unsupported_protocol_version() {
628 3
        let mut reader: &[u8] = b"{\"class\":\"VERSION\",\"release\":\"blah\",\"rev\":\"blurp\",\"proto_major\":2,\"proto_minor\":17}\x0d
629 0
";
630 3
        let mut writer = BufWriter::new(Vec::<u8>::new());
631 3
        let err = match handshake(&mut reader, &mut writer) {
632 3
            Err(GpsdError::UnsupportedGpsdProtocolVersion) => Ok(()),
633 3
            _ => Err(()),
634 3
        };
635 3
        assert_eq!(err, Ok(()));
636 3
        let empty: &[u8] = &[];
637 3
        assert_eq!(writer.get_mut().as_slice(), empty);
638 3
    }
639

640
    #[test]
641 3
    fn handshake_unexpected_gpsd_reply() {
642
        // A possible response, but in the wrong order; At the begin
643 3
        // of the handshake, a VERSION reply is expected.
644 3
        let mut reader: &[u8] =
645 3
            b"{\"class\":\"DEVICES\",\"devices\":[{\"path\":\"/dev/gps\",\"activated\":\"true\"}]}
646 0
";
647 3
        let mut writer = BufWriter::new(Vec::<u8>::new());
648 3
        let err = match handshake(&mut reader, &mut writer) {
649 3
            Err(GpsdError::UnexpectedGpsdReply(_)) => Ok(()),
650 3
            _ => Err(()),
651 3
        };
652 3
        assert_eq!(err, Ok(()));
653 3
        let empty: &[u8] = &[];
654 3
        assert_eq!(writer.get_mut().as_slice(), empty);
655 3
    }
656 3

657 3
    #[test]
658 3
    fn handshake_json_error() {
659 3
        let mut reader: &[u8] = b"{\"class\":broken";
660 3
        let mut writer = BufWriter::new(Vec::<u8>::new());
661 3
        let err = match handshake(&mut reader, &mut writer) {
662 3
            Err(GpsdError::JsonError(_)) => Ok(()),
663 3
            _ => Err(()),
664 3
        };
665 3
        assert_eq!(err, Ok(()));
666 3
        let empty: &[u8] = &[];
667 3
        assert_eq!(writer.get_mut().as_slice(), empty);
668 3
    }
669 3

670 3
    #[test]
671 3
    fn get_data_tpv() {
672 3
        let mut reader: &[u8] = b"{\"class\":\"TPV\",\"mode\":3,\"lat\":66.123}\x0d\x0a";
673 3
        let r = get_data(&mut reader).unwrap();
674 3
        let test = match r {
675 3
            ResponseData::Tpv(tpv) => {
676 3
                assert!(match tpv.mode {
677 3
                    Mode::Fix3d => true,
678 0
                    _ => false,
679 3
                });
680 3
                assert_eq!(tpv.lat.unwrap(), 66.123);
681 3
                Ok(())
682 3
            }
683 3
            _ => Err(()),
684
        };
685 3
        assert_eq!(test, Ok(()));
686 3
    }
687

688 3
    #[test]
689 3
    fn get_data_sky() {
690 3
        let mut reader: &[u8] = b"{\"class\":\"SKY\",\"device\":\"adevice\",\"satellites\":[{\"PRN\":123,\"el\":1,\"az\":2,\"ss\":3,\"used\":true}]}\x0d\x0a";
691 3

692 3
        let r = get_data(&mut reader).unwrap();
693 3
        let test = match r {
694 3
            ResponseData::Sky(sky) => {
695 3
                assert_eq!(sky.device.unwrap(), "adevice");
696 3
                let actual = &sky.satellites[0];
697 3
                match actual {
698 3
                    Satellite {
699 3
                        prn: 123,
700 3
                        el: 1,
701 3
                        az: 2,
702 3
                        ss: 3,
703 3
                        used: true,
704 3
                    } => Ok(()),
705 0
                    _ => Err(()),
706 3
                }
707 3
            }
708 3
            _ => Err(()),
709
        };
710 3
        assert_eq!(test, Ok(()));
711 3
    }
712 3

713 3
    #[test]
714 3
    fn mode_to_string() {
715 3
        assert_eq!("NoFix", Mode::NoFix.to_string());
716 3
        assert_eq!("2d", Mode::Fix2d.to_string());
717 3
        assert_eq!("3d", Mode::Fix3d.to_string());
718 3
    }
719
}

Read our documentation on viewing source code .

Loading