Skip to content

fix: re-run startup sequence when controller reconnects after power cycle#787

Open
peter-takacs wants to merge 3 commits into
jniebuhr:masterfrom
peter-takacs:fix/ble-reconnect-startup-mode
Open

fix: re-run startup sequence when controller reconnects after power cycle#787
peter-takacs wants to merge 3 commits into
jniebuhr:masterfrom
peter-takacs:fix/ble-reconnect-startup-mode

Conversation

@peter-takacs

@peter-takacs peter-takacs commented Jun 21, 2026

Copy link
Copy Markdown

Summary

Fixes the head unit getting permanently stuck in STANDBY after the machine controller power-cycles or its BLE link drops — without requiring a head-unit reboot.

This PR contains two commits:

Root cause

loaded is the gate that decides whether onSystemInfo() runs the startup sequence:

if (!loaded) {
    loaded = true;
    setMode(settings.getStartupMode());
    pluginManager->trigger("controller:ready");
}

Before this fix, a disconnect left loaded = true. On the next connect the gate was already open — setMode(startupMode) was never called and the head unit stayed locked in MODE_STANDBY with no way out short of a manual reboot.

State machine

stateDiagram-v2
    direction TB

    [*] --> Boot

    Boot --> Connecting : screenReady=true\nconnect() called

    state "BLE Lifecycle" as BLE {
        Connecting --> WaitingForController : scan > 10 s\n(controller:bluetooth:waiting)
        WaitingForController --> Connecting : scan continues
        Connecting --> StartupLoading : link up / loaded=false
        WaitingForController --> StartupLoading : link up / loaded=false
        StartupLoading --> Operational : onSystemInfo() rcvd\nloaded=true · setMode(startupMode)\ncontroller:ready fired\nconfig resent for 8 s (PR 785)
    }

    state "Operational" as Operational {
        direction LR
        [*] --> Standby : startupMode=STANDBY
        [*] --> BrewIdle : startupMode=BREW (default)

        Standby --> BrewIdle : deactivateStandby()
        BrewIdle --> Standby : activateStandby() / timeout

        BrewIdle --> BrewActive : activate()
        BrewActive --> BrewIdle : deactivate()

        BrewIdle --> Steam : steam button
        Standby --> Steam : steam button
        Steam --> BrewIdle : brew button / release
        Steam --> SteamReady : boiler at target
        SteamReady --> BrewIdle : deactivate()

        BrewIdle --> Water : water button
        Water --> WaterActive : activate()
        WaterActive --> Water : deactivate()
        Water --> BrewIdle : setMode(BREW)
    }

    Operational --> Connecting : BLE disconnect\nloaded=false [THIS FIX]\nsetMode(STANDBY)
Loading

The fix

} else if (initialized) {
    pluginManager->trigger("controller:bluetooth:disconnect");
    waitingForController = true;
    loaded = false;   // ← ensures startup sequence re-runs on next connect
    setMode(MODE_STANDBY);
}

Reconnect sequence (both fixes applied)

Head unit                        Machine board
    |<-- BLE link drops --------------|
    | loaded=false, setMode(STANDBY)  |
    |   (scanning…)                   |
    |<-- BLE link up -----------------|
    |<-- SystemInfo ------------------|
    | onSystemInfo():                 |
    |   send config, arm 8 s timer   |  ← PR #785
    |   loaded=true                   |  ← this PR
    |   setMode(startupMode)          |  ← this PR
    |   → controller:ready            |  ← this PR
    | loop() [×8 over 8 s]:           |
    |   resend config ────────────────>  ← PR #785
    | (fully operational, no reboot)  |

Test plan

  • Power-cycle the machine board while the head unit is in BREW mode → head unit returns to configured startup mode without rebooting
  • Power-cycle the machine board while the head unit is in STANDBY → same
  • Pull a shot after reconnect and confirm boiler behaviour matches the saved profile (PID/pressure settings applied correctly)
  • Confirm normal first-boot startup sequence is unchanged

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved recovery after BLE controller disconnections by ensuring the next connection performs the complete startup sequence.
    • Added temporary periodic resending of pressure, PID, and pump model settings to improve configuration reliability while connected.

centercirclesolutions and others added 2 commits June 19, 2026 14:54
The config burst (PID/pressure/pump coeffs) sent on a reconnect can be lost in
the unstable BLE window right after the link comes up, and a spurious ACK then
stops the reliable layer from retrying -- leaving the controller on firmware-
default gains (Kff 0.000) so the boiler never heats after a controller reboot.

Re-send the config burst for ~8s after each (re)connect at a 1s cadence; a copy
lands once the link settles, and coalescing keeps it to one queued payload per
type. HW-validated on bare master: controller-reset config delivery goes from
~78% fail to 30/30 pass, the re-send delivering config on the cycles that would
otherwise fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUmX6ZL8t269p38f24GSqp
…ycle

