1use serde::Serialize;
4
5use crate::nmea::{Gga, Rmc, Sentence};
6use crate::ubx::NavPvt;
7
8pub const STALE_NS: u64 = 3_000_000_000;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Default)]
14pub enum Fix {
15 #[default]
17 #[serde(rename = "none")]
18 None,
19 #[serde(rename = "2d")]
21 TwoD,
22 #[serde(rename = "3d")]
24 ThreeD,
25 #[serde(rename = "dgps")]
27 Dgps,
28 #[serde(rename = "rtk_float")]
30 RtkFloat,
31 #[serde(rename = "rtk_fixed")]
33 RtkFixed,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Default)]
38#[serde(rename_all = "snake_case")]
39pub enum Rtk {
40 #[default]
42 None,
43 Float,
45 Fixed,
47}
48
49impl Fix {
50 pub fn rtk(self) -> Rtk {
52 match self {
53 Fix::RtkFloat => Rtk::Float,
54 Fix::RtkFixed => Rtk::Fixed,
55 _ => Rtk::None,
56 }
57 }
58}
59
60pub fn fix_from_pvt(p: &NavPvt) -> Fix {
64 if !p.gnss_fix_ok {
65 return Fix::None;
66 }
67 let base = match p.fix_type {
68 2 => Fix::TwoD,
69 3 | 4 => Fix::ThreeD,
70 _ => return Fix::None,
71 };
72 match p.carr_soln {
73 2 => Fix::RtkFixed,
74 1 => Fix::RtkFloat,
75 _ if p.diff_soln => Fix::Dgps,
76 _ => base,
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Default)]
82pub struct Solution {
83 pub fix: Fix,
85 pub sats: u8,
87 pub hdop: Option<f64>,
89 pub lat_deg: Option<f64>,
91 pub lon_deg: Option<f64>,
93 pub h_ellipsoid_m: Option<f64>,
95 pub h_msl_m: Option<f64>,
97}
98
99#[derive(Debug, Clone, Default)]
101pub struct ReceiverState {
102 pub model: Option<String>,
104 pvt: Option<(u64, NavPvt)>,
105 dop: Option<(u64, f64)>,
106 gga: Option<(u64, Gga)>,
107 rmc: Option<(u64, Rmc)>,
108}
109
110fn fresh<T>(v: &Option<(u64, T)>, now: u64) -> Option<&T> {
111 v.as_ref()
112 .filter(|(t, _)| now.saturating_sub(*t) <= STALE_NS)
113 .map(|(_, x)| x)
114}
115
116impl ReceiverState {
117 pub fn new(model: Option<String>) -> Self {
119 Self {
120 model,
121 ..Self::default()
122 }
123 }
124
125 pub fn on_pvt(&mut self, now: u64, pvt: NavPvt) {
127 self.pvt = Some((now, pvt));
128 }
129
130 pub fn on_dop(&mut self, now: u64, hdop: f64) {
132 self.dop = Some((now, hdop));
133 }
134
135 pub fn on_nmea(&mut self, now: u64, s: Sentence) {
137 match s {
138 Sentence::Gga(g) => self.gga = Some((now, g)),
139 Sentence::Rmc(r) => self.rmc = Some((now, r)),
140 }
141 }
142
143 pub fn solution(&self, now: u64) -> Solution {
145 if let Some(p) = fresh(&self.pvt, now) {
146 let fix = fix_from_pvt(p);
147 let has = fix != Fix::None;
148 let hdop = fresh(&self.dop, now).copied();
150 return Solution {
151 fix,
152 sats: p.num_sv,
153 hdop,
154 lat_deg: has.then_some(p.lat_deg),
155 lon_deg: has.then_some(p.lon_deg),
156 h_ellipsoid_m: has.then_some(p.h_ellipsoid_m),
157 h_msl_m: has.then_some(p.h_msl_m),
158 };
159 }
160 if let Some(g) = fresh(&self.gga, now) {
161 return Solution {
162 fix: g.fix,
163 sats: g.sats,
164 hdop: g.hdop,
165 lat_deg: g.lat_deg,
166 lon_deg: g.lon_deg,
167 h_ellipsoid_m: g.h_ellipsoid_m,
168 h_msl_m: g.h_msl_m,
169 };
170 }
171 if let Some(r) = fresh(&self.rmc, now) {
172 return Solution {
173 fix: r.fix,
174 lat_deg: r.lat_deg,
175 lon_deg: r.lon_deg,
176 ..Solution::default()
177 };
178 }
179 Solution::default()
180 }
181}