pycentauri 0.1.1__tar.gz → 0.3.0__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.1.1 → pycentauri-0.3.0}/CHANGELOG.md +39 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/PKG-INFO +47 -4
- {pycentauri-0.1.1 → pycentauri-0.3.0}/README.md +37 -3
- {pycentauri-0.1.1 → pycentauri-0.3.0}/pyproject.toml +11 -1
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/__init__.py +1 -1
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/cli.py +32 -0
- pycentauri-0.3.0/src/pycentauri/server.py +460 -0
- pycentauri-0.3.0/src/pycentauri/web/app.js +179 -0
- pycentauri-0.3.0/src/pycentauri/web/index.html +85 -0
- pycentauri-0.3.0/src/pycentauri/web/styles.css +257 -0
- pycentauri-0.3.0/tests/integration/__init__.py +0 -0
- pycentauri-0.3.0/tests/test_server.py +111 -0
- pycentauri-0.1.1/.github/workflows/ci.yml +0 -42
- pycentauri-0.1.1/.github/workflows/publish.yml +0 -48
- pycentauri-0.1.1/CONTRIBUTING.md +0 -56
- pycentauri-0.1.1/examples/snapshot.py +0 -22
- pycentauri-0.1.1/examples/start_print.py +0 -26
- pycentauri-0.1.1/examples/status_watch.py +0 -27
- {pycentauri-0.1.1 → pycentauri-0.3.0}/.gitignore +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/LICENSE +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/camera.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/client.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/discovery.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/mcp/__init__.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/mcp/__main__.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/mcp/server.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/models.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/py.typed +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/sdcp.py +0 -0
- {pycentauri-0.1.1/tests → pycentauri-0.3.0/src/pycentauri/web}/__init__.py +0 -0
- {pycentauri-0.1.1/tests/integration → pycentauri-0.3.0/tests}/__init__.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/tests/test_client.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/tests/test_discovery.py +0 -0
- {pycentauri-0.1.1 → pycentauri-0.3.0}/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.3.0] - 2026-04-22
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Web UI** at `GET /ui/` (and `GET /` redirects there). Single static
|
|
13
|
+
dashboard bundled in the wheel — no build step, no framework. Shows
|
|
14
|
+
live webcam (MJPEG), progress bar, layer counter, temperature cards,
|
|
15
|
+
position, fans, and Z-offset. Pause / Resume / Stop buttons auto-appear
|
|
16
|
+
when the server is launched with `--enable-control`.
|
|
17
|
+
- `GET /stream` — MJPEG proxy through the API server to the printer's
|
|
18
|
+
`:3031/video`. Browsers render it directly in an `<img>` tag.
|
|
19
|
+
- `GET /api/info` — JSON health endpoint, matching what `GET /` used to
|
|
20
|
+
return before it was repurposed to redirect to `/ui/`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `GET /` now redirects to `/ui/` (307). If the web assets aren't found
|
|
24
|
+
in the wheel (custom build, etc.), `/` falls back to a minimal JSON
|
|
25
|
+
health response so scripted clients don't break.
|
|
26
|
+
|
|
27
|
+
## [0.2.0] - 2026-04-22
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **HTTP + SSE server.** `centauri server [--host IP] [--bind 127.0.0.1]
|
|
31
|
+
[--port 8787] [--enable-control]` runs a FastAPI app that wraps the
|
|
32
|
+
same client library used by the CLI and MCP server:
|
|
33
|
+
- `GET /` — health, version, connection state
|
|
34
|
+
- `GET /status` — latest status snapshot (JSON)
|
|
35
|
+
- `GET /attributes` — printer attributes (JSON)
|
|
36
|
+
- `GET /snapshot` — single JPEG frame from the webcam
|
|
37
|
+
- `GET /discover` — UDP LAN scan
|
|
38
|
+
- `GET /events/status` — Server-Sent Events stream of live status pushes
|
|
39
|
+
- `POST /print/{start,pause,resume,stop}` — only registered with
|
|
40
|
+
`--enable-control` (mirrors the MCP security posture — the routes
|
|
41
|
+
literally don't exist without the flag)
|
|
42
|
+
- New optional extra: `pip install 'pycentauri[server]'` pulls in
|
|
43
|
+
FastAPI + uvicorn + sse-starlette.
|
|
44
|
+
- The server holds a single long-lived WebSocket for its lifetime with
|
|
45
|
+
auto-reconnect and exponential backoff, so it never bumps against the
|
|
46
|
+
printer's 5-slot limit and HTTP requests return cached pushes instantly.
|
|
47
|
+
|
|
9
48
|
## [0.1.1] - 2026-04-22
|
|
10
49
|
|
|
11
50
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycentauri
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python client and MCP server for Elegoo Centauri Carbon 3D printers
|
|
5
5
|
Project-URL: Homepage, https://github.com/bjan/pycentauri
|
|
6
6
|
Project-URL: Repository, https://github.com/bjan/pycentauri
|
|
@@ -28,13 +28,22 @@ Requires-Dist: pydantic>=2.7
|
|
|
28
28
|
Requires-Dist: typer>=0.12
|
|
29
29
|
Requires-Dist: websockets>=12.0
|
|
30
30
|
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: fastapi>=0.110; extra == 'dev'
|
|
32
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
33
|
+
Requires-Dist: mcp>=1.2; extra == 'dev'
|
|
31
34
|
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
32
35
|
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
33
36
|
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
34
37
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
35
38
|
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
39
|
+
Requires-Dist: sse-starlette>=2.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == 'dev'
|
|
36
41
|
Provides-Extra: mcp
|
|
37
42
|
Requires-Dist: mcp>=1.2; extra == 'mcp'
|
|
43
|
+
Provides-Extra: server
|
|
44
|
+
Requires-Dist: fastapi>=0.110; extra == 'server'
|
|
45
|
+
Requires-Dist: sse-starlette>=2.0; extra == 'server'
|
|
46
|
+
Requires-Dist: uvicorn[standard]>=0.29; extra == 'server'
|
|
38
47
|
Description-Content-Type: text/markdown
|
|
39
48
|
|
|
40
49
|
# pycentauri
|
|
@@ -43,12 +52,16 @@ Python client, CLI, and MCP server for the [Elegoo Centauri
|
|
|
43
52
|
Carbon](https://www.elegoo.com/) 3D printer.
|
|
44
53
|
|
|
45
54
|
`pycentauri` speaks the printer's native SDCP v3 protocol over its local
|
|
46
|
-
WebSocket (port 3030) — no cloud account required. It exposes
|
|
55
|
+
WebSocket (port 3030) — no cloud account required. It exposes five surfaces:
|
|
47
56
|
|
|
48
57
|
1. An **async Python library** for direct integration.
|
|
49
58
|
2. A **`centauri` CLI** for quick status checks, snapshots, and control.
|
|
50
59
|
3. An **MCP server** so AI agents (Claude Code, Claude Desktop, Cursor, any
|
|
51
60
|
MCP-compatible client) can monitor and drive the printer as a tool.
|
|
61
|
+
4. An **HTTP + SSE server** for dashboards, reverse-proxy integration, and
|
|
62
|
+
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.
|
|
52
65
|
|
|
53
66
|
> **Status:** alpha. The protocol has been reverse-engineered from the official
|
|
54
67
|
> [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link) C++ SDK and the
|
|
@@ -59,8 +72,10 @@ WebSocket (port 3030) — no cloud account required. It exposes three surfaces:
|
|
|
59
72
|
## Install
|
|
60
73
|
|
|
61
74
|
```sh
|
|
62
|
-
pip install pycentauri
|
|
63
|
-
pip install "pycentauri[mcp]"
|
|
75
|
+
pip install pycentauri # library + CLI
|
|
76
|
+
pip install "pycentauri[mcp]" # + MCP server
|
|
77
|
+
pip install "pycentauri[server]" # + HTTP REST/SSE server
|
|
78
|
+
pip install "pycentauri[mcp,server]" # everything
|
|
64
79
|
```
|
|
65
80
|
|
|
66
81
|
## Quick start — CLI
|
|
@@ -112,6 +127,34 @@ async def main():
|
|
|
112
127
|
asyncio.run(main())
|
|
113
128
|
```
|
|
114
129
|
|
|
130
|
+
## Quick start — HTTP server
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
# Read-only, bound to loopback so only this box can hit it:
|
|
134
|
+
centauri server --host 192.168.1.209 --port 8787
|
|
135
|
+
|
|
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
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Method | Path | Notes |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `GET` | `/` | Redirects to `/ui/` |
|
|
143
|
+
| `GET` | `/ui/` | Built-in web dashboard |
|
|
144
|
+
| `GET` | `/api/info` | Health + version (JSON) |
|
|
145
|
+
| `GET` | `/status` | Latest status push (cached; updates every ~5s) |
|
|
146
|
+
| `GET` | `/attributes` | Printer attributes |
|
|
147
|
+
| `GET` | `/snapshot` | `image/jpeg` response |
|
|
148
|
+
| `GET` | `/stream` | MJPEG stream proxied from the printer (embeds in `<img>`) |
|
|
149
|
+
| `GET` | `/discover` | LAN scan |
|
|
150
|
+
| `GET` | `/events/status` | Server-Sent Events stream of pushes |
|
|
151
|
+
| `POST` | `/print/start` | Body: `{"filename": "cube.gcode"}`. Requires `--enable-control`. |
|
|
152
|
+
| `POST` | `/print/{pause,resume,stop}` | Requires `--enable-control`. |
|
|
153
|
+
|
|
154
|
+
The server holds a single long-lived WebSocket to the printer and reuses
|
|
155
|
+
it for every request — no per-request reconnect, and it won't bump into
|
|
156
|
+
the firmware's 5-slot limit.
|
|
157
|
+
|
|
115
158
|
## Quick start — MCP
|
|
116
159
|
|
|
117
160
|
Register the server with your agent. With Claude Code:
|
|
@@ -4,12 +4,16 @@ Python client, CLI, and MCP server for the [Elegoo Centauri
|
|
|
4
4
|
Carbon](https://www.elegoo.com/) 3D printer.
|
|
5
5
|
|
|
6
6
|
`pycentauri` speaks the printer's native SDCP v3 protocol over its local
|
|
7
|
-
WebSocket (port 3030) — no cloud account required. It exposes
|
|
7
|
+
WebSocket (port 3030) — no cloud account required. It exposes five surfaces:
|
|
8
8
|
|
|
9
9
|
1. An **async Python library** for direct integration.
|
|
10
10
|
2. A **`centauri` CLI** for quick status checks, snapshots, and control.
|
|
11
11
|
3. An **MCP server** so AI agents (Claude Code, Claude Desktop, Cursor, any
|
|
12
12
|
MCP-compatible client) can monitor and drive the printer as a tool.
|
|
13
|
+
4. An **HTTP + SSE server** for dashboards, reverse-proxy integration, and
|
|
14
|
+
anything that wants a plain REST API.
|
|
15
|
+
5. A **built-in web UI** (dark theme, mobile-friendly) served at `/ui/` by
|
|
16
|
+
the HTTP server — live webcam, progress, temperatures, and control.
|
|
13
17
|
|
|
14
18
|
> **Status:** alpha. The protocol has been reverse-engineered from the official
|
|
15
19
|
> [`elegoo-link`](https://github.com/ELEGOO-3D/elegoo-link) C++ SDK and the
|
|
@@ -20,8 +24,10 @@ WebSocket (port 3030) — no cloud account required. It exposes three surfaces:
|
|
|
20
24
|
## Install
|
|
21
25
|
|
|
22
26
|
```sh
|
|
23
|
-
pip install pycentauri
|
|
24
|
-
pip install "pycentauri[mcp]"
|
|
27
|
+
pip install pycentauri # library + CLI
|
|
28
|
+
pip install "pycentauri[mcp]" # + MCP server
|
|
29
|
+
pip install "pycentauri[server]" # + HTTP REST/SSE server
|
|
30
|
+
pip install "pycentauri[mcp,server]" # everything
|
|
25
31
|
```
|
|
26
32
|
|
|
27
33
|
## Quick start — CLI
|
|
@@ -73,6 +79,34 @@ async def main():
|
|
|
73
79
|
asyncio.run(main())
|
|
74
80
|
```
|
|
75
81
|
|
|
82
|
+
## Quick start — HTTP server
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
# Read-only, bound to loopback so only this box can hit it:
|
|
86
|
+
centauri server --host 192.168.1.209 --port 8787
|
|
87
|
+
|
|
88
|
+
# Read + write, bound to all interfaces (put a reverse proxy in front):
|
|
89
|
+
centauri server --host 192.168.1.209 --bind 0.0.0.0 --port 8787 --enable-control
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Method | Path | Notes |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| `GET` | `/` | Redirects to `/ui/` |
|
|
95
|
+
| `GET` | `/ui/` | Built-in web dashboard |
|
|
96
|
+
| `GET` | `/api/info` | Health + version (JSON) |
|
|
97
|
+
| `GET` | `/status` | Latest status push (cached; updates every ~5s) |
|
|
98
|
+
| `GET` | `/attributes` | Printer attributes |
|
|
99
|
+
| `GET` | `/snapshot` | `image/jpeg` response |
|
|
100
|
+
| `GET` | `/stream` | MJPEG stream proxied from the printer (embeds in `<img>`) |
|
|
101
|
+
| `GET` | `/discover` | LAN scan |
|
|
102
|
+
| `GET` | `/events/status` | Server-Sent Events stream of pushes |
|
|
103
|
+
| `POST` | `/print/start` | Body: `{"filename": "cube.gcode"}`. Requires `--enable-control`. |
|
|
104
|
+
| `POST` | `/print/{pause,resume,stop}` | Requires `--enable-control`. |
|
|
105
|
+
|
|
106
|
+
The server holds a single long-lived WebSocket to the printer and reuses
|
|
107
|
+
it for every request — no per-request reconnect, and it won't bump into
|
|
108
|
+
the firmware's 5-slot limit.
|
|
109
|
+
|
|
76
110
|
## Quick start — MCP
|
|
77
111
|
|
|
78
112
|
Register the server with your agent. With Claude Code:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pycentauri"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Python client and MCP server for Elegoo Centauri Carbon 3D printers"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -35,12 +35,15 @@ dependencies = [
|
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
mcp = ["mcp>=1.2"]
|
|
38
|
+
server = ["fastapi>=0.110", "uvicorn[standard]>=0.29", "sse-starlette>=2.0"]
|
|
38
39
|
dev = [
|
|
39
40
|
"pytest>=8.0",
|
|
40
41
|
"pytest-asyncio>=0.23",
|
|
41
42
|
"ruff>=0.5",
|
|
42
43
|
"mypy>=1.10",
|
|
43
44
|
"pytest-cov>=5.0",
|
|
45
|
+
"httpx>=0.27",
|
|
46
|
+
"pycentauri[mcp,server]",
|
|
44
47
|
]
|
|
45
48
|
|
|
46
49
|
[project.scripts]
|
|
@@ -55,6 +58,9 @@ Changelog = "https://github.com/bjan/pycentauri/blob/main/CHANGELOG.md"
|
|
|
55
58
|
[tool.hatch.build.targets.wheel]
|
|
56
59
|
packages = ["src/pycentauri"]
|
|
57
60
|
|
|
61
|
+
[tool.hatch.build.targets.sdist]
|
|
62
|
+
include = ["src/pycentauri", "tests", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
63
|
+
|
|
58
64
|
[tool.ruff]
|
|
59
65
|
line-length = 100
|
|
60
66
|
target-version = "py310"
|
|
@@ -63,6 +69,10 @@ target-version = "py310"
|
|
|
63
69
|
select = ["E", "F", "W", "I", "UP", "B", "SIM", "RUF"]
|
|
64
70
|
ignore = ["E501"]
|
|
65
71
|
|
|
72
|
+
[tool.ruff.lint.per-file-ignores]
|
|
73
|
+
# FastAPI's idiomatic DI uses `Depends()` in default values; that's B008.
|
|
74
|
+
"src/pycentauri/server.py" = ["B008"]
|
|
75
|
+
|
|
66
76
|
[tool.mypy]
|
|
67
77
|
python_version = "3.10"
|
|
68
78
|
strict = true
|
|
@@ -336,6 +336,38 @@ def cmd_print_stop(host: HostOpt = None, enable_control: ControlOpt = False) ->
|
|
|
336
336
|
_run(run())
|
|
337
337
|
|
|
338
338
|
|
|
339
|
+
@app.command("server")
|
|
340
|
+
def cmd_server(
|
|
341
|
+
host: HostOpt = None,
|
|
342
|
+
bind: str = typer.Option(
|
|
343
|
+
"127.0.0.1", "--bind", help="Interface to bind. Use 0.0.0.0 to expose on LAN."
|
|
344
|
+
),
|
|
345
|
+
port: int = typer.Option(8787, "--port", "-p"),
|
|
346
|
+
enable_control: ControlOpt = False,
|
|
347
|
+
log_level: str = typer.Option("info", "--log-level"),
|
|
348
|
+
) -> None:
|
|
349
|
+
"""Run the HTTP + SSE server (requires `pip install 'pycentauri[server]'`)."""
|
|
350
|
+
try:
|
|
351
|
+
from pycentauri.server import run as run_server
|
|
352
|
+
except ImportError as err:
|
|
353
|
+
_echo_err("Server support not installed. Install with: pip install 'pycentauri[server]'")
|
|
354
|
+
_echo_err(f"(missing dependency: {err})")
|
|
355
|
+
raise typer.Exit(code=1) from err
|
|
356
|
+
|
|
357
|
+
async def resolve() -> tuple[str, str | None]:
|
|
358
|
+
return await _resolve_target(host)
|
|
359
|
+
|
|
360
|
+
h, mid = asyncio.run(resolve())
|
|
361
|
+
run_server(
|
|
362
|
+
h,
|
|
363
|
+
bind=bind,
|
|
364
|
+
port=port,
|
|
365
|
+
enable_control=enable_control,
|
|
366
|
+
mainboard_id=mid,
|
|
367
|
+
log_level=log_level,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
|
|
339
371
|
@app.command("mcp")
|
|
340
372
|
def cmd_mcp(
|
|
341
373
|
enable_control: ControlOpt = False,
|