Skip to content

feat(wui): expose M0/QuickPause dialog message in /api/v1/status - #5400

Open
packerlschupfer wants to merge 2 commits into
prusa3d:masterfrom
packerlschupfer:pr/dialog-message
Open

feat(wui): expose M0/QuickPause dialog message in /api/v1/status#5400
packerlschupfer wants to merge 2 commits into
prusa3d:masterfrom
packerlschupfer:pr/dialog-message

Conversation

@packerlschupfer

Copy link
Copy Markdown

Summary

Extends the dialog object from #5230 to include the human-readable text of an active M0/M1 (Prusa's QuickPause FSM).

Requested by @jabdoa2 in #5230 comment for Home Assistant integration.

Example

M0 Insert magnets now produces:

{
  "dialog": {
    "id": 12345,
    "code": 31829,
    "message": "Insert magnets",
    "button0": "Continue",
    "button1": "",
    "button2": "",
    "button3": ""
  }
}

Motivation

Currently the message from an M0/M1 is only logged to serial once (via SERIAL_ECHOLN(args)) and never re-emitted. Any HTTP/API client that connects AFTER the printer stopped for user input has no way to know what the printer is asking for. This is a real gap flagged by users trying to build Home Assistant / dashboard integrations.

The message is already present in the firmware — Prusa's M0 handler (src/marlin_stubs/M0.cpp) packs the message pointer (parser.string_arg) into fsm::PhaseData. This PR just unpacks it and threads it into the JSON.

Implementation

  • src/state/printer_state.cpp — in the ClientFSM::QuickPause case of get_state_with_dialog, use the existing fsm::deserialize_data<const char *>() helper to unpack the message pointer, then set dialog->text.
  • lib/WUI/nhttp/status_renderer.cpp — read dialog->text and emit as a "message" field in the JSON.
  • Emits empty string ("") when the parameter-less form of M0 was called, to keep the JSON schema stable.

Pointer lifetime

parser.string_arg points into Marlin's global command-line buffer. Its lifetime is bounded by the M0 handler's scope, which is guaranteed to outlive the FSM because:

  1. planner.synchronize() empties the planner queue before entering the response-wait loop.
  2. idle(true) during the wait doesn't advance the gcode queue.
  3. No other gcode can be parsed while M0 blocks — meaning parser.string_arg cannot be overwritten.

Dependency

Depends on #5230 for the base dialog object structure. Once #5230 lands, this PR's diff against master becomes just the two-line addition of unpacking the message + emitting the JSON field.

Test plan

  • Firmware builds cleanly (--preset coreone --bootloader no --skip-bootstrap)
  • Runtime verification on a Core One+: send M0 test message, poll /api/v1/status, confirm dialog.message == "test message"
  • Parameter-less M0 case: confirm dialog.message == ""

Happy to bench-test on my Core One+ once #5230 is merged.

Packerlschupfer and others added 2 commits July 26, 2026 21:39
…alogs

Expose active printer dialogs in the /api/v1/status response, enabling
remote monitoring and response to printer prompts (filament operations,
crash recovery, warnings).

Status API change - when a dialog is active:
```json
{
  "printer": { ... },
  "dialog": {
    "id": 12345,
    "code": 31829,
    "button0": "Stop",
    "button1": "",
    "button2": "",
    "button3": ""
  }
}
```

Also implements the upstream TODO in printer_state.cpp to expose
dialogs for normal (non-printing) filament load/unload operations.
Previously only load/unload dialogs during printing were reported.

Note: Dialog response functionality requires the /api/v1/control
endpoint (separate PR) with {"dialog_response": "Stop"}.
Extends the dialog object from prusa3d#5230 to include the human-readable text
of an active M0/M1 (Prusa QuickPause FSM). Requested by @jabdoa2 for
Home Assistant integration in prusa3d#5230.

Example: `M0 Insert magnets` now produces

    {
      "dialog": {
        "id": 12345,
        "code": 31829,
        "message": "Insert magnets",   ← NEW
        "button0": "Continue",
        ...
      }
    }

Implementation:
- Prusa's M0 handler in src/marlin_stubs/M0.cpp already packs the
  message pointer (parser.string_arg) into fsm::PhaseData via memcpy.
- Unpack it in printer_state.cpp's QuickPause case using the existing
  fsm::deserialize_data<const char *>() helper.
- Thread it through StateWithDialog::dialog::text (field already
  existed but was never populated) into the JSON emission via a new
  "message" field.
- Emits an empty string when the parameter-less form of M0 was called
  (parser.string_arg == nullptr), to keep the JSON schema stable.

The pointer stays valid across the wait because parser.string_arg
lives in Marlin's global command line buffer, and M0 blocks the
planner via planner.synchronize() before entering the response-wait
loop — no other gcode is being parsed while the dialog is active.

Depends on prusa3d#5230 for the dialog object structure.
@packerlschupfer

Copy link
Copy Markdown
Author

Small housekeeping update — I've moved my Core One+ to Klipper (packerlschupfer/coreone-firmware) as the daily-driver firmware and stopped active Buddy-side development.

Heads-up: I've moved my Core One+ to Klipper as the daily-driver firmware. This PR is still ready-to-review as-is — code builds cleanly on current master, the M0-message-unpack logic is small and straightforward. If a Prusa maintainer wants to merge it, great; I just won't be iterating on it further.

What this means for this PR:

  • The code as-shipped is stable and was tested on hardware (Core One+) before I moved off Buddy.
  • I'm happy for a Prusa maintainer to merge as-is if it's useful.
  • I won't be picking up feature follow-ups (gcode variable, M0 message text extras, further status fields, etc.) — someone else is welcome to fork or extend.
  • If upstream drifts and this PR needs a rebase, someone else will need to do it. Its current base is fresh (rebased 2026-07-26).

Thanks for the reviews!

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