feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel)#2640
Open
disq wants to merge 12 commits into
Open
feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel)#2640disq wants to merge 12 commits into
disq wants to merge 12 commits into
Conversation
Three related issues hit by RAK3401 + RAK19007 + RAK12500 (I²C ublox). 1. -D RAK_BOARD restored on [rak3401]. It was removed in 68363d9 (revert of meshcore-dev#2401) with the symptom "RadioLib error -707", because adding RAK_BOARD enables rakGPSInit(), whose WB_IO4 fallback probe pulses pin 4 (= SX126X_RESET on RAK3401) LOW for 500 ms — hard-resetting the SX1262 after radio_init() has already configured it, and (via stop_gps + gpsResetPin = WB_IO4) leaving it held in reset afterwards. Without RAK_BOARD, RAK_WISBLOCK_GPS isn't auto-defined, the I²C ublox path is unreachable, and the firmware falls back to Serial1-NMEA detection — which silently fails on a serial-less RAK12500. Fixes 2 and 3 below make RAK_BOARD safe to re-enable. 2. RAK12500LocationProvider passed maxWait of 2/8 ms to getLatitude() etc. These are below the ublox I²C response window, so the polls routinely timed out and the SparkFun library returned stale/garbage PVT bytes. Observed: getGnssFixOk() returning true with a current latitude but a longitude from some previous fix. Bumped to 250 ms. Also switched getAltitude() (height above WGS84 ellipsoid) to getAltitudeMSL() for more intuitive altitude readings. 3. rakGPSInit() probes WB_IO2 first. On failure, gpsIsAwake() leaves the probed pin as INPUT. WB_IO2 also controls the 3V3_S switched peripheral rail on these RAK base boards, so a failed first probe left I²C peripherals (RTC, display, the GPS itself) unpowered before the WB_IO4/WB_IO5 fallbacks ran. The else-branch now restores WB_IO2 HIGH before falling through. On RAK3401, those fallback probes (WB_IO4 = pin 4 = SX126X_RESET, WB_IO5 = pin 9 = SX126X_BUSY) are also dangerous, see point 1. Adding -D FORCE_GPS_ALIVE in the RAK3401 base env skips stop_gps() after detection, so even if gpsResetPin ends up = WB_IO2 (kills the rail) or pin 4 (= radio reset), it can't be pulled LOW again after init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The loop() function printed lat/lon, then computed altitude, then printed lat/lon/alt — making the first print pure noise. Every gps_update_interval_sec (default 1 s) the log got two near-identical lines, drowning out anything else when MESH_DEBUG=1 is on. Drop the first MESH_DEBUG_PRINTLN entirely (in both the RAK_WISBLOCK_GPS and the non-RAK branches). Throttle the surviving lat/lon/alt line to roughly every 10th update via a static skip counter, so a debug build gets a position sample every ~10 s instead of every second while still covering the typical GPS visibility window. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jakymiwm
reviewed
May 28, 2026
jakymiwm
reviewed
May 28, 2026
jakymiwm
reviewed
May 28, 2026
jakymiwm
reviewed
May 28, 2026
jakymiwm
reviewed
May 28, 2026
jakymiwm
reviewed
May 28, 2026
Ublox modules sometimes report gnssFixOK=true with bogus coordinates during cold start, before any actual fix has been acquired — e.g. factory-almanac defaults that look like real positions. Observed: an unfixed module reporting plausible-looking but wrong-hemisphere coordinates persistently, with _location->isValid() returning true. Tighten RAK12500LocationProvider::loop() to require BOTH gnssFixOK (DOP/accuracy masks satisfied) AND fixType == 3 (3D fix). fixType values: 0=no fix, 1=dead reckoning only, 2=2D, 3=3D, 4=GNSS+DR, 5=time only. Only the 3D fix is a position we want to publish. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # src/helpers/sensors/EnvironmentSensorManager.cpp
…n loop
The stale-PVT fix bumped every ublox getXxx() maxWait to 250 ms. Each getter
does a synchronous I²C poll, and RAK12500LocationProvider::loop() runs on every
main-loop iteration — so the loop spent most of its time blocked on redundant
GPS polls (the GNSS solution only updates at the 1 Hz nav rate anyway). That
stall made the AIN1 button feel sluggish and drop double-clicks ("prev"), which
is what the separate interrupt-driven AIN1 button work was chasing — turns out
unnecessary once this is fixed.
Throttle the poll to once per nav epoch (~1 Hz) inside the provider, and gate
all field reads on a single getPVT(250): if that poll times out we return early
and keep the last values, so no getter fires its own ~1100 ms default-maxWait
poll on a stale cache. The throttle lives in the provider (not the shared outer
loop) because the serial/NMEA providers must keep getting loop() every iteration
to avoid dropping bytes. Same proven polling logic that was getting fixes, just
not hammered every iteration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gpsResetPin now initializes to -1 so start_gps/stop_gps pin writes are out-of-range no-ops when no enable pin exists, instead of driving pin 0 (P0.00 = LFXO crystal on nRF52). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0.09 is WisBlock IO5 (Slot D). A module driving that line, e.g. the RAK12002 RTC whose RV-3028 CLKOUT is push-pull and driven low when disabled, reads as a held user button. The resulting long-press within 8s of boot enters CLI rescue, which stops servicing the BLE/USB companion interface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
disq
added a commit
to disq/MeshCore
that referenced
this pull request
Jun 16, 2026
disq
added a commit
to disq/MeshCore
that referenced
this pull request
Jun 16, 2026
Show off / no-GPS / no-fix / 2D / 3D with a live sat count, and "---" until there's a real position. Reject ublox cold-start junk and MicroNMEA's 999-deg no-fix sentinel (both -> 0 = no data). Position renders eagerly on-screen, telemetry still reports only an accuracy-OK 3D fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Re-application of #2401
All 19003 tests had RTC in slot D and all 19007 tests had RTC in slot C.
All RAK4621 tests had
-UPIN_USER_BTNto prevent P0.09 conflict with RTC on 19003. (including 19007 tests)Changes beyond the original #2401 re-application
RAK3401 GPS enablement (b6e6730)
-D RAK_BOARDon[rak3401](removed in the feat: Enable GPS on RAK 1W kit #2401 revert due to "RadioLib error -707"). The real cause wasrakGPSInit()'s WB_IO4 fallback probe pulsing pin 4 (= SX126X_RESET on RAK3401) LOW for 500 ms, hard-resetting the radio afterradio_init(). The fixes below makeRAK_BOARDsafe to re-enable.rakGPSInit()WB_IO2 probe: WB_IO2 also controls the 3V3_S switched peripheral rail; a failed probe left it as INPUT, dropping the rail (and the RTC/display/GPS with it). Restore WB_IO2 HIGH before falling through to the WB_IO4/WB_IO5 probes.-D FORCE_GPS_ALIVEon the RAK3401 base env sostop_gps()can't later pullgpsResetPin(which may resolve to the radio reset / rail pin) LOW after init.RAK4631 GPS enablement —
FORCE_GPS_ALIVEparity (47d3753)-D FORCE_GPS_ALIVEto the[rak4631]base, matching RAK3401. Without it,rakGPSInit()ends by callingstop_gps()to park the receiver. On RAK4631 (PIN_GPS_EN=-1, GPS powered from the always-on 3V3_S rail) that "stop" is a software GNSS-stop the receiver did not reliably resume from over I²C, so the GPS booted dark (SIV stuck at 0). The flag compiles out that single boot-time stop; the runtimestart_gps()/stop_gps()paths are unchanged.PIN_GPS_TX/PIN_GPS_RX) fromplatformio.iniintovariant.h(value-identical), alongside the other GPS constants — again matching RAK3401.No dedicated GPS enable pin:
PIN_GPS_EN = -1(14f9d08)-1a real "no GPS enable pin" sentinel. DefaultgpsResetPinchanged from0(a real GPIO — P0.00 / LFXO on nRF52) to-1(0xFFFFFFFF, an out-of-range index thatpinMode()/digitalWrite()treat as a no-op), andgpsIsAwake()'s serial branch now testsPIN_GPS_EN >= 0instead of a truthyPIN_GPS_EN(which wrongly accepted-1). Adds-D PIN_GPS_EN=-1to RAK3401 so it matches RAK4631. Aligns the WisBlock path withMicroNMEALocationProvider, whereGPS_ENalready defaults to-1.GPS stop/start decoupled from the shared 3V3_S rail (ff82e66)
start_gps()/stop_gps()now branch on whether a dedicated enable pin exists. WithPIN_GPS_EN >= 0, behaviour is unchanged (drive the pin). With no enable pin (the common WisBlock case, wheregpsResetPinresolves to WB_IO2 = the shared 3V3_S rail), they no longer toggle that pin — they idle/resume just the GNSS engine in software and leave the rail HIGH:UBX-CFG-RSTcontrolled GNSS stop (resetMode0x08) / start (0x09) over I²C — stops RF/tracking but keeps the receiver's host interface alive, so it restarts reliably. (UBX-RXM-PMREQ backup was rejected: on this hardware it only wakes on EXTINT/UART, not I²C — confirmed dead.)$PMTK161,0standby / single0xFFwake byte.gpsResetPin; in the no-enable-pin case that meantstop_gps()pulled WB_IO2 LOW, browning out everything on the 3V3_S rail (RTC, I²C peripherals, boost) and relying on it coming back. GPS-off is now non-destructive, and the receiver warm/hot-starts.Stale PVT data fix (b6e6730)
RAK12500LocationProviderpassed maxWait of 2/8 ms to the ublox getters — below the I²C response window, so polls timed out and the SparkFun library returned stale/garbage PVT (observed: a current latitude paired with a longitude from a previous fix). Bumped to a 250 ms ceiling.getAltitude()(height above WGS84 ellipsoid) togetAltitudeMSL()for more intuitive altitude.Cold-start false fix (f2ad72e)
gnssFixOKandfixType == 3before reporting coordinates. Ublox reportsgnssFixOK == truewith bogus almanac-derived coords during cold start; requiring a 3D fix filters those out.Main-loop / button responsiveness fix (a13098a)
RAK12500LocationProvider::loop()running on every main-loop iteration, meant the loop spent most of its time blocked on redundant I²C polls (the GNSS solution only updates at the 1 Hz nav rate). This made the AIN1 button sluggish and dropped double-clicks ("prev").getPVT(250)(returning early on a timeout so no getter fires its own ~1100 ms default poll on a stale cache). The throttle lives in the provider rather than the shared outer loop because the serial/NMEA providers needloop()every iteration to avoid dropping bytes.RAK12501 (Quectel) serial GPS on RAK19007 Slot A (9e8cbf6)
LocationProviderpath are untouched.UI: real GPS fix type on the GPS screen (8f867bf)
getFixType()to theLocationProviderinterface (default derived fromisValid();RAK12500LocationProvideroverrides it with the actual u-blox fix type). The ui-new GPS screen now showsoff/no fix/2D/3Dinstead of a binaryfix/no fix, and the satellite count updates during acquisition (SIV is refreshed every tick rather than only on a 3D lock). Display/diagnostics only —isValid()and when a position is reported/logged are unchanged (still strict 3D).Debug-print cleanup (c9dfd1b)
Docs:
PIN_USER_BTN=9/ Slot D note (13d3496)-D PIN_USER_BTN=9RAK4631 envs noting P0.09 = WisBlock IO5 (Slot D): a Slot D module that drives IO5 (e.g. the RAK12002 RTC's RV-3028 CLKOUT) pins it LOW, which reads as a permanently-held user button → boots into CLI rescue → the BLE/USB companion link appears dead. No binary change.GPS screen: accurate fix-state display (d2e4748)
LocationProvider::getFixType(); the companion GPS page now shows off / no GPS / no fix / 2D / 3D with a live satellite count, instead of a binary fix/no-fix.---for position until there's a real fix, and reject what would otherwise flash as bogus coords: the ublox cold-startfixType==3-with-junk frames (gated onSIV>0+ in-range coords) and MicroNMEA's999°no-fix sentinel (both normalised to0).isValid()/node_latonly report an accuracy-OK (gnssFixOk) 3D fix.getLocationProvider()returnsNULLwhen no GPS is detected → the screen reads "No GPS detected" instead of a phantom unfixed GPS.Scope / affected boards
The
RAK12500LocationProvider,rakGPSInit(), andstart_gps()/stop_gps()changes compile underRAK_WISBLOCK_GPS(ENV_INCLUDE_GPS && RAK_BOARD && !RAK_WISMESH_TAG), so they affect every RAK I2C-ublox GPS board, not just the RAK 1W kit:rak3401,rak4631(all envs except the two RS232 bridges, which-UENV_INCLUDE_GPS), andgat562_30s_mesh_kit. (gat562_mesh_watch13has GPS disabled;rak_wismesh_tagis excluded and keeps its realPIN_GPS_EN=34.)Implications for the already-shipping RAK4631 / GAT562 boards:
fixType == 3gating — coordinates are now withheld until a 3D fix (filters cold-start false fixes).getAltitudeMSL()— altitude is now height above mean sea level instead of the WGS84 ellipsoid.rakGPSInit()WB_IO2 rail-restore — safety fix for a failed first probe dropping the 3V3_S peripheral rail.PIN_GPS_EN >= 0are unchanged.PIN_GPS_EN = -1sentinel — hardens the no-enable-pin path; no change for boards with a real enable pin.