pycentauri 0.3.1__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {pycentauri-0.3.1 → pycentauri-0.4.1}/CHANGELOG.md +39 -0
  2. {pycentauri-0.3.1 → pycentauri-0.4.1}/PKG-INFO +98 -41
  3. pycentauri-0.4.1/README.md +220 -0
  4. {pycentauri-0.3.1 → pycentauri-0.4.1}/pyproject.toml +2 -2
  5. pycentauri-0.4.1/src/pycentauri/__init__.py +28 -0
  6. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/cli.py +81 -13
  7. pycentauri-0.4.1/src/pycentauri/rtsp.py +259 -0
  8. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/server.py +141 -2
  9. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/app.js +118 -0
  10. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/index.html +21 -0
  11. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/styles.css +90 -0
  12. pycentauri-0.4.1/tests/test_rtsp.py +113 -0
  13. {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_server.py +100 -0
  14. pycentauri-0.3.1/README.md +0 -163
  15. pycentauri-0.3.1/src/pycentauri/__init__.py +0 -18
  16. {pycentauri-0.3.1 → pycentauri-0.4.1}/.gitignore +0 -0
  17. {pycentauri-0.3.1 → pycentauri-0.4.1}/LICENSE +0 -0
  18. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/camera.py +0 -0
  19. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/client.py +0 -0
  20. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/discovery.py +0 -0
  21. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/mcp/__init__.py +0 -0
  22. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/mcp/__main__.py +0 -0
  23. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/mcp/server.py +0 -0
  24. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/models.py +0 -0
  25. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/py.typed +0 -0
  26. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/sdcp.py +0 -0
  27. {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/__init__.py +0 -0
  28. {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/__init__.py +0 -0
  29. {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/integration/__init__.py +0 -0
  30. {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_client.py +0 -0
  31. {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_discovery.py +0 -0
  32. {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_sdcp.py +0 -0
@@ -6,6 +6,45 @@ Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.1] - 2026-04-22
10
+
11
+ ### Changed
12
+ - README audited end-to-end for accuracy: removed claims about a
13
+ `~/.config/pycentauri/config.toml` loader that was never implemented,
14
+ an `upload_file` / `list_files` MCP tool that doesn't exist, and a
15
+ `centauri files` command that was a stub. Added the RTSP endpoints
16
+ (`/api/rtsp/*`) and the FastAPI auto-docs URLs to the endpoint table,
17
+ plus a note that the RTSP bridge itself isn't gated by
18
+ `--enable-control`.
19
+ - Updated the package tagline on PyPI and in the module docstring to
20
+ reflect all six surfaces rather than just three.
21
+
22
+ ### Removed
23
+ - The stub `centauri files` CLI command. It always exited with
24
+ "not yet supported" and was only there to reserve the name; not
25
+ worth the README lie.
26
+
27
+ ## [0.4.0] - 2026-04-22
28
+
29
+ ### Added
30
+ - **RTSP bridge.** Re-stream the printer's MJPEG webcam as RTSP/H.264
31
+ so VLC, Home Assistant, Jellyfin, Frigate, Synology Surveillance, and
32
+ any other RTSP client can consume it. Powered by `MediaMTX` +
33
+ on-demand `ffmpeg` transcode — the transcoder only runs while at least
34
+ one client is connected, so idle cost is zero. Two ways to drive it:
35
+ - **Standalone**: `centauri rtsp --host <printer>` runs in the
36
+ foreground until Ctrl-C. Flags: `--port`, `--bind`, `--path`,
37
+ `--fps`, `--bitrate`, `--preset`, `--webrtc/--no-webrtc`,
38
+ `--hls/--no-hls`, and `--mediamtx-path` / `--ffmpeg-path`
39
+ overrides.
40
+ - **Integrated with `centauri server --rtsp`**: adds a "STREAM" panel
41
+ to the built-in web UI with start/stop buttons, a copy-URL button,
42
+ and live status. New endpoints: `GET /api/rtsp`,
43
+ `POST /api/rtsp/start`, `POST /api/rtsp/stop`.
44
+ - Requires `mediamtx` and `ffmpeg` on `$PATH` — clear install hints
45
+ surface in the API response and the UI panel if either is missing,
46
+ and nothing changes for users who don't enable the feature.
47
+
9
48
  ## [0.3.1] - 2026-04-22
10
49
 
11
50
  ### Changed
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycentauri
3
- Version: 0.3.1
4
- Summary: Python client and MCP server for Elegoo Centauri Carbon 3D printers
3
+ Version: 0.4.1
4
+ Summary: Local-network toolkit for Elegoo Centauri Carbon 3D printers: async Python client, CLI, MCP server, REST/SSE HTTP server, web UI, and RTSP bridge
5
5
  Project-URL: Homepage, https://github.com/bjan/pycentauri
6
6
  Project-URL: Repository, https://github.com/bjan/pycentauri
7
7
  Project-URL: Issues, https://github.com/bjan/pycentauri/issues
@@ -48,11 +48,13 @@ Description-Content-Type: text/markdown
48
48
 
49
49
  # pycentauri
50
50
 
51
- Python client, CLI, and MCP server for the [Elegoo Centauri
52
- Carbon](https://www.elegoo.com/) 3D printer.
51
+ Local-network toolkit for the [Elegoo Centauri
52
+ Carbon](https://www.elegoo.com/) 3D printer. Python library, CLI, MCP
53
+ server, REST/SSE server, built-in web UI, and an RTSP bridge — all
54
+ powered by the same async client.
53
55
 
54
56
  `pycentauri` speaks the printer's native SDCP v3 protocol over its local
55
- WebSocket (port 3030) — no cloud account required. It exposes five surfaces:
57
+ WebSocket (port 3030) — no cloud account required. It exposes six surfaces:
56
58
 
57
59
  1. An **async Python library** for direct integration.
58
60
  2. A **`centauri` CLI** for quick status checks, snapshots, and control.
@@ -60,24 +62,29 @@ WebSocket (port 3030) — no cloud account required. It exposes five surfaces:
60
62
  MCP-compatible client) can monitor and drive the printer as a tool.
61
63
  4. An **HTTP + SSE server** for dashboards, reverse-proxy integration, and
62
64
  anything that wants a plain REST API.
63
- 5. A **built-in web UI** (dark theme, mobile-friendly) served at `/ui/` by
64
- the HTTP server live webcam, progress, temperatures, and control.
65
+ 5. A **built-in web UI** (industrial instrument-panel theme, mobile-friendly)
66
+ served at `/ui/` by the HTTP server.
67
+ 6. An **RTSP bridge** that re-streams the printer's MJPEG webcam as
68
+ H.264/RTSP for Home Assistant, Jellyfin, VLC, Frigate, and NVRs.
65
69
 
66
70
  > **Status:** alpha. The protocol has been reverse-engineered from the official
67
71
  > [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link) C++ SDK and the
68
72
  > [`CentauriLink`](https://github.com/CentauriLink/Centauri-Link) project. It
69
- > works against the original Centauri Carbon on current firmware. The newer
70
- > Centauri Carbon 2 (which uses MQTT) is not supported.
73
+ > works against the original Centauri Carbon on current firmware (tested on
74
+ > V1.1.46). The newer Centauri Carbon 2 (which uses MQTT) is not supported.
71
75
 
72
76
  ## Install
73
77
 
74
78
  ```sh
75
79
  pip install pycentauri # library + CLI
76
80
  pip install "pycentauri[mcp]" # + MCP server
77
- pip install "pycentauri[server]" # + HTTP REST/SSE server
78
- pip install "pycentauri[mcp,server]" # everything
81
+ pip install "pycentauri[server]" # + HTTP REST/SSE server + web UI
82
+ pip install "pycentauri[mcp,server]" # all Python surfaces
79
83
  ```
80
84
 
85
+ The RTSP bridge additionally requires [MediaMTX](https://github.com/bluenviron/mediamtx/releases)
86
+ and `ffmpeg` on `$PATH` — see the RTSP section below for install hints.
87
+
81
88
  ## Quick start — CLI
82
89
 
83
90
  ```sh
@@ -94,17 +101,19 @@ centauri watch --host 192.168.1.209
94
101
  # Grab a webcam snapshot
95
102
  centauri snapshot --host 192.168.1.209 shot.jpg
96
103
 
97
- # Files on the printer
98
- centauri files --host 192.168.1.209
104
+ # Printer attributes (model, firmware, mainboard ID, capabilities)
105
+ centauri attributes --host 192.168.1.209
99
106
 
100
- # Control actions require --enable-control
107
+ # Control actions require --enable-control
101
108
  centauri print start cube.gcode --host 192.168.1.209 --enable-control
102
- centauri print pause --host 192.168.1.209 --enable-control
103
- centauri print resume --host 192.168.1.209 --enable-control
104
- centauri print stop --host 192.168.1.209 --enable-control
109
+ centauri print pause --host 192.168.1.209 --enable-control
110
+ centauri print resume --host 192.168.1.209 --enable-control
111
+ centauri print stop --host 192.168.1.209 --enable-control
105
112
  ```
106
113
 
107
- The host can also come from `PYCENTAURI_HOST` or `~/.config/pycentauri/config.toml`.
114
+ The host can also come from the `PYCENTAURI_HOST` environment variable.
115
+ If neither is set, every command auto-discovers via a 2.5 s UDP broadcast
116
+ and bails out if it finds zero or more than one printer.
108
117
 
109
118
  ## Quick start — Python
110
119
 
@@ -115,26 +124,30 @@ from pycentauri import Printer
115
124
  async def main():
116
125
  async with await Printer.connect("192.168.1.209") as printer:
117
126
  status = await printer.status()
118
- print(status.state, status.progress, status.temp_nozzle)
127
+ print(status.print_status, status.progress, status.temp_nozzle)
119
128
 
120
129
  jpeg = await printer.snapshot()
121
130
  with open("shot.jpg", "wb") as f:
122
131
  f.write(jpeg)
123
132
 
124
133
  async for update in printer.watch():
125
- print(update.state, update.progress)
134
+ print(update.print_status, update.progress)
126
135
 
127
136
  asyncio.run(main())
128
137
  ```
129
138
 
139
+ Control actions (`start_print`, `pause`, `resume`, `stop`) require
140
+ `Printer.connect(..., enable_control=True)`.
141
+
130
142
  ## Quick start — HTTP server
131
143
 
132
144
  ```sh
133
145
  # Read-only, bound to loopback so only this box can hit it:
134
146
  centauri server --host 192.168.1.209 --port 8787
135
147
 
136
- # Read + write, bound to all interfaces (put a reverse proxy in front):
137
- centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 --enable-control
148
+ # Read + write + RTSP, bound to all interfaces (put a reverse proxy in front):
149
+ centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 \
150
+ --enable-control --rtsp
138
151
  ```
139
152
 
140
153
  | Method | Path | Notes |
@@ -142,12 +155,15 @@ centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 --enable-control
142
155
  | `GET` | `/` | Redirects to `/ui/` |
143
156
  | `GET` | `/ui/` | Built-in web dashboard |
144
157
  | `GET` | `/api/info` | Health + version (JSON) |
145
- | `GET` | `/status` | Latest status push (cached; updates every ~5s) |
158
+ | `GET` | `/status` | Latest status push (cached; updates every ~5 s) |
146
159
  | `GET` | `/attributes` | Printer attributes |
147
160
  | `GET` | `/snapshot` | `image/jpeg` response |
148
161
  | `GET` | `/stream` | MJPEG stream proxied from the printer (embeds in `<img>`) |
149
162
  | `GET` | `/discover` | LAN scan |
150
163
  | `GET` | `/events/status` | Server-Sent Events stream of pushes |
164
+ | `GET` | `/api/rtsp` | RTSP bridge state (when `--rtsp` is set) |
165
+ | `POST` | `/api/rtsp/{start,stop}` | Start/stop the RTSP bridge (when `--rtsp` is set) |
166
+ | `GET` | `/docs` / `/redoc` | Auto-generated OpenAPI docs |
151
167
  | `POST` | `/print/start` | Body: `{"filename": "cube.gcode"}`. Requires `--enable-control`. |
152
168
  | `POST` | `/print/{pause,resume,stop}` | Requires `--enable-control`. |
153
169
 
@@ -155,51 +171,92 @@ The server holds a single long-lived WebSocket to the printer and reuses
155
171
  it for every request — no per-request reconnect, and it won't bump into
156
172
  the firmware's 5-slot limit.
157
173
 
174
+ ## Quick start — RTSP bridge
175
+
176
+ ```sh
177
+ # Install system dependencies first:
178
+ # mediamtx https://github.com/bluenviron/mediamtx/releases (or `brew install mediamtx`)
179
+ # ffmpeg `brew install ffmpeg` or `apt install ffmpeg`
180
+
181
+ # Standalone: foreground, Ctrl-C to stop
182
+ centauri rtsp --host 192.168.1.209
183
+ # → rtsp://<this-host>:8554/printer
184
+
185
+ # Or, integrated with the HTTP server + web UI
186
+ centauri server --host 192.168.1.209 --rtsp --bind 0.0.0.0
187
+ # The web UI gains a STREAM panel with a start/stop toggle, a copy-URL button,
188
+ # and live status: http://<this-host>:8787/ui/
189
+ ```
190
+
191
+ Open that URL in **VLC** (Media → Open Network Stream), point **Home Assistant**
192
+ at it via the Generic Camera integration, or feed it to **Frigate** / **Jellyfin**
193
+ / **Synology Surveillance** for NVR recording and motion detection.
194
+
195
+ MediaMTX runs the ffmpeg transcode only while at least one client is
196
+ actually connected, so idle cost is zero. Tunable flags: `--fps`,
197
+ `--bitrate`, `--preset`, `--path`, `--port`, `--bind` (standalone),
198
+ or `--rtsp-fps`, `--rtsp-bitrate`, `--rtsp-path`, `--rtsp-port`,
199
+ `--rtsp-bind` (on `centauri server`). Run `centauri rtsp --help` or
200
+ `centauri server --help` for the full list.
201
+
158
202
  ## Quick start — MCP
159
203
 
160
204
  Register the server with your agent. With Claude Code:
161
205
 
162
206
  ```sh
163
- claude mcp add pycentauri -- python -m pycentauri.mcp
164
- # or, with control actions enabled (gives the agent start/pause/stop/upload):
165
- claude mcp add pycentauri -- python -m pycentauri.mcp --enable-control
207
+ claude mcp add pycentauri --env PYCENTAURI_HOST=192.168.1.209 \
208
+ -- python -m pycentauri.mcp
209
+
210
+ # Or, with control actions enabled (gives the agent start/pause/resume/stop):
211
+ claude mcp add pycentauri --env PYCENTAURI_HOST=192.168.1.209 \
212
+ -- python -m pycentauri.mcp --enable-control
166
213
  ```
167
214
 
168
- Set `PYCENTAURI_HOST` in your MCP server env so the agent can't target an
169
- arbitrary IP. The server exposes these tools:
215
+ Setting `PYCENTAURI_HOST` in the MCP server's launch env means the agent
216
+ can't be tricked into targeting an arbitrary IP through prompt injection
217
+ the host is pinned at spawn time. Tools exposed:
170
218
 
171
219
  | Tool | Always available | Description |
172
220
  |---|---|---|
173
221
  | `get_status` | yes | State, temperatures, progress, elapsed/remaining |
174
- | `get_attributes` | yes | Model, firmware, mainboard ID |
175
- | `list_files` | yes | Files stored on the printer |
176
- | `get_snapshot` | yes | Webcam frame as MCP `Image` content |
222
+ | `get_attributes` | yes | Model, firmware, mainboard ID, capabilities |
223
+ | `get_snapshot` | yes | Webcam frame as MCP `Image` content (LLMs see the picture) |
177
224
  | `discover_printers` | yes | LAN scan |
178
- | `start_print` | only with `--enable-control` | Starts a print |
225
+ | `start_print` | only with `--enable-control` | Starts a print from a file already on the printer |
179
226
  | `pause_print` | only with `--enable-control` | Pauses the current print |
180
227
  | `resume_print` | only with `--enable-control` | Resumes a paused print |
181
228
  | `stop_print` | only with `--enable-control` | Stops the current print |
182
- | `upload_file` | only with `--enable-control` | Uploads a file to the printer |
229
+
230
+ Control tools aren't just gated — they're not *registered* without the
231
+ flag, so an LLM that wasn't given the `--enable-control` launch can't see
232
+ them in the tool list at all.
183
233
 
184
234
  ## Safety
185
235
 
186
- Control actions are gated behind an explicit `enable_control=True` (library) or
187
- `--enable-control` flag (CLI / MCP). Destructive MCP tools are not even
188
- registered when the flag is off, so an LLM never sees them. Still: leaving a
189
- printer running unattended with write-capable agents is your responsibility.
236
+ Printer control actions are gated behind an explicit `enable_control=True`
237
+ (library) or `--enable-control` flag (CLI / MCP). Destructive MCP tools
238
+ aren't registered when the flag is off, so an LLM never sees them. Still:
239
+ leaving a printer running unattended with write-capable agents is your
240
+ responsibility, as is securing the HTTP surface (it's unauthenticated — put
241
+ a reverse proxy with auth in front if you expose it beyond loopback).
242
+
243
+ The RTSP bridge itself isn't gated by `--enable-control` because it only
244
+ reads the webcam feed and doesn't change printer state; it's enabled
245
+ per-server via the `--rtsp` flag.
190
246
 
191
247
  ## Known firmware quirks
192
248
 
193
249
  - **5 concurrent WebSocket connections max.** The printer's SDCP server
194
250
  accepts up to 5 open WebSockets on port 3030; the 6th returns HTTP 500
195
251
  with body `"too many client"`. Slots release immediately when a
196
- connection closes — the CLI and MCP server each open/close one per
197
- invocation, so this is almost never a problem in practice.
252
+ connection closes — the CLI each opens and closes one per invocation,
253
+ the HTTP and MCP servers hold a single long-lived one, so this is
254
+ almost never a problem in practice.
198
255
  - **Paused / errored states don't auto-push Attributes.** The printer only
199
256
  sends its `Attributes` frame spontaneously while idle or printing. In
200
257
  paused and errored states it stays silent until asked. Since every SDCP
201
- command needs the printer's `MainboardID`, the client takes care of this
202
- by pre-seeding the mainboard ID from a UDP discovery on every connect
258
+ command needs the printer's `MainboardID`, the client handles this by
259
+ pre-seeding the mainboard ID from a UDP discovery on every connect
203
260
  (as of v0.1.1). If you call `Printer.connect()` directly without
204
261
  discovery, pass `mainboard_id=` yourself.
205
262
 
@@ -0,0 +1,220 @@
1
+ # pycentauri
2
+
3
+ Local-network toolkit for the [Elegoo Centauri
4
+ Carbon](https://www.elegoo.com/) 3D printer. Python library, CLI, MCP
5
+ server, REST/SSE server, built-in web UI, and an RTSP bridge — all
6
+ powered by the same async client.
7
+
8
+ `pycentauri` speaks the printer's native SDCP v3 protocol over its local
9
+ WebSocket (port 3030) — no cloud account required. It exposes six surfaces:
10
+
11
+ 1. An **async Python library** for direct integration.
12
+ 2. A **`centauri` CLI** for quick status checks, snapshots, and control.
13
+ 3. An **MCP server** so AI agents (Claude Code, Claude Desktop, Cursor, any
14
+ MCP-compatible client) can monitor and drive the printer as a tool.
15
+ 4. An **HTTP + SSE server** for dashboards, reverse-proxy integration, and
16
+ anything that wants a plain REST API.
17
+ 5. A **built-in web UI** (industrial instrument-panel theme, mobile-friendly)
18
+ served at `/ui/` by the HTTP server.
19
+ 6. An **RTSP bridge** that re-streams the printer's MJPEG webcam as
20
+ H.264/RTSP for Home Assistant, Jellyfin, VLC, Frigate, and NVRs.
21
+
22
+ > **Status:** alpha. The protocol has been reverse-engineered from the official
23
+ > [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link) C++ SDK and the
24
+ > [`CentauriLink`](https://github.com/CentauriLink/Centauri-Link) project. It
25
+ > works against the original Centauri Carbon on current firmware (tested on
26
+ > V1.1.46). The newer Centauri Carbon 2 (which uses MQTT) is not supported.
27
+
28
+ ## Install
29
+
30
+ ```sh
31
+ pip install pycentauri # library + CLI
32
+ pip install "pycentauri[mcp]" # + MCP server
33
+ pip install "pycentauri[server]" # + HTTP REST/SSE server + web UI
34
+ pip install "pycentauri[mcp,server]" # all Python surfaces
35
+ ```
36
+
37
+ The RTSP bridge additionally requires [MediaMTX](https://github.com/bluenviron/mediamtx/releases)
38
+ and `ffmpeg` on `$PATH` — see the RTSP section below for install hints.
39
+
40
+ ## Quick start — CLI
41
+
42
+ ```sh
43
+ # Find printers on your LAN
44
+ centauri discover
45
+
46
+ # One-shot status (pretty or JSON)
47
+ centauri status --host 192.168.1.209
48
+ centauri status --host 192.168.1.209 --json
49
+
50
+ # Stream live status updates
51
+ centauri watch --host 192.168.1.209
52
+
53
+ # Grab a webcam snapshot
54
+ centauri snapshot --host 192.168.1.209 shot.jpg
55
+
56
+ # Printer attributes (model, firmware, mainboard ID, capabilities)
57
+ centauri attributes --host 192.168.1.209
58
+
59
+ # Control actions — require --enable-control
60
+ centauri print start cube.gcode --host 192.168.1.209 --enable-control
61
+ centauri print pause --host 192.168.1.209 --enable-control
62
+ centauri print resume --host 192.168.1.209 --enable-control
63
+ centauri print stop --host 192.168.1.209 --enable-control
64
+ ```
65
+
66
+ The host can also come from the `PYCENTAURI_HOST` environment variable.
67
+ If neither is set, every command auto-discovers via a 2.5 s UDP broadcast
68
+ and bails out if it finds zero or more than one printer.
69
+
70
+ ## Quick start — Python
71
+
72
+ ```python
73
+ import asyncio
74
+ from pycentauri import Printer
75
+
76
+ async def main():
77
+ async with await Printer.connect("192.168.1.209") as printer:
78
+ status = await printer.status()
79
+ print(status.print_status, status.progress, status.temp_nozzle)
80
+
81
+ jpeg = await printer.snapshot()
82
+ with open("shot.jpg", "wb") as f:
83
+ f.write(jpeg)
84
+
85
+ async for update in printer.watch():
86
+ print(update.print_status, update.progress)
87
+
88
+ asyncio.run(main())
89
+ ```
90
+
91
+ Control actions (`start_print`, `pause`, `resume`, `stop`) require
92
+ `Printer.connect(..., enable_control=True)`.
93
+
94
+ ## Quick start — HTTP server
95
+
96
+ ```sh
97
+ # Read-only, bound to loopback so only this box can hit it:
98
+ centauri server --host 192.168.1.209 --port 8787
99
+
100
+ # Read + write + RTSP, bound to all interfaces (put a reverse proxy in front):
101
+ centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 \
102
+ --enable-control --rtsp
103
+ ```
104
+
105
+ | Method | Path | Notes |
106
+ |---|---|---|
107
+ | `GET` | `/` | Redirects to `/ui/` |
108
+ | `GET` | `/ui/` | Built-in web dashboard |
109
+ | `GET` | `/api/info` | Health + version (JSON) |
110
+ | `GET` | `/status` | Latest status push (cached; updates every ~5 s) |
111
+ | `GET` | `/attributes` | Printer attributes |
112
+ | `GET` | `/snapshot` | `image/jpeg` response |
113
+ | `GET` | `/stream` | MJPEG stream proxied from the printer (embeds in `<img>`) |
114
+ | `GET` | `/discover` | LAN scan |
115
+ | `GET` | `/events/status` | Server-Sent Events stream of pushes |
116
+ | `GET` | `/api/rtsp` | RTSP bridge state (when `--rtsp` is set) |
117
+ | `POST` | `/api/rtsp/{start,stop}` | Start/stop the RTSP bridge (when `--rtsp` is set) |
118
+ | `GET` | `/docs` / `/redoc` | Auto-generated OpenAPI docs |
119
+ | `POST` | `/print/start` | Body: `{"filename": "cube.gcode"}`. Requires `--enable-control`. |
120
+ | `POST` | `/print/{pause,resume,stop}` | Requires `--enable-control`. |
121
+
122
+ The server holds a single long-lived WebSocket to the printer and reuses
123
+ it for every request — no per-request reconnect, and it won't bump into
124
+ the firmware's 5-slot limit.
125
+
126
+ ## Quick start — RTSP bridge
127
+
128
+ ```sh
129
+ # Install system dependencies first:
130
+ # mediamtx https://github.com/bluenviron/mediamtx/releases (or `brew install mediamtx`)
131
+ # ffmpeg `brew install ffmpeg` or `apt install ffmpeg`
132
+
133
+ # Standalone: foreground, Ctrl-C to stop
134
+ centauri rtsp --host 192.168.1.209
135
+ # → rtsp://<this-host>:8554/printer
136
+
137
+ # Or, integrated with the HTTP server + web UI
138
+ centauri server --host 192.168.1.209 --rtsp --bind 0.0.0.0
139
+ # The web UI gains a STREAM panel with a start/stop toggle, a copy-URL button,
140
+ # and live status: http://<this-host>:8787/ui/
141
+ ```
142
+
143
+ Open that URL in **VLC** (Media → Open Network Stream), point **Home Assistant**
144
+ at it via the Generic Camera integration, or feed it to **Frigate** / **Jellyfin**
145
+ / **Synology Surveillance** for NVR recording and motion detection.
146
+
147
+ MediaMTX runs the ffmpeg transcode only while at least one client is
148
+ actually connected, so idle cost is zero. Tunable flags: `--fps`,
149
+ `--bitrate`, `--preset`, `--path`, `--port`, `--bind` (standalone),
150
+ or `--rtsp-fps`, `--rtsp-bitrate`, `--rtsp-path`, `--rtsp-port`,
151
+ `--rtsp-bind` (on `centauri server`). Run `centauri rtsp --help` or
152
+ `centauri server --help` for the full list.
153
+
154
+ ## Quick start — MCP
155
+
156
+ Register the server with your agent. With Claude Code:
157
+
158
+ ```sh
159
+ claude mcp add pycentauri --env PYCENTAURI_HOST=192.168.1.209 \
160
+ -- python -m pycentauri.mcp
161
+
162
+ # Or, with control actions enabled (gives the agent start/pause/resume/stop):
163
+ claude mcp add pycentauri --env PYCENTAURI_HOST=192.168.1.209 \
164
+ -- python -m pycentauri.mcp --enable-control
165
+ ```
166
+
167
+ Setting `PYCENTAURI_HOST` in the MCP server's launch env means the agent
168
+ can't be tricked into targeting an arbitrary IP through prompt injection —
169
+ the host is pinned at spawn time. Tools exposed:
170
+
171
+ | Tool | Always available | Description |
172
+ |---|---|---|
173
+ | `get_status` | yes | State, temperatures, progress, elapsed/remaining |
174
+ | `get_attributes` | yes | Model, firmware, mainboard ID, capabilities |
175
+ | `get_snapshot` | yes | Webcam frame as MCP `Image` content (LLMs see the picture) |
176
+ | `discover_printers` | yes | LAN scan |
177
+ | `start_print` | only with `--enable-control` | Starts a print from a file already on the printer |
178
+ | `pause_print` | only with `--enable-control` | Pauses the current print |
179
+ | `resume_print` | only with `--enable-control` | Resumes a paused print |
180
+ | `stop_print` | only with `--enable-control` | Stops the current print |
181
+
182
+ Control tools aren't just gated — they're not *registered* without the
183
+ flag, so an LLM that wasn't given the `--enable-control` launch can't see
184
+ them in the tool list at all.
185
+
186
+ ## Safety
187
+
188
+ Printer control actions are gated behind an explicit `enable_control=True`
189
+ (library) or `--enable-control` flag (CLI / MCP). Destructive MCP tools
190
+ aren't registered when the flag is off, so an LLM never sees them. Still:
191
+ leaving a printer running unattended with write-capable agents is your
192
+ responsibility, as is securing the HTTP surface (it's unauthenticated — put
193
+ a reverse proxy with auth in front if you expose it beyond loopback).
194
+
195
+ The RTSP bridge itself isn't gated by `--enable-control` because it only
196
+ reads the webcam feed and doesn't change printer state; it's enabled
197
+ per-server via the `--rtsp` flag.
198
+
199
+ ## Known firmware quirks
200
+
201
+ - **5 concurrent WebSocket connections max.** The printer's SDCP server
202
+ accepts up to 5 open WebSockets on port 3030; the 6th returns HTTP 500
203
+ with body `"too many client"`. Slots release immediately when a
204
+ connection closes — the CLI each opens and closes one per invocation,
205
+ the HTTP and MCP servers hold a single long-lived one, so this is
206
+ almost never a problem in practice.
207
+ - **Paused / errored states don't auto-push Attributes.** The printer only
208
+ sends its `Attributes` frame spontaneously while idle or printing. In
209
+ paused and errored states it stays silent until asked. Since every SDCP
210
+ command needs the printer's `MainboardID`, the client handles this by
211
+ pre-seeding the mainboard ID from a UDP discovery on every connect
212
+ (as of v0.1.1). If you call `Printer.connect()` directly without
213
+ discovery, pass `mainboard_id=` yourself.
214
+
215
+ ## Credits & licensing
216
+
217
+ - Protocol reference: [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link)
218
+ (Apache-2.0) and [`CentauriLink`](https://github.com/CentauriLink/Centauri-Link).
219
+ - `pycentauri` is licensed under Apache-2.0. See [LICENSE](LICENSE).
220
+ - Not affiliated with or endorsed by Elegoo.
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycentauri"
7
- version = "0.3.1"
8
- description = "Python client and MCP server for Elegoo Centauri Carbon 3D printers"
7
+ version = "0.4.1"
8
+ description = "Local-network toolkit for Elegoo Centauri Carbon 3D printers: async Python client, CLI, MCP server, REST/SSE HTTP server, web UI, and RTSP bridge"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
11
11
  license-files = ["LICENSE"]
@@ -0,0 +1,28 @@
1
+ """Local-network toolkit for Elegoo Centauri Carbon 3D printers.
2
+
3
+ Six surfaces, all backed by the same async client:
4
+
5
+ * :class:`pycentauri.Printer` — asyncio SDCP client
6
+ * the ``centauri`` CLI (``python -m pycentauri.cli``)
7
+ * an MCP stdio server at ``python -m pycentauri.mcp``
8
+ * a FastAPI + SSE HTTP server at :mod:`pycentauri.server`
9
+ * a static web UI bundled in :mod:`pycentauri.web`
10
+ * an RTSP/H.264 bridge via MediaMTX in :mod:`pycentauri.rtsp`
11
+ """
12
+
13
+ from pycentauri.client import ControlDisabledError, Printer, PrinterError
14
+ from pycentauri.discovery import DiscoveredPrinter, discover
15
+ from pycentauri.models import Attributes, PrintInfo, Status
16
+
17
+ __all__ = [
18
+ "Attributes",
19
+ "ControlDisabledError",
20
+ "DiscoveredPrinter",
21
+ "PrintInfo",
22
+ "Printer",
23
+ "PrinterError",
24
+ "Status",
25
+ "discover",
26
+ ]
27
+
28
+ __version__ = "0.4.1"