diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 69d8e7d2e..c99e9fdd5 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -146,14 +146,16 @@ public : void stop() override { _stop_calls++; - if (!_started) return; - _started = false; + // RAK discovery and board code can power GPS without begin(). Always + // force the control pins off; only release a claim acquired here. if (_pin_en != -1) { digitalWrite(_pin_en, !GPS_EN_ACTIVE); } if (_pin_reset != -1) { digitalWrite(_pin_reset, GPS_RESET_ACTIVE); } + if (!_started) return; + _started = false; release(); } diff --git a/test/fixtures/rak_gps_power.cpp b/test/fixtures/rak_gps_power.cpp index 4c9383c1b..58868de36 100644 --- a/test/fixtures/rak_gps_power.cpp +++ b/test/fixtures/rak_gps_power.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -421,6 +422,30 @@ static void dedicated() { #endif } +static void dedicatedProviderStop() { +#if defined(PIN_GPS_EN) && PIN_GPS_EN >= 0 + fresh(); + const int reset_pin=7; + MicroNMEALocationProvider location(Serial1,nullptr,reset_pin,PIN_GPS_EN); + EnvironmentSensorManager sensors(location); + Serial1.nmea(); assert(sensors.begin()); + assert(sensors.isGPSDetected()&&sensors.isGpsPowered()); + // RAK discovery controls power directly, without provider.begin(). Match + // NRF52Board::shutdownPeripherals(), which stops the selected provider. + sensors.getLocationProvider()->stop(); + assert(digitalRead(PIN_GPS_EN)==!PIN_GPS_EN_ACTIVE); + assert(digitalRead(reset_pin)==GPS_RESET_ACTIVE); + // A repeated stop must still force safe pins if another owner woke GPS. + digitalWrite(PIN_GPS_EN,PIN_GPS_EN_ACTIVE); + digitalWrite(reset_pin,!GPS_RESET_ACTIVE); + sensors.getLocationProvider()->stop(); + assert(digitalRead(PIN_GPS_EN)==!PIN_GPS_EN_ACTIVE); + assert(digitalRead(reset_pin)==GPS_RESET_ACTIVE); +#else + assert(false&&"dedicated provider scenario requires a separate enable pin"); +#endif +} + static void terminalStorage() { SensorManager unknown; assert(!unknown.canRemoveSharedStoragePowerForDeepSleep()); @@ -469,6 +494,7 @@ int main(int argc,char** argv) { else if(!std::strcmp(argv[1],"wake_bus_failure")) i2c(false,4); else if(!std::strcmp(argv[1],"initial_config_failure")) initialConfigFailure(); else if(!std::strcmp(argv[1],"dedicated")) dedicated(); + else if(!std::strcmp(argv[1],"dedicated_provider_stop")) dedicatedProviderStop(); else if(!std::strcmp(argv[1],"terminal_storage")) terminalStorage(); else assert(false&&"unknown scenario"); } diff --git a/test/test_gps_upstream_adaptations.py b/test/test_gps_upstream_adaptations.py index ce914e712..768ad0923 100644 --- a/test/test_gps_upstream_adaptations.py +++ b/test/test_gps_upstream_adaptations.py @@ -476,12 +476,19 @@ int main() { Stream serial; RefCountedDigitalPin rail(5); rail.begin(); MicroNMEALocationProvider gps(serial, nullptr, 7, 6, &rail); gps.stop(); assert(digitalRead(5)==LOW); + rail.claim(); // An external owner powers a receiver never begun here. + for (unsigned attempt=0; attempt<2; ++attempt) { + digitalWrite(6, HIGH); digitalWrite(7, HIGH); + gps.stop(); + assert(digitalRead(5)==HIGH && digitalRead(6)==LOW && digitalRead(7)==LOW); + } + rail.release(); assert(digitalRead(5)==LOW); for (unsigned cycle=0; cycle<300; ++cycle) { rail.claim(); // Another peripheral owns the same supply. gps.begin(); gps.begin(); - assert(digitalRead(5)==HIGH && digitalRead(6)==HIGH); + assert(digitalRead(5)==HIGH && digitalRead(6)==HIGH && digitalRead(7)==HIGH); gps.stop(); gps.stop(); - assert(digitalRead(5)==HIGH && digitalRead(6)==LOW); + assert(digitalRead(5)==HIGH && digitalRead(6)==LOW && digitalRead(7)==LOW); rail.release(); assert(digitalRead(5)==LOW); } } diff --git a/test/test_rak_gps_power.py b/test/test_rak_gps_power.py index b2a820d5e..f2ec6847a 100644 --- a/test/test_rak_gps_power.py +++ b/test/test_rak_gps_power.py @@ -9,7 +9,7 @@ import os import subprocess import tempfile import unittest -from test_gps_upstream_adaptations import ARDUINO, CAYENNE, MESH +from test_gps_upstream_adaptations import ARDUINO, CAYENNE, MESH, NMEA, RTC from test_replay_reset_integration import extract_braced ROOT = Path(__file__).resolve().parents[1] @@ -111,6 +111,7 @@ inline void digitalWrite(int pin,int value){assert(pin>=0&&pin<64);pin_values[pi inline int digitalRead(int pin){assert(pin>=0&&pin<64);return pin_values[pin];} ''') for name, content in {"Arduino.h": arduino, "Mesh.h": MESH, "CayenneLPP.h": CAYENNE, + "MicroNMEA.h": NMEA, "RTClib.h": RTC, "Wire.h": "#pragma once\nclass TwoWire {};\ninline TwoWire Wire;\n"}.items(): (work / name).write_text(content, encoding="utf-8") cpp, binary = work / "lifecycle.cpp", work / "lifecycle" @@ -186,7 +187,7 @@ class RakGpsPowerTests(unittest.TestCase): def test_actual_dedicated_gps_control_isolated_from_shared_slot_power(self): for active in (0, 1): with self.subTest(active_level=active): - compile_lifecycle(self, ("dedicated",), + compile_lifecycle(self, ("dedicated", "dedicated_provider_stop"), ("-DRAK_4631", "-DPIN_GPS_EN=14", f"-DPIN_GPS_EN_ACTIVE={active}")) def test_actual_boot_policy_protects_storage_and_poe_even_when_gps_is_not_compiled(self):