diff --git a/docs/content/software/architecture.md b/docs/content/software/architecture.md
index 5f91ff2d6..dfba6f6f0 100644
--- a/docs/content/software/architecture.md
+++ b/docs/content/software/architecture.md
@@ -291,7 +291,7 @@ The device also wakes for input, sensor data, and the watchdog guard.
arm the next wake, sleep again
- idle (nothing animating · GPS asleep): just the ~10 s watchdog-feed guard tick
+ idle (no animation · GNSS stopped): ~10 s watchdog-feed guard
The device sleeps between events. A hardware timer generates the display COM signal without CPU work.
diff --git a/firmware/obc-fw-nrf54l/README.md b/firmware/obc-fw-nrf54l/README.md
index dd18e14ce..85c1243e2 100644
--- a/firmware/obc-fw-nrf54l/README.md
+++ b/firmware/obc-fw-nrf54l/README.md
@@ -182,6 +182,26 @@ nothing to wire on the GPS-21834.
the receiver's RTC + ephemeris across a power-off, turning every cold ~30 s fix into a hot/warm fix
in seconds — the biggest UX win for a device switched off at each stop.
+If GPS does not answer the first startup probe, the sensor task retries at the normal DDC poll
+interval during the existing 150-second boot acquisition window. On the first response, it sends
+the GPS configuration before it reads fixes. The startup sensor-warning bundle is published then,
+or at the deadline if GPS remains absent. This can delay the altimeter and compass warnings too;
+their original probe results are retained. A successful first GPS probe publishes immediately.
+This is a one-time startup result, not a report of live sensor availability. Intentional idle
+GNSS stop still produces no DDC polling.
+
+The Qwiic prototype has no GPS wake or reset wire. Idle sends `UBX-CFG-RST` controlled GNSS stop;
+tracking resumes with controlled GNSS start. Startup also sends START after the receiver answers,
+so an MCU reset can recover a receiver left stopped by the previous session. The driver sends
+configuration after START. These operations retain receiver configuration and navigation data.
+They do not enter backup sleep, and their idle current has not been measured. The receiver does
+not acknowledge CFG-RST; logs report a command write, while new NAV-PVT epochs confirm acquisition.
+The existing `power_saver` tracking mode is unchanged.
+
+An older image can leave the receiver in indefinite software standby. I²C traffic is not a
+supported wake source for that mode. If the receiver remains absent, remove its power before
+starting this image. See the [SAM-M10Q integration manual, sections 3.3 and 3.5.3.3](https://content.u-blox.com/sites/default/files/documents/SAM-M10Q_IntegrationManual_UBX-22020019.pdf).
+
## Build & flash
**One-time prerequisite (#617): flash the bootloader.** The app is linked at `0x8000` —
diff --git a/firmware/obc-fw-nrf54l/src/ride.rs b/firmware/obc-fw-nrf54l/src/ride.rs
index 762dc5121..004203cad 100644
--- a/firmware/obc-fw-nrf54l/src/ride.rs
+++ b/firmware/obc-fw-nrf54l/src/ride.rs
@@ -733,7 +733,7 @@ impl RideExec {
}
}
-/// The GPS power state the ride wants: deep-sleep when not tracking, full-power fixes while riding, or
+/// The GPS power state the ride wants: stopped GNSS when not tracking, full-power fixes while riding, or
/// the M10's low-power tracking when the `power_saver` toggle is on. Recomputed each frame in
/// [`run_app`] and pushed to the sensor task (via [`SensorControl::set_power`]) only on a change.
/// Real-sensor build only — the `synth` / `debug-uart` feeds have no power-managed receiver.
@@ -1112,9 +1112,8 @@ pub(crate) async fn run_app(
}
// ── Sensor presence → warning (issue #504), real-sensor build, once ──
- // The sensor task publishes its boot I²C probe result a moment after boot; map any chip that
- // didn't answer to a dismissable warning card. `try_take` yields once, so this fires a single
- // pass; an empty flag set is a no-op.
+ // The sensor task publishes once GPS responds or its startup deadline passes. Map chips
+ // absent at that point to a dismissable warning; this is not a live-availability stream.
#[cfg(all(not(feature = "debug-uart"), not(feature = "synth")))]
if let Some(p) = consumer.take_presence() {
let mut w = obc_app::WarningFlags::NONE;
diff --git a/firmware/obc-fw-nrf54l/src/sensors.rs b/firmware/obc-fw-nrf54l/src/sensors.rs
index d822a7758..c44dc40ac 100644
--- a/firmware/obc-fw-nrf54l/src/sensors.rs
+++ b/firmware/obc-fw-nrf54l/src/sensors.rs
@@ -24,7 +24,7 @@
//! heading is **never stored** — it only orients a heading-up *map while the rider is stopped*. So it
//! runs on its **own cadence, decoupled from the GPS fix**: ~5 Hz while stationary (lively as you
//! rotate the device by hand, independent of a slow / power-saving fix rate), and silent while moving
-//! (the GPS course is the heading then) or idle (the receiver is asleep). See [`sensor_task`].
+//! (the GPS course is the heading then) or idle (GNSS processing is stopped). See [`sensor_task`].
//!
//! ## Event-driven, with a robust fallback (the "no fix" story)
//! The task waits on the **TX-Ready edge** so it does **zero** bus work between fixes. But TX-Ready
@@ -36,12 +36,12 @@
//! simply pauses. Every stage logs over RTT (defmt) so acquisition is watchable live.
//!
//! ## Power management
-//! Continuous tracking is ~20 mA — left on while idle it would flatten the pack in days. So after one
-//! **boot fix** (which sets the clock + warms the ephemeris), the task follows the app's
-//! [`GpsPower`] request: **deep-sleep** (`RXM-PMREQ` backup, ~µA, zero bus traffic) whenever a ride
-//! isn't running, waking on a DDC poke for a fast *warm* fix when one starts; full-power fixes while
-//! riding, or the M10's on-chip **low-power** tracking when the `power_saver` toggle is on. The
-//! `RXM-PMREQ` / `CFG-PM` encodings live host-tested in [`obc_sensors::ubx`].
+//! After boot acquisition, the task follows the app's [`GpsPower`] request. When idle, it sends
+//! `CFG-RST` controlled GNSS stop and parks without DDC polling. Tracking resumes with controlled
+//! GNSS start, which works over I²C and retains receiver configuration and navigation data. This
+//! is not backup sleep; idle current has not been measured. While riding, the task requests full
+//! power or the M10's on-chip low-power tracking when `power_saver` is on. The command encodings
+//! live host-tested in [`obc_sensors::ubx`].
use defmt::{debug, error, info, warn};
use embassy_futures::select::{select, select4, Either, Either4};
@@ -80,7 +80,7 @@ const ACC_CAP: usize = 300;
/// Bound on the boot-fix acquisition: the task holds awake at most this long for the first fix —
/// which sets the clock + warms the ephemeris — before dropping into the power-managed steady state,
-/// so a boot under cover (no sky) still eventually deep-sleeps when idle.
+/// so a boot under cover (no sky) still eventually stops GNSS processing when idle.
const BOOT_ACQUIRE_TIMEOUT_S: u64 = 150;
/// Per-board **hard-iron offset** (µT) subtracted from each magnetometer axis before the heading.
@@ -140,9 +140,9 @@ struct FixState {
///
/// 1. **Boot acquisition** — hold awake until the first valid fix (which sets the clock + warms the
/// ephemeris) or [`BOOT_ACQUIRE_TIMEOUT_S`], **ignoring** the app's power request so an idle
-/// boot still gets one fix before it can deep-sleep.
-/// 2. **Steady state** — honour the app's [`GpsPower`] request: deep-sleep (`RXM-PMREQ` backup, zero
-/// bus traffic) when idle; full- (or `power_saver` low-) power fixes while riding. Each waking
+/// boot gets an acquisition window before GNSS can stop.
+/// 2. **Steady state** — honour the app's [`GpsPower`] request: stop GNSS processing with no DDC
+/// polling when idle; full- (or `power_saver` low-) power fixes while riding. Each waking
/// cycle waits for a TX-Ready edge / poll timeout / rate change / power change, then drains +
/// publishes through [`drain_and_publish`]. While **riding and stationary** it *also* ticks the
/// compass on its own [`COMPASS_INTERVAL_MS`] cadence (the heading isn't logged, so it's decoupled
@@ -158,7 +158,7 @@ pub async fn sensor_task(mut twim: Twim<'static>, mut txready: Input<'static>, l
// --- Boot probe: loud RTT so a wiring/power fault is obvious before anything else. ---
let baro_addr = probe_bmp581(&mut twim).await;
let icm_addr = probe_icm20948(&mut twim).await;
- let gps_ok = probe_m10(&mut twim).await;
+ let mut gps_ok = probe_m10(&mut twim).await;
if let Some(addr) = baro_addr {
configure_bmp581(&mut twim, addr).await;
@@ -167,19 +167,22 @@ pub async fn sensor_task(mut twim: Twim<'static>, mut txready: Input<'static>, l
configure_icm20948(&mut twim, addr).await;
}
if gps_ok {
+ set_gnss_running(&mut twim, true).await;
configure_m10(&mut twim, DEFAULT_INTERVAL_S).await;
} else {
- warn!("sensors: GPS not answering — the loop will keep polling so a late-powered module is picked up");
+ warn!("sensors: GPS not answering — retrying during boot acquisition");
}
// Whether the compass is live — the AK09916 magnetometer is read at AK_ADDR through the ICM's
// bypass, so only its *presence* (a successful ICM probe + config) matters at read time.
let compass_ok = icm_addr.is_some();
- // Surface the probe result on glass (issue #504): any chip that didn't answer becomes a
- // dismissable warning the ride loop raises. Published once — a missing module is a wiring/power
- // fault, not a transient. (A missing GPS *module* is distinct from "no fix yet".)
- link.dispatch_presence(SensorPresence { gps: gps_ok, altimeter: baro_addr.is_some(), compass: compass_ok });
+ // The warning bundle is published once: immediately when GPS responds, otherwise after the
+ // bounded startup window. A receiver still starting must not leave a stale missing-GPS warning.
+ let mut presence = SensorPresence { gps: gps_ok, altimeter: baro_addr.is_some(), compass: compass_ok };
+ if gps_ok {
+ link.dispatch_presence(presence);
+ }
let mut acc = [0u8; ACC_CAP];
let mut acc_len = 0usize;
@@ -188,12 +191,28 @@ pub async fn sensor_task(mut twim: Twim<'static>, mut txready: Input<'static>, l
// --- Phase 1: boot acquisition. Hold awake until the first valid fix or a bounded timeout,
// ignoring the app's power request — so the clock gets set and the ephemeris warms even on an idle
- // boot, before the steady state below is allowed to deep-sleep. ---
+ // boot, before the steady state below can stop GNSS processing. ---
info!("sensors: boot acquisition — holding awake for the first fix (≤ {=u64}s)", BOOT_ACQUIRE_TIMEOUT_S);
let boot_deadline = Instant::now() + Duration::from_secs(BOOT_ACQUIRE_TIMEOUT_S);
loop {
- wait_data_event(&mut txready, interval_s, &mut st).await;
- if drain_and_publish(&mut twim, &mut acc, &mut acc_len, baro_addr, &mut st, link).await {
+ if gps_ok {
+ wait_data_event(&mut txready, interval_s, &mut st).await;
+ } else {
+ // An absent receiver cannot supply a useful TX-Ready edge. Keep its probes at the
+ // normal poll cadence even if that unconnected input is noisy.
+ Timer::at((Instant::now() + poll_deadline(interval_s)).min(boot_deadline)).await;
+ if Instant::now() >= boot_deadline {
+ break;
+ }
+ gps_ok = probe_m10(&mut twim).await;
+ if gps_ok {
+ set_gnss_running(&mut twim, true).await;
+ configure_m10(&mut twim, interval_s).await;
+ presence.gps = true;
+ link.dispatch_presence(presence);
+ }
+ }
+ if gps_ok && drain_and_publish(&mut twim, &mut acc, &mut acc_len, baro_addr, &mut st, link).await {
break; // got the boot fix
}
if Instant::now() >= boot_deadline {
@@ -204,40 +223,43 @@ pub async fn sensor_task(mut twim: Twim<'static>, mut txready: Input<'static>, l
break;
}
}
+ if !gps_ok {
+ error!("sensors: GPS did not answer during boot acquisition — check wiring / power");
+ link.dispatch_presence(presence);
+ }
- // --- Phase 2: power-managed steady state. Honour the app's requested GpsPower — deep-sleep when
+ // --- Phase 2: power-managed steady state. Honour the app's requested GpsPower — stop GNSS when
// idle, full / low-power fixes while riding — and keep streaming fixes. ---
let mut power = GpsPower::Active;
- let mut asleep = false; // so backup is commanded once on entry, not re-sent each parked iteration
- // Absolute deadline for the next DDC poll fallback. Absolute (not a fresh `Timer::after` each
- // iteration) so the stationary compass ticks below don't keep restarting it — which would starve
- // a TX-Ready-less receiver's stationary fixes. Reset only after an actual fix cycle / rate change.
+ // Send STOP once per idle entry, even if its write fails.
+ let mut parked = false;
+ // Absolute deadline: compass ticks must not restart it and starve DDC fallback polling.
let mut next_poll = Instant::now() + poll_deadline(interval_s);
loop {
if power == GpsPower::Sleep {
- if !asleep {
- enter_backup(&mut twim).await;
- asleep = true;
+ if !parked {
+ set_gnss_running(&mut twim, false).await;
+ parked = true;
}
- // Asleep: zero DDC traffic. Wait only for a power change (or a rate change to apply on
- // the next wake — `CFG-RATE` can't take effect while the receiver is in backup).
+ // No DDC polling while idle. Apply rate changes when tracking resumes.
match select(link.wait_power(), link.wait_rate()).await {
Either::First(p) => power = p,
Either::Second(s) => {
interval_s = s.max(1);
- continue; // still asleep — re-park; the new rate applies on the next wake
+ continue; // still idle — re-park; apply the new rate on resume
}
}
if power == GpsPower::Sleep {
continue; // a redundant Sleep request — stay parked
}
- // Woken → poke the receiver out of backup and re-assert config at the current rate/mode.
- asleep = false;
- wake_receiver(&mut twim).await;
+ parked = false;
+ set_gnss_running(&mut twim, true).await;
+ // Configuration reads use a separate buffer; discard any partial pre-stop frame.
+ acc_len = 0;
configure_m10(&mut twim, interval_s).await;
set_power_mode(&mut twim, power).await;
- st.had_fix = false; // re-acquiring from a warm start
- st.stationary = false; // motion state unknown until the first warm fix → compass off
+ st.had_fix = false; // acquiring again after GNSS start
+ st.stationary = false; // motion state unknown until the first new fix → compass off
next_poll = Instant::now() + poll_deadline(interval_s);
continue;
}
@@ -273,7 +295,7 @@ pub async fn sensor_task(mut twim: Twim<'static>, mut txready: Input<'static>, l
if p != power {
power = p;
if power == GpsPower::Sleep {
- info!("sensors: tracking stopped → GPS will deep-sleep");
+ info!("sensors: tracking stopped → requesting GNSS stop");
} else {
info!("sensors: GPS power → {=str}", power_name(power));
set_power_mode(&mut twim, power).await;
@@ -361,8 +383,8 @@ async fn drain_and_publish(
// The key acquisition line — watch fixType climb 0→3 and hAcc fall as the receiver locks.
debug!(
- "NAV-PVT fix={=u8} sats={=u8} hAcc={=u32}mm pDOP={=u16} lat={=i32} lon={=i32}",
- pvt.fix_type, pvt.num_sv, pvt.hacc_mm, pvt.pdop, pvt.lat, pvt.lon
+ "NAV-PVT iTOW={=u32} fix={=u8} sats={=u8} hAcc={=u32}mm pDOP={=u16} lat={=i32} lon={=i32}",
+ pvt.itow, pvt.fix_type, pvt.num_sv, pvt.hacc_mm, pvt.pdop, pvt.lat, pvt.lon
);
// Publish the receiver's UTC time the moment it's valid + fully resolved — **before**
@@ -419,28 +441,17 @@ fn power_name(p: GpsPower) -> &'static str {
}
}
-/// Put the M10 into **backup** deep sleep — `RXM-PMREQ`, infinite duration. The
-/// receiver keeps its RTC + ephemeris on ~µA and wakes on the next DDC activity, so the restart is a
-/// fast *warm* fix. Best-effort: a failed write is logged, not fatal.
-async fn enter_backup(twim: &mut Twim<'static>) {
- let mut frame = [0u8; 24];
- let Some(n) = ubx::pmreq_backup(&mut frame) else { return };
+/// Send a controlled GNSS start/stop without clearing receiver configuration or navigation data.
+/// CFG-RST has no ACK. A successful write confirms only that the command was sent.
+async fn set_gnss_running(twim: &mut Twim<'static>, running: bool) {
+ let mut frame = [0u8; 12];
+ let Some(n) = ubx::cfg_gnss_running(&mut frame, running) else { return };
+ let action = if running { "start" } else { "stop" };
if twim.write(M10_ADDR, &frame[..n]).await.is_err() {
- warn!("sensors: RXM-PMREQ (sleep) write failed — GPS may keep tracking");
+ warn!("sensors: CFG-RST GNSS {=str} write failed", action);
} else {
- info!("sensors: GPS → deep sleep (RXM-PMREQ backup); zero bus traffic until tracking resumes");
- }
-}
-
-/// Wake the M10 from backup: any DDC activity wakes it, but the first transaction can be
-/// lost while it powers up, so poke the byte-count register a few times with a short settle.
-async fn wake_receiver(twim: &mut Twim<'static>) {
- for _ in 0..3 {
- let mut cnt = [0u8; 2];
- let _ = twim.write_read(M10_ADDR, &[DDC_COUNT_REG], &mut cnt).await;
- Timer::after_millis(20).await;
+ info!("sensors: CFG-RST GNSS {=str} sent", action);
}
- info!("sensors: GPS woken from backup");
}
/// Set the M10's tracking power mode: full power, or the on-chip low-power tracking when
@@ -479,14 +490,13 @@ async fn probe_bmp581(twim: &mut Twim<'static>) -> Option {
None
}
-/// Probe the SAM-M10Q by reading its DDC byte-count register; log whether it answers.
+/// Probe the SAM-M10Q by reading its DDC byte-count register. Absence is reported at the deadline.
async fn probe_m10(twim: &mut Twim<'static>) -> bool {
let mut cnt = [0u8; 2];
if twim.write_read(M10_ADDR, &[DDC_COUNT_REG], &mut cnt).await.is_ok() {
info!("SAM-M10Q alive @ {=u8:#04x} ({=u16} DDC bytes pending)", M10_ADDR, u16::from_be_bytes(cnt));
true
} else {
- error!("SAM-M10Q no ACK on DDC {=u8:#04x} — check Qwiic wiring / 3V3 / V_BCKP", M10_ADDR);
false
}
}
diff --git a/firmware/obc-platform/src/sensor_hub.rs b/firmware/obc-platform/src/sensor_hub.rs
index b1fcfcc3c..fef847f9d 100644
--- a/firmware/obc-platform/src/sensor_hub.rs
+++ b/firmware/obc-platform/src/sensor_hub.rs
@@ -56,7 +56,7 @@ use obc_ports::{
/// run on different executors / priorities on the board.
type Sig = Signal;
-/// Which sensors answered the boot I²C probe — the sensor task's probe results, carried to the app
+/// Which sensors answered during startup — the sensor task's results, carried to the app
/// so a missing module surfaces as a dismissable warning rather than only an RTT line. A missing
/// GPS is distinct from "no fix yet" (the receiver is there, just no sky): this is the *module*.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -70,9 +70,8 @@ pub struct SensorPresence {
}
/// The GPS receiver's requested power state. The ride loop derives one from whether a ride is active
-/// and the `power_saver` toggle, and the sensor task drives the M10 to match: deep sleep when idle
-/// (~µA vs. the ~20 mA of continuous tracking), full-power fixes while riding, or the M10's on-chip
-/// low-power tracking when `power_saver` is on.
+/// and the `power_saver` toggle. The sensor task requests stopped GNSS processing when idle,
+/// full-power fixes while riding, or the M10's on-chip low-power tracking when `power_saver` is on.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpsPower {
/// Riding, full-power continuous fixes at the configured rate.
@@ -80,8 +79,8 @@ pub enum GpsPower {
/// Riding with `power_saver` on — the M10's low-power tracking mode (lower power, same rate, at
/// the cost of some fix latency).
LowPower,
- /// Not tracking — deep sleep (`RXM-PMREQ` backup); woken on the next [`Active`](GpsPower::Active)
- /// / [`LowPower`](GpsPower::LowPower) request for a fast warm fix.
+ /// Not tracking — stop GNSS processing and park host polling. Resume on the next
+ /// [`Active`](GpsPower::Active) / [`LowPower`](GpsPower::LowPower) request.
Sleep,
}
@@ -262,7 +261,7 @@ impl SensorTaskLink<'_> {
self.0.publish(&self.0.heading, deg);
}
- /// Publish the boot probe result (once, after the sensor task probes all three chips). Pulses the
+ /// Publish the startup result once, after GPS responds or its acquisition deadline passes. Pulses the
/// event so the ride loop wakes and drains it via [`SensorConsumer::take_presence`].
pub fn dispatch_presence(&self, p: SensorPresence) {
self.0.publish(&self.0.presence, p);
@@ -380,7 +379,7 @@ impl<'a> SensorConsumer<'a> {
SensorCadence(&self.0.cadence)
}
- /// Drain the boot probe result — `Some` exactly once, on the pass after the task publishes it,
+ /// Drain the startup result — `Some` exactly once, on the pass after the task publishes it,
/// then `None`. The ride loop maps any absent sensor to a warning flag (issue #504).
pub fn take_presence(&self) -> Option {
self.0.presence.try_take()
diff --git a/firmware/obc-sensors/src/ubx.rs b/firmware/obc-sensors/src/ubx.rs
index 7b403eb6a..e1b79fd21 100644
--- a/firmware/obc-sensors/src/ubx.rs
+++ b/firmware/obc-sensors/src/ubx.rs
@@ -43,10 +43,8 @@ pub const ID_ACK_NAK: u8 = 0x00;
pub const CLASS_CFG: u8 = 0x06;
pub const ID_CFG_VALSET: u8 = 0x8A;
-/// `UBX-RXM` class + the `PMREQ` (power-management request) message id — the deep-sleep command the
-/// driver issues when tracking stops. See [`pmreq_backup`].
-pub const CLASS_RXM: u8 = 0x02;
-pub const ID_RXM_PMREQ: u8 = 0x41;
+/// Controlled GNSS start/stop command. See [`cfg_gnss_running`].
+pub const ID_CFG_RST: u8 = 0x04;
// Little-endian field readers — UBX is little-endian throughout. Each returns 0 if the slice is
// too short (callers gate on length first).
@@ -404,24 +402,12 @@ pub fn valset_u16(out: &mut [u8], key: u32, val: u16) -> Option {
frame(out, CLASS_CFG, ID_CFG_VALSET, &payload)
}
-/// `RXM-PMREQ` `flags`: request **backup** mode (deep sleep, RTC + RAM retained) and **force** it
-/// even with active comms. Woken by activity on the comms port (the driver pokes the DDC).
-const PMREQ_FLAG_BACKUP: u32 = 0x02;
-const PMREQ_FLAG_FORCE: u32 = 0x04;
-
-/// Build a `UBX-RXM-PMREQ` frame requesting **backup** (deep sleep) for an infinite duration —
-/// the M10 retains its RTC + ephemeris on ~microamps and wakes on the next DDC activity (a fast
-/// *warm* fix). 16-byte v0 payload: `version(1) | reserved(3) | duration(4 LE, 0 = until woken) |
-/// flags(4 LE, backup|force) | wakeupSources(4, 0 = comms activity)`. Returns the frame length
-/// written to `out` (24 B), or `None` if `out` is too small.
-pub fn pmreq_backup(out: &mut [u8]) -> Option {
- let mut payload = [0u8; 16];
- payload[0] = 0x00; // version 0
- // payload[1..4] reserved, payload[4..8] duration = 0 (infinite, until woken)
- let flags = PMREQ_FLAG_BACKUP | PMREQ_FLAG_FORCE;
- payload[8..12].copy_from_slice(&flags.to_le_bytes());
- // payload[12..16] wakeupSources = 0 — any traffic on the (I²C) comms port wakes it.
- frame(out, CLASS_RXM, ID_RXM_PMREQ, &payload)
+/// Start or stop GNSS tasks without clearing navigation data or receiver configuration.
+/// `CFG-RST` payload: zero `navBbrMask`, controlled start/stop mode, reserved zero.
+/// The receiver does not acknowledge this command. Returns 12 bytes, or `None` if too small.
+pub fn cfg_gnss_running(out: &mut [u8], running: bool) -> Option {
+ let mode = if running { 0x09 } else { 0x08 };
+ frame(out, CLASS_CFG, ID_CFG_RST, &[0, 0, mode, 0])
}
/// Common VALSET payload prefix (first 8 bytes): `version=0 | layers=RAM | reserved(2) | key(4 LE)`.
@@ -612,21 +598,15 @@ mod tests {
}
#[test]
- fn pmreq_backup_frames_an_infinite_backup_request() {
- let mut out = [0u8; 24];
- let n = pmreq_backup(&mut out).unwrap();
- match scan_ubx(&out[..n]) {
- Scan::Frame { frame, consumed } => {
- assert_eq!((frame.class, frame.id), (CLASS_RXM, ID_RXM_PMREQ));
- assert_eq!(consumed, n);
- assert_eq!(frame.payload.len(), 16, "v0 PMREQ payload");
- assert_eq!(frame.payload[0], 0, "version 0");
- assert_eq!(&frame.payload[4..8], &[0, 0, 0, 0], "duration 0 = until woken");
- let flags =
- u32::from_le_bytes([frame.payload[8], frame.payload[9], frame.payload[10], frame.payload[11]]);
- assert_eq!(flags, 0x06, "backup | force");
- }
- other => panic!("expected a frame, got {other:?}"),
+ fn gnss_control_preserves_navigation_data_and_uses_controlled_modes() {
+ for (running, expected) in [
+ (false, [0xb5, 0x62, 0x06, 0x04, 0x04, 0, 0, 0, 0x08, 0, 0x16, 0x74]),
+ (true, [0xb5, 0x62, 0x06, 0x04, 0x04, 0, 0, 0, 0x09, 0, 0x17, 0x76]),
+ ] {
+ let mut out = [0u8; 12];
+ assert_eq!(cfg_gnss_running(&mut out, running), Some(expected.len()));
+ assert_eq!(out, expected);
+ assert_eq!(cfg_gnss_running(&mut out[..11], running), None);
}
}