#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
use serde::de::*;
use serde::Deserializer;
use std::fmt;
use std::io;
pub const PROTO_MAJOR_MIN: u8 = 3;
pub const ENABLE_WATCH_CMD: &str = "?WATCH={\"enable\":true,\"json\":true};\r\n";
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Version {
pub release: String,
pub rev: String,
pub proto_major: u8,
pub proto_minor: u8,
pub remote: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Devices {
pub devices: Vec<DeviceInfo>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct DeviceInfo {
pub path: Option<String>,
pub activated: Option<String>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Watch {
pub enable: Option<bool>,
pub json: Option<bool>,
pub nmea: Option<bool>,
pub raw: Option<u8>,
pub scaled: Option<bool>,
pub timing: Option<bool>,
pub split24: Option<bool>,
pub pps: Option<bool>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[serde(tag = "class")]
#[serde(rename_all = "UPPERCASE")]
pub enum ResponseHandshake {
Version(Version),
Devices(Devices),
Watch(Watch),
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Device {
pub path: Option<String>,
pub activated: Option<String>,
pub flags: Option<i32>,
pub driver: Option<String>,
pub subtype: Option<String>,
pub bps: Option<u16>,
pub parity: Option<String>,
pub stopbits: Option<u8>,
pub native: Option<u8>,
pub cycle: Option<f32>,
pub mincycle: Option<f32>,
}
#[derive(Debug, Copy, Clone)]
pub enum Mode {
NoFix,
Fix2d,
Fix3d,
}
impl fmt::Display for Mode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Mode::NoFix => write!(f, "NoFix"),
Mode::Fix2d => write!(f, "2d"),
Mode::Fix3d => write!(f, "3d"),
}
}
}
fn mode_from_str<'de, D>(deserializer: D) -> Result<Mode, D::Error>
where
D: Deserializer<'de>,
{
let s = u8::deserialize(deserializer)?;
match s {
2 => Ok(Mode::Fix2d),
3 => Ok(Mode::Fix3d),
_ => Ok(Mode::NoFix),
}
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Tpv {
pub device: Option<String>,
pub status: Option<i32>,
#[serde(deserialize_with = "mode_from_str")]
pub mode: Mode,
pub time: Option<String>,
pub ept: Option<f32>,
pub leapseconds: Option<i32>,
#[serde(rename = "altMSL")]
pub alt_msl: Option<f32>,
#[serde(rename = "altHAE")]
pub alt_hae: Option<f32>,
#[serde(rename = "geoidSep")]
pub geoid_sep: Option<f32>,
pub lat: Option<f64>,
pub lon: Option<f64>,
pub alt: Option<f32>,
pub epx: Option<f32>,
pub epy: Option<f32>,
pub epv: Option<f32>,
pub track: Option<f32>,
pub speed: Option<f32>,
pub climb: Option<f32>,
pub epd: Option<f32>,
pub eps: Option<f32>,
pub epc: Option<f32>,
pub eph: Option<f32>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Satellite {
#[serde(rename = "PRN")]
pub prn: i16,
pub el: Option<f32>,
pub az: Option<f32>,
pub ss: Option<f32>,
pub used: bool,
pub gnssid: Option<u8>,
pub svid: Option<u16>,
pub health: Option<u8>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Sky {
pub device: Option<String>,
pub xdop: Option<f32>,
pub ydop: Option<f32>,
pub vdop: Option<f32>,
pub tdop: Option<f32>,
pub hdop: Option<f32>,
pub gdop: Option<f32>,
pub pdop: Option<f32>,
pub satellites: Option<Vec<Satellite>>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Pps {
pub device: String,
pub real_sec: f32,
pub real_nsec: f32,
pub clock_sec: f32,
pub clock_nsec: f32,
pub precision: f32,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
pub struct Gst {
pub device: Option<String>,
pub time: Option<String>,
pub rms: Option<f32>,
pub major: Option<f32>,
pub minor: Option<f32>,
pub orient: Option<f32>,
pub lat: Option<f32>,
pub lon: Option<f32>,
pub alt: Option<f32>,
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[serde(tag = "class")]
#[serde(rename_all = "UPPERCASE")]
pub enum ResponseData {
Device(Device),
Tpv(Tpv),
Sky(Sky),
Pps(Pps),
Gst(Gst),
}
#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize))]
#[serde(tag = "class")]
#[serde(rename_all = "UPPERCASE")]
pub enum UnifiedResponse {
Version(Version),
Devices(Devices),
Watch(Watch),
Device(Device),
Tpv(Tpv),
Sky(Sky),
Pps(Pps),
Gst(Gst),
}
#[derive(Debug)]
pub enum GpsdError {
IoError(io::Error),
JsonError(serde_json::Error),
UnsupportedGpsdProtocolVersion,
UnexpectedGpsdReply(String),
WatchFail(String),
}
impl From<io::Error> for GpsdError {
fn from(err: io::Error) -> GpsdError {
GpsdError::IoError(err)
}
}
impl From<serde_json::Error> for GpsdError {
fn from(err: serde_json::Error) -> GpsdError {
GpsdError::JsonError(err)
}
}
impl fmt::Display for GpsdError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GpsdError::IoError(e) => write!(f, "IoError: {}", e),
GpsdError::JsonError(e) => write!(f, "JsonError: {}", e),
GpsdError::UnsupportedGpsdProtocolVersion => {
write!(f, "UnsupportedGpsdProtocolVersion")
}
GpsdError::UnexpectedGpsdReply(e) => write!(f, "UnexpectedGpsdReply: {}", e),
GpsdError::WatchFail(e) => write!(f, "WatchFail: {}", e),
}
}
}
pub fn handshake(
reader: &mut dyn io::BufRead,
writer: &mut dyn io::Write,
) -> Result<(), GpsdError> {
let mut data = Vec::new();
reader.read_until(b'\n', &mut data)?;
trace!("{}", String::from_utf8(data.clone()).unwrap());
let msg: ResponseHandshake = serde_json::from_slice(&data)?;
match msg {
ResponseHandshake::Version(v) => {
if v.proto_major < PROTO_MAJOR_MIN {
return Err(GpsdError::UnsupportedGpsdProtocolVersion);
}
}
_ => {
return Err(GpsdError::UnexpectedGpsdReply(
String::from_utf8(data).unwrap(),
))
}
}
writer.write_all(ENABLE_WATCH_CMD.as_bytes())?;
writer.flush()?;
let mut data = Vec::new();
reader.read_until(b'\n', &mut data)?;
trace!("{}", String::from_utf8(data.clone()).unwrap());
let msg: ResponseHandshake = serde_json::from_slice(&data)?;
match msg {
ResponseHandshake::Devices(_) => {}
_ => {
return Err(GpsdError::UnexpectedGpsdReply(
String::from_utf8(data).unwrap(),
))
}
}
let mut data = Vec::new();
reader.read_until(b'\n', &mut data)?;
trace!("{}", String::from_utf8(data.clone()).unwrap());
let msg: ResponseHandshake = serde_json::from_slice(&data)?;
match msg {
ResponseHandshake::Watch(w) => {
if let (false, false, true) = (
w.enable.unwrap_or(false),
w.json.unwrap_or(false),
w.nmea.unwrap_or(false),
) {
return Err(GpsdError::WatchFail(
String::from_utf8(data).unwrap(),
));
}
}
_ => {
return Err(GpsdError::UnexpectedGpsdReply(
String::from_utf8(data).unwrap(),
))
}
}
Ok(())
}
pub fn get_data(reader: &mut dyn io::BufRead) -> Result<ResponseData, GpsdError> {
let mut data = Vec::new();
reader.read_until(b'\n', &mut data)?;
trace!("{}", String::from_utf8(data.clone()).unwrap());
let msg: ResponseData = serde_json::from_slice(&data)?;
Ok(msg)
}
#[cfg(test)]
mod tests {
use super::{get_data, handshake, GpsdError, Mode, ResponseData, ENABLE_WATCH_CMD};
use std::io::BufWriter;
#[test]
fn handshake_ok() {
let mut reader: &[u8] = b"{\"class\":\"VERSION\",\"release\":\"blah\",\"rev\":\"blurp\",\"proto_major\":3,\"proto_minor\":12}\x0d
{\"class\":\"DEVICES\",\"devices\":[{\"path\":\"/dev/gps\",\"activated\":\"true\"}]}
{\"class\":\"WATCH\",\"enable\":true,\"json\":true,\"nmea\":false}
";
let mut writer = BufWriter::new(Vec::<u8>::new());
let r = handshake(&mut reader, &mut writer);
assert!(r.is_ok());
assert_eq!(writer.get_mut().as_slice(), ENABLE_WATCH_CMD.as_bytes());
}
#[test]
fn handshake_unsupported_protocol_version() {
let mut reader: &[u8] = b"{\"class\":\"VERSION\",\"release\":\"blah\",\"rev\":\"blurp\",\"proto_major\":2,\"proto_minor\":17}\x0d
";
let mut writer = BufWriter::new(Vec::<u8>::new());
let err = match handshake(&mut reader, &mut writer) {
Err(GpsdError::UnsupportedGpsdProtocolVersion) => Ok(()),
_ => Err(()),
};
assert_eq!(err, Ok(()));
let empty: &[u8] = &[];
assert_eq!(writer.get_mut().as_slice(), empty);
}
#[test]
fn handshake_unexpected_gpsd_reply() {
let mut reader: &[u8] =
b"{\"class\":\"DEVICES\",\"devices\":[{\"path\":\"/dev/gps\",\"activated\":\"true\"}]}
";
let mut writer = BufWriter::new(Vec::<u8>::new());
let err = match handshake(&mut reader, &mut writer) {
Err(GpsdError::UnexpectedGpsdReply(_)) => Ok(()),
_ => Err(()),
};
assert_eq!(err, Ok(()));
let empty: &[u8] = &[];
assert_eq!(writer.get_mut().as_slice(), empty);
}
#[test]
fn handshake_json_error() {
let mut reader: &[u8] = b"{\"class\":broken";
let mut writer = BufWriter::new(Vec::<u8>::new());
let err = match handshake(&mut reader, &mut writer) {
Err(GpsdError::JsonError(_)) => Ok(()),
_ => Err(()),
};
assert_eq!(err, Ok(()));
let empty: &[u8] = &[];
assert_eq!(writer.get_mut().as_slice(), empty);
}
#[test]
fn get_data_tpv() {
let mut reader: &[u8] = b"{\"class\":\"TPV\",\"mode\":3,\"lat\":66.123}\x0d\x0a";
let r = get_data(&mut reader).unwrap();
let test = match r {
ResponseData::Tpv(tpv) => {
assert!(match tpv.mode {
Mode::Fix3d => true,
_ => false,
});
assert_eq!(tpv.lat.unwrap(), 66.123);
Ok(())
}
_ => Err(()),
};
assert_eq!(test, Ok(()));
}
#[test]
fn get_data_sky() {
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";
let r = get_data(&mut reader).unwrap();
let test = match r {
ResponseData::Sky(sky) => {
assert_eq!(sky.device.unwrap(), "aDevice");
let actual = &sky.satellites.unwrap()[0];
assert_eq!(actual.prn, 123);
assert_eq!(actual.el, Some(1.));
assert_eq!(actual.az, Some(2.));
assert_eq!(actual.ss, Some(3.));
assert_eq!(actual.used, true);
assert_eq!(actual.gnssid, Some(1));
assert_eq!(actual.svid, Some(271));
assert_eq!(actual.health, Some(1));
Ok(())
}
_ => Err(()),
};
assert_eq!(test, Ok(()));
}
#[test]
fn mode_to_string() {
assert_eq!("NoFix", Mode::NoFix.to_string());
assert_eq!("2d", Mode::Fix2d.to_string());
assert_eq!("3d", Mode::Fix3d.to_string());
}
}