Skip to content

stm32wl(rak3172): implement cpuDeepSleep()/shutdown() via STM32LowPower#10993

Open
ndoo wants to merge 2 commits into
meshtastic:developfrom
meshmy:feat/stm32wl-lowpower
Open

stm32wl(rak3172): implement cpuDeepSleep()/shutdown() via STM32LowPower#10993
ndoo wants to merge 2 commits into
meshtastic:developfrom
meshmy:feat/stm32wl-lowpower

Conversation

@ndoo

@ndoo ndoo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements deep-sleep/shutdown for STM32WL (rak3172) using the STM32LowPower Arduino library, building on #10961's HAS_LSE/STM32RTC infrastructure.

  • cpuDeepSleep() (src/platform/stm32wl/main-stm32wl.cpp) was an empty stub, so the SDS deep-sleep PowerFSM state and low-battery shutdown never actually slept.
  • Power::shutdown() didn't include STM32WL in its arch list, so an explicit shutdown command just logged a FIXME warning.

Both gaps are closed for rak3172, gated behind the existing HAS_LSE flag (default 0, only set for rak3172). The main reason to only implement it in RAK3172 is because the remaining FLASH memory is too tight on other variants, including the RTC and LowPower libraries overflow on most other STM32WL variants.

In future, this can (and should) be rolled out to all STM32WL variants that have a hardware LSE crystal (most of them, it seems).

Dependency

This PR is based on #10961 (open, not yet merged). stm32wl-hardware-rtc (its head branch) only exists on the mesh-malaysia fork, not upstream, so this PR targets develop directly — the diff currently includes #10961's commits too, until #10961 merges.

Implementation

Both cpuDeepSleep() paths use LowPower.shutdown() (Standby mode) — with an RTC alarm for a finite wake (SDS/low-battery), without one for a forever shutdown (Power::shutdown()).

Flash budget

Flash
rak3172 before 76.7% (190,100 / 247,808)
rak3172 after 77.1% (191,452 / 247,808)
wio-e5 before/after 96.8%, byte-identical

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (please specify below)

Tested on a RAK3172-T unit, with PR #10964's TCXO fixes cherry-picked in for that testing session (not included in this PR — a RAK3172-T needs #10964 to boot at all, but a base RAK3172 does not, and this PR's changes don't depend on it). Confirmed on hardware:

  • Explicit shutdown (Power::shutdown(), Standby mode, no alarm): device saves state, sleeps the radio, enters Standby, and stays silent until manually reset.
  • Low-battery deep sleep (Standby mode with RTC alarm, finite wake): low_voltage_counter debounce fires EVENT_LOW_BATTERY, device enters deep sleep, then resets and rejoins the mesh on its own once sds_secs elapses.

Build-verified no regression on wio-e5, russell, CDEBYTE_E77-MBL (HAS_LSE defaults to 0, all new code compiled out). milesight_gs301 fails to build on develop independent of this PR (pre-existing NANOLIB_FLOAT_PRINTF/debug-print config issue, confirmed via a clean checkout).

Summary by CodeRabbit

  • New Features
    • Added reliable deep-sleep support for STM32WL devices with hardware RTC availability checks.
    • STM32WL devices now support timed wake-up and indefinite deep sleep.
  • Bug Fixes
    • Shutdown now consistently turns off indicator LEDs before entering deep sleep on supported STM32WL hardware.
    • Added safeguards to reset the device if deep-sleep recovery does not complete as expected.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d35f05f4-b36d-46fa-8ef3-92833cea5981

📥 Commits

Reviewing files that changed from the base of the PR and between c5023e4 and 01297c7.

📒 Files selected for processing (3)
  • src/Power.cpp
  • src/platform/stm32wl/main-stm32wl.cpp
  • variants/stm32/rak3172/platformio.ini
🚧 Files skipped from review as they are similar to previous changes (2)
  • variants/stm32/rak3172/platformio.ini
  • src/Power.cpp

📝 Walkthrough

Walkthrough

Changes

STM32WL now initializes STM32LowPower and implements RTC-aware timed or indefinite deep sleep. The common shutdown path also turns off indicators before deep sleep, and the RAK3172 configuration pins the required low-power library.

STM32WL low-power integration