When the display head unit runs headless (always-on) and the machine is
power-cycled, the controller board reboots and reconnects via BLE. On
reconnect, onSystemInfo() was a no-op for startup because `loaded` was
already true: setMode(startupMode) and controller:ready never fired
again, leaving the display stuck in STANDBY with no way back to BREW
without manually rebooting the head unit.

Reset `loaded` to false on BLE disconnect so the next onSystemInfo()
re-runs the full startup sequence: applies the configured startup mode,
fires controller:ready (re-initialising BoilerFillPlugin, LedControl,
OTA), and resends PID/pressure/pump config.

Intended to sit on top of jniebuhr#785 which addresses reliable config delivery
in the same reconnect window.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@cla-bot

cla-bot Bot commented Jun 21, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

@coderabbitai

coderabbitai Bot commented Jun 21, 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53c9592a-bac1-4c78-907b-43d37ea6b66b

📥 Commits

Reviewing files that changed from the base of the PR and between d17f198 and ac155fd.

📒 Files selected for processing (1)
  • src/display/core/Controller.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/display/core/Controller.cpp

📝 Walkthrough

Walkthrough

Controller now re-runs startup after an initialized BLE disconnect and periodically resends pressure, PID, and pump-model configuration during a bounded window after system information is received.

Changes

BLE Reconnect State Reset and Config Resend

Layer / File(s) Summary
Reset loaded flag on BLE disconnect
src/display/core/Controller.cpp
Initialized BLE disconnects set loaded to false, causing the next connection to repeat startup processing.
Open resend window and resend configuration
src/display/core/Controller.cpp
onSystemInfo() starts a timed resend window, while loop() periodically resends pressure scale, PID settings, and pump model coefficients when connected.

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

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main reconnect fix: rerunning the startup sequence after the controller power-cycles or reconnects.
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.

@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 (1)
src/display/core/Controller.cpp (1)

524-531: 💤 Low value

Optional: use subtraction pattern for millis() overflow safety.

The check now < configResendUntil can misbehave if millis() wraps around (~49.7 days uptime) during the 8-second window. The standard overflow-safe idiom is to store the start time and compare elapsed duration:

-    unsigned long configResendUntil = 0;
+    unsigned long configResendStart = 0;
-    configResendUntil = millis() + CONFIG_RESEND_WINDOW_MS;
+    configResendStart = millis();
-    if (comms.isConnected() && now < configResendUntil && (now - lastConfigResend) >= CONFIG_RESEND_INTERVAL_MS) {
+    if (comms.isConnected() && (now - configResendStart) < CONFIG_RESEND_WINDOW_MS && (now - lastConfigResend) >= CONFIG_RESEND_INTERVAL_MS) {

This is a very rare edge case—the initial config send in onSystemInfo() still happens regardless—so not urgent.

🤖 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/display/core/Controller.cpp` around lines 524 - 531, The comparison `now
< configResendUntil` is vulnerable to millis() overflow issues that can occur
after ~49.7 days of uptime. Replace this direct deadline comparison with an
overflow-safe subtraction pattern by storing a reference time (such as the
reconnect time or configuration window start time) and checking if the elapsed
duration `(now - referenceTime) < WINDOW_DURATION_MS` remains within the allowed
window. This aligns with the overflow-safe subtraction pattern already used in
the `(now - lastConfigResend) >= CONFIG_RESEND_INTERVAL_MS` check on the same
line.
🤖 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/display/core/Controller.cpp`:
- Around line 524-531: The comparison `now < configResendUntil` is vulnerable to
millis() overflow issues that can occur after ~49.7 days of uptime. Replace this
direct deadline comparison with an overflow-safe subtraction pattern by storing
a reference time (such as the reconnect time or configuration window start time)
and checking if the elapsed duration `(now - referenceTime) <
WINDOW_DURATION_MS` remains within the allowed window. This aligns with the
overflow-safe subtraction pattern already used in the `(now - lastConfigResend)
>= CONFIG_RESEND_INTERVAL_MS` check on the same line.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43467c55-5362-44f2-b9f4-de5dde3d2f86

📥 Commits

Reviewing files that changed from the base of the PR and between 79a7086 and d17f198.

📒 Files selected for processing (2)
  • src/display/core/Controller.cpp
  • src/display/core/Controller.h

@cla-bot

cla-bot Bot commented Jul 11, 2026

Copy link
Copy Markdown

We require contributors to sign our Contributor License Agreement, and we don't have yours on file. In order for us to review and merge your code, please contact @jniebuhr (mdwasp) on Discord to get yourself added.

@sonarqubecloud

Copy link
Copy Markdown

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