Skip to content

Fix camera on CC1 firmware >= V1.4.x: enable video stream via SDCP Cmd 386 - #4

Open
AeromXundes wants to merge 1 commit into
bjan:mainfrom
AeromXundes:fix/video-stream-cmd-386
Open

Fix camera on CC1 firmware >= V1.4.x: enable video stream via SDCP Cmd 386#4
AeromXundes wants to merge 1 commit into
bjan:mainfrom
AeromXundes:fix/video-stream-cmd-386

Conversation

@AeromXundes

Copy link
Copy Markdown

Problem

On a CC1 updated to firmware V1.4.49, every camera surface times out:
centauri snapshot raises ReadTimeout, and the server's /snapshot,
/stream, and RTSP bridge all 502. The MJPEG port itself looks healthy —
GET http://<printer>:3031/video answers HTTP 200 — but the body is
zero bytes, indefinitely.

This is distinct from the connection-slot starvation fixed in 0.7.0's
CameraBroadcaster: it reproduces from a fresh printer boot with exactly one
client connected.

Timing note: V1.4.49 shipped 2026-07-29 — after this library's last release
(v0.9.0, 2026-07-16) — which may be why it hasn't been reported. I can only
attest V1.1.29 (no gate) and V1.4.49 (gate); the earlier V1.4.x releases
(April/May 2026) are untested and may also be affected.

Root cause

Newer firmware gates the MJPEG stream behind SDCP Cmd 386 ("enable video
stream"): the port accepts connections but emits no frames until the stream is
enabled. pycentauri never sends Cmd 386 — older firmware streamed
unconditionally, so it never had to.

Cmd 386 is present-but-commented-out in the official SDK
(elegoo_fdm_cc_message_adapter.cpp: // {MethodType::VIDEO_STREAM, 386}, // Get camera video stream) — the same situation as Cmds 258/259, which this
library already ships. Request payload is {"Enable": 1|0}; the response
Data carries {"Ack": 0} plus a VideoUrl. Ack codes (per the OpenCentauri
SDCP docs, https://docs.opencentauri.cc/software/api/): 0 = success,
1 = max streams exceeded, 2 = camera does not exist, 3 = unknown error.

Verified live on V1.4.49: sending Cmd 386 once makes :3031/video stream
immediately; the enable is global on the printer and resets when it reboots.

Changes

  • sdcp.Cmd.ENABLE_VIDEO_STREAM = 386, with the SDK reference comment.
  • New public Printer.set_video_stream(enabled=True, force=False) — returns
    the firmware-reported VideoUrl, if any. Version-gated: the command is
    only sent when the firmware version the printer reports in its Attributes
    parses to >= V1.4; on older or unknown firmware it is a no-op returning
    None unless force=True. CC2Printer overrides it as a documented no-op
    (the CC2 camera does not gate on SDCP).
  • Printer.snapshot() enables the stream first (capped at
    min(timeout, 8 s) so SDCP waits can't eat the camera-read budget;
    failures logged, read still attempted), then adopts the port/path of
    the returned VideoUrl while always connecting to the configured host —
    the printer reports its own LAN address in the URL, which is the wrong
    host behind a tunnel/NAT, and a firmware-controlled string shouldn't pick
    the fetch target anyway. Note: V1.4.49 returns the URL scheme-less
    (172.16.2.78:3031/video) even though the OpenCentauri docs show
    http://… — the client normalizes before parsing.
  • The standalone centauri rtsp command sends the enable (short-lived SDCP
    connection) before wiring ffmpeg to the MJPEG port — it previously relied
    on the stream being ungated. If the printer reboots while the bridge
    runs, re-run the command.
  • Known limitation: the server's /stream broadcaster and the RTSP config
    still build their upstream URL from the static host:port. Today V1.4.49
    reports the same <ip>:3031/video, so behavior is identical; unifying
    all camera consumers behind one resolved port/path is left for a
    follow-up to keep this diff small.
  • PrinterManager (server) sends the enable on every (re)connect, so
    /stream, /snapshot, and RTSP recover automatically after printer
    reboots.
  • Changelog under [Unreleased]; three new unit tests (the fake printer
    models Cmd 386 from its firmware version, and its VideoUrl deliberately
    carries a different host than the fake itself so a client that dials
    the reported host fails the suite): round-trip envelope/normalization,
    snapshot ordering + port/path-but-not-host adoption (asserted at camera
    read time), and pre-V1.4 firmware never receiving the command without
    force=True.

Compatibility

Pre-V1.4 firmware streams without the enable (this library never sent Cmd 386
and the camera worked). The command is documented for those versions too, but
I could not test it there — so rather than send an unverified command at
someone else's firmware, the client never sends Cmd 386 below V1.4 (or
when the version is unknown). force=True exists for anyone who wants to
probe their own printer deliberately.

Testing

  • ruff check / ruff format --check clean; full suite: 96 passed
    (mypy has pre-existing errors in mcp/server.py only, untouched here).
  • Live on a CC1 @ V1.4.49: CLI snapshot, server /snapshot, /stream, and
    reverse-proxied access all verified working from a cold service start with
    this branch installed; before the change all of them timed out.

🤖 Generated with Claude Code

…d 386

Newer CC1 firmware gates the MJPEG port behind Cmd 386: :3031/video
accepts connections but emits no frames until the stream is enabled, so
snapshot, /stream, and the RTSP bridge all timed out. Verified live on
V1.4.49; V0.3.0-o streams unconditionally and acks the enable harmlessly.

- add Cmd.ENABLE_VIDEO_STREAM = 386 (commented out in the official SDK
  but firmware-handled, same story as Cmds 258/259)
- add Printer.enable_video_stream() returning the firmware VideoUrl;
  no-op override on CC2Printer (its camera does not gate on SDCP)
- snapshot() sends the enable best-effort before reading the camera
- server re-sends the enable on every (re)connect so /stream, /snapshot
  and RTSP recover after printer reboots (the enable resets with them)
- tests: fake printer acks Cmd 386 with a VideoUrl; round-trip + snapshot-ordering tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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