mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-20 06:51:57 +00:00
Merge IoTThinks PowerSaving-v17 into keymindCascade
Integrate PowerSaving-v17 through a27e78e4 (six new commits).
Use its 10-minute awake / 30-minute asleep companion GPS profile and
separate requested GPS state from receiver power in CLI diagnostics.
Include its nRF52 sensor debug-console settle delay and debug option.
Resolve overlapping GPS v2 changes using the current sensor callbacks,
which already implement live power-saving transitions, user-off control,
telemetry acquisition, wrap-safe sleep/wake timers and retained Airoha
RTC backup. Preserve preference persistence, telemetry cache/holds and
UART/shared-rail ownership safeguards across all four GPS drivers.
Add a compiled production GPS status regression test and run it in CI.
Validated 23 native GPS/power tests, preference and nRF52 pin contracts,
and firmware builds for T1000-E BLE Companion, MeshTracker X1 USB
Companion, Heltec Tracker Repeater, Mesh Solar Room and RAK4631 Sensor.
This commit is contained in:
@@ -175,6 +175,9 @@ jobs:
|
||||
- name: Verify ESP32 USB sleep and G3 button wake
|
||||
run: python3 -B test/test_esp32_usb_sleep.py
|
||||
|
||||
- name: Verify GPS status distinguishes user settings from receiver sleep
|
||||
run: python3 -B test/test_gps_status.py
|
||||
|
||||
- name: Verify Companion ASCII defaults and USB session cleanup
|
||||
run: |
|
||||
python3 -B test/test_companion_usb_default.py
|
||||
|
||||
@@ -3197,9 +3197,9 @@ void setup() {
|
||||
sensors.begin();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
// Device power saving applies a 10-minute awake, 5-minute sleep GPS cycle.
|
||||
// Device power saving applies a 10-minute awake, 30-minute sleep GPS cycle.
|
||||
if (sensors.getLocationProvider() != NULL) {
|
||||
sensors.getLocationProvider()->setPowerSavingProfile(600, 300);
|
||||
sensors.getLocationProvider()->setPowerSavingProfile(600, 1800);
|
||||
sensors.setPowerSavingEnabled(
|
||||
the_mesh.getNodePrefs()->powersaving_enabled != 0);
|
||||
}
|
||||
|
||||
@@ -81,6 +81,11 @@ void setup() {
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
|
||||
// Allow the USB console to settle before printing boot diagnostics.
|
||||
delay(5000);
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (display.begin()) {
|
||||
display.turnOff();
|
||||
|
||||
+10
-12
@@ -3057,18 +3057,21 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
const char* gps_setting = _sensors->getSettingByKey("gps");
|
||||
bool active = gps_setting != NULL && strcmp(gps_setting, "1") == 0;
|
||||
|
||||
if (_prefs->powersaving_enabled && l->getGPSPowerSaving()) { // GPS Power Saving
|
||||
if (!active) {
|
||||
snprintf(reply, 160, "off, %s", enabled ? "powered" : "unpowered");
|
||||
} else if (_prefs->powersaving_enabled && l->getGPSPowerSaving()) { // GPS Power Saving
|
||||
unsigned long now = millis();
|
||||
unsigned long next_off = l->getNextGPSOff();
|
||||
unsigned long deadline = next_off != 0 ? next_off : l->getNextGPSOn();
|
||||
long remaining_ms = deadline == 0 ? 0 : (long)(deadline - now);
|
||||
unsigned long mins = remaining_ms > 0 ? (unsigned long)remaining_ms / 60000UL : 0;
|
||||
if (next_off != 0) {
|
||||
snprintf(reply, 160, "on (powersaving, sleep in %luh %lum), %s, %s, %d sats",
|
||||
mins / 60UL, mins % 60UL, active ? "active" : "deactivated",
|
||||
snprintf(reply, 160, "on, %s (powersaving, sleep in %luh %lum), %s, %d sats",
|
||||
enabled ? "powered" : "unpowered", mins / 60UL, mins % 60UL,
|
||||
fix ? "fix" : "no fix", sats);
|
||||
} else {
|
||||
snprintf(reply, 160, "off (powersaving, wake in %luh %lum)", mins / 60UL, mins % 60UL);
|
||||
snprintf(reply, 160, "on, %s (powersaving, wake in %luh %lum)",
|
||||
enabled ? "powered" : "unpowered", mins / 60UL, mins % 60UL);
|
||||
}
|
||||
|
||||
// "last sync" from GPS
|
||||
@@ -3082,14 +3085,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
dt.hour(), dt.minute(), dt.day(), dt.month(), dt.year());
|
||||
}
|
||||
} else { // Normal mode
|
||||
if (enabled) {
|
||||
sprintf(reply, "on, %s, %s, %d sats",
|
||||
active?"active":"deactivated",
|
||||
fix?"fix":"no fix",
|
||||
sats);
|
||||
} else {
|
||||
strcpy(reply, "off");
|
||||
}
|
||||
snprintf(reply, 160, "on, %s, %s, %d sats",
|
||||
enabled ? "powered" : "unpowered",
|
||||
fix ? "fix" : "no fix", sats);
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Can't find GPS");
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
"""Run the production GPS status branch against user and receiver states."""
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_replay_reset_integration import extract_braced
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
class GpsStatusTest(unittest.TestCase):
|
||||
def test_requested_state_is_distinct_from_sleeping_receiver(self):
|
||||
compiler = shutil.which("g++") or shutil.which("clang++")
|
||||
self.assertIsNotNone(compiler)
|
||||
source = (ROOT / "src/helpers/CommonCLI.cpp").read_text()
|
||||
branch = extract_braced(source, 'else if (memcmp(command, "gps", 3) == 0)')
|
||||
branch = branch[branch.index("{"):].replace("#endif", "")
|
||||
harness = r'''
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
unsigned long now = 1000;
|
||||
unsigned long millis() { return now; }
|
||||
struct DateTime {
|
||||
explicit DateTime(unsigned long) {}
|
||||
int hour() { return 12; } int minute() { return 34; }
|
||||
int day() { return 30; } int month() { return 9; } int year() { return 2026; }
|
||||
};
|
||||
struct LocationProvider {
|
||||
bool powered = false, fix = false, saving = false;
|
||||
unsigned long off = 0, on = 0, sync = 0;
|
||||
bool isEnabled() { return powered; } bool isValid() { return fix; }
|
||||
int satellitesCount() { return 5; }
|
||||
bool getGPSPowerSaving() { return saving; }
|
||||
unsigned long getNextGPSOff() { return off; }
|
||||
unsigned long getNextGPSOn() { return on; }
|
||||
unsigned long getLastValidTimeSync() { return sync; }
|
||||
};
|
||||
struct Sensors {
|
||||
LocationProvider* provider;
|
||||
const char* user = "0";
|
||||
LocationProvider* getLocationProvider() { return provider; }
|
||||
const char* getSettingByKey(const char*) { return user; }
|
||||
};
|
||||
struct Prefs { bool powersaving_enabled = true; };
|
||||
void status(Sensors* _sensors, Prefs* _prefs, char* reply) @BRANCH@
|
||||
int main() {
|
||||
LocationProvider gps;
|
||||
Sensors sensors{&gps}; Prefs prefs;
|
||||
char reply[160];
|
||||
// Manual off must not be described as a periodic sleep, even with stale timers.
|
||||
gps.saving = true; gps.on = now + 1800000;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "off, unpowered"));
|
||||
// A shared power rail can keep the receiver powered after GPS is disabled.
|
||||
gps.powered = true;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "off, powered"));
|
||||
sensors.user = "1"; gps.powered = false;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "on, unpowered (powersaving, wake in 0h 30m), last sync: none"));
|
||||
// Sleep can leave a shared rail powered; the wake timer is still meaningful.
|
||||
gps.powered = true;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "on, powered (powersaving, wake in 0h 30m), last sync: none"));
|
||||
gps.off = now + 600000; gps.powered = true; gps.fix = true; gps.sync = 1;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "on, powered (powersaving, sleep in 0h 10m), fix, 5 sats, last sync: 12:34 - 30/9/2026 UTC"));
|
||||
// An overdue timer must not underflow into a huge remaining duration.
|
||||
gps.off = now - 1;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(strstr(reply, "sleep in 0h 0m"));
|
||||
prefs.powersaving_enabled = false; gps.powered = false;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "on, unpowered, fix, 5 sats"));
|
||||
// No GPS setting is a valid disabled state; avoid a null strcmp.
|
||||
sensors.user = nullptr;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "off, unpowered"));
|
||||
sensors.provider = nullptr;
|
||||
status(&sensors, &prefs, reply);
|
||||
assert(!strcmp(reply, "Can't find GPS"));
|
||||
}
|
||||
'''.replace("@BRANCH@", branch)
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-gps-status-") as directory:
|
||||
path = Path(directory)
|
||||
(path / "status.cpp").write_text(harness)
|
||||
subprocess.run([compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
str(path / "status.cpp"), "-o", str(path / "status")], check=True)
|
||||
subprocess.run([str(path / "status")], check=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -144,6 +144,7 @@ build_flags = ${t1000-e.build_flags}
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
; -D POWERSAVING_DEBUG=1
|
||||
build_src_filter = ${t1000-e.build_src_filter}
|
||||
+<../examples/simple_sensor>
|
||||
lib_deps = ${t1000-e.lib_deps}
|
||||
|
||||
Reference in New Issue
Block a user