Skip to content

GPS power cycling between fixes (companion) 🤖🤖 - #3684

Closed
ajgray2017 wants to merge 6 commits into
meshcore-dev:devfrom
ajgray2017:feature/gps-duty-cycle
Closed

ajgray2017 wants to merge 6 commits into
meshcore-dev:devfrom
ajgray2017:feature/gps-duty-cycle

Conversation

@ajgray2017

@ajgray2017 ajgray2017 commented Oct 26, 2026 •

Copy link
Copy Markdown

Problem

On battery companions the GPS is either powered all the time or off. Meshtastic users expect an interval mode: wake, get a fix, power down, repeat. Nodes that rarely move save a lot of battery this way.

Change

  • New sensor setting gps_duty: seconds between fixes, 0 = always on (the default), max 86400. It uses the existing CMD_GET/SET_CUSTOM_VAR, so there is no protocol change.
  • Opt-in: listed only after the firmware calls EnvironmentSensorManager::configureGPSDuty(). The companion calls it because it saves the value. Repeater and room server firmware don't list it, so sensor set can't create an unsaved setting there.
  • While GPS is on and gps_duty > 0, EnvironmentSensorManager::loop():
    1. powers the GPS on and waits for a fix;
    2. holds the fix GPS_DUTY_SETTLE_SECS (default 10 s) so the RTC can sync;
    3. stores the position and powers the GPS off;
    4. waits gps_duty seconds before the next wake.
  • The NMEA parser doesn't run while the GPS is off. It still holds the last fix, and running it would let the periodic RTC resync set the clock back to that fix's time.
  • gps_active stays true while the GPS is off, so the gps setting, adverts and telemetry keep using the last fix. Setting gps_duty=0 while asleep powers the GPS straight back on.
  • With gps_duty=0, behaviour is the same as before. The parser clear and RTC re-sync on wake only happen when cycling.
  • Saved in NodePrefs (gps.duty) and re-applied in applyGpsPrefs().
  • OLED GPS page:
    • It shows gps on 5m, or gps sleep 5m while the GPS is off.
    • Long-press on the GPS page now steps through off → on → 1m → 5m → 15m → 1h → off.
    • Boards without gps_duty keep the old toggle, and the sensors page keeps the plain toggle.
  • Optional GPS_DUTY_MAX_ON_SECS gives up without a fix (default 0 = wait for a fix).
  • Not applied to RAK WisBlock GPS boards, which have their own power handling.
  • FAQ entry 5.17. It notes that boards with no GPS power switch save no power; the setting only limits how often the position updates.

Testing

Heltec V3 companion, u-blox module powered from a GPIO enable pin (GPIO42):

  • Bounds: gps_duty=-5 and gps_duty=90000 are rejected. 60 and 300 are accepted and persist across a reboot.

  • No fix, indoors (debug build, GPS_DUTY_MAX_ON_SECS=45, gps_duty=60): no fix, sleeping 45 s after power-on, then waking 60.0 s later, repeating.

  • gps_duty=0 while asleep: NMEA output resumes 0.2 s later and the GPS stays on (no sleep for 60 s). Setting 60 again resumes cycling.

  • Repeater with the same GPS: sensor list shows only gps, and sensor set gps_duty 60 replies can't find custom var.

  • Unit tests: pio test -e native -e native_kiss_modem passes (54/54).

  • Cycle with a fix (gps_duty=300, indoors near a window): fix, power down, wake 300.0 s later, new fix (541 s cold start), power down.

  • RTC during sleep, original vs. this PR. Test-only build with the provider's resync interval cut from 30 min to 120 s so the bug shows within one sleep. The clock was checked against the host:

    at fix 150 s into sleep 270 s into sleep
    parser left running while asleep (first version) −1 s −114 s −234 s
    this PR 0 s 0 s 0 s

    With the parser running, each resync put the clock back to the last fix's time. With the stock 30 min interval and the 1h preset, this sets the clock back by up to an hour.

  • OLED: long-press on the GPS page steps through the presets (checked by hand).

  • Boards: the fix and RTC tests ran on a second Heltec V3 with the same wiring. The other tests ran on the first board.

Compile-checked (all build, no new warnings; wio_wm1110_repeater shows the same 5 ENV_INCLUDE_GPS redefined warnings as dev):
Heltec_v3_companion_radio_usb, Heltec_v3_companion_radio_ble, Heltec_v3_repeater, Heltec_v3_room_server,
Heltec_t114_companion_radio_ble, Heltec_Wireless_Tracker_companion_radio_ble, Tbeam_SX1276_companion_radio_ble,
Tbeam_SX1276_repeater, LilyGo_TDeck_companion_radio_ble, RAK_4631_companion_radio_ble, RAK_4631_companion_radio_ethernet,
RAK_4631_repeater, RAK_4631_repeater_ethernet, RAK_4631_room_server, RAK_4631_room_server_ethernet,
PicoW_repeater, wio-e5-mini_repeater, LilyGo_Tlora_C6_repeater_, wio_wm1110_repeater.

