use anyhow::Result;
use serde_json::{json, Value};
use serialport::{self, SerialPort};
use std::io::Read;
use std::str;
use std::time::Duration;
pub fn gps_init(path: &str, baud_rate: u32) -> Result<Box<dyn SerialPort>> {
let port = serialport::new(path, baud_rate)
.timeout(Duration::from_millis(1000))
.open()?;
Ok(port)
}
pub fn read_complete_gps_message(port: &mut Box<dyn SerialPort>) -> Result<Option<Vec<u8>>> {
let mut buffer = vec![0; 1024];
let mut data_accumulator = Vec::new();
let mut current_message = Vec::new();
let mut collecting = false;
loop {
match port.read(&mut buffer) {
Ok(bytes) if bytes > 0 => {
data_accumulator.extend_from_slice(&buffer[..bytes]);
while let Some(index) = data_accumulator.iter().position(|&x| x == b'\n') {
let line = &data_accumulator[..index + 1];
let sentence = String::from_utf8_lossy(line);
if sentence.starts_with("$GNGGA") {
current_message.clear();
collecting = true;
}
if collecting {
current_message.extend_from_slice(line);
}
if sentence.starts_with("$GPTXT") && collecting {
data_accumulator.drain(..index + 1);
return Ok(Some(current_message.clone())); }
data_accumulator.drain(..index + 1);
}
}
Ok(_) => continue,
Err(e) => {
eprintln!("Error reading from serial port: {:?}", e);
return Err(e.into());
}
}
}
}
pub fn parse_gps_data(nmea_data: &[u8]) -> Value {
let data_str = str::from_utf8(nmea_data).unwrap_or_default();
let mut result = json!({});
for line in data_str.lines() {
if let Some(sentence) = line.split_once(',') {
let (sentence_type, rest) = sentence;
let sentence_type = sentence_type.trim_start_matches('$');
let fields: Vec<&str> = rest.split(',').collect();
match sentence_type {
"GNGGA" => {
result[sentence_type] = json!({
"time": fields.get(0).unwrap_or(&""),
"latitude": fields.get(1).unwrap_or(&""),
"longitude": fields.get(3).unwrap_or(&""),
"fix_quality": fields.get(5).unwrap_or(&""),
"num_of_satellites": fields.get(6).unwrap_or(&""),
"horizontal_dilution": fields.get(7).unwrap_or(&""),
"altitude": fields.get(8).unwrap_or(&""),
"height_of_geoid": fields.get(10).unwrap_or(&"")
});
}
"GNGLL" => {
result[sentence_type] = json!({
"latitude": fields.get(0).unwrap_or(&""),
"longitude": fields.get(1).unwrap_or(&""),
"time": fields.get(4).unwrap_or(&""),
"status": fields.get(5).unwrap_or(&"")
});
}
"GPGSA" | "BDGSA" => {
result[sentence_type] = json!({
"mode": fields.get(0).unwrap_or(&""),
"fix_type": fields.get(1).unwrap_or(&""),
"pdop": fields.get(14).unwrap_or(&""),
"hdop": fields.get(15).unwrap_or(&""),
"vdop": fields.get(16).unwrap_or(&"")
});
}
"GPGSV" | "BDGSV" => {
result[sentence_type] = json!({
"num_of_messages": fields.get(0).unwrap_or(&""),
"message_number": fields.get(1).unwrap_or(&""),
"satellites_in_view": fields.get(2).unwrap_or(&"")
});
}
"GNRMC" => {
result[sentence_type] = json!({
"time": fields.get(0).unwrap_or(&""),
"status": fields.get(1).unwrap_or(&""),
"latitude": fields.get(2).unwrap_or(&""),
"longitude": fields.get(4).unwrap_or(&""),
"speed": fields.get(6).unwrap_or(&""),
"date": fields.get(8).unwrap_or(&""),
"variation": fields.get(9).unwrap_or(&"")
});
}
"GNVTG" => {
result[sentence_type] = json!({
"track_degrees_true": fields.get(0).unwrap_or(&""),
"track_degrees_magnetic": fields.get(2).unwrap_or(&""),
"speed_knots": fields.get(4).unwrap_or(&""),
"speed_kmph": fields.get(6).unwrap_or(&"")
});
}
"GNZDA" => {
result[sentence_type] = json!({
"time": fields.get(0).unwrap_or(&""),
"day": fields.get(1).unwrap_or(&""),
"month": fields.get(2).unwrap_or(&""),
"year": fields.get(3).unwrap_or(&""),
"local_zone_hours": fields.get(4).unwrap_or(&""),
"local_zone_minutes": fields.get(5).unwrap_or(&"")
});
}
"GPTXT" => {
result[sentence_type] = json!({
"text": rest
});
}
_ => {}
}
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fn_read_complete_gps_message() {
let mut port = Box::new(serialport::new("COM3", 9600).open().unwrap());
let result = read_complete_gps_message(&mut port).unwrap();
assert!(result.is_some());
}
}