Skip to content

feat(python): add reset_device() to SerialMonitor#52

Merged
zackees merged 3 commits intomainfrom
feat/serial-monitor-reset-device
Apr 16, 2026
Merged

feat(python): add reset_device() to SerialMonitor#52
zackees merged 3 commits intomainfrom
feat/serial-monitor-reset-device

Conversation

@zackees
Copy link
Copy Markdown
Member

@zackees zackees commented Apr 16, 2026

Summary

  • Adds reset_device(board=None) method to SerialMonitor PyO3 binding
  • Calls daemon's existing POST /api/reset endpoint for platform-specific DTR/RTS sequences
  • Works without __enter__ — uses HTTP, not WebSocket
  • TDD: 2 unit tests + 2 integration tests (ESP32-S3 on COM13)

Why

FastLED's autoresearch tool needs to reboot an ESP32 that's already booted and idle. Currently it bypasses fbuild with raw pyserial DTR toggling, which fights the daemon for the serial port. This gives fbuild a proper API for device reset.

Test plan

  • test_serial_monitor_has_reset_device — method exists on class
  • test_serial_monitor_reset_device_is_callable — method is callable
  • test_reset_esp32s3_and_read_output — resets real ESP32-S3, verifies boot output
  • test_reset_device_without_enter — works without __enter__

Closes #50

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added reset_device() method to SerialMonitor, enabling users to reset connected devices with optional board specification.
    • Method returns a boolean indicating success or failure of the reset operation.
  • Tests

    • Added unit and integration tests validating the reset functionality.

Adds a reset_device(board=None) method that calls the daemon's
POST /api/reset endpoint to perform platform-specific DTR/RTS
reset sequences. Works without __enter__ (uses HTTP, not WebSocket).

Closes #50

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Warning

Rate limit exceeded

@zackees has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 30 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 30 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a8b652a-f96f-4615-8dfe-181e17bfa862

📥 Commits

Reviewing files that changed from the base of the PR and between 731f36a and ad61c02.

📒 Files selected for processing (2)
  • .coderabbit.yaml
  • tests/test_serial_reset_integration.py
📝 Walkthrough

Walkthrough

A new reset_device method is added to SerialMonitor that sends HTTP POST requests to the fbuild daemon to reset connected devices, with accompanying unit and integration tests.

Changes

Cohort / File(s) Summary
Core Implementation
crates/fbuild-python/src/lib.rs
Added reset_device(board: Optional[str] = None) -> bool method to SerialMonitor that constructs an HTTP endpoint, sends a POST request with port and board information, and returns success status from daemon response.
Unit Tests
tests/test_serial_reset.py
Added basic attribute and callability tests verifying that SerialMonitor.reset_device exists and is invokable from Python.
Integration Tests
tests/test_serial_reset_integration.py
Added hardware integration tests with conditional skip logic for missing hardware/daemon, testing both context-manager and non-context-manager usage patterns with real ESP32-S3 device reset and serial output validation.

Sequence Diagram

sequenceDiagram
    participant Client as Python Client
    participant SM as SerialMonitor
    participant HTTP as HTTP Client
    participant Daemon as fbuild Daemon
    participant Device as ESP32 Device

    Client->>SM: reset_device(board="esp32s3")
    activate SM
    SM->>SM: Build endpoint URL from daemon_url
    SM->>HTTP: POST /api/reset with {port, board}
    activate HTTP
    HTTP->>Daemon: HTTP POST request
    activate Daemon
    Daemon->>Device: Toggle DTR/send reset signal
    Device-->>Daemon: Device reboots
    Daemon-->>HTTP: JSON response {success: true}
    deactivate Daemon
    HTTP-->>SM: Response parsed
    deactivate HTTP
    SM->>SM: Extract success field (default false)
    SM-->>Client: return bool
    deactivate SM
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Poem

🐰 A reset button appears, so fine,
Post requests through the daemon line,
ESP32 reboots with glee,
Serial monkeys can now dance free! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR adds reset_device(board=None) to SerialMonitor that calls the daemon's /api/reset endpoint. Issue #50 requires DTR-based reset with ~100ms hold and ~3s boot wait. The implementation uses HTTP instead of DTR toggles and omits timing parameters, partially addressing the issue's intent but not meeting the full specification. Either align the implementation to match issue #50's requirements (DTR toggle with dtr_hold_ms and boot_wait_ms parameters), or clarify in issue #50 that the HTTP daemon approach supersedes the original DTR-based proposal.
✅ 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 accurately describes the main change: adding a reset_device() method to the SerialMonitor Python binding.
Out of Scope Changes check ✅ Passed All changes align with the PR objectives: method implementation, unit tests for presence/callability, and integration tests for reset behavior on ESP32-S3. No extraneous modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/serial-monitor-reset-device

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/test_serial_reset_integration.py (1)

55-64: Nit: use with instead of explicit __enter__/__exit__.

The explicit dunder calls work but lose the guarantees a with block provides (and make exception paths harder to reason about vs the current try/finally). Minor readability nit.

♻️ Suggested change
-    mon2 = SerialMonitor(port="COM13", baud_rate=115200)
-    mon2.__enter__()
-    try:
-        lines = mon2.read_lines(timeout=5.0)
-        assert len(lines) > 0, (
-            "Device should produce serial output after reset. "
-            "Got 0 lines — device may not have rebooted."
-        )
-    finally:
-        mon2.__exit__(None, None, None)
+    with SerialMonitor(port="COM13", baud_rate=115200) as mon2:
+        lines = mon2.read_lines(timeout=5.0)
+        assert len(lines) > 0, (
+            "Device should produce serial output after reset. "
+            "Got 0 lines — device may not have rebooted."
+        )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_serial_reset_integration.py` around lines 55 - 64, Replace the
manual context management for SerialMonitor with a with-block: instead of
calling mon2 = SerialMonitor(...); mon2.__enter__(); try: ... finally:
mon2.__exit__(...), instantiate SerialMonitor in a with SerialMonitor(...) as
mon2: block and call mon2.read_lines(timeout=5.0); this ensures proper
enter/exit semantics and exception safety around the SerialMonitor class and the
read_lines call.
crates/fbuild-python/src/lib.rs (1)

477-488: Silent failure on reset: surface daemon diagnostics.

When the daemon returns success: false, this method discards the message (and any stdout) fields and just returns false. Users will see a failed reset with no way to know why (timeout? USB busy? platform detection failed?). The existing send_op helper at lines 730–738 already establishes a pattern of logging message/stderr to stderr on failure — consider mirroring that here for parity.

♻️ Suggested change
-        Ok(body
-            .get("success")
-            .and_then(|v| v.as_bool())
-            .unwrap_or(false))
+        let success = body
+            .get("success")
+            .and_then(|v| v.as_bool())
+            .unwrap_or(false);
+        if !success {
+            if let Some(msg) = body.get("message").and_then(|v| v.as_str()) {
+                eprintln!("[fbuild] reset_device failed: {}", msg);
+            }
+            if let Some(stdout) = body.get("stdout").and_then(|v| v.as_str()) {
+                if !stdout.is_empty() {
+                    eprintln!("[fbuild] reset stdout:\n{}", stdout);
+                }
+            }
+        }
+        Ok(success)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/fbuild-python/src/lib.rs` around lines 477 - 488, The reset handler
currently parses the JSON response into `body` and returns the boolean success
but discards diagnostic fields; update the reset method (the block that reads
`let body: serde_json::Value = resp.json()...` and returns the boolean) to
mirror `send_op`'s behavior: when `body.get("success").and_then(|v|
v.as_bool())` is false, extract `body.get("message")` and `body.get("stderr")`
(or `stdout` if present) and log them to stderr (e.g., via eprintln! or the same
logging used in `send_op`) before returning false so daemon diagnostics are
surfaced to the user. Ensure extraction handles missing/non-string fields
safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_serial_reset_integration.py`:
- Around line 19-26: The _port_exists function currently opens the serial port
(pyserial.Serial) which can conflict with a running daemon (SharedSerialManager)
that already holds the port; change _port_exists to detect the port via
enumeration instead of opening it—use pyserial.tools.list_ports.comports() to
look for the target port name (e.g., "COM13") and return True if present, False
otherwise, leaving _daemon_running and any daemon checks untouched.
- Around line 29-37: The _daemon_running function currently hardcodes
"http://127.0.0.1:8765/health", which can diverge from the daemon URL resolution
used by reset_device; replace this check to use the Daemon API from
fbuild._native so it resolves the same port/URL (e.g., import Daemon from
fbuild._native and call the Daemon availability method on it rather than
hardcoding the URL). Ensure you reference and invoke the same Daemon resolution
used by reset_device (use the Daemon class/method provided by fbuild._native to
check reachability) and return True/False based on that result.

---

Nitpick comments:
In `@crates/fbuild-python/src/lib.rs`:
- Around line 477-488: The reset handler currently parses the JSON response into
`body` and returns the boolean success but discards diagnostic fields; update
the reset method (the block that reads `let body: serde_json::Value =
resp.json()...` and returns the boolean) to mirror `send_op`'s behavior: when
`body.get("success").and_then(|v| v.as_bool())` is false, extract
`body.get("message")` and `body.get("stderr")` (or `stdout` if present) and log
them to stderr (e.g., via eprintln! or the same logging used in `send_op`)
before returning false so daemon diagnostics are surfaced to the user. Ensure
extraction handles missing/non-string fields safely.

In `@tests/test_serial_reset_integration.py`:
- Around line 55-64: Replace the manual context management for SerialMonitor
with a with-block: instead of calling mon2 = SerialMonitor(...);
mon2.__enter__(); try: ... finally: mon2.__exit__(...), instantiate
SerialMonitor in a with SerialMonitor(...) as mon2: block and call
mon2.read_lines(timeout=5.0); this ensures proper enter/exit semantics and
exception safety around the SerialMonitor class and the read_lines call.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: bd698377-20b4-4d31-91f4-dcaf5485f9d0

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff51d7 and 731f36a.

📒 Files selected for processing (3)
  • crates/fbuild-python/src/lib.rs
  • tests/test_serial_reset.py
  • tests/test_serial_reset_integration.py

Comment thread tests/test_serial_reset_integration.py Outdated
Comment thread tests/test_serial_reset_integration.py
zackees and others added 2 commits April 16, 2026 14:46
- Use port enumeration instead of opening port (avoids daemon conflict)
- Use `with` context manager instead of explicit __enter__/__exit__

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d reviews

When enabled, CodeRabbit submits REQUEST_CHANGES reviews instead of
just comments. PRs cannot be merged until all review comments are
acknowledged and CodeRabbit auto-approves.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zackees zackees merged commit 516deac into main Apr 16, 2026
76 checks passed
@zackees zackees deleted the feat/serial-monitor-reset-device branch April 16, 2026 22:06
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.

feat: FbuildSerialAdapter should support DTR reset for device reboot

1 participant