Skip to content

ThinkNode M6: fix GPS and status LED in the repeater build - #3287

Closed
andyhoobing wants to merge 3 commits into
meshcore-dev:devfrom
andyhoobing:m6-gps-leds-dev
Closed

ThinkNode M6: fix GPS and status LED in the repeater build#3287
andyhoobing wants to merge 3 commits into
meshcore-dev:devfrom
andyhoobing:m6-gps-leds-dev

Conversation

@andyhoobing

Copy link
Copy Markdown

What this fixes

On the Elecrow ThinkNode M6 (nRF52840 + SX1262 + L76K GNSS, sealed IP65 solar
enclosure) the ThinkNode_M6_repeater build shipped with an unusable GNSS and
one of its two LEDs completely dead. Pin definitions were already correct; the
problems were behavioural.

GPS

  1. simple_repeater defaults gps_enabled = 0, so sensors.begin() detected
    the L76K and then immediately powered it back down via _location->stop()
    (which drives PIN_GPS_EN low). The M6 is a sealed box with no button or
    screen, so there was no way to switch it on short of a USB console.
  2. Even with GPS on, the repeater defaults advert_loc_policy to
    ADVERT_LOC_PREFS, so adverts carried the fixed ADVERT_LAT/ADVERT_LON
    (0,0) and ignored the GNSS entirely.
  3. The 1-second "is a GPS attached?" probe in initBasicGPS() gates the gps
    setting; losing that race leaves GPS unreachable until reboot. The M6 always
    has the module fitted, so the probe is skipped for this board.

Rather than changing behaviour for everyone, the two simple_repeater defaults
are now build-time overridable:

#ifndef GPS_ENABLED_DEFAULT
  #define GPS_ENABLED_DEFAULT 0
#endif
#ifndef ADVERT_LOC_POLICY_DEFAULT
  #define ADVERT_LOC_POLICY_DEFAULT ADVERT_LOC_PREFS
#endif

Every other board keeps its existing defaults; only the M6 repeater env opts
in. Both remain adjustable at runtime via gps on/gps off and
gps advert prefs|share|none.

LEDs

Only the blue LED was driven (LoRa TX activity). The red LED — the enclosure's
power LED, on the bottom face next to the USB-C port — was configured as an
output in initVariant() and then never touched again.

It now indicates state: solid through boot, a three-flash signature at
onBootComplete(), then a ~3% duty heartbeat — one blink per 5s while running,
two once the GNSS has a fix. That makes a deployed node diagnosable through the
case without a laptop. powerOff() clears it too.

The heartbeat is driven from a small EnvironmentSensorManager subclass — the
same pattern thinknode_m1 already uses — since its loop() already runs every
iteration and holds the live GNSS state. No core or shared-example changes
were needed for the LED work.

Testing

  • ThinkNode_M6_repeater builds against dev (flash 44.5%, RAM 14.5%).
  • RAK_4631_repeater builds unchanged, confirming the shared simple_repeater
    defaults are untouched for other boards.
  • Verified on real M6 hardware over the serial CLI: gps -> on, active, fix, 10 sats, gps advert -> share, and the settings survive a reboot.

Note for maintainers

One behavioural caveat worth being aware of, which cost me some debugging: these
compiled defaults only apply to a fresh install. A UF2 update does not erase
InternalFS, so a device that has run MeshCore before keeps its saved prefs and
silently ignores the new defaults — it has to be reconfigured at runtime or
factory reset. That is pre-existing behaviour, not something this PR changes.

Opened as a draft: happy to split the shared simple_repeater change out from
the M6-only variant changes if you would prefer two PRs per the one-fix-per-PR
guideline.


Authored with assistance from Claude (see Co-Authored-By trailers on the
commits).

andyhoobing and others added 3 commits August 23, 2026 18:39
The M6 is a sealed solar repeater with a soldered-on L76K GNSS and two
LEDs, but neither was usable in the ThinkNode_M6_repeater build.

GPS: the repeater's NodePrefs default gps_enabled=0, so sensors.begin()
detected the L76K and then immediately powered it back down via
_location->stop(). With no button or screen on a sealed enclosure there
was no way to turn it on short of a USB console. Even once enabled, the
repeater defaulted advert_loc_policy to ADVERT_LOC_PREFS, so adverts
carried the fixed ADVERT_LAT/ADVERT_LON (0,0) and ignored the GNSS
entirely.

Both defaults are now build-time overridable (GPS_ENABLED_DEFAULT and
ADVERT_LOC_POLICY_DEFAULT) and unchanged for every other board; the M6
repeater env opts into GPS-on and ADVERT_LOC_SHARE. It also sets
ENV_SKIP_GPS_DETECT, because the 1s "is a GPS attached?" probe gates the
'gps' setting, and losing that race left GPS unreachable until reboot --
the M6 always has the module fitted. Everything stays runtime-adjustable
via 'gps on'/'gps off' and 'gps advert prefs|share|none'.

LEDs: only the blue LED was driven (LoRa TX activity). The red LED --
the enclosure's power LED -- was configured as an output and then never
touched. It now indicates state: solid through boot, then a ~1% duty
heartbeat, one blink per 5s while running and two once the GNSS has a
fix, so a deployed node can be checked through the case without a
laptop. onBootComplete() and powerOff() handle it too.

The heartbeat is driven from a small EnvironmentSensorManager subclass,
the same pattern thinknode_m1 uses, since its loop() already runs every
iteration and holds the live GNSS state -- no core or shared example
changes needed.

Verified: ThinkNode_M6_repeater builds (flash 47.8%, RAM 13.4%) and
RAK_4631_repeater still builds unchanged, confirming the shared
simple_repeater defaults are untouched for other boards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7b3E6KcBEGZ2zr2jZMrBL
A 40ms blink every 5s is easy to miss entirely at arm's length. The LEDs
sit on the bottom face of the enclosure next to the USB-C port, read in
daylight, so stretch each blink to 150ms (still ~3% duty) and add a
three-flash boot signature on both LEDs at the end of setup() -- that is
how you confirm a freshly flashed node came up, without a console.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7b3E6KcBEGZ2zr2jZMrBL
simple_room_server has the identical defect the repeater had: it defaults
gps_enabled=0 and advert_loc_policy=ADVERT_LOC_PREFS, so on a sealed M6
the L76K is detected at boot and then powered straight back down, and
adverts carry the fixed 0,0 instead of the GNSS position.

Same approach as the repeater: both defaults become build-time
overridable (declared in MyMesh.h alongside this example's other
ADVERT_* fallbacks, matching its own convention) and are unchanged for
every other board. The M6 room_server env opts in, and also skips the 1s
GPS probe since the module is always fitted.

The LED work needed nothing here -- the heartbeat lives in the variant's
sensor manager, and simple_room_server already calls board.onBootComplete()
and sensors.loop().

Verified: ThinkNode_M6_room_server and RAK_4631_room_server both build,
and envdump confirms the new flags resolve for the M6 while remaining
absent for RAK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H7b3E6KcBEGZ2zr2jZMrBL
@andyhoobing

Copy link
Copy Markdown
Author

Superseded — split into two self-contained PRs per the one-fix-per-PR guideline in CONTRIBUTING.md:

They share no files and are independent, so they can be reviewed or merged in any order. Verified the split reconstructs this branch's tree exactly (41 + 116 = 157 insertions, 4 + 3 = 7 deletions across the same 8 files), and each branch builds standalone against dev.

Closing this one to avoid duplicate review.

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.

1 participant