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.
- {pycentauri-0.3.1 → pycentauri-0.4.1}/CHANGELOG.md +39 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/PKG-INFO +98 -41
- pycentauri-0.4.1/README.md +220 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/pyproject.toml +2 -2
- pycentauri-0.4.1/src/pycentauri/__init__.py +28 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/cli.py +81 -13
- pycentauri-0.4.1/src/pycentauri/rtsp.py +259 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/server.py +141 -2
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/app.js +118 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/index.html +21 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/styles.css +90 -0
- pycentauri-0.4.1/tests/test_rtsp.py +113 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_server.py +100 -0
- pycentauri-0.3.1/README.md +0 -163
- pycentauri-0.3.1/src/pycentauri/__init__.py +0 -18
- {pycentauri-0.3.1 → pycentauri-0.4.1}/.gitignore +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/LICENSE +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/camera.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/client.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/discovery.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/mcp/__init__.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/mcp/__main__.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/mcp/server.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/models.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/py.typed +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/sdcp.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/src/pycentauri/web/__init__.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/__init__.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/integration/__init__.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_client.py +0 -0
- {pycentauri-0.3.1 → pycentauri-0.4.1}/tests/test_discovery.py +0 -0
- {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.
|
|
4
|
-
Summary: Python client
|
|
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
|
-
|
|
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
|
|
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** (
|
|
64
|
-
|
|
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
|
|
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]" #
|
|
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
|
-
#
|
|
98
|
-
centauri
|
|
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
|
|
103
|
-
centauri print resume
|
|
104
|
-
centauri print stop
|
|
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`
|
|
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.
|
|
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.
|
|
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
|
|
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 ~
|
|
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 --
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
169
|
-
arbitrary IP
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
`--enable-control` flag (CLI / MCP). Destructive MCP tools
|
|
188
|
-
registered when the flag is off, so an LLM never sees them. Still:
|
|
189
|
-
printer running unattended with write-capable agents is your
|
|
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
|
|
197
|
-
|
|
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
|
|
202
|
-
|
|
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.
|
|
8
|
-
description = "Python client
|
|
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"
|