Layer / File(s) Summary
Low-power configuration and initialization
src/platform/stm32wl/main-stm32wl.cpp, variants/stm32/rak3172/platformio.ini
The STM32WL platform initializes LowPower after RTC setup, and RAK3172 pins the STM32LowPower dependency.
STM32WL deep-sleep execution
src/platform/stm32wl/main-stm32wl.cpp, src/Power.cpp
Deep sleep checks RTC availability, closes serial output, selects timed or indefinite shutdown, resets if needed, and includes STM32WL in the LED-off shutdown branch.

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

Sequence Diagram(s)

sequenceDiagram
  participant Power
  participant STM32WL
  participant LowPower
  Power->>STM32WL: Request deep sleep
  STM32WL->>STM32WL: Check RTC availability
  STM32WL->>LowPower: Shutdown with timeout or indefinitely
  LowPower-->>STM32WL: Return unexpectedly
  STM32WL->>STM32WL: Force system reset
Loading

Possibly related PRs

Suggested reviewers: jamesarich

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 clearly summarizes the main change: adding cpuDeepSleep()/shutdown support for STM32WL via STM32LowPower.
Description check ✅ Passed The description includes summary, dependency, implementation details, flash budget, and attestations, matching the template well.
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

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.

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Note

Building this pull request… the flash button, badges and supported-board
list will appear here automatically once CI finishes.

@ndoo

ndoo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Only commit 79d126a should be reviewed (IMHO) because the commits preceding it were reviewed in #10961

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 10993 --repo meshtastic/firmware --json commits --jq '.commits[] | "\(.oid) \(.messageHeadline)"'
echo "---"
gh api repos/meshtastic/firmware/commits/79d126add6de98827fe47182d0749cb50dc30f9b --jq '.files[].filename'
echo "---diff---"
gh api repos/meshtastic/firmware/commits/79d126add6de98827fe47182d0749cb50dc30f9b --jq '.files[].patch'

Length of output: 3050


🏁 Script executed:

#!/bin/bash
set -euo pipefail
git clone --depth 60 https://github.com/meshtastic/firmware.git repo 2>&1 | tail -5
cd repo
git fetch origin pull/10993/head:pr10993 2>&1 | tail -5
git checkout pr10993 2>&1 | tail -5
sed -n '860,960p' src/Power.cpp
echo "----main-stm32wl.cpp----"
sed -n '1,150p' src/platform/stm32wl/main-stm32wl.cpp
echo "----callers of cpuDeepSleep----"
rg -n "cpuDeepSleep" -C3
echo "----portMAX_DELAY usage----"
rg -n "portMAX_DELAY" src/platform/stm32wl

Length of output: 8988


🏁 Script executed:

#!/bin/bash
set -uo pipefail
cd repo 2>/dev/null || true
echo "----callers of cpuDeepSleep----"
rg -n "cpuDeepSleep" -C3 || true
echo "----portMAX_DELAY usage----"
rg -n "portMAX_DELAY" src/platform/stm32wl || true
echo "----DELAY_FOREVER def----"
rg -n "DELAY_FOREVER" || true
echo "----doDeepSleep def----"
rg -n "void doDeepSleep|doDeepSleep\(" -C5 || true

Length of output: 530


🏁 Script executed:

#!/bin/bash
set -uo pipefail
pwd
ls
if [ -d repo ]; then
  cd repo
  git status | head -5
  echo "---"
  git grep -n "cpuDeepSleep" || true
  echo "---doDeepSleep---"
  git grep -n "doDeepSleep" || true
  echo "---DELAY_FOREVER---"
  git grep -n "DELAY_FOREVER" || true
else
  echo "no repo dir, re-cloning"
  git clone --depth 60 https://github.com/meshtastic/firmware.git repo
  cd repo
  git fetch origin pull/10993/head:pr10993
  git checkout pr10993
  git grep -n "cpuDeepSleep" || true
  echo "---doDeepSleep---"
  git grep -n "doDeepSleep" || true
  echo "---DELAY_FOREVER---"
  git grep -n "DELAY_FOREVER" || true
fi

Length of output: 4540


🏁 Script executed:

