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.
Files changed (34) hide show
  1. {pycentauri-0.1.1 → pycentauri-0.3.0}/CHANGELOG.md +39 -0
  2. {pycentauri-0.1.1 → pycentauri-0.3.0}/PKG-INFO +47 -4
  3. {pycentauri-0.1.1 → pycentauri-0.3.0}/README.md +37 -3
  4. {pycentauri-0.1.1 → pycentauri-0.3.0}/pyproject.toml +11 -1
  5. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/__init__.py +1 -1
  6. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/cli.py +32 -0
  7. pycentauri-0.3.0/src/pycentauri/server.py +460 -0
  8. pycentauri-0.3.0/src/pycentauri/web/app.js +179 -0
  9. pycentauri-0.3.0/src/pycentauri/web/index.html +85 -0
  10. pycentauri-0.3.0/src/pycentauri/web/styles.css +257 -0
  11. pycentauri-0.3.0/tests/integration/__init__.py +0 -0
  12. pycentauri-0.3.0/tests/test_server.py +111 -0
  13. pycentauri-0.1.1/.github/workflows/ci.yml +0 -42
  14. pycentauri-0.1.1/.github/workflows/publish.yml +0 -48
  15. pycentauri-0.1.1/CONTRIBUTING.md +0 -56
  16. pycentauri-0.1.1/examples/snapshot.py +0 -22
  17. pycentauri-0.1.1/examples/start_print.py +0 -26
  18. pycentauri-0.1.1/examples/status_watch.py +0 -27
  19. {pycentauri-0.1.1 → pycentauri-0.3.0}/.gitignore +0 -0
  20. {pycentauri-0.1.1 → pycentauri-0.3.0}/LICENSE +0 -0
  21. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/camera.py +0 -0
  22. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/client.py +0 -0
  23. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/discovery.py +0 -0
  24. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/mcp/__init__.py +0 -0
  25. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/mcp/__main__.py +0 -0
  26. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/mcp/server.py +0 -0
  27. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/models.py +0 -0
  28. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/py.typed +0 -0
  29. {pycentauri-0.1.1 → pycentauri-0.3.0}/src/pycentauri/sdcp.py +0 -0
  30. {pycentauri-0.1.1/tests → pycentauri-0.3.0/src/pycentauri/web}/__init__.py +0 -0
  31. {pycentauri-0.1.1/tests/integration → pycentauri-0.3.0/tests}/__init__.py +0 -0
  32. {pycentauri-0.1.1 → pycentauri-0.3.0}/tests/test_client.py +0 -0
  33. {pycentauri-0.1.1 → pycentauri-0.3.0}/tests/test_discovery.py +0 -0
  34. {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.1.1
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 three surfaces:
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 # library + CLI
63
- pip install "pycentauri[mcp]" # + MCP server dependencies
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 three surfaces:
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 # library + CLI
24
- pip install "pycentauri[mcp]" # + MCP server dependencies
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.1.1"
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
@@ -15,4 +15,4 @@ __all__ = [
15
15
  "discover",
16
16
  ]
17
17
 
18
- __version__ = "0.1.1"
18
+ __version__ = "0.3.0"
@@ -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,