1use crate::error::{Error, ErrorSource};
2use arrayvec::{ArrayString, ArrayVec};
3use core::{
4 cell::RefCell,
5 mem::{size_of, MaybeUninit},
6 pin::Pin,
7 sync::atomic::{AtomicU32, Ordering},
8 task::{Context, Poll},
9};
10use critical_section::Mutex;
11use futures::{task::AtomicWaker, Stream};
12use num_enum::{FromPrimitive, IntoPrimitive, TryFromPrimitive};
13
14const MAX_NMEA_BURST_SIZE: usize = 5;
15
16static GNSS_WAKER: AtomicWaker = AtomicWaker::new();
17static GNSS_NOTICED_EVENTS: AtomicU32 = AtomicU32::new(0);
18static GNSS_NMEA_STRINGS: Mutex<RefCell<ArrayVec<Result<GnssData, Error>, MAX_NMEA_BURST_SIZE>>> =
19 Mutex::new(RefCell::new(ArrayVec::new_const()));
20
21unsafe extern "C" fn gnss_callback(event: i32) {
22 let event_type = GnssEventType::from(event as u32);
23
24 #[cfg(feature = "defmt")]
25 defmt::trace!("Gnss -> {}", event_type);
26
27 if matches!(event_type, GnssEventType::Nmea) {
28 critical_section::with(|cs| {
29 GNSS_NMEA_STRINGS
30 .borrow_ref_mut(cs)
31 .try_push(GnssData::read_from_modem(GnssDataType::Nmea))
32 .ok()
33 });
34 }
35
36 GNSS_NOTICED_EVENTS.fetch_or(1 << event as u32, Ordering::SeqCst);
37
38 GNSS_WAKER.wake();
39}
40
41pub struct Gnss {}
45
46impl Gnss {
47 pub async fn new() -> Result<Self, Error> {
49 if unsafe { !nrfxlib_sys::nrf_modem_is_initialized() } {
50 return Err(Error::ModemNotInitialized);
51 }
52
53 crate::MODEM_RUNTIME_STATE.activate_gps().await?;
54
55 unsafe {
56 nrfxlib_sys::nrf_modem_gnss_event_handler_set(Some(gnss_callback));
57 }
58
59 Ok(Gnss {})
60 }
61
62 pub fn start_single_fix(
69 mut self,
70 config: GnssConfig,
71 timeout_seconds: u16,
72 ) -> Result<GnssStream, Error> {
73 #[cfg(feature = "defmt")]
74 defmt::trace!("Setting single fix");
75
76 unsafe {
77 nrfxlib_sys::nrf_modem_gnss_fix_interval_set(0).into_result()?;
78 nrfxlib_sys::nrf_modem_gnss_fix_retry_set(timeout_seconds).into_result()?;
79 }
80
81 #[cfg(feature = "defmt")]
82 defmt::trace!("Apply config");
83
84 self.apply_config(config)?;
85
86 #[cfg(feature = "defmt")]
87 defmt::debug!("Starting gnss");
88
89 let gnss_stream = GnssStream::new(true, self);
90 unsafe {
91 nrfxlib_sys::nrf_modem_gnss_start();
92 }
93
94 Ok(gnss_stream)
95 }
96
97 pub fn start_continuous_fix(mut self, config: GnssConfig) -> Result<GnssStream, Error> {
98 #[cfg(feature = "defmt")]
99 defmt::trace!("Setting single fix");
100
101 unsafe {
102 nrfxlib_sys::nrf_modem_gnss_fix_interval_set(1)
103 .into_result()
104 .unwrap();
105 }
106
107 #[cfg(feature = "defmt")]
108 defmt::trace!("Apply config");
109
110 self.apply_config(config)?;
111
112 #[cfg(feature = "defmt")]
113 defmt::debug!("Starting gnss");
114
115 let gnss_stream = GnssStream::new(false, self);
116 unsafe {
117 nrfxlib_sys::nrf_modem_gnss_start();
118 }
119
120 Ok(gnss_stream)
121 }
122
123 pub fn start_periodic_fix(
124 mut self,
125 config: GnssConfig,
126 period_seconds: u16,
127 ) -> Result<GnssStream, Error> {
128 #[cfg(feature = "defmt")]
129 defmt::trace!("Setting single fix");
130
131 unsafe {
132 nrfxlib_sys::nrf_modem_gnss_fix_interval_set(period_seconds.max(10))
133 .into_result()
134 .unwrap();
135 }
136
137 #[cfg(feature = "defmt")]
138 defmt::trace!("Apply config");
139
140 self.apply_config(config)?;
141
142 #[cfg(feature = "defmt")]
143 defmt::debug!("Starting gnss");
144
145 let gnss_stream = GnssStream::new(false, self);
146 unsafe {
147 nrfxlib_sys::nrf_modem_gnss_start();
148 }
149
150 Ok(gnss_stream)
151 }
152
153 fn apply_config(&mut self, config: GnssConfig) -> Result<(), Error> {
154 unsafe {
155 nrfxlib_sys::nrf_modem_gnss_elevation_threshold_set(config.elevation_threshold_angle)
156 .into_result()?;
157 nrfxlib_sys::nrf_modem_gnss_use_case_set(config.use_case.into()).into_result()?;
158 nrfxlib_sys::nrf_modem_gnss_nmea_mask_set(config.nmea_mask.into()).into_result()?;
159 nrfxlib_sys::nrf_modem_gnss_power_mode_set(u32::from(config.power_mode) as _)
160 .into_result()?;
161 nrfxlib_sys::nrf_modem_gnss_timing_source_set(u32::from(config.timing_source) as _)
162 .into_result()?;
163 }
164 Ok(())
165 }
166
167 pub async fn deactivate(self) -> Result<(), Error> {
168 core::mem::forget(self);
169 let result = crate::MODEM_RUNTIME_STATE.deactivate_gps().await;
170
171 if result.is_err() {
172 crate::MODEM_RUNTIME_STATE.set_error_active();
173 }
174
175 result
176 }
177}
178
179impl Drop for Gnss {
180 fn drop(&mut self) {
181 #[cfg(feature = "defmt")]
182 defmt::warn!(
183 "Turning off GNSS synchronously. Use async function `deactivate` to avoid blocking and to get more guarantees that the modem is actually shut off."
184 );
185
186 if let Err(_e) = crate::MODEM_RUNTIME_STATE.deactivate_gps_blocking() {
187 #[cfg(feature = "defmt")]
188 defmt::error!("Could not turn off the gnss: {}", _e);
189 crate::MODEM_RUNTIME_STATE.set_error_active();
190 }
191 }
192}
193
194#[cfg_attr(feature = "defmt", derive(defmt::Format))]
195pub struct NmeaMask {
196 pub gga: bool,
198 pub gll: bool,
200 pub gsa: bool,
202 pub gsv: bool,
204 pub rmc: bool,
206}
207
208impl Default for NmeaMask {
209 fn default() -> Self {
210 Self {
211 gga: true,
212 gll: true,
213 gsa: true,
214 gsv: true,
215 rmc: true,
216 }
217 }
218}
219
220impl From<NmeaMask> for u16 {
221 fn from(mask: NmeaMask) -> Self {
222 (mask.gga as u16 * nrfxlib_sys::NRF_MODEM_GNSS_NMEA_GGA_MASK as u16)
223 | (mask.gll as u16 * nrfxlib_sys::NRF_MODEM_GNSS_NMEA_GLL_MASK as u16)
224 | (mask.gsa as u16 * nrfxlib_sys::NRF_MODEM_GNSS_NMEA_GSA_MASK as u16)
225 | (mask.gsv as u16 * nrfxlib_sys::NRF_MODEM_GNSS_NMEA_GSV_MASK as u16)
226 | (mask.rmc as u16 * nrfxlib_sys::NRF_MODEM_GNSS_NMEA_RMC_MASK as u16)
227 }
228}
229
230#[derive(Copy, Clone, IntoPrimitive, FromPrimitive, Debug, Default)]
231#[cfg_attr(feature = "defmt", derive(defmt::Format))]
232#[repr(u32)]
233enum GnssEventType {
234 #[default]
235 None = 0,
236 Pvt = nrfxlib_sys::NRF_MODEM_GNSS_EVT_PVT,
238 GnssFix = nrfxlib_sys::NRF_MODEM_GNSS_EVT_FIX,
240 Nmea = nrfxlib_sys::NRF_MODEM_GNSS_EVT_NMEA,
242 AgpsRequest = nrfxlib_sys::NRF_MODEM_GNSS_EVT_AGNSS_REQ,
244 BlockedByLte = nrfxlib_sys::NRF_MODEM_GNSS_EVT_BLOCKED,
246 UnblockedByLte = nrfxlib_sys::NRF_MODEM_GNSS_EVT_UNBLOCKED,
248 PeriodicWakeup = nrfxlib_sys::NRF_MODEM_GNSS_EVT_PERIODIC_WAKEUP,
252 RetryTimeoutReached = nrfxlib_sys::NRF_MODEM_GNSS_EVT_SLEEP_AFTER_TIMEOUT,
254 SleepAfterFix = nrfxlib_sys::NRF_MODEM_GNSS_EVT_SLEEP_AFTER_FIX,
256 ReferenceAltitudeExpired = nrfxlib_sys::NRF_MODEM_GNSS_EVT_REF_ALT_EXPIRED,
258}
259
260impl GnssEventType {
261 pub fn get_from_bit_packed(container: u32) -> Self {
262 let variants = [
263 Self::ReferenceAltitudeExpired,
264 Self::SleepAfterFix,
265 Self::RetryTimeoutReached,
266 Self::PeriodicWakeup,
267 Self::UnblockedByLte,
268 Self::BlockedByLte,
269 Self::AgpsRequest,
270 Self::Nmea,
271 Self::GnssFix,
272 Self::Pvt,
273 ];
274
275 for variant in variants {
276 if container & (1 << variant as u32) != 0 {
277 return variant;
278 }
279 }
280
281 Self::None
282 }
283}
284
285#[cfg_attr(feature = "defmt", derive(defmt::Format))]
286pub struct GnssConfig {
287 pub elevation_threshold_angle: u8,
293 pub use_case: GnssUsecase,
294 pub nmea_mask: NmeaMask,
295 pub timing_source: GnssTimingSource,
296 pub power_mode: GnssPowerSaveMode,
297}
298
299impl Default for GnssConfig {
300 fn default() -> Self {
301 Self {
302 elevation_threshold_angle: 5,
303 use_case: Default::default(),
304 nmea_mask: Default::default(),
305 timing_source: Default::default(),
306 power_mode: Default::default(),
307 }
308 }
309}
310
311#[cfg_attr(feature = "defmt", derive(defmt::Format))]
312#[derive(Debug, Clone, Default)]
313pub struct GnssUsecase {
314 pub low_accuracy: bool,
320 pub scheduled_downloads_disable: bool,
331}
332
333impl From<GnssUsecase> for u8 {
334 fn from(usecase: GnssUsecase) -> Self {
335 nrfxlib_sys::NRF_MODEM_GNSS_USE_CASE_MULTIPLE_HOT_START as u8
336 | (usecase.low_accuracy as u8 * nrfxlib_sys::NRF_MODEM_GNSS_USE_CASE_LOW_ACCURACY as u8)
337 | (usecase.scheduled_downloads_disable as u8
338 * nrfxlib_sys::NRF_MODEM_GNSS_USE_CASE_SCHED_DOWNLOAD_DISABLE as u8)
339 }
340}
341
342#[derive(Debug, Clone, Default, IntoPrimitive, TryFromPrimitive)]
350#[cfg_attr(feature = "defmt", derive(defmt::Format))]
351#[repr(u32)]
352pub enum GnssTimingSource {
353 #[default]
354 Rtc = nrfxlib_sys::NRF_MODEM_GNSS_TIMING_SOURCE_RTC,
355 Tcxo = nrfxlib_sys::NRF_MODEM_GNSS_TIMING_SOURCE_TCXO,
356}
357
358#[derive(Debug, Clone, Default, IntoPrimitive, TryFromPrimitive)]
365#[cfg_attr(feature = "defmt", derive(defmt::Format))]
366#[repr(u32)]
367pub enum GnssPowerSaveMode {
368 #[default]
369 Disabled = nrfxlib_sys::NRF_MODEM_GNSS_PSM_DISABLED,
370 DutyCyclingPerformance = nrfxlib_sys::NRF_MODEM_GNSS_PSM_DUTY_CYCLING_PERFORMANCE,
371 DutyCycling = nrfxlib_sys::NRF_MODEM_GNSS_PSM_DUTY_CYCLING_POWER,
372}
373
374#[derive(Debug, Clone, IntoPrimitive, TryFromPrimitive)]
375#[cfg_attr(feature = "defmt", derive(defmt::Format))]
376#[repr(u32)]
377enum GnssDataType {
378 PositionVelocityTime = nrfxlib_sys::NRF_MODEM_GNSS_DATA_PVT,
379 Nmea = nrfxlib_sys::NRF_MODEM_GNSS_DATA_NMEA,
380 Agps = nrfxlib_sys::NRF_MODEM_GNSS_DATA_AGNSS_REQ,
381}
382
383#[derive(Debug, Clone)]
385pub enum GnssData {
386 PositionVelocityTime(nrfxlib_sys::nrf_modem_gnss_pvt_data_frame),
388 Nmea(ArrayString<83>),
390 Agps(nrfxlib_sys::nrf_modem_gnss_agnss_data_frame),
392}
393
394impl GnssData {
395 fn read_from_modem(data_type: GnssDataType) -> Result<Self, Error> {
396 match data_type {
397 GnssDataType::PositionVelocityTime => {
398 let mut data = MaybeUninit::uninit();
399
400 unsafe {
401 nrfxlib_sys::nrf_modem_gnss_read(
402 data.as_mut_ptr() as *mut _,
403 size_of::<nrfxlib_sys::nrf_modem_gnss_pvt_data_frame>() as i32,
404 data_type as u32 as _,
405 )
406 .into_result()?;
407 Ok(GnssData::PositionVelocityTime(data.assume_init()))
408 }
409 }
410 GnssDataType::Nmea => {
411 let mut data: MaybeUninit<nrfxlib_sys::nrf_modem_gnss_nmea_data_frame> =
412 MaybeUninit::uninit();
413
414 unsafe {
415 nrfxlib_sys::nrf_modem_gnss_read(
416 data.as_mut_ptr() as *mut _,
417 size_of::<nrfxlib_sys::nrf_modem_gnss_nmea_data_frame>() as i32,
418 data_type as u32 as _,
419 )
420 .into_result()?;
421
422 #[allow(clippy::useless_transmute, clippy::missing_transmute_annotations)]
424 let data = core::mem::transmute::<_, [u8; 83]>(data.assume_init().nmea_str); let mut string_data = ArrayString::from_byte_string(&data)?;
426 string_data.truncate(
427 string_data
428 .as_bytes()
429 .iter()
430 .take_while(|b| **b != 0)
431 .count(),
432 );
433 Ok(GnssData::Nmea(string_data))
434 }
435 }
436 GnssDataType::Agps => {
437 let mut data = MaybeUninit::uninit();
438
439 unsafe {
440 nrfxlib_sys::nrf_modem_gnss_read(
441 data.as_mut_ptr() as *mut _,
442 size_of::<nrfxlib_sys::nrf_modem_gnss_agnss_data_frame>() as i32,
443 data_type as u32 as _,
444 )
445 .into_result()?;
446 Ok(GnssData::Agps(data.assume_init()))
447 }
448 }
449 }
450 }
451}
452
453pub struct GnssStream {
457 single_fix: bool,
458 done: bool,
459 gnss: Option<Gnss>,
460}
461
462impl GnssStream {
463 fn new(single_fix: bool, gnss: Gnss) -> Self {
464 GNSS_NOTICED_EVENTS.store(0, Ordering::SeqCst);
465 Self {
466 single_fix,
467 done: false,
468 gnss: Some(gnss),
469 }
470 }
471
472 pub async fn deactivate(self) -> Result<(), Error> {
473 self.free().deactivate().await
474 }
475
476 pub fn free(mut self) -> Gnss {
478 self.gnss.take().unwrap()
479 }
480}
481
482impl Stream for GnssStream {
483 type Item = Result<GnssData, Error>;
484
485 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
486 if self.done {
487 #[cfg(feature = "defmt")]
488 defmt::trace!("Gnss is done");
489
490 return Poll::Ready(None);
491 }
492
493 let event_bits = GNSS_NOTICED_EVENTS.load(Ordering::SeqCst);
494 let event = GnssEventType::get_from_bit_packed(event_bits);
495
496 let mut left_over_nmea_strings = false;
497
498 let data = match event {
499 GnssEventType::Pvt => Some(GnssData::read_from_modem(
500 GnssDataType::PositionVelocityTime,
501 )),
502 GnssEventType::GnssFix if self.single_fix => {
503 self.get_mut().done = true;
504 Some(GnssData::read_from_modem(
505 GnssDataType::PositionVelocityTime,
506 ))
507 }
508 GnssEventType::GnssFix => Some(GnssData::read_from_modem(
509 GnssDataType::PositionVelocityTime,
510 )),
511 GnssEventType::Nmea => critical_section::with(|cs| {
512 let mut strings = GNSS_NMEA_STRINGS.borrow_ref_mut(cs);
513 left_over_nmea_strings = strings.len() > 1;
514 strings.pop_at(0)
515 }),
516 GnssEventType::AgpsRequest => Some(GnssData::read_from_modem(GnssDataType::Agps)),
517 GnssEventType::RetryTimeoutReached | GnssEventType::SleepAfterFix
518 if self.single_fix =>
519 {
520 self.get_mut().done = true;
521 return Poll::Ready(None);
522 }
523 _ => None,
524 };
525
526 let left_over_event_bits = if !left_over_nmea_strings {
527 GNSS_NOTICED_EVENTS.fetch_and(!(1 << event as u32), Ordering::SeqCst) != 0
528 } else {
529 true
530 };
531
532 if left_over_event_bits {
533 cx.waker().wake_by_ref();
534 } else {
535 GNSS_WAKER.register(cx.waker());
536 }
537
538 match data {
539 Some(data) => Poll::Ready(Some(data)),
540 None => Poll::Pending,
541 }
542 }
543}
544
545impl Drop for GnssStream {
546 fn drop(&mut self) {
547 unsafe {
548 #[cfg(feature = "defmt")]
549 defmt::debug!("Stopping gnss");
550
551 nrfxlib_sys::nrf_modem_gnss_stop();
552 }
553 }
554}