#!/bin/bash
cd repo
sed -n '200,260p' src/sleep.cpp
echo "---sleep.cpp near 380-410---"
sed -n '370,410p' src/sleep.cpp
echo "---esp32 cpuDeepSleep---"
sed -n '280,380p' src/platform/esp32/main-esp32.cpp
echo "---nrf52 cpuDeepSleep---"
sed -n '420,470p' src/platform/nrf52/main-nrf52.cpp

Length of output: 9233


@ndoo Thanks for the pointer — I focused my review on commit 79d126a only, per your note that the preceding commits were already covered in #10961.

Overall this commit looks solid:

  • The portMAX_DELAY/DELAY_FOREVER sentinel convention matches how src/Power.cpp and src/sleep.cpp already distinguish "shutdown forever" from "sleep for N ms" on ESP32/nRF52.
  • Serial.end() before LowPower.shutdown() mirrors the nRF52 cpuDeepSleep() pattern.
  • The trailing HAL_NVIC_SystemReset() as a defensive fallback (in case LowPower.shutdown() somehow returns) is a nice touch, and well commented.
  • Adding ARCH_STM32WL to Power::shutdown()'s LED-off/doDeepSleep() branch in src/Power.cpp is the correct minimal change to wire this up.
  • STM32LowPower 1.5.0 is pinned with a renovate comment, consistent with how STM32RTC is already pinned.

One thing worth a second look, in cpuDeepSleep() (src/platform/stm32wl/main-stm32wl.cpp, Line 134-onward):

if (!stm32wlRtcAvailable())
    return;

If the LSE crystal fails to lock at boot (e.g., missing/faulty crystal on a given rak3172 unit), this makes every later cpuDeepSleep() call — both explicit Power::shutdown() and low-battery SDS — a silent no-op. doDeepSleep()/Power::shutdown() still run through their full flow (LEDs off, radio off, nodeDB->saveToDisk(), notifyObservers(), etc.), but the MCU never actually enters Standby or resets — it just falls through and keeps running as if nothing happened. stm32wlSetup() does log a LOG_WARN once at boot when the LSE fails, but there's nothing logged at the actual moment a shutdown/deep-sleep is attempted and silently skipped, which could be confusing when debugging a unit that "shut down" but stayed alive.

This isn't a regression (the previous empty stub was equally silent), and it's an edge case gated behind a boot-time hardware fault, so it's not blocking. A small improvement would be to add a LOG_WARN right before the early return so it's visible in the logs at the time of the failed shutdown attempt, not just at boot:

if (!stm32wlRtcAvailable()) {
    LOG_WARN("STM32WL: hardware RTC unavailable, cannot deep sleep/shutdown");
    return;
}
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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.

🧹 Nitpick comments (2)
src/Power.cpp (1)

906-919: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make failed STM32WL sleep attempts observable.

If LSE initialization fails, the STM32WL deep-sleep implementation can return early when the RTC is unavailable, leaving the device running after shutdown was requested. Emit a warning at the attempted sleep/shutdown point, not only during boot, so this failure is diagnosable.

