Skip to content

Stop accelerometer thread when double-tap/wake-on-motion disabled at runtime#11025

Merged
thebentern merged 6 commits into
developfrom
claude/compassionate-tesla-de5f79
Jul 18, 2026
Merged

Stop accelerometer thread when double-tap/wake-on-motion disabled at runtime#11025
thebentern merged 6 commits into
developfrom
claude/compassionate-tesla-de5f79

Conversation

@thebentern

@thebentern thebentern commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

double_tap_as_button_press (device config) and wake_on_tap_or_motion (display config) are applied live, without a reboot — the requiresReboot equality gates in handleSetConfig deliberately exclude these two fields. On the OFF→ON edge, each handler calls accelerometerThread->start() and sets enabled = true.

But there was no ON→OFF branch anywhere (grep 'accelerometerThread->' shows only calibrate, enabled = true, and start()). So:

  • Turning either flag off at runtime left the accelerometer thread running — polling I2C and drawing power — until the next reboot.
  • Because enabled stayed stuck true, a later OFF→ON edge became a no-op (the enabled == false guard blocked the re-start()), leaving the feature un-restartable without a reboot.

Fix

Add the symmetric ON→OFF branch to both handlers. When a flag transitions true→false and the other consumer of the shared thread is also off, call accelerometerThread->disable():

  • OSThread::disable() sets enabled = false and setInterval(INT32_MAX), halting runOnce() (no more I2C polling) without deleting the sensor. Since init() is idempotent, a later start() cleanly resumes — no leak, fully restartable.
  • Each branch checks the other flag first, so disabling one feature never stops the sensor while the other still needs it.

Chose the live disable() (symmetric with the existing live start()) over adding these fields to the requiresReboot gate, since the flags are intentionally applied live — a reboot gate would clash with the existing live-start.

Transition matrix (D = double-tap, W = wake-on-motion; only one config variant changes per setConfig):

Transition Other flag After fix
D off→on W off start()
D on→off W off disable() (off, enabled cleared)
D on→off W on skip — W still needs it
W off→on D off start()
W on→off D off disable()
W on→off D on skip — D still needs it

This is a cooperative OSThread scheduled on mainController in the same loop context as setConfig, so disable() is safe with no concurrency concern.

Testing

  • Compile-verified for the rak4631 (nRF52840) target, where the accelerometer block is active (the native/portduino test build #if-excludes this region). AdminModule.cpp compiled cleanly.
  • Low severity: power/behavior, not a crash. No hardware required.

Summary by CodeRabbit

  • Bug Fixes
    • Improved power management by coordinating accelerometer start/stop across double-tap and wake-on-tap/motion configuration changes.
    • Prevented compass/heading freezing by detecting when the active sensor can directly provide heading and keeping motion updates running appropriately.
    • Reduced update inconsistencies by handling shared accelerometer-thread ownership more safely when motion processing availability changes.

…runtime

double_tap_as_button_press and wake_on_tap_or_motion are applied live: the
OFF->ON edge calls accelerometerThread->start(), but there was no ON->OFF
branch, so turning either flag off left the sensor thread running (polling
I2C, drawing power) until reboot. Worse, because enabled stayed true, a later
OFF->ON edge was a no-op (the enabled==false guard blocked re-start), leaving
the feature un-restartable without a reboot.

Add the symmetric ON->OFF branch in both handlers. When a flag goes true->off
and the other consumer of the shared thread is also off, call
accelerometerThread->disable() (stops runOnce polling and clears enabled so a
later re-enable can start() again). Each branch checks the other flag first so
disabling one feature never stops the sensor while the other still needs it.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

AdminModule::handleSetConfig() now centralizes accelerometer-thread reconciliation for device and display wake features. Motion sensors expose heading capability, and heading-producing sensors prevent the thread from being disabled.

Changes

Accelerometer configuration

Layer / File(s) Summary
Expose sensor heading capabilities
src/motion/MotionSensor.h, src/motion/AccelerometerThread.h, src/motion/BMM150Sensor.h, src/motion/BMX160Sensor.h, src/motion/ICM20948Sensor.h
Motion sensors report whether they provide heading data, with heading-capable implementations returning true.
Reconcile shared accelerometer ownership
src/modules/AdminModule.cpp
Device and display configuration updates use a shared helper that guards null threads, enables on feature activation, coordinates both wake features, and avoids disabling heading-producing sensors.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminModule
  participant AccelerometerThread
  participant MotionSensor
  AdminModule->>AccelerometerThread: Reconcile device or display feature transition
  AccelerometerThread->>MotionSensor: Query providesHeading()
  MotionSensor-->>AccelerometerThread: Return heading capability
  AccelerometerThread-->>AdminModule: Enable, disable, or retain thread state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main runtime accelerometer-thread change.
Description check ✅ Passed The description covers the problem, fix, transition matrix, and testing; only the template attestation checklist is omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/compassionate-tesla-de5f79

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/AdminModule.cpp (1)

791-803: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Null-pointer dereference on devices without an accelerometer.

accelerometerThread is only allocated if compatible hardware is detected at runtime. If an admin configuration update toggling these settings is received on a node lacking an accelerometer, dereferencing accelerometerThread->enabled will cause a null-pointer crash. Both the new disable() paths and the adjacent existing start() paths need to be guarded against a null pointer.

  • src/modules/AdminModule.cpp#L791-L803: Add a null check before accessing accelerometerThread for the device configuration toggle.
  • src/modules/AdminModule.cpp#L902-L913: Add a null check before accessing accelerometerThread for the display configuration toggle.
🛡️ Proposed fixes

For src/modules/AdminModule.cpp#L791-L803:

-        if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true &&
-            accelerometerThread->enabled == false) {
+        if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true &&
+            accelerometerThread && accelerometerThread->enabled == false) {
             config.device.double_tap_as_button_press = c.payload_variant.device.double_tap_as_button_press;
             accelerometerThread->enabled = true;
             accelerometerThread->start();
         }
         // Turning double-tap off: stop the accelerometer thread, but only if wake-on-motion doesn't still need it.
         // disable() clears enabled so a later re-enable can restart it.
