@@ -2385,14 +2385,102 @@ static void test_setConfigPosition_liveFieldChange_doesNotReboot()
23852385 TEST_ASSERT_EQUAL_UINT32 (0 , rebootAtMsec);
23862386}
23872387
2388- static void test_setConfigPosition_gpsModeChange_schedulesReboot ()
2388+ // A GPIO reassignment is boot-only however it arrives: the pin is claimed during GPS construction.
2389+ static void test_setConfigPosition_gpioChangeStillReboots ()
2390+ {
2391+ config.position .rx_gpio = 0 ; // known start state
2392+ rebootAtMsec = 0 ;
2393+ meshtastic_Config c = meshtastic_Config_init_zero;
2394+ c.which_payload_variant = meshtastic_Config_position_tag;
2395+ c.payload_variant .position = config.position ;
2396+ c.payload_variant .position .rx_gpio = 17 ;
2397+ sendSetConfig (c);
2398+
2399+ TEST_ASSERT_NOT_EQUAL (0 , rebootAtMsec);
2400+ }
2401+
2402+ // -----------------------------------------------------------------------
2403+ // gps_mode: ENABLED <-> DISABLED is applied live (AdminModule drives gps->enable()/disable(),
2404+ // as the on-device menus have always done), so it must not reboot. Every transition involving
2405+ // NOT_PRESENT still must, because the driver object only exists from boot.
2406+ // -----------------------------------------------------------------------
2407+
2408+ static void test_setConfigPosition_gpsEnableIsLive_doesNotReboot ()
2409+ {
2410+ config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; // known start state
2411+ rebootAtMsec = 0 ;
2412+ ConfigChangedCounter counter;
2413+ counter.observe (&service->configChanged );
2414+
2415+ meshtastic_Config c = meshtastic_Config_init_zero;
2416+ c.which_payload_variant = meshtastic_Config_position_tag;
2417+ c.payload_variant .position = config.position ;
2418+ c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
2419+ sendSetConfig (c);
2420+
2421+ TEST_ASSERT_EQUAL_INT (meshtastic_Config_PositionConfig_GpsMode_ENABLED, (int )config.position .gps_mode ); // persisted
2422+ TEST_ASSERT_EQUAL_UINT32 (0 , rebootAtMsec);
2423+ TEST_ASSERT_EQUAL_INT (0 , counter.count ); // and still no reason to touch the radio
2424+ }
2425+
2426+ static void test_setConfigPosition_gpsDisableIsLive_doesNotReboot ()
2427+ {
2428+ config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; // known start state
2429+ config.position .fixed_position = true ; // keep the nested clearLocalPosition save out of this test
2430+ rebootAtMsec = 0 ;
2431+
2432+ meshtastic_Config c = meshtastic_Config_init_zero;
2433+ c.which_payload_variant = meshtastic_Config_position_tag;
2434+ c.payload_variant .position = config.position ;
2435+ c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
2436+ sendSetConfig (c);
2437+
2438+ TEST_ASSERT_EQUAL_INT (meshtastic_Config_PositionConfig_GpsMode_DISABLED, (int )config.position .gps_mode );
2439+ TEST_ASSERT_EQUAL_UINT32 (0 , rebootAtMsec);
2440+ }
2441+
2442+ // The live exemption is exactly one pair. Declaring the GPS absent tears down state that only
2443+ // boot rebuilds, so it stays on the reboot path...
2444+ static void test_setConfigPosition_gpsToNotPresent_stillReboots ()
2445+ {
2446+ config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED; // known start state
2447+ config.position .fixed_position = true ;
2448+ rebootAtMsec = 0 ;
2449+ meshtastic_Config c = meshtastic_Config_init_zero;
2450+ c.which_payload_variant = meshtastic_Config_position_tag;
2451+ c.payload_variant .position = config.position ;
2452+ c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT;
2453+ sendSetConfig (c);
2454+
2455+ TEST_ASSERT_NOT_EQUAL (0 , rebootAtMsec);
2456+ }
2457+
2458+ // ...and so does the reverse: there is no driver to enable until a boot has constructed one.
2459+ static void test_setConfigPosition_gpsFromNotPresent_stillReboots ()
2460+ {
2461+ config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT; // known start state
2462+ rebootAtMsec = 0 ;
2463+ meshtastic_Config c = meshtastic_Config_init_zero;
2464+ c.which_payload_variant = meshtastic_Config_position_tag;
2465+ c.payload_variant .position = config.position ;
2466+ c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
2467+ sendSetConfig (c);
2468+
2469+ TEST_ASSERT_NOT_EQUAL (0 , rebootAtMsec);
2470+ }
2471+
2472+ // The exemption is for gps_mode alone - it must not smuggle a genuinely boot-only field past the
2473+ // memcmp fail-safe when both change in the same set.
2474+ static void test_setConfigPosition_gpsToggleWithGpioChange_stillReboots ()
23892475{
23902476 config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED; // known start state
2477+ config.position .rx_gpio = 0 ;
23912478 rebootAtMsec = 0 ;
23922479 meshtastic_Config c = meshtastic_Config_init_zero;
23932480 c.which_payload_variant = meshtastic_Config_position_tag;
23942481 c.payload_variant .position = config.position ;
23952482 c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
2483+ c.payload_variant .position .rx_gpio = 17 ;
23962484 sendSetConfig (c);
23972485
23982486 TEST_ASSERT_NOT_EQUAL (0 , rebootAtMsec);
@@ -2536,10 +2624,11 @@ static void test_transaction_loraSet_stillReloadsRadioAtCommit()
25362624}
25372625
25382626// ...and the reboot axis on its own: a boot-only field inside a transaction reboots at the commit
2539- // without dragging the radio reconfigure along with it.
2540- static void test_transaction_gpsModeSet_reboots_butDoesNotReloadRadio ()
2627+ // without dragging the radio reconfigure along with it. rx_gpio rather than gps_mode - the latter
2628+ // is applied live now, so it would no longer exercise the reboot axis at all.
2629+ static void test_transaction_gpioSet_reboots_butDoesNotReloadRadio ()
25412630{
2542- config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED ; // known start state
2631+ config.position .rx_gpio = 0 ; // known start state
25432632 rebootAtMsec = 0 ;
25442633 ConfigChangedCounter counter;
25452634 counter.observe (&service->configChanged );
@@ -2548,7 +2637,7 @@ static void test_transaction_gpsModeSet_reboots_butDoesNotReloadRadio()
25482637 meshtastic_Config c = meshtastic_Config_init_zero;
25492638 c.which_payload_variant = meshtastic_Config_position_tag;
25502639 c.payload_variant .position = config.position ;
2551- c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED ;
2640+ c.payload_variant .position .rx_gpio = 17 ;
25522641 sendSetConfig (c);
25532642
25542643 TEST_ASSERT_EQUAL_UINT32 (0 , rebootAtMsec); // deferred until the commit
@@ -2676,14 +2765,14 @@ static void test_abandonedTransaction_loraSet_stillReloadsRadioOnExpiry()
26762765// the client that asked for the restart is, by definition, gone.
26772766static void test_abandonedTransaction_rebootingFieldDoesNotRebootOnExpiry ()
26782767{
2679- config.position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED ; // known start state
2768+ config.position .rx_gpio = 0 ; // a genuinely boot-only field, so the drop is a real drop
26802769 rebootAtMsec = 0 ;
26812770
26822771 sendBeginEdit ();
26832772 meshtastic_Config c = meshtastic_Config_init_zero;
26842773 c.which_payload_variant = meshtastic_Config_position_tag;
26852774 c.payload_variant .position = config.position ;
2686- c.payload_variant .position .gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED ;
2775+ c.payload_variant .position .rx_gpio = 17 ;
26872776 sendSetConfig (c);
26882777
26892778 testAdmin->ageEditTransaction ();
@@ -2904,15 +2993,20 @@ void setup()
29042993 RUN_TEST (test_setConfigBluetooth_noopSet_doesNotReboot);
29052994 RUN_TEST (test_setConfigBluetooth_realChange_schedulesReboot);
29062995 RUN_TEST (test_setConfigPosition_liveFieldChange_doesNotReboot);
2907- RUN_TEST (test_setConfigPosition_gpsModeChange_schedulesReboot);
2996+ RUN_TEST (test_setConfigPosition_gpioChangeStillReboots);
2997+ RUN_TEST (test_setConfigPosition_gpsEnableIsLive_doesNotReboot);
2998+ RUN_TEST (test_setConfigPosition_gpsDisableIsLive_doesNotReboot);
2999+ RUN_TEST (test_setConfigPosition_gpsToNotPresent_stillReboots);
3000+ RUN_TEST (test_setConfigPosition_gpsFromNotPresent_stillReboots);
3001+ RUN_TEST (test_setConfigPosition_gpsToggleWithGpioChange_stillReboots);
29083002
29093003 // Edit-transaction deferral (the path phone apps use)
29103004 RUN_TEST (test_transaction_favoriteNode_doesNotReloadRadioAtCommit);
29113005 RUN_TEST (test_transaction_toggleMutedNode_doesNotReloadRadioAtCommit);
29123006 RUN_TEST (test_transaction_moduleConfigSet_doesNotReloadRadioAtCommit);
29133007 RUN_TEST (test_transaction_positionGpsOffNestedSave_doesNotReloadRadioAtCommit);
29143008 RUN_TEST (test_transaction_loraSet_stillReloadsRadioAtCommit);
2915- RUN_TEST (test_transaction_gpsModeSet_reboots_butDoesNotReloadRadio );
3009+ RUN_TEST (test_transaction_gpioSet_reboots_butDoesNotReloadRadio );
29163010 RUN_TEST (test_transaction_liveOnlyBatch_neitherRebootsNorReloads);
29173011 RUN_TEST (test_transaction_flagsDoNotLeakIntoTheNextTransaction);
29183012 RUN_TEST (test_commitWithoutBegin_persistsButDoesNotReloadOrReboot);
0 commit comments