🤖 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/Power.cpp` around lines 906 - 919, Update the STM32WL shutdown path
surrounding doDeepSleep to emit a warning when the deep-sleep attempt cannot
proceed because the RTC/LSE is unavailable. Keep the warning at the
sleep/shutdown call site so failed attempts are observable, while preserving the
existing LED shutdown behavior and sleep flow for successful attempts.
src/platform/stm32wl/main-stm32wl.cpp (1)

137-138: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Log skipped deep-sleep requests.

If LSE initialization failed, this return makes shutdown/deep sleep a no-op; the boot-time warning does not identify the skipped sleep attempt. Emit LOG_WARN before returning.

Proposed change
     if (!stm32wlRtcAvailable())
+        LOG_WARN("STM32WL: cannot enter deep sleep; hardware RTC unavailable");
+    if (!stm32wlRtcAvailable())
         return;

As per coding guidelines, prefer LOG_WARN for logging.

🤖 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/platform/stm32wl/main-stm32wl.cpp` around lines 137 - 138, In the
shutdown/deep-sleep path guarded by stm32wlRtcAvailable(), emit a LOG_WARN
describing that the sleep request is being skipped before returning when the RTC
is unavailable. Keep the existing early-return behavior unchanged.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@src/platform/stm32wl/main-stm32wl.cpp`:
- Around line 137-138: In the shutdown/deep-sleep path guarded by
stm32wlRtcAvailable(), emit a LOG_WARN describing that the sleep request is
being skipped before returning when the RTC is unavailable. Keep the existing
early-return behavior unchanged.

In `@src/Power.cpp`:
- Around line 906-919: Update the STM32WL shutdown path surrounding doDeepSleep
to emit a warning when the deep-sleep attempt cannot proceed because the RTC/LSE
is unavailable. Keep the warning at the sleep/shutdown call site so failed
attempts are observable, while preserving the existing LED shutdown behavior and
sleep flow for successful attempts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 54007e5c-9872-42ba-b69f-c56331efcbb9

📥 Commits

Reviewing files that changed from the base of the PR and between 7a25ffd and 79d126a.

📒 Files selected for processing (54)
  • src/Power.cpp
  • src/RedirectablePrint.cpp
  • src/gps/GPS.cpp
  • src/gps/NMEAWPL.cpp
  • src/gps/RTC.cpp
  • src/gps/RTC.h
  • src/graphics/SharedUIDisplay.cpp
  • src/graphics/draw/UIRenderer.cpp
  • src/graphics/niche/InkHUD/Applet.cpp
  • src/graphics/niche/InkHUD/Applets/Bases/NodeList/NodeListApplet.cpp
  • src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp
  • src/graphics/niche/InkHUD/Applets/System/Notification/NotificationApplet.cpp
  • src/graphics/niche/InkHUD/Applets/User/Heard/HeardApplet.cpp
  • src/graphics/niche/InkHUD/Applets/User/RecentsList/RecentsListApplet.cpp
  • src/graphics/niche/InkHUD/Applets/User/ThreadedMessage/ThreadedMessageApplet.cpp
  • src/graphics/niche/InkHUD/Events.cpp
  • src/main.cpp
  • src/main.h
  • src/mesh/MeshService.cpp
  • src/mesh/NodeDB.cpp
  • src/mesh/PhoneAPI.cpp
  • src/mesh/Router.cpp
  • src/mesh/StreamAPI.cpp
  • src/mesh/TransmitHistory.cpp
  • src/mesh/eth/ethClient.cpp
  • src/mesh/wifi/WiFiAPClient.cpp
  • src/modules/AdminModule.cpp
  • src/modules/ExternalNotificationModule.cpp
  • src/modules/KeyVerificationModule.cpp
  • src/modules/MeshBeaconModule.cpp
  • src/modules/NeighborInfoModule.cpp
  • src/modules/NodeInfoModule.cpp
  • src/modules/PositionModule.cpp
  • src/modules/PowerStressModule.cpp
  • src/modules/RangeTestModule.cpp
  • src/modules/RemoteHardwareModule.cpp
  • src/modules/SerialModule.cpp
  • src/modules/StoreForwardModule.cpp
  • src/modules/Telemetry/AirQualityTelemetry.cpp
  • src/modules/Telemetry/DeviceTelemetry.cpp
  • src/modules/Telemetry/EnvironmentTelemetry.cpp
  • src/modules/Telemetry/HealthTelemetry.cpp
  • src/modules/Telemetry/PowerTelemetry.cpp
  • src/modules/Telemetry/Sensor/PMSA003ISensor.h
  • src/modules/Telemetry/Sensor/SCD4XSensor.h
  • src/modules/Telemetry/Sensor/SEN5XSensor.h
  • src/modules/Telemetry/Sensor/SFA30Sensor.h
  • src/modules/esp32/AudioModule.cpp
  • src/mqtt/MQTT.cpp
  • src/platform/stm32wl/architecture.h
  • src/platform/stm32wl/main-stm32wl.cpp
  • src/security/EncryptedStorage.cpp
  • variants/stm32/rak3172/platformio.ini
  • variants/stm32/rak3172/variant.h

@ndoo

ndoo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

One thing worth a second look, in cpuDeepSleep() (src/platform/stm32wl/main-stm32wl.cpp, Line 134-onward):

if (!stm32wlRtcAvailable())
    return;

If the LSE crystal fails to lock at boot (e.g., missing/faulty crystal on a given rak3172 unit), this makes every later cpuDeepSleep() call — both explicit Power::shutdown() and low-battery SDS — a silent no-op. doDeepSleep()/Power::shutdown() still run through their full flow (LEDs off, radio off, nodeDB->saveToDisk(), notifyObservers(), etc.), but the MCU never actually enters Standby or resets — it just falls through and keeps running as if nothing happened. stm32wlSetup() does log a LOG_WARN once at boot when the LSE fails, but there's nothing logged at the actual moment a shutdown/deep-sleep is attempted and silently skipped, which could be confusing when debugging a unit that "shut down" but stayed alive.

This isn't a regression (the previous empty stub was equally silent), and it's an edge case gated behind a boot-time hardware fault, so it's not blocking. A small improvement would be to add a LOG_WARN right before the early return so it's visible in the logs at the time of the failed shutdown attempt, not just at boot:

if (!stm32wlRtcAvailable()) {
    LOG_WARN("STM32WL: hardware RTC unavailable, cannot deep sleep/shutdown");
    return;
}
  1. Added the logging as suggested, thank you
  2. Added Serial flushes before both Serial.end() to ensure logging from here or elsewhere successfully make it to the receiving device.
  3. Reset the MCU to avoid the user ending up with a stalled device

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 10993 --repo meshtastic/firmware --json commits --jq '.commits[-3:] | .[] | "\(.oid) \(.messageHeadline)"'
echo "---latest diff for main-stm32wl.cpp---"
gh pr diff 10993 --repo meshtastic/firmware -- src/platform/stm32wl/main-stm32wl.cpp

Length of output: 959


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 10993 --repo meshtastic/firmware --json commits --jq '.commits[] | "\(.oid) \(.messageHeadline)"'

Length of output: 863


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 10993 --repo meshtastic/firmware --json headRefOid,updatedAt --jq '.'
echo "---"
gh api repos/meshtastic/firmware/commits/79d126add6de98827fe47182d0749cb50dc30f9b --jq '.commit.author.date'
echo "---file at head---"
gh api repos/meshtastic/firmware/contents/src/platform/stm32wl/main-stm32wl.cpp?ref=pr10993 2>/dev/null || true

Length of output: 773


🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh api repos/meshtastic/firmware/contents/src/platform/stm32wl/main-stm32wl.cpp?ref=79d126add6de98827fe47182d0749cb50dc30f9b --jq '.content' | base64 -d | sed -n '1,200p'

Length of output: 6808


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rm -rf repo
git clone --depth 100 https://github.com/meshtastic/firmware.git repo 2>&1 | tail -3
cd repo
git fetch origin pull/10993/head 2>&1 | tail -5
git rev-parse FETCH_HEAD
git log --oneline -5 FETCH_HEAD

Length of output: 953


Confirmed: the current head of the PR is still 79d126a, which is the same commit I already reviewed — the changes you mentioned (LOG_WARN on the failed shutdown path, Serial.flush() before Serial.end(), and the added MCU reset) don't appear to have been pushed to this branch yet. I checked cpuDeepSleep() at the head commit and it's unchanged from what I reviewed:

void cpuDeepSleep(uint32_t msecToWake)
{
`#if` HAS_LSE
    if (!stm32wlRtcAvailable())
        return;

    if (Serial)
        Serial.end();
    ...

No LOG_WARN, no Serial.flush(), and the previous non-LSE case already relies on the fallback HAL_NVIC_SystemReset() at the end — but that's still gated behind #if HAS_LSE, so if stm32wlRtcAvailable() is false, there's still no reset (only the early return) in what's currently pushed.

Could you push the commit with these changes? Once it's on the branch I'll take another look at the updated cpuDeepSleep()/stm32wlSetup() logic — specifically the LOG_WARN placement, the Serial.flush() calls, and where you added the reset for the no-RTC case.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ndoo

ndoo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Pushed

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 10993 --repo meshtastic/firmware --json headRefOid,commits --jq '.headRefOid, (.commits[-3:] | .[] | "\(.oid) \(.messageHeadline)")'
echo "---diff for main-stm32wl.cpp---"
gh pr diff 10993 --repo meshtastic/firmware -- src/platform/stm32wl/main-stm32wl.cpp

Length of output: 1027


🏁 Script executed:

#!/bin/bash
set -euo pipefail
cd /tmp
rm -rf repo
git clone --depth 100 https://github.com/meshtastic/firmware.git repo 2>&1 | tail -5
cd repo
git fetch origin pull/10993/head 2>&1 | tail -5
git checkout FETCH_HEAD 2>&1 | tail -5
echo "---full file---"
cat -n src/platform/stm32wl/main-stm32wl.cpp | sed -n '1,220p'