Open questions for maintainers

  1. Is it OK for the GPS-page long-press to cycle presets instead of a pure on/off toggle?
  2. Should GPS_DUTY_MAX_ON_SECS default to a timeout (e.g. 300 s), so the GPS doesn't drain the battery indoors waiting for a fix?

Note

This is independent of #3683 (runtime GPS pins) and branches from dev. Both touch the GPS settings list, GPSPrefs and the FAQ numbering. I test-merged the two: the conflicts are "keep both sides", plus one shared #if block in the header and the FAQ renumbered to 5.18. The result runs on a Heltec V3 with all six GPS settings working.

🤖 Generated with Claude Code

ajgray2017 and others added 6 commits October 26, 2026 11:00
Battery companions with a GPS enable pin could only keep the GPS powered
all the time or turn it off.

- New sensor setting gps_duty (seconds between fixes, 0 = always on,
  default; max 86400) via the existing custom vars, persisted in
  NodePrefs (gps.duty) and re-applied in applyGpsPrefs().
- While GPS is on and gps_duty > 0: power on, wait for a fix, hold it
  GPS_DUTY_SETTLE_SECS (10 s) so the RTC syncs, store the position, power
  off, wake gps_duty seconds later. gps_active stays true throughout, so
  adverts/telemetry keep the last fix. gps_duty=0 behaves as before.
- Optional GPS_DUTY_MAX_ON_SECS gives up without a fix (default 0).
- OLED GPS page shows "gps on 5m" / "gps sleep 5m"; long-press on the GPS
  page cycles off -> on -> 1m -> 5m -> 15m -> 60m -> off (plain toggle if
  the board has no gps_duty).
- Not applied to RAK WisBlock GPS boards. FAQ 5.17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With gps_duty > 0, loop() kept calling _location->loop() while the GPS
was powered down. The NMEA parser still held the last fix (it is only
cleared on wake), so isValid() stayed true, and the provider's 30-minute
RTC re-sync could fire during the sleep and set the clock to the last
fix's time: up to gps_duty seconds in the past (an hour with the 1h
preset, a day at the 86400 maximum).

Only run the parser while the GPS is powered, or when not duty cycling.
The stored position is unaffected; it is copied before power-down.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gps_duty was listed whenever a GPS was detected, so repeater and room
server firmware showed it in "sensor list" and accepted "sensor set
gps_duty N" with an immediate effect, but never saved it: the value was
lost on reboot. Only the companion persists gps_duty.

Follow the same opt-in as the runtime GPS pin settings: the setting is
listed and accepted only after the firmware calls configureGPSDuty(),
which the companion does from applyGpsPrefs(). ENV_GPS_DUTY_CYCLE moves
to the header so the companion can test for it; boards whose sensor
manager is not an EnvironmentSensorManager (e.g. Heltec Wireless
Tracker) don't define it and are unaffected.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On PERSISTANT_GPS boards initBasicGPS() leaves the GPS powered and sets
gps_active without going through start_gps(), so gps_powered stayed
false. With gps_duty > 0 the first loop then "woke" a GPS that was
already on: a second begin()/reset() and parser clear right after boot,
and an extra claim on the location provider. None of today's
PERSISTANT_GPS boards (T-Beam, T-Beam 1W, ThinkNode M5) pass a shared
power pin, so the extra claim had no effect yet.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The GPS page showed a 3600 s duty as "gps on 1h" while the long-press
alert for the same preset said "GPS: 60m". Format both with one helper
that picks the largest exact unit (s, m or h), and update the FAQ preset
list to match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gps_duty is listed on every board with a detected GPS, but it can only
power the GPS down where the firmware controls its power (an enable pin
or switched supply). Say so, and what it still does on other boards.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@fdlamotte

Copy link
Copy Markdown
Collaborator

Totally agree with you, and we have had several attempts to implement duty cycle but sadly we didn't merge it lacking time to test on all boards ...

I think the first step is to merge #1799 don't know if it will happen with this PR (because it is old and should have been taken care of sooner), but I want a clean frontier between LocationManager and EnvironmentSensors and things have not gone in the good way lately ...

Then, when things are clearer we'll implement duty cycle in LocationManager and add cli commands to control it

You're proposal is interesting but I prefer when we go small steps (and in first steps I don't see why we would make changes to the UI). Also we can use AI, but don't let it do everything as it seems to be the case with this PR.

@AlexandrTyshchenko

AlexandrTyshchenko commented Oct 29, 2026 •

