pub mod send_pmtk {
use std::str;
use crate::gps::{GetGpsData, Gps, is_valid_checksum};
#[derive(Debug)]
#[derive(PartialEq)]
pub enum Pmtk001Ack {
Invalid,
Unsupported,
Failed,
Success,
NoPacket,
}
#[derive(Debug)]
#[derive(PartialEq)]
pub enum DgpsMode {
NoDgps,
RTCM,
WAAS,
Unknown,
}
#[derive(Debug)]
#[derive(PartialEq)]
pub enum Sbas {
Enabled,
Disabled,
Unknown,
}
#[derive(Debug)]
#[derive(PartialEq)]
pub enum SbasMode {
Testing,
Integrity,
Unknown,
}
#[derive(Debug)]
#[derive(PartialEq)]
pub struct NmeaOutput {
pub gll: i8,
pub rmc: i8,
pub vtg: i8,
pub gga: i8,
pub gsa: i8,
pub gsv: i8,
pub pmtkchn_interval: i8,
}
#[derive(Debug)]
#[derive(PartialEq)]
pub struct EpoData {
pub set: i8,
pub fwn_ftow_week_number: i8,
pub fwn_ftow_tow: i8,
pub lwn_ltow_week_number: i8,
pub lwn_ltow_tow: i8,
pub fcwn_fctow_week_number: i8,
pub fcwn_fctow_tow: i8,
pub lcwn_lctow_week_number: i8,
pub lcwn_lctow_tow: i8,
}
pub fn add_checksum(sentence: String) -> String {
let mut checksum = 0;
for char in sentence.as_bytes() {
checksum ^= *char;
}
let checksum = format!("{:X}", checksum);
let checksumed_sentence = format!("${}*{}\r\n", sentence, checksum).as_str().to_ascii_uppercase();
return checksumed_sentence;
}
pub trait SendPmtk {
fn send_command(&mut self, cmd: &str) ;
fn pmtk_001(&mut self, search_depth: i32) -> Pmtk001Ack;
fn pmtk_500(&mut self) -> Option<String>;
fn pmtk_startup(&mut self) -> bool;
fn pmtk_101_cmd_hot_start(&mut self) -> bool;
fn pmtk_102_cmd_warm_start(&mut self) -> bool;
fn pmtk_103_cmd_cold_start(&mut self) -> bool;
fn pmtk_104_cmd_full_cold_start(&mut self) -> bool;
fn pmtk_220_set_nmea_updaterate(&mut self, update_rate: &str) -> Pmtk001Ack;
fn pmtk_251_set_nmea_baudrate(&mut self, baud_rate: &str) -> Pmtk001Ack;
fn pmtk_301_api_set_dgps_mode(&mut self, dgps_mode: DgpsMode) -> Pmtk001Ack;
fn pmtk_401_api_q_dgps_mode(&mut self) -> DgpsMode;
fn pmtk_313_api_set_sbas_enabled(&mut self, sbas: Sbas) -> Pmtk001Ack;
fn pmtk_413_api_q_sbas_enabled(&mut self) -> Sbas;
fn pmtk_314_api_set_nmea_output(&mut self, gll: i8, rmc: i8, vtg: i8, gga: i8, gsa: i8, gsv: i8, pmtkchn_interval: i8) -> Pmtk001Ack;
fn pmtk_414_api_q_nmea_output(&mut self) -> NmeaOutput;
fn pmtk_319_api_set_sbas_mode(&mut self, sbas_mode: SbasMode) -> bool;
fn pmtk_419_api_q_sbas_mode(&mut self) -> SbasMode;
fn pmtk_605_q_release(&mut self) -> String;
fn pmtk_607_q_epo_info(&mut self) -> EpoData;
fn pmtk_127_cmd_clear_epo(&mut self) -> Pmtk001Ack;
fn pmtk_397_set_nav_speed_threshold(&mut self, nav_threshold: f32) -> Pmtk001Ack;
fn pmtk_386_set_nav_speed_threshold(&mut self, nav_threshold: f32) -> Pmtk001Ack;
fn pmtk_447_q_nav_threshold(&mut self) -> f32;
fn pmtk_161_cmd_standby_mode(&mut self) -> Pmtk001Ack;
fn pmtk_223_set_al_dee_cfg(&mut self, sv: i8, snr: i8, ext_threshold: i32, ext_gap: i32) -> Pmtk001Ack;
fn pmtk_225_cmd_periodic_mode(&mut self, run_type: u8, run_time: u32, sleep_time: u32,
second_run_time: u32, second_sleep_time: u32) -> Pmtk001Ack;
fn pmtk_286_cmd_aic_mode(&mut self, aic: bool) -> Pmtk001Ack;
fn pmtk_869_cmd_easy_enable(&mut self, enable_easy: bool) -> Pmtk001Ack;
fn pmtk_869_cmd_easy_query(&mut self) -> bool;
fn pmtk_187_locus_config(&mut self, locus_interval: i8) -> Pmtk001Ack;
fn pmtk_330_api_set_datum(&mut self, datum: u16) -> Pmtk001Ack;
fn pmtk_430_api_q_datum(&mut self) -> u16;
fn pmtk_351_api_set_support_qzss_nmea(&mut self, enable_qzss: bool) -> Pmtk001Ack;
fn pmtk_352_api_set_stop_qzss(&mut self, enable: bool) -> Pmtk001Ack;
}
impl SendPmtk for Gps {
#[allow(unused_must_use)]
fn send_command(&mut self, cmd: &str) {
let cmd = add_checksum(cmd.to_string());
let byte_cmd = cmd.as_bytes();
self.port.clear(serialport::ClearBuffer::Input);
self.port.write(byte_cmd);
}
fn pmtk_001(&mut self, search_depth: i32) -> Pmtk001Ack {
for _i in 0..search_depth {
let line = self.read_line();
if is_valid_checksum(&line) {
if &line[0..8] == "$PMTK001" {
let line = line.trim();
let line: Vec<&str> = line.split("*").collect();
let line: &str = line.get(0).unwrap();
let args: Vec<&str> = line.split(",").collect();
let flag: &str = args.get(2).expect("pmtk001 format not correct");
return if flag == "0" {
Pmtk001Ack::Invalid
} else if flag == "1" {
Pmtk001Ack::Unsupported
} else if flag == "2" {
Pmtk001Ack::Failed
} else if flag == "3" {
Pmtk001Ack::Success
} else {
Pmtk001Ack::NoPacket
};
} else {
continue;
}
} else {
continue;
}
}
return Pmtk001Ack::NoPacket;
}
fn pmtk_500(&mut self) -> Option<String> {
for _i in 0..10 {
let line = self.read_line();
if (&line[0..5] == "$PMTK") && (is_valid_checksum(&line)) {
let line = line.trim();
let line: Vec<&str> = line.split("*").collect();
let line: &str = line.get(0).unwrap();
return Some(line.to_string());
}
}
return None;
}
fn pmtk_startup(&mut self) -> bool {
for _i in 0..10 {
let line = self.read_line();
if (&line[0..8] == "$PMTK011") && (is_valid_checksum(&line)) {
return true;
}
}
false
}
fn pmtk_101_cmd_hot_start(&mut self) -> bool {
self.send_command("PMTK101");
self.pmtk_startup()
}
fn pmtk_102_cmd_warm_start(&mut self) -> bool {
self.send_command("PMTK102");
self.pmtk_startup()
}
fn pmtk_103_cmd_cold_start(&mut self) -> bool {
self.send_command("PMTK103");
self.pmtk_startup()
}
fn pmtk_104_cmd_full_cold_start(&mut self) -> bool {
self.send_command("PMTK104");
self.pmtk_startup()
}
fn pmtk_220_set_nmea_updaterate(&mut self, update_rate: &str) -> Pmtk001Ack {
self.send_command(format!("PMTK220,{}", update_rate).as_str());
self.pmtk_001(10)
}
fn pmtk_251_set_nmea_baudrate(&mut self, baud_rate: &str) -> Pmtk001Ack {
self.send_command(format!("PMTK251,{}", baud_rate).as_str());
self.pmtk_001(10)
}
fn pmtk_301_api_set_dgps_mode(&mut self, dgps_mode: DgpsMode) -> Pmtk001Ack {
match dgps_mode {
DgpsMode::NoDgps => self.send_command("PMTK301,0"),
DgpsMode::RTCM => self.send_command("PMTK301,1"),
DgpsMode::WAAS => self.send_command("PMTK301,2"),
DgpsMode::Unknown => (),
}
self.pmtk_001(10)
}
fn pmtk_401_api_q_dgps_mode(&mut self) -> DgpsMode {
self.send_command("PMTK401");
return match self.pmtk_500() {
Some(args) => {
if args.len() != 10 {
return DgpsMode::Unknown;
}
let mode: String = args.chars().nth_back(0).unwrap().to_string();
let mode: &str = mode.as_str();
if mode == "0" {
return DgpsMode::NoDgps;
} else if mode == "1" {
DgpsMode::RTCM
} else if mode == "2" {
DgpsMode::WAAS
} else {
DgpsMode::Unknown
}
}
None => DgpsMode::Unknown
};
}
fn pmtk_313_api_set_sbas_enabled(&mut self, sbas: Sbas) -> Pmtk001Ack {
match sbas {
Sbas::Enabled => self.send_command("PMTK313,1"),
Sbas::Disabled => self.send_command("PMTK313,0"),
Sbas::Unknown => ()
}
self.pmtk_001(10)
}
fn pmtk_413_api_q_sbas_enabled(&mut self) -> Sbas {
self.send_command("PMTK413");
return match self.pmtk_500() {
Some(args) => {
if args.len() != 10 {
return Sbas::Unknown;
}
let mode = args.chars().nth_back(0).unwrap().to_string();
let mode = mode.as_str();
if mode == "0" {
Sbas::Disabled
} else if mode == "1" {
Sbas::Enabled
} else {
Sbas::Unknown
}
}
None => Sbas::Unknown
};
}
fn pmtk_314_api_set_nmea_output(&mut self, gll: i8, rmc: i8, vtg: i8, gga: i8, gsa: i8, gsv: i8, pmtkchn_interval: i8) -> Pmtk001Ack {
self.send_command(format!("PMTK314,{},{},{},{},{},{},0,0,0,0,0,0,0,{}",
gll, rmc, vtg, gga, gsa, gsv, pmtkchn_interval).as_str());
self.pmtk_001(10)
}
fn pmtk_414_api_q_nmea_output(&mut self) -> NmeaOutput {
self.send_command("PMTK414");
return match self.pmtk_500() {
Some(args) => {
let args: Vec<&str> = args.split(",").collect();
let gll: &str = args.get(1).unwrap_or(&"-1");
let rmc: &str = args.get(2).unwrap_or(&"-1");
let vtg: &str = args.get(3).unwrap_or(&"-1");
let gga: &str = args.get(4).unwrap_or(&"-1");
let gsa: &str = args.get(5).unwrap_or(&"-1");
let gsv: &str = args.get(6).unwrap_or(&"-1");
let pmtkchn_interval: &str = args.get(18).unwrap_or(&"-1");
NmeaOutput {
gll: gll.parse::<i8>().unwrap(),
rmc: rmc.parse::<i8>().unwrap(),
vtg: vtg.parse::<i8>().unwrap(),
gga: gga.parse::<i8>().unwrap(),
gsa: gsa.parse::<i8>().unwrap(),
gsv: gsv.parse::<i8>().unwrap(),
pmtkchn_interval: pmtkchn_interval.parse::<i8>().unwrap(),
}
}
None => NmeaOutput {
gll: -1,
rmc: -1,
vtg: -1,
gga: -1,
gsa: -1,
gsv: -1,
pmtkchn_interval: -1,
}
};
}
fn pmtk_319_api_set_sbas_mode(&mut self, sbas_mode: SbasMode) -> bool {
match sbas_mode {
SbasMode::Integrity => self.send_command("PMTK391,1"),
SbasMode::Testing => self.send_command("PMTK391,0"),
SbasMode::Unknown => (),
}
self.pmtk_startup()
}
fn pmtk_419_api_q_sbas_mode(&mut self) -> SbasMode {
self.send_command("PMTK419");
return match self.pmtk_500() {
Some(args) => {
let arg = args.chars().nth_back(0).unwrap().to_string();
let arg = arg.as_str();
if arg == "0" {
SbasMode::Testing
} else if arg == "1" {
SbasMode::Integrity
} else {
SbasMode::Unknown
}
}
None => SbasMode::Unknown
};
}
fn pmtk_605_q_release(&mut self) -> String {
self.send_command("PMTK605");
return match self.pmtk_500() {
Some(args) => {
args[9..args.len()].to_string()
}
None => "".to_string()
};
}
fn pmtk_607_q_epo_info(&mut self) -> EpoData {
let args = self.pmtk_500().unwrap_or("PMTK,-1,-1,-1,-1,-1,-1,-1,-1,-1".to_string());
let args: Vec<&str> = args.split(",").collect();
return EpoData {
set: args.get(1).unwrap_or(&"-1").parse::<i8>().unwrap(),
fwn_ftow_week_number: args.get(2).unwrap_or(&"-1").parse::<i8>().unwrap(),
fwn_ftow_tow: args.get(3).unwrap_or(&"-1").parse::<i8>().unwrap(),
lwn_ltow_week_number: args.get(4).unwrap_or(&"-1").parse::<i8>().unwrap(),
lwn_ltow_tow: args.get(5).unwrap_or(&"-1").parse::<i8>().unwrap(),
fcwn_fctow_week_number: args.get(6).unwrap_or(&"-1").parse::<i8>().unwrap(),
fcwn_fctow_tow: args.get(7).unwrap_or(&"-1").parse::<i8>().unwrap(),
lcwn_lctow_week_number: args.get(8).unwrap_or(&"-1").parse::<i8>().unwrap(),
lcwn_lctow_tow: args.get(9).unwrap_or(&"-1").parse::<i8>().unwrap(),
};
}
fn pmtk_127_cmd_clear_epo(&mut self) -> Pmtk001Ack {
self.send_command("PMTK127");
self.pmtk_001(50)
}
fn pmtk_397_set_nav_speed_threshold(&mut self, nav_threshold: f32) -> Pmtk001Ack {
self.send_command(format!("PMTK397,{:.1}", nav_threshold).as_str());
self.pmtk_001(10)
}
fn pmtk_386_set_nav_speed_threshold(&mut self, nav_threshold: f32) -> Pmtk001Ack {
self.send_command(format!("PMTK397,{:.1}", nav_threshold).as_str());
self.pmtk_001(10)
}
fn pmtk_447_q_nav_threshold(&mut self) -> f32 {
self.send_command("PMTK447");
return match self.pmtk_500() {
Some(args) => {
let args: Vec<&str> = args.split(",").collect();
let nav_threshold: f32 = args.get(1).unwrap().parse::<f32>().unwrap();
nav_threshold
}
None => return -1.0
};
}
fn pmtk_161_cmd_standby_mode(&mut self) -> Pmtk001Ack {
self.send_command("PMTK161,0");
self.pmtk_001(10)
}
fn pmtk_223_set_al_dee_cfg(&mut self, sv: i8, snr: i8, ext_threshold: i32, ext_gap: i32) -> Pmtk001Ack {
self.send_command(format!("PMTK223,{},{},{},{}", sv, snr, ext_threshold, ext_gap).as_str());
self.pmtk_001(10)
}
fn pmtk_225_cmd_periodic_mode(&mut self, run_type: u8, run_time: u32, sleep_time: u32,
second_run_time: u32, second_sleep_time: u32) -> Pmtk001Ack {
self.send_command(format!("PMTK223,{},{},{},{},{}",
run_type, run_time, sleep_time, second_run_time, second_sleep_time).as_str());
self.pmtk_001(10)
}
fn pmtk_286_cmd_aic_mode(&mut self, aic: bool) -> Pmtk001Ack {
if aic {
self.send_command("PMTK286,1")
} else {
self.send_command("PMTK286,0")
}
self.pmtk_001(10)
}
fn pmtk_869_cmd_easy_enable(&mut self, enable_easy: bool) -> Pmtk001Ack {
if enable_easy {
self.send_command("PMTK869,1,1")
} else {
self.send_command("PMTK869,1,0")
}
self.pmtk_001(10)
}
fn pmtk_869_cmd_easy_query(&mut self) -> bool {
self.send_command("PMTK869,0");
return match self.pmtk_500() {
Some(args) => {
let args: Vec<&str> = args.split(",").collect();
if args.get(2).unwrap() == &"0" {
false
} else {
true
}
}
None => true
};
}
fn pmtk_187_locus_config(&mut self, locus_interval: i8) -> Pmtk001Ack {
self.send_command(format!("PMTK187,1,{}", locus_interval).as_str());
self.pmtk_001(10)
}
fn pmtk_330_api_set_datum(&mut self, datum: u16) -> Pmtk001Ack {
self.send_command(format!("PMTK330,{}", datum).as_str());
self.pmtk_001(10)
}
fn pmtk_430_api_q_datum(&mut self) -> u16 {
self.send_command("PMTK430");
return match self.pmtk_500() {
Some(args) => {
let args: Vec<&str> = args.split(",").collect();
let datum = args.get(1).unwrap_or(&"0").parse::<u16>().unwrap();
datum
}
None => 0
};
}
fn pmtk_351_api_set_support_qzss_nmea(&mut self, enable_qzss: bool) -> Pmtk001Ack {
if enable_qzss {
self.send_command("PMTK351,1")
} else {
self.send_command("PMTK351,0")
}
self.pmtk_001(10)
}
fn pmtk_352_api_set_stop_qzss(&mut self, enable: bool) -> Pmtk001Ack {
if enable {
self.send_command("PMTK352,0")
} else {
self.send_command("PMTK352,1")
}
self.pmtk_001(10)
}
}
}
#[cfg(test)]
mod pmtktests {
use std::thread::sleep;
use std::time::Duration;
use super::send_pmtk::{DgpsMode, EpoData, NmeaOutput, Pmtk001Ack, Sbas, SbasMode};
use super::send_pmtk::add_checksum;
use super::send_pmtk::SendPmtk;
use super::super::gps::{Gps, open_port};
#[test]
fn checksum() {
assert_eq!(add_checksum("GNGGA,165419.000,5132.7378,N,00005.9192,W,1,7,1.93,34.4,M,47.0,M,,".to_string()), "$GNGGA,165419.000,5132.7378,N,00005.9192,W,1,7,1.93,34.4,M,47.0,M,,*6A\r\n".to_string());
assert_eq!(add_checksum("PMTK103".to_string()), "$PMTK103*30\r\n")
}
fn port_setup() -> Gps {
let port = open_port("/dev/serial0", 9600);
let gps = Gps { port , satellite_data: true, naviagtion_data: true };
sleep(Duration::from_secs(1));
return gps;
}
#[test]
fn test_pmtk_101_cmd_hot_start() { assert_eq!(port_setup().pmtk_101_cmd_hot_start(), true); }
#[test]
fn test_pmtk_102_cmd_warm_start() { assert_eq!(port_setup().pmtk_102_cmd_warm_start(), true); }
#[test]
fn test_pmtk_103_cmd_cold_start() { assert_eq!(port_setup().pmtk_103_cmd_cold_start(), true); }
#[test]
fn test_pmtk_104_cmd_full_cold_start() { assert_eq!(port_setup().pmtk_104_cmd_full_cold_start(), true); }
#[test]
fn test_pmtk_220_set_nmea_updaterate() {
assert_eq!(port_setup().pmtk_220_set_nmea_updaterate("1000"), Pmtk001Ack::Success);
assert_eq!(port_setup().pmtk_220_set_nmea_updaterate("200"), Pmtk001Ack::Success);
assert_eq!(port_setup().pmtk_220_set_nmea_updaterate("10000"), Pmtk001Ack::Success);
}
#[test]
fn test_pmtk_251_set_nmea_baudrate() { assert_eq!(port_setup().pmtk_251_set_nmea_baudrate("9600"), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_301_api_set_dgps_mode() { assert_eq!(port_setup().pmtk_301_api_set_dgps_mode(DgpsMode::NoDgps), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_401_api_q_dgps_mode() { assert_eq!(port_setup().pmtk_401_api_q_dgps_mode(), DgpsMode::WAAS); }
#[test]
fn test_pmtk_313_api_set_sbas_enabled() { assert_eq!(port_setup().pmtk_313_api_set_sbas_enabled(Sbas::Enabled), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_413_api_q_sbas_enabled() { assert_eq!(port_setup().pmtk_413_api_q_sbas_enabled(), Sbas::Enabled); }
#[test]
#[test]
fn test_pmtk_414_api_q_nmea_output() {
assert_eq!(port_setup().pmtk_414_api_q_nmea_output(), NmeaOutput {
gll: 0,
rmc: 1,
vtg: 1,
gga: 1,
gsa: 1,
gsv: 5,
pmtkchn_interval: 0,
});
}
#[test]
fn test_pmtk_319_api_set_sbas_mode() { assert_eq!(port_setup().pmtk_319_api_set_sbas_mode(SbasMode::Integrity), true); }
#[test]
fn test_pmtk_419_api_q_sbas_mode() { assert_eq!(port_setup().pmtk_419_api_q_sbas_mode(), SbasMode::Integrity); }
#[test]
fn test_pmtk_605_q_release() { assert_eq!(port_setup().pmtk_605_q_release(), "AXN_5.1.7_3333_19020118,0027,PA1010D,1.0".to_string()); }
#[test]
fn test_pmtk_127_cmd_clear_epo() { assert_eq!(port_setup().pmtk_127_cmd_clear_epo(), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_607_q_epo_info() {
assert_eq!(port_setup().pmtk_607_q_epo_info(), EpoData {
set: 0,
fwn_ftow_week_number: 0,
fwn_ftow_tow: 0,
lwn_ltow_week_number: 0,
lwn_ltow_tow: 0,
fcwn_fctow_week_number: 0,
fcwn_fctow_tow: 0,
lcwn_lctow_week_number: 0,
lcwn_lctow_tow: 0,
});
}
#[test]
fn test_pmtk_397_set_nav_speed_threshold() {
assert_eq!(port_setup().pmtk_397_set_nav_speed_threshold(0.2), Pmtk001Ack::Success);
assert_eq!(port_setup().pmtk_397_set_nav_speed_threshold(0.4), Pmtk001Ack::Success);
assert_eq!(port_setup().pmtk_397_set_nav_speed_threshold(0.8), Pmtk001Ack::Success);
}
#[test]
fn test_pmtk_386_set_nav_speed_threshold() { assert_eq!(port_setup().pmtk_386_set_nav_speed_threshold(0.2), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_447_q_nav_threshold() { assert_eq!(port_setup().pmtk_447_q_nav_threshold(), 0.0); }
#[test]
fn test_pmtk_223_set_al_dee_cfg() { assert_eq!(port_setup().pmtk_223_set_al_dee_cfg(1, 30, 180000, 60000), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_286_cmd_aic_mode() { assert_eq!(port_setup().pmtk_286_cmd_aic_mode(true), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_869_cmd_easy_enable() { assert_eq!(port_setup().pmtk_869_cmd_easy_enable(true), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_869_cmd_easy_query() { assert_eq!(port_setup().pmtk_869_cmd_easy_query(), true); }
#[test]
fn test_pmtk_330_api_set_datum() { assert_eq!(port_setup().pmtk_330_api_set_datum(0), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_430_api_q_datum() { assert_eq!(port_setup().pmtk_430_api_q_datum(), 0); }
#[test]
fn test_pmtk_351_api_set_support_qzss_nmea() { assert_eq!(port_setup().pmtk_351_api_set_support_qzss_nmea(false), Pmtk001Ack::Success); }
#[test]
fn test_pmtk_352_api_set_stop_qzss() { assert_eq!(port_setup().pmtk_352_api_set_stop_qzss(true), Pmtk001Ack::Success); }
}