Length of output: 9501


@ndoo Thanks for pushing bfe8c291fc139d0d187df1181ad20e2e61e829c7 — confirmed it's now on the branch and I reviewed it against my earlier comment.

The three changes you mentioned are all there and look good:

  • LOG_WARN("STM32WL: hardware RTC failed, cannot deep sleep/shutdown") is now logged right at the failed shutdown/deep-sleep attempt (Line 138), in addition to the boot-time warning in stm32wlSetup(). This resolves the "silent no-op" concern I raised.
  • Serial.flush() calls before Serial.end() in both branches should help ensure any pending log output (including the new warning) actually reaches the receiving terminal before the UART is torn down.
  • The no-RTC path now calls HAL_NVIC_SystemReset() instead of just returning, so a unit with a failed LSE won't get stuck "silently alive" after a shutdown attempt — it'll cycle instead.

Two small nits, neither blocking:

  1. In the no-RTC branch (Lines 138-141), Serial.flush()/Serial.end() are called unconditionally, while the normal path a few lines below guards the flush with if (Serial) (Line 144). For consistency (and to avoid touching an uninitialized/disabled Serial object on boards where it's compiled out), you could mirror the same guard in the no-RTC branch.
  2. Like the final fallback reset at Line 154, the reset in the no-RTC branch relies on HAL_NVIC_SystemReset() never returning; a short comment (or restructuring as an else) would make it clearer that falling through into LowPower.shutdown() without a working RTC is unintended, matching the documentation style already used for the bottom fallback.

Nice work — this addresses the concern well.

[resolve]

ᐟ(•ㅅ•)ᐟ

@ndoo
ndoo force-pushed the feat/stm32wl-lowpower branch from bfe8c29 to 21878ea Compare July 11, 2026 19:37
@ndoo

ndoo commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

The three changes you mentioned are all there and look good:

  • LOG_WARN("STM32WL: hardware RTC failed, cannot deep sleep/shutdown") is now logged right at the failed shutdown/deep-sleep attempt (Line 138), in addition to the boot-time warning in stm32wlSetup(). This resolves the "silent no-op" concern I raised.
  • Serial.flush() calls before Serial.end() in both branches should help ensure any pending log output (including the new warning) actually reaches the receiving terminal before the UART is torn down.
  • The no-RTC path now calls HAL_NVIC_SystemReset() instead of just returning, so a unit with a failed LSE won't get stuck "silently alive" after a shutdown attempt — it'll cycle instead.

Two small nits, neither blocking:

  1. In the no-RTC branch (Lines 138-141), Serial.flush()/Serial.end() are called unconditionally, while the normal path a few lines below guards the flush with if (Serial) (Line 144). For consistency (and to avoid touching an uninitialized/disabled Serial object on boards where it's compiled out), you could mirror the same guard in the no-RTC branch.
  2. Like the final fallback reset at Line 154, the reset in the no-RTC branch relies on HAL_NVIC_SystemReset() never returning; a short comment (or restructuring as an else) would make it clearer that falling through into LowPower.shutdown() without a working RTC is unintended, matching the documentation style already used for the bottom fallback.

Force-pushed for a few fixes:

  1. Done and fixed an indentation/scope error for the second Serial.end()
  2. Comment added

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 10993 --repo meshtastic/firmware --json headRefOid,commits --jq '.headRefOid, (.commits[-3:] | .[] | "\(.oid) \(.messageHeadline)")'
echo "---diff for main-stm32wl.cpp---"
gh pr diff 10993 --repo meshtastic/firmware -- src/platform/stm32wl/main-stm32wl.cpp
echo "---full file at head---"
HEAD=$(gh pr view 10993 --repo meshtastic/firmware --json headRefOid --jq '.headRefOid')
gh api repos/meshtastic/firmware/contents/src/platform/stm32wl/main-stm32wl.cpp?ref=$HEAD --jq '.content' | base64 -d | cat -n | sed -n '1,220p'

Length of output: 1292


🏁 Script executed:

#!/bin/bash
set -euo pipefail
HEAD="21878ea3c36909c8e76213825b7d2772c4d2d2e2"
echo "---diff for main-stm32wl.cpp---"
gh pr diff 10993 --repo meshtastic/firmware --patch | awk '/^diff --git a\/src\/platform\/stm32wl\/main-stm32wl.cpp/,/^diff --git a\/[^s]/' | head -300
echo "---full file at head---"
gh api repos/meshtastic/firmware/contents/src/platform/stm32wl/main-stm32wl.cpp?ref=$HEAD --jq '.content' | base64 -d | cat -n | sed -n '1,220p'

Length of output: 12519


🏁 Script executed:

#!/bin/bash
set -euo pipefail
HEAD="21878ea3c36909c8e76213825b7d2772c4d2d2e2"
echo "---full file at head---"
gh api repos/meshtastic/firmware/contents/src/platform/stm32wl/main-stm32wl.cpp?ref=$HEAD --jq '.content' | base64 -d | cat -n

Length of output: 11232


@ndoo Confirmed on the current head (21878ea3c36909c8e76213825b7d2772c4d2d2e2) — both nits are addressed:

  1. The Serial guard is now consistent in both branches — the no-RTC path at Line 141-144 now uses if (Serial) { Serial.flush(); Serial.end(); }, matching the normal path at Line 148-151.
  2. The comment at Line 138-139 (// Hardware can't shutdown, but firmware has already prepared itself for shutdown / // Do not leave the device unresponsive, reset instead) clarifies the intent of the reset in the no-RTC branch, consistent with the documentation style used for the bottom fallback reset at Line 158-159.

This looks good to me now — nice work on cpuDeepSleep()/stm32wlSetup().

[resolve]

@ndoo
ndoo marked this pull request as ready for review July 11, 2026 19:41
@thebentern

Copy link
Copy Markdown
Contributor

Please fix the merge conflicts

@ndoo

ndoo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Please fix the merge conflicts

#10961 must merge before this PR btw, so I will fix the conflicts there too first

@ndoo
ndoo force-pushed the feat/stm32wl-lowpower branch from 21878ea to c5023e4 Compare July 16, 2026 16:01
@ndoo

ndoo commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Please fix the merge conflicts

fixed the merge conflicts in #10961 then rebased this PR on that cleanly

@thebentern thebentern added the enhancement New feature or request label Jul 16, 2026
ndoo added 2 commits July 17, 2026 08:34
cpuDeepSleep() was an empty stub, so the SDS deep-sleep PowerFSM state
and low-battery shutdown did nothing but leave the CPU running at full
power. Power::shutdown() also didn't include STM32WL in its arch list,
so an explicit shutdown command just logged a FIXME warning.

Adds STM32LowPower as a lib_dep (rak3172 only, mirroring STM32RTC) and
implements cpuDeepSleep() using it. Standby mode is used for both the
finite-wake (SDS/low battery) and forever (shutdown) paths, via
LowPower.shutdown(), with or without an RTC alarm.

If the LSE-backed hardware RTC never came up (stm32wlRtcAvailable()
false), this is a no-op - safer than sleeping without a confirmed wake
source. LowPower.shutdown() resets the MCU on wake and should never
return; if it somehow does, force a reset via HAL_NVIC_SystemReset()
rather than hanging the device silently forever.

Gated behind the existing HAS_LSE flag, so this is inert on every
variant but rak3172.

Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>
This is to prevent leaving the firmware catanonic when firmware has run
its shutdown routine but doesn't actually shutdown.

Signed-off-by: Andrew Yong <me@ndoo.sg>
@ndoo
ndoo force-pushed the feat/stm32wl-lowpower branch from c5023e4 to 01297c7 Compare July 17, 2026 00:34
@ndoo

ndoo commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

rebased this on HEAD of develop now that #10961 has been merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants