pycentauri 0.4.0__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 (31) hide show
  1. {pycentauri-0.4.0 → pycentauri-0.4.1}/CHANGELOG.md +18 -0
  2. {pycentauri-0.4.0 → pycentauri-0.4.1}/PKG-INFO +74 -45
  3. {pycentauri-0.4.0 → pycentauri-0.4.1}/README.md +72 -43
  4. {pycentauri-0.4.0 → pycentauri-0.4.1}/pyproject.toml +2 -2
  5. pycentauri-0.4.1/src/pycentauri/__init__.py +28 -0
  6. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/cli.py +0 -13
  7. pycentauri-0.4.0/src/pycentauri/__init__.py +0 -18
  8. {pycentauri-0.4.0 → pycentauri-0.4.1}/.gitignore +0 -0
  9. {pycentauri-0.4.0 → pycentauri-0.4.1}/LICENSE +0 -0
  10. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/camera.py +0 -0
  11. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/client.py +0 -0
  12. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/discovery.py +0 -0
  13. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/mcp/__init__.py +0 -0
  14. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/mcp/__main__.py +0 -0
  15. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/mcp/server.py +0 -0
  16. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/models.py +0 -0
  17. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/py.typed +0 -0
  18. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/rtsp.py +0 -0
  19. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/sdcp.py +0 -0
  20. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/server.py +0 -0
  21. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/web/__init__.py +0 -0
  22. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/web/app.js +0 -0
  23. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/web/index.html +0 -0
  24. {pycentauri-0.4.0 → pycentauri-0.4.1}/src/pycentauri/web/styles.css +0 -0
  25. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/__init__.py +0 -0
  26. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/integration/__init__.py +0 -0
  27. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/test_client.py +0 -0
  28. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/test_discovery.py +0 -0
  29. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/test_rtsp.py +0 -0
  30. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/test_sdcp.py +0 -0
  31. {pycentauri-0.4.0 → pycentauri-0.4.1}/tests/test_server.py +0 -0
@@ -6,6 +6,24 @@ 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
+
9
27
  ## [0.4.0] - 2026-04-22
10
28
 
11
29
  ### Added
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycentauri
3
- Version: 0.4.0
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,8 +48,10 @@ 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
57
  WebSocket (port 3030) — no cloud account required. It exposes six surfaces:
@@ -68,18 +70,21 @@ WebSocket (port 3030) — no cloud account required. It exposes six surfaces:
68
70
  > **Status:** alpha. The protocol has been reverse-engineered from the official
69
71
  > [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link) C++ SDK and the
70
72
  > [`CentauriLink`](https://github.com/CentauriLink/Centauri-Link) project. It
71
- > works against the original Centauri Carbon on current firmware. The newer
72
- > 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.
73
75
 
74
76
  ## Install
75
77
 
76
78
  ```sh
77
79
  pip install pycentauri # library + CLI
78
80
  pip install "pycentauri[mcp]" # + MCP server
79
- pip install "pycentauri[server]" # + HTTP REST/SSE server
80
- 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
81
83
  ```
82
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
+
83
88
  ## Quick start — CLI
84
89
 
85
90
  ```sh
@@ -96,17 +101,19 @@ centauri watch --host 192.168.1.209
96
101
  # Grab a webcam snapshot
97
102
  centauri snapshot --host 192.168.1.209 shot.jpg
98
103
 
99
- # Files on the printer
100
- centauri files --host 192.168.1.209
104
+ # Printer attributes (model, firmware, mainboard ID, capabilities)
105
+ centauri attributes --host 192.168.1.209
101
106
 
102
- # Control actions require --enable-control
107
+ # Control actions require --enable-control
103
108
  centauri print start cube.gcode --host 192.168.1.209 --enable-control
104
- centauri print pause --host 192.168.1.209 --enable-control
105
- centauri print resume --host 192.168.1.209 --enable-control
106
- 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
107
112
  ```
108
113
 
109
- 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.
110
117
 
111
118
  ## Quick start — Python
112
119
 
@@ -117,26 +124,30 @@ from pycentauri import Printer
117
124
  async def main():
118
125
  async with await Printer.connect("192.168.1.209") as printer:
119
126
  status = await printer.status()
120
- print(status.state, status.progress, status.temp_nozzle)
127
+ print(status.print_status, status.progress, status.temp_nozzle)
121
128
 
122
129
  jpeg = await printer.snapshot()
123
130
  with open("shot.jpg", "wb") as f:
124
131
  f.write(jpeg)
125
132
 
126
133
  async for update in printer.watch():
127
- print(update.state, update.progress)
134
+ print(update.print_status, update.progress)
128
135
 
129
136
  asyncio.run(main())
130
137
  ```
131
138
 
139
+ Control actions (`start_print`, `pause`, `resume`, `stop`) require
140
+ `Printer.connect(..., enable_control=True)`.
141
+
132
142
  ## Quick start — HTTP server
133
143
 
134
144
  ```sh
135
145
  # Read-only, bound to loopback so only this box can hit it:
136
146
  centauri server --host 192.168.1.209 --port 8787
137
147
 
138
- # Read + write, bound to all interfaces (put a reverse proxy in front):
139
- 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
140
151
  ```
141
152
 
142
153
  | Method | Path | Notes |
@@ -144,12 +155,15 @@ centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 --enable-control
144
155
  | `GET` | `/` | Redirects to `/ui/` |
145
156
  | `GET` | `/ui/` | Built-in web dashboard |
146
157
  | `GET` | `/api/info` | Health + version (JSON) |
147
- | `GET` | `/status` | Latest status push (cached; updates every ~5s) |
158
+ | `GET` | `/status` | Latest status push (cached; updates every ~5 s) |
148
159
  | `GET` | `/attributes` | Printer attributes |
149
160
  | `GET` | `/snapshot` | `image/jpeg` response |
150
161
  | `GET` | `/stream` | MJPEG stream proxied from the printer (embeds in `<img>`) |
151
162
  | `GET` | `/discover` | LAN scan |
152
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 |
153
167
  | `POST` | `/print/start` | Body: `{"filename": "cube.gcode"}`. Requires `--enable-control`. |
154
168
  | `POST` | `/print/{pause,resume,stop}` | Requires `--enable-control`. |
155
169
 
@@ -160,7 +174,7 @@ the firmware's 5-slot limit.
160
174
  ## Quick start — RTSP bridge
161
175
 
162
176
  ```sh
163
- # Install dependencies first:
177
+ # Install system dependencies first:
164
178
  # mediamtx https://github.com/bluenviron/mediamtx/releases (or `brew install mediamtx`)
165
179
  # ffmpeg `brew install ffmpeg` or `apt install ffmpeg`
166
180
 
@@ -170,64 +184,79 @@ centauri rtsp --host 192.168.1.209
170
184
 
171
185
  # Or, integrated with the HTTP server + web UI
172
186
  centauri server --host 192.168.1.209 --rtsp --bind 0.0.0.0
173
- # Web UI gains a STREAM panel with a toggle, a copy-URL button, and live status
174
- # at http://<this-host>:8787/ui/
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/
175
189
  ```
176
190
 
177
191
  Open that URL in **VLC** (Media → Open Network Stream), point **Home Assistant**
178
192
  at it via the Generic Camera integration, or feed it to **Frigate** / **Jellyfin**
179
193
  / **Synology Surveillance** for NVR recording and motion detection.
180
194
 
181
- MediaMTX runs the ffmpeg transcode only while at least one client is actually
182
- connected, so idle cost is zero. Tunable: `--fps`, `--bitrate`, `--preset`,
183
- `--path`, `--port`, `--bind`. Run `centauri rtsp --help` (or
184
- `centauri server --help`) for the full list.
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.
185
201
 
186
202
  ## Quick start — MCP
187
203
 
188
204
  Register the server with your agent. With Claude Code:
189
205
 
190
206
  ```sh
191
- claude mcp add pycentauri -- python -m pycentauri.mcp
192
- # or, with control actions enabled (gives the agent start/pause/stop/upload):
193
- 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
194
213
  ```
195
214
 
196
- Set `PYCENTAURI_HOST` in your MCP server env so the agent can't target an
197
- 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:
198
218
 
199
219
  | Tool | Always available | Description |
200
220
  |---|---|---|
201
221
  | `get_status` | yes | State, temperatures, progress, elapsed/remaining |
202
- | `get_attributes` | yes | Model, firmware, mainboard ID |
203
- | `list_files` | yes | Files stored on the printer |
204
- | `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) |
205
224
  | `discover_printers` | yes | LAN scan |
206
- | `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 |
207
226
  | `pause_print` | only with `--enable-control` | Pauses the current print |
208
227
  | `resume_print` | only with `--enable-control` | Resumes a paused print |
209
228
  | `stop_print` | only with `--enable-control` | Stops the current print |
210
- | `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.
211
233
 
212
234
  ## Safety
213
235
 
214
- Control actions are gated behind an explicit `enable_control=True` (library) or
215
- `--enable-control` flag (CLI / MCP). Destructive MCP tools are not even
216
- registered when the flag is off, so an LLM never sees them. Still: leaving a
217
- 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.
218
246
 
219
247
  ## Known firmware quirks
220
248
 
221
249
  - **5 concurrent WebSocket connections max.** The printer's SDCP server
222
250
  accepts up to 5 open WebSockets on port 3030; the 6th returns HTTP 500
223
251
  with body `"too many client"`. Slots release immediately when a
224
- connection closes — the CLI and MCP server each open/close one per
225
- 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.
226
255
  - **Paused / errored states don't auto-push Attributes.** The printer only
227
256
  sends its `Attributes` frame spontaneously while idle or printing. In
228
257
  paused and errored states it stays silent until asked. Since every SDCP
229
- command needs the printer's `MainboardID`, the client takes care of this
230
- 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
231
260
  (as of v0.1.1). If you call `Printer.connect()` directly without
232
261
  discovery, pass `mainboard_id=` yourself.
233
262
 
@@ -1,7 +1,9 @@
1
1
  # pycentauri
2
2
 
3
- Python client, CLI, and MCP server for the [Elegoo Centauri
4
- Carbon](https://www.elegoo.com/) 3D printer.
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.
5
7
 
6
8
  `pycentauri` speaks the printer's native SDCP v3 protocol over its local
7
9
  WebSocket (port 3030) — no cloud account required. It exposes six surfaces:
@@ -20,18 +22,21 @@ WebSocket (port 3030) — no cloud account required. It exposes six surfaces:
20
22
  > **Status:** alpha. The protocol has been reverse-engineered from the official
21
23
  > [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link) C++ SDK and the
22
24
  > [`CentauriLink`](https://github.com/CentauriLink/Centauri-Link) project. It
23
- > works against the original Centauri Carbon on current firmware. The newer
24
- > Centauri Carbon 2 (which uses MQTT) is not supported.
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.
25
27
 
26
28
  ## Install
27
29
 
28
30
  ```sh
29
31
  pip install pycentauri # library + CLI
30
32
  pip install "pycentauri[mcp]" # + MCP server
31
- pip install "pycentauri[server]" # + HTTP REST/SSE server
32
- pip install "pycentauri[mcp,server]" # everything
33
+ pip install "pycentauri[server]" # + HTTP REST/SSE server + web UI
34
+ pip install "pycentauri[mcp,server]" # all Python surfaces
33
35
  ```
34
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
+
35
40
  ## Quick start — CLI
36
41
 
37
42
  ```sh
@@ -48,17 +53,19 @@ centauri watch --host 192.168.1.209
48
53
  # Grab a webcam snapshot
49
54
  centauri snapshot --host 192.168.1.209 shot.jpg
50
55
 
51
- # Files on the printer
52
- centauri files --host 192.168.1.209
56
+ # Printer attributes (model, firmware, mainboard ID, capabilities)
57
+ centauri attributes --host 192.168.1.209
53
58
 
54
- # Control actions require --enable-control
59
+ # Control actions require --enable-control
55
60
  centauri print start cube.gcode --host 192.168.1.209 --enable-control
56
- centauri print pause --host 192.168.1.209 --enable-control
57
- centauri print resume --host 192.168.1.209 --enable-control
58
- centauri print stop --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
59
64
  ```
60
65
 
61
- The host can also come from `PYCENTAURI_HOST` or `~/.config/pycentauri/config.toml`.
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.
62
69
 
63
70
  ## Quick start — Python
64
71
 
@@ -69,26 +76,30 @@ from pycentauri import Printer
69
76
  async def main():
70
77
  async with await Printer.connect("192.168.1.209") as printer:
71
78
  status = await printer.status()
72
- print(status.state, status.progress, status.temp_nozzle)
79
+ print(status.print_status, status.progress, status.temp_nozzle)
73
80
 
74
81
  jpeg = await printer.snapshot()
75
82
  with open("shot.jpg", "wb") as f:
76
83
  f.write(jpeg)
77
84
 
78
85
  async for update in printer.watch():
79
- print(update.state, update.progress)
86
+ print(update.print_status, update.progress)
80
87
 
81
88
  asyncio.run(main())
82
89
  ```
83
90
 
91
+ Control actions (`start_print`, `pause`, `resume`, `stop`) require
92
+ `Printer.connect(..., enable_control=True)`.
93
+
84
94
  ## Quick start — HTTP server
85
95
 
86
96
  ```sh
87
97
  # Read-only, bound to loopback so only this box can hit it:
88
98
  centauri server --host 192.168.1.209 --port 8787
89
99
 
90
- # Read + write, bound to all interfaces (put a reverse proxy in front):
91
- centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 --enable-control
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
92
103
  ```
93
104
 
94
105
  | Method | Path | Notes |
@@ -96,12 +107,15 @@ centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 --enable-control
96
107
  | `GET` | `/` | Redirects to `/ui/` |
97
108
  | `GET` | `/ui/` | Built-in web dashboard |
98
109
  | `GET` | `/api/info` | Health + version (JSON) |
99
- | `GET` | `/status` | Latest status push (cached; updates every ~5s) |
110
+ | `GET` | `/status` | Latest status push (cached; updates every ~5 s) |
100
111
  | `GET` | `/attributes` | Printer attributes |
101
112
  | `GET` | `/snapshot` | `image/jpeg` response |
102
113
  | `GET` | `/stream` | MJPEG stream proxied from the printer (embeds in `<img>`) |
103
114
  | `GET` | `/discover` | LAN scan |
104
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 |
105
119
  | `POST` | `/print/start` | Body: `{"filename": "cube.gcode"}`. Requires `--enable-control`. |
106
120
  | `POST` | `/print/{pause,resume,stop}` | Requires `--enable-control`. |
107
121
 
@@ -112,7 +126,7 @@ the firmware's 5-slot limit.
112
126
  ## Quick start — RTSP bridge
113
127
 
114
128
  ```sh
115
- # Install dependencies first:
129
+ # Install system dependencies first:
116
130
  # mediamtx https://github.com/bluenviron/mediamtx/releases (or `brew install mediamtx`)
117
131
  # ffmpeg `brew install ffmpeg` or `apt install ffmpeg`
118
132
 
@@ -122,64 +136,79 @@ centauri rtsp --host 192.168.1.209
122
136
 
123
137
  # Or, integrated with the HTTP server + web UI
124
138
  centauri server --host 192.168.1.209 --rtsp --bind 0.0.0.0
125
- # Web UI gains a STREAM panel with a toggle, a copy-URL button, and live status
126
- # at http://<this-host>:8787/ui/
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/
127
141
  ```
128
142
 
129
143
  Open that URL in **VLC** (Media → Open Network Stream), point **Home Assistant**
130
144
  at it via the Generic Camera integration, or feed it to **Frigate** / **Jellyfin**
131
145
  / **Synology Surveillance** for NVR recording and motion detection.
132
146
 
133
- MediaMTX runs the ffmpeg transcode only while at least one client is actually
134
- connected, so idle cost is zero. Tunable: `--fps`, `--bitrate`, `--preset`,
135
- `--path`, `--port`, `--bind`. Run `centauri rtsp --help` (or
136
- `centauri server --help`) for the full list.
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.
137
153
 
138
154
  ## Quick start — MCP
139
155
 
140
156
  Register the server with your agent. With Claude Code:
141
157
 
142
158
  ```sh
143
- claude mcp add pycentauri -- python -m pycentauri.mcp
144
- # or, with control actions enabled (gives the agent start/pause/stop/upload):
145
- claude mcp add pycentauri -- python -m pycentauri.mcp --enable-control
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
146
165
  ```
147
166
 
148
- Set `PYCENTAURI_HOST` in your MCP server env so the agent can't target an
149
- arbitrary IP. The server exposes these tools:
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:
150
170
 
151
171
  | Tool | Always available | Description |
152
172
  |---|---|---|
153
173
  | `get_status` | yes | State, temperatures, progress, elapsed/remaining |
154
- | `get_attributes` | yes | Model, firmware, mainboard ID |
155
- | `list_files` | yes | Files stored on the printer |
156
- | `get_snapshot` | yes | Webcam frame as MCP `Image` content |
174
+ | `get_attributes` | yes | Model, firmware, mainboard ID, capabilities |
175
+ | `get_snapshot` | yes | Webcam frame as MCP `Image` content (LLMs see the picture) |
157
176
  | `discover_printers` | yes | LAN scan |
158
- | `start_print` | only with `--enable-control` | Starts a print |
177
+ | `start_print` | only with `--enable-control` | Starts a print from a file already on the printer |
159
178
  | `pause_print` | only with `--enable-control` | Pauses the current print |
160
179
  | `resume_print` | only with `--enable-control` | Resumes a paused print |
161
180
  | `stop_print` | only with `--enable-control` | Stops the current print |
162
- | `upload_file` | only with `--enable-control` | Uploads a file to the printer |
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.
163
185
 
164
186
  ## Safety
165
187
 
166
- Control actions are gated behind an explicit `enable_control=True` (library) or
167
- `--enable-control` flag (CLI / MCP). Destructive MCP tools are not even
168
- registered when the flag is off, so an LLM never sees them. Still: leaving a
169
- printer running unattended with write-capable agents is your responsibility.
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.
170
198
 
171
199
  ## Known firmware quirks
172
200
 
173
201
  - **5 concurrent WebSocket connections max.** The printer's SDCP server
174
202
  accepts up to 5 open WebSockets on port 3030; the 6th returns HTTP 500
175
203
  with body `"too many client"`. Slots release immediately when a
176
- connection closes — the CLI and MCP server each open/close one per
177
- invocation, so this is almost never a problem in practice.
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.
178
207
  - **Paused / errored states don't auto-push Attributes.** The printer only
179
208
  sends its `Attributes` frame spontaneously while idle or printing. In
180
209
  paused and errored states it stays silent until asked. Since every SDCP
181
- command needs the printer's `MainboardID`, the client takes care of this
182
- by pre-seeding the mainboard ID from a UDP discovery on every connect
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
183
212
  (as of v0.1.1). If you call `Printer.connect()` directly without
184
213
  discovery, pass `mainboard_id=` yourself.
185
214
 
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pycentauri"
7
- version = "0.4.0"
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"
@@ -250,19 +250,6 @@ def cmd_snapshot(
250
250
  _run(run())
251
251
 
252
252
 
253
- @app.command("files")
254
- def cmd_files(host: HostOpt = None, as_json: JsonOpt = False) -> None:
255
- """List files stored on the printer.
256
-
257
- Note: the file-list command isn't wired in v0.1 (the upstream SDK also
258
- marks it as not-implemented on CC); this is a stub so the command name
259
- is reserved.
260
- """
261
-
262
- _echo_err("file listing is not yet supported on the original Centauri Carbon firmware")
263
- raise typer.Exit(code=1)
264
-
265
-
266
253
  @print_cmd.command("start")
267
254
  def cmd_print_start(
268
255
  filename: Annotated[str, typer.Argument(help="File name as it appears on the printer.")],
@@ -1,18 +0,0 @@
1
- """Python client for Elegoo Centauri Carbon 3D printers."""
2
-
3
- from pycentauri.client import ControlDisabledError, Printer, PrinterError
4
- from pycentauri.discovery import DiscoveredPrinter, discover
5
- from pycentauri.models import Attributes, PrintInfo, Status
6
-
7
- __all__ = [
8
- "Attributes",
9
- "ControlDisabledError",
10
- "DiscoveredPrinter",
11
- "PrintInfo",
12
- "Printer",
13
- "PrinterError",
14
- "Status",
15
- "discover",
16
- ]
17
-
18
- __version__ = "0.4.0"
File without changes
File without changes
File without changes