-        else if (config.device.double_tap_as_button_press == true &&
-                 c.payload_variant.device.double_tap_as_button_press == false && config.display.wake_on_tap_or_motion == false &&
-                 accelerometerThread->enabled == true) {
+        else if (config.device.double_tap_as_button_press == true &&
+                 c.payload_variant.device.double_tap_as_button_press == false && config.display.wake_on_tap_or_motion == false &&
+                 accelerometerThread && accelerometerThread->enabled == true) {
             accelerometerThread->disable();
         }

For src/modules/AdminModule.cpp#L902-L913:

-        if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true &&
-            accelerometerThread->enabled == false) {
+        if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true &&
+            accelerometerThread && accelerometerThread->enabled == false) {
             config.display.wake_on_tap_or_motion = c.payload_variant.display.wake_on_tap_or_motion;
             accelerometerThread->enabled = true;
             accelerometerThread->start();
         }
         // Turning wake-on-motion off: stop the accelerometer thread, but only if double-tap doesn't still need it.
         // disable() clears enabled so a later re-enable can restart it.
-        else if (config.display.wake_on_tap_or_motion == true && c.payload_variant.display.wake_on_tap_or_motion == false &&
-                 config.device.double_tap_as_button_press == false && accelerometerThread->enabled == true) {
+        else if (config.display.wake_on_tap_or_motion == true && c.payload_variant.display.wake_on_tap_or_motion == false &&
+                 config.device.double_tap_as_button_press == false && accelerometerThread && accelerometerThread->enabled == true) {
             accelerometerThread->disable();
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/AdminModule.cpp` around lines 791 - 803, Guard every
accelerometerThread access in the device configuration toggle at
src/modules/AdminModule.cpp lines 791-803 and the display configuration toggle
at src/modules/AdminModule.cpp lines 902-913 with a null check, including
enabled checks, start(), and disable(). Preserve the existing toggle conditions
while ensuring updates on devices without an accelerometer do not dereference
the null pointer.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/modules/AdminModule.cpp`:
- Around line 791-803: Guard every accelerometerThread access in the device
configuration toggle at src/modules/AdminModule.cpp lines 791-803 and the
display configuration toggle at src/modules/AdminModule.cpp lines 902-913 with a
null check, including enabled checks, start(), and disable(). Preserve the
existing toggle conditions while ensuring updates on devices without an
accelerometer do not dereference the null pointer.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e4bc40e5-52c8-4c59-acb2-135433be5a7c

📥 Commits

Reviewing files that changed from the base of the PR and between 53a6b5e and d6caa4c.

📒 Files selected for processing (1)
  • src/modules/AdminModule.cpp

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Flash this PR in the Web Flasher

firmware commit boards expires

Warning

This is an automated, unreviewed CI test build. Back up your device configuration
before flashing, and only flash devices you are able to recover.

Supported boards built by this PR (29)
Device Board Platform
Crowpanel Adv 3.5 TFT elecrow-adv-35-tft esp32-s3
Heltec HT62 heltec-ht62-esp32c3-sx1262 esp32-c3
Heltec Mesh Node 096 heltec-mesh-node-t096 nrf52840
Heltec Mesh Node T1 heltec-mesh-node-t1 nrf52840
Heltec Mesh Node T114 heltec-mesh-node-t114 nrf52840
Heltec V3 heltec-v3 esp32-s3
Heltec V4 heltec-v4 esp32-s3
Meshnology W10 meshnology_w10 esp32-s3
Raspberry Pi Pico pico rp2040
Raspberry Pi Pico W picow rp2040
RAK WisMesh Pocket V3 rak_wismesh_pocket nrf52840
RAK WisMesh Repeater Mini V2 rak_wismesh_repeater_mini nrf52840
RAK WisMesh Tag rak_wismeshtag nrf52840
RAK WisBlock 11200 rak11200 esp32
RAK WisBlock 11310 rak11310 rp2040
RAK3312 rak3312 esp32-s3
RAK WisBlock 4631 rak4631 nrf52840
Seeed SenseCAP Mesh-Tracker-X1 seeed_mesh_tracker_X1 nrf52840
Seeed Wio Tracker L1 seeed_wio_tracker_L1 nrf52840
Seeed Xiao NRF52840 Kit seeed_xiao_nrf52840_kit nrf52840
Seeed Xiao ESP32-S3 seeed-xiao-s3 esp32-s3
Station G2 station-g2 esp32-s3
Station G3 station-g3 esp32-s3
LILYGO T-Deck t-deck-tft esp32-s3
LILYGO T-Echo t-echo nrf52840
LILYGO T-Echo Plus t-echo-plus nrf52840
LILYGO T-Impulse Plus t-impulse-plus nrf52840
LilyGo T3-C6 tlora-c6 esp32-c6
Seeed SenseCAP T1000-E tracker-t1000-e nrf52840

Build artifacts expire on 2026-08-16. Updated for 038841d.

@jp-bennett

Copy link
Copy Markdown
Collaborator

Does this break the compass, particularly on devices that are 6+3 axis?

@caveman99

Copy link
Copy Markdown
Member

Does this break the compass, particularly on devices that are 6+3 axis?

Not any more

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/motion/MotionSensor.h`:
- Around line 45-49: Shorten the comment above providesHeading() to no more than
two lines, retaining only the essential capability and runtime-thread behavior
details while removing redundant examples and obvious explanation. Leave the
providesHeading() implementation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 91be94b0-d4a7-48d3-87a7-d106b16fb469

📥 Commits

Reviewing files that changed from the base of the PR and between d6caa4c and 16f78be.

📒 Files selected for processing (6)
  • src/modules/AdminModule.cpp
  • src/motion/AccelerometerThread.h
  • src/motion/BMM150Sensor.h
  • src/motion/BMX160Sensor.h
  • src/motion/ICM20948Sensor.h
  • src/motion/MotionSensor.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/modules/AdminModule.cpp

Comment thread src/motion/MotionSensor.h
@thebentern thebentern added the bugfix Pull request that fixes bugs label Jul 17, 2026
The device and display config handlers had mirror-image blocks reconciling the
shared accelerometer thread. Extract reconcileAccelerometerThread(wasOn, nowOn,
otherFeatureOn) so the null guard, edge logic, compass (providesHeading) guard,
and rationale live in one place; each call site is now a single call. Behavior
is unchanged. Also drops the redundant per-field assignment that the
whole-struct `config.device = ...` / `config.display = ...` overwrites anyway.
@thebentern
thebentern force-pushed the claude/compassionate-tesla-de5f79 branch from 038841d to 44a9247 Compare July 18, 2026 11:20
@thebentern
thebentern merged commit b20d899 into develop Jul 18, 2026
22 checks passed
@thebentern
thebentern deleted the claude/compassionate-tesla-de5f79 branch July 18, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Pull request that fixes bugs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants