Authored by Claude (Opus 5 on UltraCode), approved by me - a human 😊
Summary
Printer.connect(host, enable_control=True) times out against an idle Centauri Carbon, because
it waits for the printer to spontaneously push an Attributes frame in order to learn the
MainboardID — and this firmware does not push one while idle.
The practical effect is that start_print() is unusable 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 only that
the explanation is incomplete, and in a way that costs debugging time:
— the firmware does not push Attributes while paused or errored.
It also does not push them while idle. My printer was not paused and not errored; it was sitting
idle after a completed job.
Reproduction
- Leave a Centauri Carbon idle for a few minutes — long enough that spontaneous pushes stop.
(Immediately after a job finishes it is still chatty; the failure appears once it goes quiet.)
await Printer.connect(host, enable_control=True) without mainboard_id.
- It raises after ~5 s.
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 appears to volunteer nothing at all. 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 connect() path has no equivalent prompt, so it is left waiting for something that is not coming.
Suggested fixes, cheapest first
- 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.
- Optionally: fall back to UDP discovery automatically when no
Attributes frame arrives.
discover() already returns the MainboardID, and the error text already points users at it, so
the library could simply do it.
- 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 connect() 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, firmware V1.4.46, with CANVAS attached
- Reproduced consistently; happy to test a patch, I have the hardware in this state routinely.
Best Regards - InfiniteBSOD
Authored by Claude (Opus 5 on UltraCode), approved by me - a human 😊
Summary
Printer.connect(host, enable_control=True)times out against an idle Centauri Carbon, becauseit waits for the printer to spontaneously push an
Attributesframe in order to learn theMainboardID — and this firmware does not push one while idle.
The practical effect is that
start_print()is unusable in the one printer state you would evercall it from.
What I saw
Traceback bottoms out in
wait_for_mainboard()(client.py:167-169), reached fromstart_print()(client.py:425).The error is well-written and its suggested fix works perfectly — passing
mainboard_idresolved it completely and the print then started and ran to completion. The problem is only that
the explanation is incomplete, and in a way that costs debugging time:
It also does not push them while idle. My printer was not paused and not errored; it was sitting
idle after a completed job.
Reproduction
(Immediately after a job finishes it is still chatty; the failure appears once it goes quiet.)
await Printer.connect(host, enable_control=True)withoutmainboard_id.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 appears to volunteer nothing at all. 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 ownwatch()already sends (client.py:231).The
connect()path has no equivalent prompt, so it is left waiting for something that is not coming.Suggested fixes, cheapest first
wait_for_mainboard()docstring to say "while idle, paused, orerrored". This alone would have saved me the debugging, and it is a one-line change.
Attributesframe arrives.discover()already returns the MainboardID, and the error text already points users at it, sothe library could simply do it.
watch()does.I would suggest (1) regardless; (2) and (3) are only worth it if you would rather
connect()justworked 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 theSTART_PRINTrequest insidestart_print(), so a timeout here means no command reached the printerand 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
Best Regards - InfiniteBSOD