Skip to content

start_print() fails on an idle Centauri Carbon: every command waits up to 5s for a MainboardID frame the printer does not reliably pushΒ #3

Description

@InfiniteBSOD

Authored by Claude (Opus 5 on UltraCode), approved by me - a human 😊


✏️ Edited 2026-08-09 β€” I had the mechanism wrong. Corrected in place.

(This edit was also AI-authored, same model and mode, and human-approved.)

I went back and measured this properly instead of inferring it, and two things in my original
report were wrong. I've corrected them here rather than leaving the wrong version standing with a
correction buried in a comment. What changed:

  1. connect() is not what waits. I claimed Printer.connect() times out. It does not β€” it
    opens the socket, starts the reader and returns, in any printer state. The 5 s gate is
    wait_for_mainboard(), which every command calls. My own traceback said so; the summary
    contradicted it.
  2. The printer does not "never" push β€” it pushes intermittently. I wrote that an idle CC1
    "volunteers nothing at all". Measured, that is too strong, and the real behaviour is worse.

What still stands, unchanged: the error string and docstring say "paused or errored" and omit
idle, which is the state this actually bites in; passing mainboard_id fixes it every time;
and the failure is a clean no-op. Suggested fix (1) is unchanged and still the cheap one.

Summary

Every command that needs the MainboardID β€” including start_print() β€” can fail against an idle
Centauri Carbon. Each one calls wait_for_mainboard(), which waits up to 5 s for the printer to
volunteer a frame carrying the MainboardID, and an idle CC1 does not reliably volunteer one.

The practical effect is that start_print() is unreliable in the one printer state you would ever
call it from.

What I saw

pycentauri.client.PrinterError: printer did not push Attributes within 5.0s.
  Pass mainboard_id=... to Printer.connect() (discover() provides one)
  β€” the firmware does not push Attributes while paused or errored.

Traceback bottoms out in wait_for_mainboard() (client.py:167-169), reached from
start_print() (client.py:425).

The error is well-written and its suggested fix works perfectly β€” passing mainboard_id
resolved it completely and the print then started and ran to completion. The problem is that the
explanation is incomplete, in a way that costs debugging time:

β€” the firmware does not push Attributes while paused or errored.

It also fails to push them while idle. My printer was not paused and not errored; it was sitting
idle after a completed job.

Measurements (2026-08-09)

I tested the gate directly, reaching it through attributes() rather than start_print() so that a
test could never start a job, with enable_control=False. A print finished at 16:07:48; both
samples are after it, machine idle:

quiet for passive wait_for_mainboard(), no command sent attributes(), no mainboard_id attributes(), mainboard_id set
8.5 min frame arrived at 2.06 s raised at 5.01 s succeeded, 0.13 s
13.1 min nothing in 5.01 s raised at 5.01 s succeeded, 0.16 s

The same script during a print, as a control: a frame arrived in 3.06 s and the call succeeded in
1.20 s.

Two things fall out of this that I did not understand when I filed:

  • The pushes are intermittent, not absent. At 8.5 min a frame arrived for the passive listen and
    then nothing arrived for the very next call, seconds later. The machine only went reliably silent
    somewhere between 8.5 and 13 minutes after the job ended.
  • That makes it worse, not better. It fires while residual frames are still coming, so it
    presents as flaky rather than deterministic β€” the kind of thing a user writes off as the printer
    being temperamental rather than reporting.

Note also the margin in the good case: even mid-print the frame took 3.06 s against a 5 s timeout.
The gate is not comfortably satisfied there, just satisfied.

Reproduction

  1. Leave a Centauri Carbon idle. It reproduces from roughly 8 minutes after a job ends, and
    reliably once the machine has gone quiet (~13 min for me). It will not reproduce while a
    print is running, which is what makes it easy to miss.
  2. await Printer.connect(host, enable_control=True) without mainboard_id. ⚠️ This step
    succeeds β€” connect() never awaits the MainboardID. My original report had this wrong.
  3. Call anything that needs the MainboardID β€” start_print(), attributes(), status(). It raises
    after ~5 s from wait_for_mainboard().

Passing mainboard_id=<id from discover()> makes it work every time.

Why I think this is the same root cause as the status-push behaviour

An idle CC1 volunteers frames only sporadically. I hit the identical shape of problem when reading
status: a purely passive listener times out at idle, and the fix was to stop waiting and ask β€”
Cmd 0 GET_PRINTER_STATUS, which pycentauri's own watch() already sends (client.py:231).
The wait_for_mainboard() path has no equivalent prompt, so it is left waiting for something that
may or may not come.

Suggested fixes, cheapest first

  1. Amend the message and the wait_for_mainboard() docstring to say "while idle, paused, or
    errored"
    . This alone would have saved me the debugging, and it is a one-line change.
  2. Optionally: fall back to UDP discovery automatically when no frame arrives. discover()
    already returns the MainboardID (and the firmware version), and the error text already points
    users at it, so the library could simply do it.
  3. Optionally: send Cmd 0 during connect to prompt a frame, mirroring what watch() does.

I would suggest (1) regardless; (2) and (3) are only worth it if you would rather commands just
worked without callers needing to know this.

Worth noting for anyone else hitting it

The failure happens before anything is put on the wire β€” wait_for_mainboard() runs ahead of the
START_PRINT request inside start_print(), so a timeout here means no command reached the printer
and nothing was started. I verified the printer was still idle with no active job afterwards. That
matters because Cmd 128's idempotency is undocumented, so it is useful to know this particular
failure is definitively a no-op rather than an ambiguous one.

Environment

  • pycentauri 0.9.0, Python 3.13, Windows
  • Centauri Carbon with CANVAS attached
  • Originally reported on firmware V1.4.46. The 2026-08-09 measurements above are on V1.4.49,
    so this is still present after a firmware update, not something that has since been fixed.
  • Reproduced consistently; happy to test a patch, I have the hardware in this state routinely.

Best Regards - InfiniteBSOD

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions