Limit repeater GPS acquisition and repair CI tests

This commit is contained in:
mikecarper committed 2026-09-16 21:27:15 -07:00
1 parent 4ba26ff4af
commit fd920624a2
8 files changed
+84 -9

No files matched your search

+1
View File
@@ -20,6 +20,7 @@ jobs:
- name: Install real crypto implementations for cross-language tests
run: |
python3 -m pip install platformio pycryptodome==3.23.0
mkdir -p .pio/libdeps/management-test
pio pkg install --global --library 'rweather/Crypto@0.4.0' --storage-dir .pio/libdeps/management-test
- name: Verify protocol, encryption, scheduling and MQTT decoding
run: python3 -B test/test_management_report.py -v
+4
View File
@@ -4288,6 +4288,10 @@ Companion reports `on` or `off`; infrastructure includes receiver details:
- `off` when the GPS hardware is disabled
- `on, {active|deactivated}, {fix|no fix}, {sat count} sats` when the GPS hardware is enabled
On repeaters, `gps off` cancels any acquisition already in progress. A later
authorized location-telemetry request may wake the receiver, but only until its
first valid fix is cached; the receiver then powers down again.
---
#### Sync this node's clock with GPS time
+17 -8
View File
@@ -31,16 +31,25 @@ The cache behavior is:
- Omit GPS from telemetry if there is no fix or if the cached fix is more than
12 hours old.
When a telemetry request asks for location, GPS is kept on for 2 hours after the
latest location request. During that hold window, later location telemetry
requests can use fresh GPS data as soon as valid fixes are available.
When a telemetry request asks for location, GPS is normally kept on for 2 hours
after the latest location request. During that hold window, later location
telemetry requests can use fresh GPS data as soon as valid fixes are available.
If GPS is manually enabled, it stays on and valid fixes continue to update the
cache.
If no contact or ACL client can receive location telemetry, the scheduled
2-hour refresh does not run. A real location telemetry request still turns GPS
on for the 2-hour hold window, and manual GPS-on still keeps the cache updated.
2-hour refresh does not run. A real location telemetry request can still turn
GPS on, and manual GPS-on still keeps the cache updated.
## Repeater Nodes
Repeaters use a shorter on-demand policy when GPS is manually off. An authorized
location-telemetry request wakes the receiver only when its cached position is
stale. The first valid fix is cached and the receiver immediately powers down;
the next request can return that fix without another wake. `gps off` also
cancels an acquisition already in progress. An acquisition that never obtains a
fix retains the normal 15-minute safety timeout.
## Companion/Client Nodes
@@ -55,6 +64,9 @@ Location telemetry is sent only when the requester's effective telemetry
permissions include location. Those permissions are derived from the companion
telemetry mode settings and contact flags.
No new phone app behavior is required. Existing clients see the existing GPS
telemetry field when it is present.
The scheduled GPS cache refresh runs only when at least one stored contact has
effective location telemetry access:
@@ -62,9 +74,6 @@ effective location telemetry access:
- `location: allow flags` with at least one stored contact whose flags include
location
No new phone app behavior is required. Existing clients see the existing GPS
telemetry field when it is present.
## Sensor Nodes
Sensor telemetry access is controlled by:
+3
View File
@@ -204,6 +204,9 @@ void setup() {
#endif
#if ENV_INCLUDE_GPS == 1
// With GPS manually off, an authorized location telemetry request may wake
// the receiver only until the first valid fix has been cached.
sensors.setGpsTelemetryStopAfterFix(true);
if (sensors.getLocationProvider() != NULL) {
// Keep GPS awake for at most 10 minutes, then asleep for one day.
sensors.getLocationProvider()->setPowerSavingProfile(600, 86400);
+17
View File
@@ -115,6 +115,18 @@ bool SensorManager::queryGpsTelemetry(uint8_t requester_permissions, CayenneLPP&
gps_cache_altitude);
return true;
}
if (gps_stop_after_fix && !gps_user_enabled) {
// Repeaters acquire only when the cached position is stale. The request
// returns without GPS while acquisition is in progress; the next request
// can use the cached fix without waking the receiver again.
if (!gpsTelemetryCacheFresh(now) && !gps_acquiring) {
beginGpsTelemetryAcquisition(now);
}
if (!gpsTelemetryCacheFresh(now)) return false;
telemetry.addGPS(TELEM_CHANNEL_SELF, gps_cache_lat, gps_cache_lon,
gps_cache_altitude);
return true;
}
gps_hold_until = now + GPS_TELEMETRY_HOLD_SEC * 1000UL;
if (!telemetryGpsActive()) telemetryGpsStart();
if (!gpsTelemetryCacheFresh(now) && !gps_acquiring) beginGpsTelemetryAcquisition(now);
@@ -138,6 +150,11 @@ void SensorManager::processGpsTelemetryFix(float lat, float lon, float altitude,
gps_stable_started_at = now;
gps_stable_origin_lat = lat;
gps_stable_origin_lon = lon;
if (gps_stop_after_fix && !gps_user_enabled) {
updateGpsTelemetryCache(lat, lon, altitude, now);
finishGpsTelemetryAcquisition(now, false);
return;
}
}
static const float STABLE_RADIUS_M2 =
+8
View File
@@ -18,6 +18,7 @@ class SensorManager {
bool gps_location_access_available = false;
bool gps_transport_available = true;
bool gps_user_enabled = false;
bool gps_stop_after_fix = false;
bool gps_acquiring = false;
bool gps_acquire_has_fix = false;
float gps_cache_lat = 0;
@@ -100,6 +101,13 @@ public:
}
virtual void loop() { }
virtual void setTelemetryLocationAccessAvailable(bool available);
void setGpsTelemetryStopAfterFix(bool enabled) {
#if ENV_INCLUDE_GPS
gps_stop_after_fix = enabled;
#else
(void)enabled;
#endif
}
virtual int getNumSettings() const { return 0; }
virtual const char* getSettingName(int i) const { return NULL; }
virtual const char* getSettingValue(int i) const { return NULL; }
@@ -18,6 +18,9 @@ public:
void setLocationAccessAvailable(bool available) {
setTelemetryLocationAccessAvailable(available);
}
void setStopAfterFix(bool enabled) {
setGpsTelemetryStopAfterFix(enabled);
}
void service(unsigned long now) { loopGpsTelemetry(now); }
bool receiverRequired(unsigned long now) const {
return gpsTelemetryReceiverRequired(now);
@@ -103,6 +106,29 @@ TEST(GpsTransportOwnership, UserOffCancelsTelemetryAcquisitionAndHold) {
EXPECT_EQ(1, sensors.starts);
}
TEST(GpsTransportOwnership, RepeaterTelemetryStopsAfterFirstFix) {
resetArduinoMock();
TestGpsSensorManager sensors;
CayenneLPP telemetry(64);
sensors.setStopAfterFix(true);
EXPECT_FALSE(sensors.queryLocation(telemetry));
ASSERT_TRUE(sensors.active);
ASSERT_TRUE(sensors.receiverRequired(millis()));
sensors.acceptFix(47.61f, -122.33f, 125.0f, millis());
EXPECT_FALSE(sensors.active);
EXPECT_FALSE(sensors.receiverRequired(millis()));
EXPECT_EQ(1, sensors.starts);
EXPECT_EQ(1, sensors.stops);
// The next request receives the cached fix without another GPS wake.
EXPECT_TRUE(sensors.queryLocation(telemetry));
EXPECT_EQ(1, telemetry.gps_count);
EXPECT_FALSE(sensors.active);
EXPECT_EQ(1, sensors.starts);
}
TEST(GpsTransportOwnership, LastGoodCacheSurvivesTemporaryUartOwnership) {
resetArduinoMock();
TestGpsSensorManager sensors;
@@ -112,6 +138,9 @@ TEST(GpsTransportOwnership, LastGoodCacheSurvivesTemporaryUartOwnership) {
sensors.acceptFix(47.61f, -122.33f, 125.0f, millis());
g_mock_millis += 31000;
sensors.acceptFix(47.61f, -122.33f, 125.0f, millis());
// Non-repeater roles retain the normal two-hour request hold.
EXPECT_TRUE(sensors.active);
EXPECT_TRUE(sensors.receiverRequired(millis()));
ASSERT_TRUE(sensors.queryLocation(telemetry));
ASSERT_EQ(1, telemetry.gps_count);
+5 -1
View File
@@ -196,8 +196,12 @@ TEST(ManagementRouting, FloodForwardingDoesNotNeedAKeyAndStillHonorsFilters) {
mesh.forwardFloods = true;
mesh::Packet p;
p.header = (PAYLOAD_TYPE_GRP_DATA << PH_TYPE_SHIFT) | ROUTE_TYPE_FLOOD;
p.setPathHashSizeAndCount(1, 0); memcpy(p.payload, "MGR1", 4); p.payload[79] = 1;
p.setPathHashSizeAndCount(1, 0);
p.payload_len = mesh::management::floodSize(mesh::management::HEADER + mesh::management::TAG);
memset(p.payload, 0, p.payload_len);
memcpy(p.payload, "MGR1", 4);
p.payload[79] = 1; // one page containing zero ACL entries
ASSERT_TRUE(mesh::management::validPage(p.payload, p.payload_len, true));
EXPECT_NE(ACTION_RELEASE, mesh.receivePacket(&p));
EXPECT_FALSE(mesh.groupPacketObserved); // never delivered as decrypted channel data
mesh.rejectFloods = true;