From b6e6730feb9c3b13d2f9abe52677407e1dc0b713 Mon Sep 17 00:00:00 2001 From: Kemal Hadimli Date: Thu, 28 May 2026 16:00:10 +0100 Subject: [PATCH 1/3] RAK3401: re-enable GPS, fix stale PVT data, prevent radio rail-kill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three related issues hit by RAK3401 + RAK19007 + RAK12500 (I²C ublox). 1. -D RAK_BOARD restored on [rak3401]. It was removed in 68363d9e (revert of #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) --- .../sensors/EnvironmentSensorManager.cpp | 46 ++++++++++++------- variants/rak3401/platformio.ini | 3 ++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b5e23b3fe8..51492304d9 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -168,16 +168,21 @@ class RAK12500LocationProvider : public LocationProvider { void begin() override { } void stop() override { } void loop() override { - if (ublox_GNSS.getGnssFixOk(8)) { + // The numeric arg is maxWait (ms) for the I²C poll, not a field index. + // The previous 2/8 ms values were below the ublox response window, so polls + // routinely timed out and returned stale/garbage PVT data (observed: random + // wrong-hemisphere longitudes despite getGnssFixOk() returning true). + // 250 ms matches the SparkFun library's typical response time. + if (ublox_GNSS.getGnssFixOk(250)) { _fix = true; - _lat = ublox_GNSS.getLatitude(2) / 10; - _lng = ublox_GNSS.getLongitude(2) / 10; - _alt = ublox_GNSS.getAltitude(2); - _sats = ublox_GNSS.getSIV(2); + _lat = ublox_GNSS.getLatitude(250) / 10; + _lng = ublox_GNSS.getLongitude(250) / 10; + _alt = ublox_GNSS.getAltitudeMSL(250); // height above mean sea level, not ellipsoid + _sats = ublox_GNSS.getSIV(250); } else { _fix = false; } - _epoch = ublox_GNSS.getUnixEpoch(2); + _epoch = ublox_GNSS.getUnixEpoch(250); } bool isEnabled() override { return true; } }; @@ -702,16 +707,25 @@ void EnvironmentSensorManager::rakGPSInit(){ //search for the correct IO standby pin depending on socket used if(gpsIsAwake(WB_IO2)){ } - else if(gpsIsAwake(WB_IO4)){ - } - else if(gpsIsAwake(WB_IO5)){ - } - else{ - MESH_DEBUG_PRINTLN("No GPS found"); - gps_active = false; - gps_detected = false; - Serial1.end(); - return; + else { + // WB_IO2 controls the 3V3_S switched peripheral rail on these RAK boards. + // gpsIsAwake() leaves the pin as INPUT on failure, which drops the rail and + // takes I²C peripherals (RTC, display, the GPS itself) down with it. + // Restore it before probing other sockets. + pinMode(WB_IO2, OUTPUT); + digitalWrite(WB_IO2, HIGH); + + if(gpsIsAwake(WB_IO4)){ + } + else if(gpsIsAwake(WB_IO5)){ + } + else{ + MESH_DEBUG_PRINTLN("No GPS found"); + gps_active = false; + gps_detected = false; + Serial1.end(); + return; + } } #ifndef FORCE_GPS_ALIVE // for use with repeaters, until GPS toggle is implimented diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 20a8a548b9..2040faa4df 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -6,6 +6,7 @@ build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak3401 -D RAK_3401 + -D RAK_BOARD -D NRF52_POWER_MANAGEMENT -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper @@ -13,6 +14,8 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM + -D FORCE_GPS_ALIVE ; on RAK3401, gpsResetPin ends up = WB_IO2 (kills 3V3_S rail) or + ; pin 4 (= SX126X_RESET, holds radio in reset). stop_gps() must not run. build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> + From c9dfd1bde3ea58e6b8cb166f5e8e85122103b148 Mon Sep 17 00:00:00 2001 From: Kemal Hadimli Date: Thu, 28 May 2026 16:31:55 +0100 Subject: [PATCH 2/3] GPS: drop duplicate lat/lon debug print, throttle the remaining one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/helpers/sensors/EnvironmentSensorManager.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 51492304d9..b9d79712d1 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -815,6 +815,7 @@ void EnvironmentSensorManager::stop_gps() { void EnvironmentSensorManager::loop() { static long next_gps_update = 0; + static uint8_t gps_debug_skip = 0; // throttle: print roughly every 10th update tick #if ENV_INCLUDE_GPS if (gps_active) { @@ -827,17 +828,21 @@ void EnvironmentSensorManager::loop() { if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + if (gps_debug_skip-- == 0) { + gps_debug_skip = 9; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + } } #else if (_location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + if (gps_debug_skip-- == 0) { + gps_debug_skip = 9; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + } } #endif } From f2ad72e619468a9084542ee9797130980884f9a8 Mon Sep 17 00:00:00 2001 From: Kemal Hadimli Date: Thu, 28 May 2026 20:17:01 +0100 Subject: [PATCH 3/3] GPS: require fixType==3 in addition to gnssFixOK before reporting coords MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/helpers/sensors/EnvironmentSensorManager.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b9d79712d1..8db2788afd 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -173,7 +173,12 @@ class RAK12500LocationProvider : public LocationProvider { // routinely timed out and returned stale/garbage PVT data (observed: random // wrong-hemisphere longitudes despite getGnssFixOk() returning true). // 250 ms matches the SparkFun library's typical response time. - if (ublox_GNSS.getGnssFixOk(250)) { + // + // Require BOTH gnssFixOK (DOP/accuracy mask passed) AND fixType==3 (3D fix). + // Ublox reports gnssFixOK=true with bogus coords during cold start (e.g. an + // unfixed module on first boot reporting coordinates from factory almanac); + // requiring fixType==3 filters those out. + if (ublox_GNSS.getGnssFixOk(250) && ublox_GNSS.getFixType(250) == 3) { _fix = true; _lat = ublox_GNSS.getLatitude(250) / 10; _lng = ublox_GNSS.getLongitude(250) / 10;