Skip to main content
The navigation stack turns GNSS position, IMU orientation, and magnetic heading into a “point me to my friend / to the meetup” compass. It spans compass.py, compassing.py, ubx_gnss.py, imu_fusion_auto.py, mag_wmm_data.py, task_mag_declination.py, nav_helpers.py, nav_logger.py, and peer_helpers.py.

GNSS (u-blox)

The receiver is a u-blox module spoken to over the UBX binary protocol. A representative fix log:
(7-decimal latitude/longitude ≈ centimetre resolution; pAcc is the receiver’s own accuracy estimate used to gate whether a fix is trusted.)

AssistNow (A-GNSS)

To get a fast first fix, the device uses u-blox AssistNow (MGA) assistance data, delivered to the GNSS chip and cached in the filesystem. Within the GNSS driver, assistance data is only ever loaded from the on-device filesystem (Checking VFS for assist now files) and streamed to the receiver in chunks (Sending AssistNow chunk {}/{} ({} bytes), attempt {}). Delivery of that data over BLE/mesh is covered by the message-format subsystem, not the GNSS driver.

IMU fusion

imu_fusion_auto.py fuses accelerometer and gyroscope data for orientation and motion. In v5.0.3 it is the sole fusion module (byte-identical to v5.0.2), imported by compass, compassing, ble_manager, espnow_conn_v2, nav_logger, and debugger. v5.0.2 also froze an older imu_fusion.py — an unused Madgwick Fusion/Cal implementation that nothing imported — which v5.0.3 removes. The fusion engine provides:
  • Orientation filter is a Madgwick AHRS with an adaptive beta gain (madgwick, is_adaptive_beta, _BETA_MIN_SCALE).
  • Tilt/orientation feeds the 2D↔3D compass mode switch.
  • Shake detection (_SHAKE_ACCEL_DEV, _shake_count, _shake_start_ms) triggers a manual magnetometer bias reset (Manual bias reset triggered via shake pattern, is_shake_reset).
  • Accel deviation bounds gate magnetometer calibration (_CAL3D_MAX_ACCEL_DEV).

Magnetic compass & calibration

Heading comes from a magnetometer, corrected for hard/soft-iron errors and for magnetic declination.

Calibration

The 2D→3D “bias handoff” upgrades from a flat-plane heading to a full 3D solution once enough motion is seen: Bias handoff 2D->3D ({}) | gap_xy={:.1f}uT | 2D age={}s | cals={}. All of this calibration telemetry lives in imu_fusion_auto; it is unrelated to Vibe Mode (new_vibe.py, a sound-reactive LED effect — see Power, input & sensors). The --- Magnetic Calibration Samples --- string cited in earlier revisions existed only in the unused v5.0.2 imu_fusion.py and is gone in v5.0.3.

Declination (World Magnetic Model)

mag_wmm_data.py embeds the World Magnetic Model — specifically WMM 2025 (valid 2025–2030; WMM_2025) — so the device converts magnetic heading to true heading based on location, refreshed periodically:
task_mag_declination.py recomputes declination only when the device has moved far enough or enough time has passed — a power optimization. The embedded WGS84 axes (6378.137 / 6356.7523142 km) and the 6371.2 km geomagnetic reference radius — all present as IEEE-754 doubles in the binary — confirm a genuine spherical-harmonic declination computation rather than a lookup table.

Heading output

The compass surface the UI consumes: nav_logger.py records navigation events, schedulable remotely: nav_logger.check_log_storage normally rotates events.bin at 7100 B. In v5.0.3, when modes.is_nav_log_fast_rotate is set — by the BLE log uploader when no events-*.gz backlog exists — it rotates at ≥1024 B instead (Nav log fast rotation at {} B (BLE uploads active, backlog empty)).

Peer staleness & proximity display

How a bonded peer is shown on the ring depends on whether its coordinates are fresh. v5.0.3 changes both the staleness timeout and how stale peers are displayed:

Promo activation

compass.activation_check creates a time-limited promo point of interest (log strings Add Promo Activation: {} | Secs until start: {}, Creating promo activation in: {}, No promo activation, [activation_check] Activation expired, [activation_check] Activation doesn't start for 12hrs). In v5.0.2 the function existed but was never called; v5.0.3 launches it from backend_checks. The promo’s coordinates are embedded in the firmware and are intentionally not reproduced here.

Receiver lifecycle & auto-reset

The GNSS subsystem manages the receiver end-to-end and can self-heal: The embedded config frames all begin B5 62 and their trailing checksum bytes verify, so the frame contents are confirmed; the CFG-MSGOUT key→message-name mapping is inferred from the u-blox protocol spec.