diff --git a/AGENTS.md b/AGENTS.md index 515a69a3686..763a1f75e6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -73,6 +73,8 @@ duty-cycle auto src/helpers/CommonRadioPrefs.{cpp,h}, src/helpers/CommonCLI.{c examples/simple_repeater/MyMesh.h, examples/simple_room_server/MyMesh.h, examples/simple_secure_chat/main.cpp, examples/companion_radio/{MyMesh.cpp,NodePrefs.h}, examples/simple_sensor/SensorMesh.cpp +gps default on src/helpers/CommonCLI.{cpp,h}, examples/companion_radio/{MyMesh.cpp,NodePrefs.h}, + variants/sensecap_solar/platformio.ini, docs/cli_commands.md variant tweaks variants/minewsemi_me25ls01/platformio.ini, variants/tenstar_c3/target.h ``` diff --git a/docs/cli_commands.md b/docs/cli_commands.md index eb5cbb799b0..a50d202e348 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1293,7 +1293,7 @@ region save **Parameters:** - `state`: `on`|`off` -**Default:** `off` +**Default:** `off`, except on boards built with `GPS_DEFAULT_ON` (SenseCAP Solar), where the first boot turns it `on`. That includes nodes upgraded from older firmware. A later `gps off` is kept across reboots. **Note:** Output format: - `off` when the GPS hardware is disabled diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 335046627e1..74cc35b631a 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1,5 +1,6 @@ #include "MyMesh.h" #include +#include #include // needed for PlatformIO #ifdef ENABLE_WIFI_INTERFACE @@ -998,6 +999,11 @@ void MyMesh::begin() { _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER); _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours +#ifdef GPS_DEFAULT_ON + if (applyGpsDefaultOn(_prefs.gps_enabled, _prefs.gps_default_applied)) { + savePrefs(); + } +#endif _active_ble_pin = _prefs.ble_pin; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 02c0dfea591..9faf246434a 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -35,6 +35,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file uint8_t vibe_quiet = 0; uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval = 0; // GPS read interval in seconds + uint8_t gps_default_applied = 0; // GPS_DEFAULT_ON has been applied once (boolean) uint8_t autoadd_config = 0; // bitmask for auto-add contacts config uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted) uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA) @@ -134,6 +135,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file def("en", _parent->gps_enabled); // boolean def("int", _parent->gps_interval); // interval in seconds def("adv_loc", _parent->advert_loc_policy); + def("dflt_on", _parent->gps_default_applied); } public: GPSPrefs(NodePrefs* parent) : _parent(parent) { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 91c7513da82..bdf283eb654 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -1,6 +1,7 @@ #include #include "CommonCLI.h" #include "DutyCycleLimits.h" +#include "GpsDefault.h" #include "TxtDataHelpers.h" #include "AdvertDataHelpers.h" #include "TxtDataHelpers.h" @@ -45,6 +46,11 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { // fs->remove("/com_prefs"); // remove old } } +#ifdef GPS_DEFAULT_ON + if (applyGpsDefaultOn(_prefs->gps_enabled, _prefs->gps_default_applied)) { + savePrefs(fs); + } +#endif } void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy prefs loader diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index f8925559c29..08a42eb0dd9 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -61,6 +61,7 @@ class NodePrefs : public ConfigSerializer { // Gps settings uint8_t gps_enabled = 0; uint32_t gps_interval = 0; // in seconds + uint8_t gps_default_applied = 0; // GPS_DEFAULT_ON has been applied once (boolean) uint8_t advert_loc_policy = 0; uint32_t discovery_mod_timestamp = 0; float adc_multiplier = 0; @@ -166,6 +167,7 @@ class NodePrefs : public ConfigSerializer { def("en", _parent->gps_enabled); // boolean def("int", _parent->gps_interval); // interval in seconds def("adv_loc", _parent->advert_loc_policy); + def("dflt_on", _parent->gps_default_applied); } public: GPSPrefs(NodePrefs* parent) : _parent(parent) { } diff --git a/src/helpers/GpsDefault.h b/src/helpers/GpsDefault.h new file mode 100644 index 00000000000..b5a50035bbe --- /dev/null +++ b/src/helpers/GpsDefault.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +// Boards built with GPS_DEFAULT_ON turn the GPS on once, including nodes whose saved +// prefs predate the marker. A later "gps off" persists, because the marker stays set. +// Returns true when the prefs changed and need saving. +inline bool applyGpsDefaultOn(uint8_t& gps_enabled, uint8_t& gps_default_applied) { + if (gps_default_applied) return false; + gps_enabled = 1; + gps_default_applied = 1; + return true; +} diff --git a/test/test_gps_default/test_gps_default.cpp b/test/test_gps_default/test_gps_default.cpp new file mode 100644 index 00000000000..3a4b515f1f5 --- /dev/null +++ b/test/test_gps_default/test_gps_default.cpp @@ -0,0 +1,102 @@ +#include + +#include +#include +#include + +#include "helpers/GpsDefault.h" +#include "../../examples/companion_radio/NodePrefs.h" + +// CommonRadioPrefs.cpp needs target.h, so it is not in the native build. These tests use no radio keys. +bool CommonRadioPrefs::setByKey(const char*, const char*) { return false; } +bool CommonRadioPrefs::getByKey(const char*, char*, size_t) { return false; } + +class ReplayStream : public Stream { + const char* _text; + int _pos = 0; + int _len; + +public: + explicit ReplayStream(const char* text) : _text(text), _len(strlen(text)) { } + + int available() override { return _len - _pos; } + int read() override { return _pos < _len ? _text[_pos++] : -1; } + int peek() override { return _pos < _len ? _text[_pos] : -1; } +}; + +class CaptureStream : public Stream { + std::string _text; + + size_t emit(long long value) { + char text[24]; + int length = snprintf(text, sizeof(text), "%lld", value); + return write(reinterpret_cast(text), length); + } + +public: + size_t write(uint8_t value) override { + _text.push_back(static_cast(value)); + return 1; + } + + size_t write(const uint8_t* buffer, size_t size) override { + _text.append(reinterpret_cast(buffer), size); + return size; + } + + size_t print(unsigned char value, int = DEC) override { return emit(value); } + size_t print(int value, int = DEC) override { return emit(value); } + size_t print(unsigned int value, int = DEC) override { return emit(value); } + size_t print(long value, int = DEC) override { return emit(value); } + size_t print(unsigned long value, int = DEC) override { return emit(value); } + size_t print(long long value, int = DEC) override { return emit(value); } + size_t print(unsigned long long value, int = DEC) override { return emit(value); } + + const std::string& text() const { return _text; } +}; + +TEST(GpsDefault, TurnsGpsOnOnce) { + uint8_t enabled = 0, applied = 0; + EXPECT_TRUE(applyGpsDefaultOn(enabled, applied)); + EXPECT_EQ(1, enabled); + EXPECT_EQ(1, applied); +} + +TEST(GpsDefault, KeepsGpsOffAfterTheDefaultWasApplied) { + uint8_t enabled = 0, applied = 1; + EXPECT_FALSE(applyGpsDefaultOn(enabled, applied)); + EXPECT_EQ(0, enabled); + EXPECT_EQ(1, applied); +} + +TEST(GpsDefault, PrefsWrittenBeforeTheMarkerExistedGetGpsOn) { + ReplayStream input("{gps:{en:0,int:0}}"); + NodePrefs prefs; + ASSERT_TRUE(prefs.loadSerial(input)); + EXPECT_EQ(0, prefs.gps_default_applied); + + EXPECT_TRUE(applyGpsDefaultOn(prefs.gps_enabled, prefs.gps_default_applied)); + EXPECT_EQ(1, prefs.gps_enabled); +} + +TEST(GpsDefault, GpsOffChosenAfterTheMarkerSurvivesAReload) { + NodePrefs saved; + saved.gps_enabled = 0; + saved.gps_default_applied = 1; + + CaptureStream output; + ASSERT_TRUE(saved.saveSerial(output)); + + ReplayStream input(output.text().c_str()); + NodePrefs loaded; + ASSERT_TRUE(loaded.loadSerial(input)); + EXPECT_EQ(1, loaded.gps_default_applied); + + EXPECT_FALSE(applyGpsDefaultOn(loaded.gps_enabled, loaded.gps_default_applied)); + EXPECT_EQ(0, loaded.gps_enabled); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index a2a9c85af9e..a115021c4f8 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -26,6 +26,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D ENV_INCLUDE_GPS=1 + -D GPS_DEFAULT_ON=1 build_src_filter = ${nrf52_base.build_src_filter} + +