#[warn(dead_code)]
use chrono::{DateTime, Utc, NaiveDateTime};
use chrono_tz::{self, Tz};
use round::round;
use serde_json::{self, Value};
use std::fmt;
use std::fmt::Formatter;
use std::io::{self, BufRead, BufReader, Write};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpStream};
const TIME_FMT: &'static str = "%Y-%m-%dT%H:%M:%S.%fZ";
const DIRECTION: [&'static str; 8] = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
#[derive(Debug)]
pub enum GPSError {
TcpStreamError(String),
BufReadError(io::Error),
WelcomeMessageError,
SerdeJsonError(serde_json::Error),
TimeZoneParseError(chrono_tz::ParseError),
WatchError,
TimeConvertError(String),
}
impl From<io::Error> for GPSError {
fn from(err: io::Error) -> Self {
GPSError::BufReadError(err)
}
}
impl From<serde_json::Error> for GPSError {
fn from(err: serde_json::Error) -> Self {
GPSError::SerdeJsonError(err)
}
}
impl fmt::Display for GPSError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
GPSError::TcpStreamError(e) => write!(f, "{e}"),
GPSError::BufReadError(e) => write!(f, "IO BufReader Error: {e}"),
GPSError::WelcomeMessageError => write!(f, "Unexpected data received as welcome."),
GPSError::SerdeJsonError(e) => write!(f, "Serde Json Error: {e}"),
GPSError::TimeZoneParseError(e) => write!(f, "Chrono-Tz Parse Error: {e}"),
GPSError::WatchError => write!(f, "GPSD Watch Error: No watch returned."),
GPSError::TimeConvertError(e) => write!(f, "Time Convert Error: {e}"),
}
}
}
#[derive(Debug)]
pub struct GPS {
gps_socket: TcpStream,
gps_stream: BufReader<TcpStream>,
pub version: Value,
pub watch: Value,
pub devices: Value,
}
impl GPS {
pub fn connect() -> Result<GPS, GPSError> {
let host: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let port: u16 = 2947;
let sock_addr: SocketAddr = SocketAddr::new(host, port);
let mut gps_socket: TcpStream = TcpStream::connect(sock_addr)
.map_err(|e| GPSError::TcpStreamError(format!("{e}")))?;
let mut gps_stream: BufReader<TcpStream> = BufReader::new(
gps_socket.try_clone()
.map_err(|e| GPSError::BufReadError(e))?
);
let mut welcom_messase: String = String::new();
gps_stream.read_line(&mut welcom_messase)?;
let version: Value = parse_into_json(&welcom_messase)?;
if version["class"] != "VERSION" {
return Err(GPSError::WelcomeMessageError);
}
let watch_command: &[u8] ="?WATCH={\"enable\": true}\n".as_bytes();
gps_socket.write_all(watch_command)?;
gps_socket.flush()?;
let (w, d) = parse_info(&mut gps_stream)?;
let watch: Value = match w {
Some(t) => t,
None => return Err(GPSError::WatchError)
};
let devices: Value = match d {
Some(t) => t,
None => return Err(GPSError::WatchError)
};
Ok(GPS { gps_socket, gps_stream, version, watch, devices })
}
pub fn current_data(&mut self) -> Result<GPSData, GPSError> {
self.gps_socket.write_all("?POLL;\n".as_bytes())?;
self.gps_socket.flush()?;
let mut raw: String = String::new();
self.gps_stream.read_line(&mut raw)?;
let raw_json: Value = parse_into_json(&raw)?;
if raw_json["class"] != "POLL" {
return Ok(GPSData::default());
}
Ok(GPSData::new(&raw_json))
}
pub fn close(self) {
drop(self.gps_stream);
self.gps_socket.shutdown(std::net::Shutdown::Both).unwrap();
}
}
#[derive(Debug, Default, Clone)]
pub struct GPSData {
pub mode: Fix,
pub sats: u8,
pub sats_valid: u8,
pub lon: f64,
pub lat: f64,
pub alt_hae: f64,
pub alt_msl: f64,
pub alt: f32,
pub track: f32,
pub speed: f32,
pub climb: f32,
pub time: String,
pub epc: f32,
pub eps: f32,
pub ept: f32,
pub epv: f32,
pub epx: f32,
pub epy: f32,
}
impl GPSData {
fn new(values: &Value) -> Self {
let sky: Value = values["sky"][0].to_owned();
let tpv: Value = values["tpv"][0].to_owned();
let fix: u8 = if tpv.get("mode").is_some() { parse_value_to_u8(&tpv["mode"]) } else { 0 };
let mode: Fix = Fix::new(fix);
let sats: u8 = if sky.get("nSat").is_some() { parse_value_to_u8(&sky["nSat"]) } else { 0 };
let sats_valid: u8 = if sky.get("satellites").is_some() { get_valid_satellites(&sky["satellites"]) } else { 0 };
let lon: f64 = if fix >= 2 { parse_value_to_f64(&tpv["lon"]) } else { 0.0 };
let lat: f64 = if fix >= 2 { parse_value_to_f64(&tpv["lat"]) } else { 0.0 };
let alt_hae: f64 = if tpv.get("altHAE").is_some() { parse_value_to_f64(&tpv["altHAE"]) } else { 0.0 };
let alt_msl: f64 = if tpv.get("altMSL").is_some() { parse_value_to_f64(&tpv["altMSL"]) } else { 0.0 };
let alt: f32 = if fix >= 3 { parse_value_to_f32(&tpv["alt"]) } else { 0.0 };
let track: f32 = if fix >= 2 { parse_value_to_f32(&tpv["track"]) } else { 0.0 };
let speed: f32 = if fix >= 2 { parse_value_to_f32(&tpv["speed"]) } else { 0.0 };
let climb: f32 = if fix >= 3 { parse_value_to_f32(&tpv["climb"]) } else { 0.0 };
let time: String = if fix >= 2 { parse_value_to_string_time(&tpv["time"]) } else { get_time() };
let epc: f32 = if tpv.get("epc").is_some() { parse_value_to_f32(&tpv["epc"]) } else { 0.0 };
let eps: f32 = if tpv.get("eps").is_some() { parse_value_to_f32(&tpv["eps"]) } else { 0.0 };
let ept: f32 = if tpv.get("ept").is_some() { parse_value_to_f32(&tpv["ept"]) } else { 0.0 };
let epv: f32 = if tpv.get("epv").is_some() { parse_value_to_f32(&tpv["epv"]) } else { 0.0 };
let epx: f32 = if tpv.get("epx").is_some() { parse_value_to_f32(&tpv["epx"]) } else { 0.0 };
let epy: f32 = if tpv.get("epy").is_some() { parse_value_to_f32(&tpv["epy"]) } else { 0.0 };
GPSData{ mode, sats, sats_valid, lon, lat, alt_hae, alt_msl, alt, track, speed, climb, time, epc, eps, ept, epv, epx, epy}
}
pub fn convert_speed(&self, mph: bool) -> f32 {
let speed: f32 = match mph {
true => self.speed * 2.237,
_ => self.speed * 3.6
};
round(speed as f64, 1) as f32
}
pub fn travel_direction(&self) -> String {
let degree: f64 = (self.track / 45.0) as f64;
let index: usize = (round(degree, 0) as usize) % 8;
DIRECTION[index].to_string()
}
pub fn convert_time(&self, timezone: &str) -> Result<String, GPSError> {
let time_zone: Tz = timezone.parse().map_err(|e| GPSError::TimeZoneParseError(e))?;
let native_time: NaiveDateTime = NaiveDateTime::parse_from_str(&self.time, TIME_FMT)
.map_err(|_| GPSError::TimeConvertError(format!("Could not convert the time {}", self.time)))?;
let utc_time: DateTime<Utc> = DateTime::from_naive_utc_and_offset(native_time, Utc);
let current_time: DateTime<Tz> = utc_time.with_timezone(&time_zone);
Ok(current_time.format(TIME_FMT).to_string())
}
}
#[derive(Debug, Default, Clone, Copy)]
pub enum Fix {
#[default]
None,
Fix2D,
Fix3D
}
impl fmt::Display for Fix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Fix::None => write!(f, "None"),
Fix::Fix2D => write!(f, "Fix 2D"),
Fix::Fix3D => write!(f, "Fix 2D"),
}
}
}
impl Fix {
fn new(mode: u8) -> Self {
match mode {
2 => Fix::Fix2D,
3 => Fix::Fix3D,
_ => Fix::None,
}
}
}
pub fn format_time(time: &str, new_format: &str) -> Result<String, GPSError> {
let native_time: NaiveDateTime = NaiveDateTime::parse_from_str(time, TIME_FMT)
.map_err(|_| GPSError::TimeConvertError(format!("Could not convert the time {}", time)))?;
let current: DateTime<Utc> = DateTime::from_naive_utc_and_offset(native_time, Utc);
Ok(current.format(new_format).to_string())
}
fn parse_into_json(info: &str) -> Result<Value, GPSError> {
let value: Value = serde_json::from_str(info)
.map_err(|e| GPSError::SerdeJsonError(e))?;
Ok(value)
}
fn parse_info(gps_stream: &mut BufReader<TcpStream>) -> Result<(Option<Value>, Option<Value>), GPSError> {
let mut watch: Option<Value> = None;
let mut devices: Option<Value> = None;
for _ in 0..2 {
let mut raw = String::new();
gps_stream.read_line(&mut raw)?;
let parsed: Value = parse_into_json(&raw)?;
if parsed["class"] == "DEVICES" { devices = Some(parsed.to_owned()) }
if parsed["class"] == "WATCH" { watch = Some(parsed.to_owned()) }
}
Ok((watch, devices))
}
fn parse_value_to_u8(value: &Value) -> u8 {
match value {
Value::Number(x) => {
let numb: u64 = x.as_u64().unwrap();
match numb <= u8::MAX as u64 {
true => numb as u8,
_ => 0
}
}
_ => 0
}
}
fn parse_value_to_f32(value: &Value) -> f32 {
match value {
Value::Number(x) => {
let number: f64 = x.as_f64().unwrap();
if number <= f32::MAX as f64 {
number as f32
} else {
0.0
}
},
_ => 0.0
}
}
fn parse_value_to_f64(value: &Value) -> f64 {
match value {
Value::Number(x) => x.as_f64().unwrap(),
_ => 0.0
}
}
fn parse_value_to_string_time(value: &Value) -> String {
match value {
Value::String(x) => x.to_string(),
_ => get_time()
}
}
fn get_time() -> String {
let current_time: DateTime<Utc> = Utc::now();
current_time.format(TIME_FMT).to_string()
}
fn get_valid_satellites(values: &Value) -> u8 {
let mut count: u8 = 0;
let sat_array: &Vec<Value> = values.as_array().unwrap();
for i in sat_array.iter() {
if i["used"] == true {
count += 1;
}
}
count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checking_satellites() {
let value: Value = serde_json::from_str(
"{\"Test\": [\
{\"used\":true,\"gnssid\":0,\"svid\":5},\
{\"used\":true,\"gnssid\":0,\"svid\":5},\
{\"used\":false,\"gnssid\":0,\"svid\":5},\
{\"used\":false,\"gnssid\":0,\"svid\":5},\
{\"used\":true,\"gnssid\":0,\"svid\":5}\
]}"
).unwrap();
let results: u8 = get_valid_satellites(&value["Test"]);
assert_eq!(results, 3);
}
#[test]
fn check_parse_to_json() {
let value: Value = parse_into_json(
"{\"Test\": [\
{\"used\":true,\"gnssid\":0,\"svid\":5},\
{\"used\":true,\"gnssid\":0,\"svid\":5},\
{\"used\":false,\"gnssid\":0,\"svid\":5},\
{\"used\":false,\"gnssid\":0,\"svid\":5},\
{\"used\":true,\"gnssid\":0,\"svid\":5}\
]}"
).unwrap();
assert_eq!(value.is_object(), true);
}
#[test]
fn check_my_time() {
let gps_data: GPSData = GPSData{
mode: Fix::Fix2D,
sats: 0,
sats_valid: 0,
lon: 0.0,
lat: 0.0,
alt_hae: 0.0,
alt_msl: 0.0,
alt: 0.0,
track: 0.0,
speed: 0.0,
climb: 0.0,
time: String::from("2024-01-07T00:59:47.0000Z"),
epc: 0.0,
eps: 0.0,
ept: 0.0,
epv: 0.0,
epx: 0.0,
epy: 0.0,
};
let my_time: String = gps_data.convert_time("America/New_York")
.unwrap()
.split(".")
.next()
.unwrap()
.to_string();
assert_eq!(my_time, "2024-01-06T19:59:47".to_string());
}
#[test]
fn check_speed() {
let gps_data: GPSData = GPSData{
mode: Fix::Fix2D,
sats: 0,
sats_valid: 0,
lon: 0.0,
lat: 0.0,
alt_hae: 0.0,
alt_msl: 0.0,
alt: 0.0,
track: 0.0,
speed: 32.9,
climb: 0.0,
time: String::from("2024-01-07T00:59:47.0000Z"),
epc: 0.0,
eps: 0.0,
ept: 0.0,
epv: 0.0,
epx: 0.0,
epy: 0.0,
};
let mph: f32 = gps_data.convert_speed(true);
let kmh: f32 = gps_data.convert_speed(false);
assert_eq!(mph, 73.6);
assert_eq!(kmh, 118.4);
}
#[test]
fn check_travel_direction() {
let gps_data: GPSData = GPSData{
mode: Fix::Fix2D,
sats: 0,
sats_valid: 0,
lon: 0.0,
lat: 0.0,
alt_hae: 0.0,
alt_msl: 0.0,
alt: 0.0,
track: 38.7,
speed: 32.9,
climb: 0.0,
time: String::from("2024-01-07T00:59:47.0000Z"),
epc: 0.0,
eps: 0.0,
ept: 0.0,
epv: 0.0,
epx: 0.0,
epy: 0.0,
};
let direction: String = gps_data.travel_direction();
assert_eq!(direction, "NE");
}
}