sms_types/
gnss.rs

1//! GNSS position report types.
2
3use serde::{Deserialize, Serialize};
4
5/// GNSS (Global Navigation Satellite System) fix status.
6#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
7pub enum FixStatus {
8    /// GNSS fix status is unknown.
9    Unknown,
10
11    /// No GNSS fix.
12    NotFix,
13
14    /// 2D GNSS fix (latitude and longitude only).
15    Fix2D,
16
17    /// 3D GNSS fix (latitude, longitude, and altitude).
18    Fix3D,
19}
20impl TryFrom<&str> for FixStatus {
21    type Error = String;
22
23    fn try_from(value: &str) -> Result<Self, Self::Error> {
24        match value.trim() {
25            "Location Unknown" | "Unknown" => Ok(FixStatus::Unknown),
26            "Location Not Fix" | "Not Fix" => Ok(FixStatus::NotFix),
27            "Location 2D Fix" | "2D Fix" => Ok(FixStatus::Fix2D),
28            "Location 3D Fix" | "3D Fix" => Ok(FixStatus::Fix3D),
29            _ => Err(format!("Invalid GNSS fix status: '{value}'")),
30        }
31    }
32}
33impl From<u8> for FixStatus {
34    fn from(value: u8) -> Self {
35        match value {
36            0 => FixStatus::NotFix,
37            1 => FixStatus::Fix2D,
38            2 => FixStatus::Fix3D,
39            _ => FixStatus::Unknown,
40        }
41    }
42}
43
44/// Represents a GNSS position report with optional fields for satellite info.
45#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
46pub struct PositionReport {
47    /// Indicates whether the GNSS receiver is currently running.
48    pub run_status: bool,
49
50    /// Whether a valid fix has been obtained.
51    pub fix_status: bool,
52
53    /// UTC time of the position report in ISO 8601 format.
54    pub utc_time: String,
55
56    /// Latitude in decimal degrees.
57    pub latitude: Option<f64>,
58
59    /// Longitude in decimal degrees.
60    pub longitude: Option<f64>,
61
62    /// Mean sea level altitude in meters.
63    pub msl_altitude: Option<f64>,
64
65    /// Ground speed in meters per second.
66    pub ground_speed: Option<f32>,
67
68    /// Ground course in degrees.
69    pub ground_course: Option<f32>,
70
71    /// Fix mode indicating 2D/3D fix or unknown.
72    pub fix_mode: FixStatus,
73
74    /// Horizontal Dilution of Precision.
75    pub hdop: Option<f32>,
76
77    /// Position Dilution of Precision.
78    pub pdop: Option<f32>,
79
80    /// Vertical Dilution of Precision.
81    pub vdop: Option<f32>,
82
83    /// Number of GPS satellites in view.
84    pub gps_in_view: Option<u8>,
85
86    /// Number of GNSS satellites used in the fix.
87    pub gnss_used: Option<u8>,
88
89    /// Number of GLONASS satellites in view.
90    pub glonass_in_view: Option<u8>,
91}
92impl TryFrom<Vec<&str>> for PositionReport {
93    type Error = String;
94
95    fn try_from(fields: Vec<&str>) -> Result<Self, Self::Error> {
96        if fields.len() < 15 {
97            return Err(format!(
98                "Insufficient GNSS data fields got {}",
99                fields.len()
100            ));
101        }
102
103        // Based on: https://simcom.ee/documents/SIM868/SIM868_GNSS_Application%20Note_V1.00.pdf (2.3)
104        Ok(Self {
105            run_status: fields[0] == "1",
106            fix_status: fields[1] == "1",
107            utc_time: fields[2].to_string(),
108            latitude: fields[3].parse().ok(),
109            longitude: fields[4].parse().ok(),
110            msl_altitude: fields[5].parse().ok(),
111            ground_speed: fields[6].parse().ok(),
112            ground_course: fields[7].parse().ok(),
113            fix_mode: FixStatus::from(fields[8].parse::<u8>().unwrap_or(0)),
114            // Reserved1
115            hdop: fields[10].parse().ok(),
116            pdop: fields[11].parse().ok(),
117            vdop: fields[12].parse().ok(),
118            // Reserved2
119            gps_in_view: fields[14].parse().ok(),
120            gnss_used: fields[15].parse().ok(),
121            glonass_in_view: fields[16].parse().ok(),
122        })
123    }
124}
125impl std::fmt::Display for PositionReport {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        fn convert_opt<T: std::fmt::Display>(opt: Option<&T>) -> String {
128            match opt {
129                Some(value) => value.to_string(),
130                None => "None".to_string(),
131            }
132        }
133
134        write!(
135            f,
136            "Lat: {}, Lon: {}, Alt: {}, Speed: {}, Course: {}",
137            convert_opt(self.latitude.as_ref()),
138            convert_opt(self.longitude.as_ref()),
139            convert_opt(self.msl_altitude.as_ref()),
140            convert_opt(self.ground_speed.as_ref()),
141            convert_opt(self.ground_course.as_ref())
142        )
143    }
144}