Copy link
Copy Markdown

Thanks for this — we run a similar GPS duty cycle on a solar repeater (ProMicro + serial GNSS with EN pin) and can share some field notes:

  • GPS_DUTY_MAX_ON_SECS default: we started at 120 s and had to raise it to 300 s. With the module's power fully cut between fixes it effectively cold-starts each time, and 120 s was often not enough. 300 s seems a sensible default; 0 (wait forever) can drain a battery when the sky view is poor.
  • Stale fix on wake: we call nmea.clear() when powering the GPS back on. Otherwise isValid() and the old position/time from the previous window remain visible until the first new sentence arrives, and the settle timer can count against old data.
  • Repeaters/sensors: solar repeaters are where this saves the most power. Would you consider exposing gps_duty in the repeater and sensor firmware too (stored in their prefs), not only companion?

We're also proposing a GPS anti-spoofing guard (#3733) that hooks into the same loop(); happy to build it on top of this PR.

@ajgray2017

Copy link
Copy Markdown
Author

Thanks for this — we run a similar GPS duty cycle on a solar repeater (ProMicro + serial GNSS with EN pin) and can share some field notes:

  • GPS_DUTY_MAX_ON_SECS default: we started at 120 s and had to raise it to 300 s. With the module's power fully cut between fixes it effectively cold-starts each time, and 120 s was often not enough. 300 s seems a sensible default; 0 (wait forever) can drain a battery when the sky view is poor.
  • Stale fix on wake: we call nmea.clear() when powering the GPS back on. Otherwise isValid() and the old position/time from the previous window remain visible until the first new sentence arrives, and the settle timer can count against old data.
  • Repeaters/sensors: solar repeaters are where this saves the most power. Would you consider exposing gps_duty in the repeater and sensor firmware too (stored in their prefs), not only companion?

We're also proposing a GPS anti-spoofing guard (#3733) that hooks into the same loop(); happy to build it on top of this PR.

Take whatever youd like! i wrote this for myself, since the little heltec v3's I built didnt have huge batteries. @AlexandrTyshchenko

@AlexandrTyshchenko

AlexandrTyshchenko commented Oct 31, 2026 •

Copy link
Copy Markdown

Thanks @ajgray2017, much appreciated! My duty cycle lives in the repeater build for now, so it's good to see the same approach working on a companion.

Per @fdlamotte's comment I'll wait for #1799 and then propose duty cycling as small steps on top of LocationProvider, with the repeater/sensor case in mind. Your RTC-resync finding (stopping the parser while the GPS is off) is a good catch and I'll make sure to keep it.

@fdlamotte

Copy link
Copy Markdown
Collaborator

I might not wait for #1799 after all ...

Hope to find time next week to refactor GPS, would like a clean interface for all the roles to use it and configure via cli

then adding duty_cycle is easy

#778 was close to what I wanted but we didn't finish it (and for some time back then I couldn't work on meshcore due to other things)

@AlexandrTyshchenko

Copy link
Copy Markdown

Sounds good, @fdlamotte. A clean GPS interface shared by all roles is exactly what I was missing on the repeater side.

Two small things it would help to leave room for, so I can port my PRs onto it instead of hooking into EnvironmentSensorManager:

#3729 (UART baud autodetect) would fit naturally into the NMEA provider's start-up. Happy to test the refactor on a ProMicro repeater with a serial GNSS (EN pin) and duty cycling, and to rebase both PRs once it lands.

@fdlamotte

Copy link
Copy Markdown
Collaborator

it's nice to have some discussion about this before doing the implementation task

on the separation between LP and ESM, after reading #778 one of the key factor of this separation is to bring GpsDriver ... I think the reason why things got pushed to ESM is because LP is seen as the driver and ESM the managerm where for GPS, LP should be the manager (and should report only the values to ESM), so we need a driver to talk to ;) (If I recall well, this is what we had come to when discussing this with @awolden )

I will open a PR as soon as I have the first bits working so people can discuss which direction to go to ...

@AlexandrTyshchenko

AlexandrTyshchenko commented Oct 31, 2026 •

Copy link
Copy Markdown

Makes sense to me - LP as the GPS manager with a GpsDriver underneath is a cleaner split than what we have now, and it maps well onto my PRs:

I'll hold off on rebasing until your first PR is up, then port both onto it and test on a ProMicro repeater with a serial GNSS (EN pin) and duty cycling.

@fdlamotte fdlamotte mentioned this pull request Nov 2, 2026
@fdlamotte

Copy link
Copy Markdown
Collaborator

Refactor of the GPS has now been done ... and it covers the aim of this PR

Thanks for proposing that again .. that made me move to concretize what I had in mind for a long time

@fdlamotte fdlamotte closed this Nov 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants