anycubic-lan 0.1.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.
- anycubic_lan-0.1.0/.gitignore +13 -0
- anycubic_lan-0.1.0/LICENSE +21 -0
- anycubic_lan-0.1.0/PKG-INFO +134 -0
- anycubic_lan-0.1.0/README.md +105 -0
- anycubic_lan-0.1.0/docs/CLEAN-ROOM.md +61 -0
- anycubic_lan-0.1.0/docs/INTEGRATION-SPEC.md +121 -0
- anycubic_lan-0.1.0/docs/PROTOCOL.md +537 -0
- anycubic_lan-0.1.0/docs/QUESTIONS.md +208 -0
- anycubic_lan-0.1.0/pyproject.toml +95 -0
- anycubic_lan-0.1.0/src/anycubic_lan/__init__.py +91 -0
- anycubic_lan-0.1.0/src/anycubic_lan/client.py +468 -0
- anycubic_lan-0.1.0/src/anycubic_lan/exceptions.py +58 -0
- anycubic_lan-0.1.0/src/anycubic_lan/handshake.py +430 -0
- anycubic_lan-0.1.0/src/anycubic_lan/models.py +156 -0
- anycubic_lan-0.1.0/src/anycubic_lan/py.typed +0 -0
- anycubic_lan-0.1.0/src/anycubic_lan/reports.py +1080 -0
- anycubic_lan-0.1.0/tests/__init__.py +0 -0
- anycubic_lan-0.1.0/tests/payloads.py +196 -0
- anycubic_lan-0.1.0/tests/test_client.py +604 -0
- anycubic_lan-0.1.0/tests/test_handshake.py +480 -0
- anycubic_lan-0.1.0/tests/test_reports.py +714 -0
- anycubic_lan-0.1.0/tests/test_state.py +564 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nino Bondonno
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: anycubic-lan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async client for Anycubic 3D printers in LAN Mode (local MQTT, no cloud).
|
|
5
|
+
Project-URL: Source, https://github.com/Nino6689/anycubic-lan
|
|
6
|
+
Project-URL: Issues, https://github.com/Nino6689/anycubic-lan/issues
|
|
7
|
+
Author: Nino Bondonno
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: 3d-printer,anycubic,asyncio,home-assistant,mqtt
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Home Automation
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: aiohttp>=3.10
|
|
22
|
+
Requires-Dist: cryptography>=43
|
|
23
|
+
Requires-Dist: paho-mqtt>=2.1
|
|
24
|
+
Provides-Extra: test
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
|
|
26
|
+
Requires-Dist: pytest-cov>=5; extra == 'test'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# anycubic-lan
|
|
31
|
+
|
|
32
|
+
Talk to an Anycubic 3D printer in **LAN Mode**, directly on your network: no
|
|
33
|
+
Anycubic account, no cloud.
|
|
34
|
+
|
|
35
|
+
A printer with LAN Mode switched on runs its own MQTT broker. This library
|
|
36
|
+
performs the printer's handshake to get the broker credentials, connects,
|
|
37
|
+
asks for state, parses the reports into typed objects, and sends the commands
|
|
38
|
+
the printer accepts locally. It is fully async and has no cloud code and no
|
|
39
|
+
bundled certificates or keys.
|
|
40
|
+
|
|
41
|
+
Tested on a Kobra S1 (firmware 2.7.2.7). Reports from a Kobra X (firmware
|
|
42
|
+
2.0.2.2) informed the parser.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
pip install anycubic-lan
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Python 3.13 or later. Dependencies: `aiohttp`, `paho-mqtt`, `cryptography`.
|
|
51
|
+
|
|
52
|
+
Switch LAN Mode on at the printer first: **Settings → Network → LAN Mode**.
|
|
53
|
+
While LAN Mode is on the printer is not connected to the Anycubic cloud.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import asyncio
|
|
59
|
+
|
|
60
|
+
import aiohttp
|
|
61
|
+
|
|
62
|
+
from anycubic_lan import AnycubicLanClient, AnycubicLanError, handshake
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def main() -> None:
|
|
66
|
+
async with aiohttp.ClientSession() as session:
|
|
67
|
+
info = await handshake(session, "192.168.1.50") # the printer's IP
|
|
68
|
+
print(info.model_name, info.device_id)
|
|
69
|
+
|
|
70
|
+
client = AnycubicLanClient(info)
|
|
71
|
+
client.add_state_listener(
|
|
72
|
+
lambda state: print(state.status, state.temperatures.nozzle, state.job)
|
|
73
|
+
)
|
|
74
|
+
# Fires on changes after connect(): False when lost, True when restored.
|
|
75
|
+
client.add_connection_listener(lambda up: print("restored" if up else "lost"))
|
|
76
|
+
|
|
77
|
+
await client.connect() # subscribes and sends every query
|
|
78
|
+
try:
|
|
79
|
+
for _ in range(4):
|
|
80
|
+
await asyncio.sleep(15)
|
|
81
|
+
await client.query_all() # the printer answers queries; poll them
|
|
82
|
+
await client.light_on()
|
|
83
|
+
except AnycubicLanError as err:
|
|
84
|
+
print("printer error:", err)
|
|
85
|
+
finally:
|
|
86
|
+
await client.disconnect()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
asyncio.run(main())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- `handshake()` raises `PrinterUnreachableError`, `LanModeDisabledError`,
|
|
93
|
+
`UnsupportedPrinterError`, `RequestRejectedError` or `InvalidResponseError`
|
|
94
|
+
(all subclasses of `AnycubicLanError`).
|
|
95
|
+
- `client.state` is an immutable `PrinterState` merged from every report:
|
|
96
|
+
status, temperatures, fans, speed mode, firmware, the current job (progress,
|
|
97
|
+
layers, times, task id), ACE boxes and slots, lights, head position,
|
|
98
|
+
capability flags, camera URL and the last printer error code.
|
|
99
|
+
- Commands: `pause()`, `resume()`, `stop()` (for the current job),
|
|
100
|
+
`light_on()`, `light_off()`, `set_light_brightness()`.
|
|
101
|
+
- The broker credentials rotate when the printer restarts. They are held in
|
|
102
|
+
memory only; if the connection does not come back, run the handshake again.
|
|
103
|
+
|
|
104
|
+
## Supported printers
|
|
105
|
+
|
|
106
|
+
| Model | Model id | Status |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| Kobra S1 | 20025 | Tested (firmware 2.7.2.7, with ACE Pro) |
|
|
109
|
+
| Kobra X | 20030 | Parser informed by user logs (firmware 2.0.2.2) |
|
|
110
|
+
|
|
111
|
+
Other models that offer LAN Mode with the same handshake should work and are
|
|
112
|
+
reported as "Anycubic printer (model <id>)". Older models (Kobra 2 and
|
|
113
|
+
earlier) are detected and refused as unsupported.
|
|
114
|
+
|
|
115
|
+
## Status
|
|
116
|
+
|
|
117
|
+
Version 0.1.0, alpha. The specification in [`docs/`](docs/) comes first; the
|
|
118
|
+
implementation follows it. Questions to the specification team and their
|
|
119
|
+
answers are in [`docs/QUESTIONS.md`](docs/QUESTIONS.md). See
|
|
120
|
+
[`docs/CLEAN-ROOM.md`](docs/CLEAN-ROOM.md) for how this library was written
|
|
121
|
+
and by whom.
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
pip install -e ".[test]" ruff mypy
|
|
127
|
+
ruff check . && ruff format --check .
|
|
128
|
+
mypy --strict src
|
|
129
|
+
pytest
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Licence
|
|
133
|
+
|
|
134
|
+
MIT.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# anycubic-lan
|
|
2
|
+
|
|
3
|
+
Talk to an Anycubic 3D printer in **LAN Mode**, directly on your network: no
|
|
4
|
+
Anycubic account, no cloud.
|
|
5
|
+
|
|
6
|
+
A printer with LAN Mode switched on runs its own MQTT broker. This library
|
|
7
|
+
performs the printer's handshake to get the broker credentials, connects,
|
|
8
|
+
asks for state, parses the reports into typed objects, and sends the commands
|
|
9
|
+
the printer accepts locally. It is fully async and has no cloud code and no
|
|
10
|
+
bundled certificates or keys.
|
|
11
|
+
|
|
12
|
+
Tested on a Kobra S1 (firmware 2.7.2.7). Reports from a Kobra X (firmware
|
|
13
|
+
2.0.2.2) informed the parser.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pip install anycubic-lan
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Python 3.13 or later. Dependencies: `aiohttp`, `paho-mqtt`, `cryptography`.
|
|
22
|
+
|
|
23
|
+
Switch LAN Mode on at the printer first: **Settings → Network → LAN Mode**.
|
|
24
|
+
While LAN Mode is on the printer is not connected to the Anycubic cloud.
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import asyncio
|
|
30
|
+
|
|
31
|
+
import aiohttp
|
|
32
|
+
|
|
33
|
+
from anycubic_lan import AnycubicLanClient, AnycubicLanError, handshake
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def main() -> None:
|
|
37
|
+
async with aiohttp.ClientSession() as session:
|
|
38
|
+
info = await handshake(session, "192.168.1.50") # the printer's IP
|
|
39
|
+
print(info.model_name, info.device_id)
|
|
40
|
+
|
|
41
|
+
client = AnycubicLanClient(info)
|
|
42
|
+
client.add_state_listener(
|
|
43
|
+
lambda state: print(state.status, state.temperatures.nozzle, state.job)
|
|
44
|
+
)
|
|
45
|
+
# Fires on changes after connect(): False when lost, True when restored.
|
|
46
|
+
client.add_connection_listener(lambda up: print("restored" if up else "lost"))
|
|
47
|
+
|
|
48
|
+
await client.connect() # subscribes and sends every query
|
|
49
|
+
try:
|
|
50
|
+
for _ in range(4):
|
|
51
|
+
await asyncio.sleep(15)
|
|
52
|
+
await client.query_all() # the printer answers queries; poll them
|
|
53
|
+
await client.light_on()
|
|
54
|
+
except AnycubicLanError as err:
|
|
55
|
+
print("printer error:", err)
|
|
56
|
+
finally:
|
|
57
|
+
await client.disconnect()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
asyncio.run(main())
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `handshake()` raises `PrinterUnreachableError`, `LanModeDisabledError`,
|
|
64
|
+
`UnsupportedPrinterError`, `RequestRejectedError` or `InvalidResponseError`
|
|
65
|
+
(all subclasses of `AnycubicLanError`).
|
|
66
|
+
- `client.state` is an immutable `PrinterState` merged from every report:
|
|
67
|
+
status, temperatures, fans, speed mode, firmware, the current job (progress,
|
|
68
|
+
layers, times, task id), ACE boxes and slots, lights, head position,
|
|
69
|
+
capability flags, camera URL and the last printer error code.
|
|
70
|
+
- Commands: `pause()`, `resume()`, `stop()` (for the current job),
|
|
71
|
+
`light_on()`, `light_off()`, `set_light_brightness()`.
|
|
72
|
+
- The broker credentials rotate when the printer restarts. They are held in
|
|
73
|
+
memory only; if the connection does not come back, run the handshake again.
|
|
74
|
+
|
|
75
|
+
## Supported printers
|
|
76
|
+
|
|
77
|
+
| Model | Model id | Status |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| Kobra S1 | 20025 | Tested (firmware 2.7.2.7, with ACE Pro) |
|
|
80
|
+
| Kobra X | 20030 | Parser informed by user logs (firmware 2.0.2.2) |
|
|
81
|
+
|
|
82
|
+
Other models that offer LAN Mode with the same handshake should work and are
|
|
83
|
+
reported as "Anycubic printer (model <id>)". Older models (Kobra 2 and
|
|
84
|
+
earlier) are detected and refused as unsupported.
|
|
85
|
+
|
|
86
|
+
## Status
|
|
87
|
+
|
|
88
|
+
Version 0.1.0, alpha. The specification in [`docs/`](docs/) comes first; the
|
|
89
|
+
implementation follows it. Questions to the specification team and their
|
|
90
|
+
answers are in [`docs/QUESTIONS.md`](docs/QUESTIONS.md). See
|
|
91
|
+
[`docs/CLEAN-ROOM.md`](docs/CLEAN-ROOM.md) for how this library was written
|
|
92
|
+
and by whom.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
pip install -e ".[test]" ruff mypy
|
|
98
|
+
ruff check . && ruff format --check .
|
|
99
|
+
mypy --strict src
|
|
100
|
+
pytest
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Licence
|
|
104
|
+
|
|
105
|
+
MIT.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Clean-room record
|
|
2
|
+
|
|
3
|
+
This library and the Home Assistant core integration built on it are written
|
|
4
|
+
so that they contain **no code or expression** from two GPL-3.0 projects:
|
|
5
|
+
|
|
6
|
+
- `WaresWichall/hass-anycubic_cloud` (the original Anycubic Cloud integration)
|
|
7
|
+
- `Nino6689/hass-anycubic` and `Nino6689/anycubic-cloud-api` (its fork and
|
|
8
|
+
the fork's API library)
|
|
9
|
+
|
|
10
|
+
The fork's maintainer (Nino Bondonno) has worked on those projects, so the
|
|
11
|
+
work is split into two teams.
|
|
12
|
+
|
|
13
|
+
## Roles
|
|
14
|
+
|
|
15
|
+
| Role | Who | May read | Must not read |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| **Specification team** ("dirty") | Nino Bondonno, and Claude sessions on his machine that have read the GPL projects | Everything | — |
|
|
18
|
+
| **Implementation team** ("clean") | A separate Claude cloud session per phase, with no access to Nino's machine | This repository's `docs/`, the `anycubic-lan` package, Python and dependency documentation, the Home Assistant developer docs and `home-assistant/core` | The three repositories above, and any other Anycubic Home Assistant integration (e.g. adamoutler, chrisfore) |
|
|
19
|
+
|
|
20
|
+
Rules:
|
|
21
|
+
|
|
22
|
+
1. The specification team writes **facts and requirements only** — protocol
|
|
23
|
+
behaviour, captured payloads, entity lists — in `docs/`. It never writes or
|
|
24
|
+
edits code in this repository or in the core integration.
|
|
25
|
+
2. The implementation team writes **all code**, from `docs/` and public
|
|
26
|
+
documentation. When a fact is missing it asks in `docs/QUESTIONS.md`; it
|
|
27
|
+
does not look elsewhere.
|
|
28
|
+
3. Feedback from hardware testing reaches the implementation team only as
|
|
29
|
+
functional bug reports (observed behaviour vs expected), never as code or
|
|
30
|
+
diffs.
|
|
31
|
+
4. Before publication, the finished code is compared mechanically against the
|
|
32
|
+
GPL projects and the result recorded below.
|
|
33
|
+
|
|
34
|
+
## Log
|
|
35
|
+
|
|
36
|
+
| Date | Team | Session | Inputs given | Outputs | Did not read the excluded repos |
|
|
37
|
+
|---|---|---|---|---|---|
|
|
38
|
+
| 2026-09-26 | Specification | Claude (local, Nino's machine) | Captured payloads from Nino's Kobra S1; Kobra X logs from a user; the public `anycubic_ha_local` protocol description; notes from testing the fork | `PROTOCOL.md`, `INTEGRATION-SPEC.md`, this file, `LICENSE`, `README.md`, CI workflows | n/a — specification team |
|
|
39
|
+
| 2026-09-26 | Implementation | Claude cloud session (routine) | `docs/CLEAN-ROOM.md`, `docs/PROTOCOL.md`, `docs/INTEGRATION-SPEC.md`, `docs/QUESTIONS.md`, `README.md`, `LICENSE`, CI workflows; general knowledge of the Python, aiohttp, paho-mqtt, cryptography and pytest APIs and the installed packages themselves (no web searches, no Anycubic code) | `src/anycubic_lan/` (v0.1.0), `tests/`, `pyproject.toml`, `.gitignore`, README sections, questions Q1–Q9 in `QUESTIONS.md` | Yes |
|
|
40
|
+
| 2026-09-26 | Specification | Claude (local) | The v0.1.0 implementation (for acceptance only); the GPL projects (for the similarity check) | Answers to Q1–Q9 in `QUESTIONS.md`; `PROTOCOL.md` §6.7 corrected; similarity checks below | n/a — specification team |
|
|
41
|
+
| 2026-09-26 | Implementation | Claude cloud session (routine, round 2) | `docs/QUESTIONS.md` (answers to Q1–Q9), `docs/PROTOCOL.md` (incl. corrected §6.7), `docs/INTEGRATION-SPEC.md`, `docs/CLEAN-ROOM.md`, `README.md`, the CI workflow, this branch's own code and tests; the installed Python, paho-mqtt, pytest, mypy and ruff (no web searches, no Anycubic code) | `src/anycubic_lan/` and `tests/` updated for Q1–Q4 and Q9, comments for Q5–Q8; "Applied in round 2" marks in `QUESTIONS.md`; README status line; this row. Merged the concurrent commits `ce0ee48` and `ca86d53` pushed to this branch during the session, keeping this session's Q1–Q4/Q9 code where both overlapped | Yes |
|
|
42
|
+
|
|
43
|
+
| 2026-09-28 | Specification | Claude (local) | Nino's Kobra S1 in LAN Mode; the v0.1.0 branch's public API (black-box script, not committed) | Hardware report HW1–HW3 in `QUESTIONS.md`; corrections to `PROTOCOL.md` §2, §6.8, §6.9 and the Q5 answer | n/a — specification team |
|
|
44
|
+
| 2026-09-28 | Implementation | Claude cloud session (routine, round 3) | `docs/QUESTIONS.md` (HW1–HW3), `docs/PROTOCOL.md` (corrected §2, §6.8, §6.9), this branch's code and tests (no web searches, no Anycubic code) | Fixes for HW1–HW3 in `reports.py`, `client.py` docstring and `README.md`; tests from the live captures; "Fixed" notes in `QUESTIONS.md`; this row | Yes |
|
|
45
|
+
|
|
46
|
+
| 2026-09-28 | Specification | Claude (local) | The core branch run against the Kobra S1 in a throwaway Home Assistant; the library re-run after round 3 | HW1–HW2 confirmed fixed on hardware; HW4 reported; `PROTOCOL.md` §2 discovery fields added | n/a — specification team |
|
|
47
|
+
| 2026-09-28 | Implementation | Claude cloud session (core integration, round 2) | `docs/QUESTIONS.md` (HW4), `docs/PROTOCOL.md` §2 and §6.1, this library's source at `3f8ac33` (`DiscoveryInfo.as_redacted_dict`, `parse_discovery`); core's own code and dev tooling for house style (no web searches, no Anycubic code) | Core branch `anycubic-integration` `68757a01` (diagnostics redact `fileUploadurl` by key, use the library's redacted discovery copy, scrub any string containing `gcode_upload?s=`) and `cfb7317c` (discovery fixture refreshed with the live fields and a string `modelId`, regression test, snapshot); "Fixed on core branch" note under HW4; this row | Yes |
|
|
48
|
+
|
|
49
|
+
| 2026-09-28 | Specification | Claude (local) | Core branch `cfb7317` and library `5b8b406`, both run against the Kobra S1 | HW1, HW2 and HW4 confirmed fixed on hardware (HW3 settled by documentation); similarity rechecked | n/a — specification team |
|
|
50
|
+
|
|
51
|
+
## Similarity checks
|
|
52
|
+
|
|
53
|
+
| Date | Artefact | Compared against | Longest identical run (non-blank, non-comment lines) | Result |
|
|
54
|
+
|---|---|---|---|---|
|
|
55
|
+
| 2026-09-26 | `src/anycubic_lan/` @ `b7c0042` | `hass-anycubic` integration, `anycubic-cloud-api` library, `WaresWichall/hass-anycubic_cloud` | 6 — alphabetised stdlib imports (`hashlib, json, re, secrets, string, time`); next 4 — the standard `ssl` no-verification idiom | Clean: both are dictated by the language, not copied expression |
|
|
56
|
+
| 2026-09-26 | `tests/` @ `b7c0042` | the same, plus `anycubic-cloud-api/tests` | 15 — the captured `info.project` payload | Expected: data captured from a printer, copied from `PROTOCOL.md` §6.2 |
|
|
57
|
+
| 2026-09-26 | `src/anycubic_lan/` @ `b87f11e` (after round 2) | same three projects | 6 — the same stdlib import block; next 4 — the same `ssl` idiom | Clean; unchanged by round 2 |
|
|
58
|
+
| 2026-09-28 | `src/anycubic_lan/` @ `5b8b406` (after hardware fixes) | same three projects | 6 — the same stdlib import block | Clean; unchanged by rounds 3–4 |
|
|
59
|
+
| 2026-09-28 | core `homeassistant/components/anycubic/` @ `cfb7317` (after HW4) | same three projects | 5 — HA's sensor import block | Clean; unchanged by the HW4 fix |
|
|
60
|
+
| 2026-09-28 | core `tests/components/anycubic/` @ `cfb7317` | the same, plus both GPL test suites | 2 | Clean |
|
|
61
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Home Assistant core integration `anycubic` — functional specification
|
|
2
|
+
|
|
3
|
+
What the core integration must do. How it is built is up to the implementer,
|
|
4
|
+
following the Home Assistant developer documentation and house style. All
|
|
5
|
+
wire behaviour comes from `anycubic-lan` (see `PROTOCOL.md`).
|
|
6
|
+
|
|
7
|
+
## Identity
|
|
8
|
+
|
|
9
|
+
| manifest field | value |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `domain` | `anycubic` |
|
|
12
|
+
| `name` | `Anycubic` |
|
|
13
|
+
| `integration_type` | `device` |
|
|
14
|
+
| `iot_class` | `local_push` |
|
|
15
|
+
| `config_flow` | `true` |
|
|
16
|
+
| `requirements` | `["anycubic-lan==<released version>"]` |
|
|
17
|
+
| `codeowners` | `["@Nino6689"]` |
|
|
18
|
+
| `dhcp` | `[{"macaddress": "A4E88D*"}, {"hostname": "anycubic*"}, {"hostname": "kobra*"}]` |
|
|
19
|
+
| `quality_scale` | `bronze` in the first PR |
|
|
20
|
+
|
|
21
|
+
## Library API the integration needs (the library must provide this)
|
|
22
|
+
|
|
23
|
+
- An async **handshake**: given an `aiohttp.ClientSession` and a host, return
|
|
24
|
+
broker credentials plus printer identity (model id, model name, serial,
|
|
25
|
+
MAC, device id). Distinct exceptions for: unreachable, LAN Mode off,
|
|
26
|
+
unsupported printer, bad/rejected response.
|
|
27
|
+
- An async **client**: connect with those credentials; register a callback
|
|
28
|
+
that receives parsed state updates; `query_all()`; `disconnect()`;
|
|
29
|
+
`is_connected`; a connection-lost / connection-restored signal.
|
|
30
|
+
- A **state model**: one object per printer with typed fields for everything
|
|
31
|
+
in `PROTOCOL.md` §6 (temperatures, fans, state, speed mode, firmware,
|
|
32
|
+
job with progress/layers/times/task id, ACE boxes and slots, light,
|
|
33
|
+
position, last error code/message, capability flags, camera URL).
|
|
34
|
+
- **Commands**: pause, resume, stop (using the current job's task id), light
|
|
35
|
+
on/off.
|
|
36
|
+
|
|
37
|
+
## Config flow
|
|
38
|
+
|
|
39
|
+
- **user** step: one field, `host`. Run the handshake.
|
|
40
|
+
- Errors: `cannot_connect` (unreachable), `not_in_lan_mode`,
|
|
41
|
+
`unsupported_printer`, `unknown`.
|
|
42
|
+
- `unique_id` = the printer's `deviceId` from the handshake.
|
|
43
|
+
Abort `already_configured` if it exists (and update the host).
|
|
44
|
+
- Entry title = model name (e.g. "Anycubic Kobra S1").
|
|
45
|
+
- `data_description` text for `host` explaining where to find the IP and
|
|
46
|
+
that LAN Mode must be switched on at the printer (Settings → Network →
|
|
47
|
+
LAN Mode).
|
|
48
|
+
- **dhcp** step: from the manifest matchers. Probe the host's discovery
|
|
49
|
+
document; if it is an Anycubic printer in LAN Mode, set `unique_id` and
|
|
50
|
+
abort `already_configured` with the new host if known
|
|
51
|
+
(`discovery-update-info`); otherwise confirm with the user
|
|
52
|
+
(`discovery_confirm`). If LAN Mode is off, abort `not_in_lan_mode`.
|
|
53
|
+
- **reconfigure** step: change the host; must be the same printer
|
|
54
|
+
(`unique_id` match) or abort `wrong_device`.
|
|
55
|
+
- No credentials are stored (the printer rotates them) → reauthentication is
|
|
56
|
+
exempt.
|
|
57
|
+
|
|
58
|
+
## Runtime
|
|
59
|
+
|
|
60
|
+
- Setup: handshake + connect; on failure raise `ConfigEntryNotReady`. Store
|
|
61
|
+
the client and coordinator in `entry.runtime_data`.
|
|
62
|
+
- Coordinator: push-based. Apply each state update as it arrives; send
|
|
63
|
+
`query_all()` on connect and every **15 seconds**.
|
|
64
|
+
- Connection lost: entities become **unavailable once**, stay unavailable, log
|
|
65
|
+
a single warning; on return log a single info line and re-run the handshake
|
|
66
|
+
if needed. Never alternate between stale values and unavailable.
|
|
67
|
+
- Unload: disconnect cleanly.
|
|
68
|
+
- Diagnostics: the latest parsed state and the discovery document, with
|
|
69
|
+
`token`, credentials, serial and MAC redacted.
|
|
70
|
+
|
|
71
|
+
## Device
|
|
72
|
+
|
|
73
|
+
One device per entry: manufacturer `Anycubic`, model from the model id
|
|
74
|
+
(`PROTOCOL.md` §9), name from the printer, `sw_version` = firmware,
|
|
75
|
+
`serial_number` = `cn`, connection = MAC, `configuration_url` optional.
|
|
76
|
+
|
|
77
|
+
## Entities — first PR: `sensor` only
|
|
78
|
+
|
|
79
|
+
All with `has_entity_name`, translated names (`entity-translations`), icons
|
|
80
|
+
via `icons.json`, unique id = `<deviceId>_<key>`.
|
|
81
|
+
|
|
82
|
+
| key | what | device class / unit | state class | category / default |
|
|
83
|
+
|---|---|---|---|---|
|
|
84
|
+
| `status` | printer state: `idle`, `printing`, `paused`, `busy`, `unknown` | enum | — | — |
|
|
85
|
+
| `nozzle_temperature` | current nozzle °C | temperature, °C | measurement | — |
|
|
86
|
+
| `nozzle_target_temperature` | target nozzle °C | temperature, °C | measurement | — |
|
|
87
|
+
| `bed_temperature` | current bed °C | temperature, °C | measurement | — |
|
|
88
|
+
| `bed_target_temperature` | target bed °C | temperature, °C | measurement | — |
|
|
89
|
+
| `chamber_temperature` | only created if the printer reports a non-zero chamber reading | temperature, °C | measurement | — |
|
|
90
|
+
| `fan_speed` | part fan % | %, | measurement | disabled by default |
|
|
91
|
+
| `auxiliary_fan_speed` | aux fan % | % | measurement | disabled by default |
|
|
92
|
+
| `job_progress` | % | % | measurement | — |
|
|
93
|
+
| `job_current_layer` | layer | — | measurement | — |
|
|
94
|
+
| `job_total_layers` | layers | — | — | — |
|
|
95
|
+
| `job_elapsed` | minutes | duration, min | — | — |
|
|
96
|
+
| `job_remaining` | minutes | duration, min | — | — |
|
|
97
|
+
| `job_end_time` | now + remaining | timestamp | — | — |
|
|
98
|
+
| `job_name` | file name without path/extension | — | — | — |
|
|
99
|
+
| `job_status` | enum from `print_status`: `printing`, `complete`, `cancelled`, `downloading`, `checking`, `preheating`, `slicing`, `levelling`; `idle` when no job | enum | — | — |
|
|
100
|
+
| `speed_mode` | enum: `silent`, `standard`, `sport` (raw otherwise) | enum | — | — |
|
|
101
|
+
| `last_error_code` | printer error code; `none` when 200 | — | — | diagnostic |
|
|
102
|
+
|
|
103
|
+
Job sensors must read `idle`/unknown sensibly when there is no job — never
|
|
104
|
+
`unavailable` while the printer is connected.
|
|
105
|
+
|
|
106
|
+
## Later PRs (one each, after the first merges)
|
|
107
|
+
|
|
108
|
+
1. `binary_sensor`: printing, paused, ACE drying, runout-refill.
|
|
109
|
+
2. `button`: pause, resume, stop (available only when applicable).
|
|
110
|
+
3. `light`: printer light (type 2), on/off + brightness.
|
|
111
|
+
4. ACE sensors: per-slot material/colour/status, loaded slot, box temperature,
|
|
112
|
+
drying remaining.
|
|
113
|
+
5. `number` + `select`: target temperatures, speed mode.
|
|
114
|
+
6. `camera`: the FLV stream URL from `info.urls.rtspUrl` via ffmpeg.
|
|
115
|
+
7. `update`: firmware version (read-only unless a local update path exists).
|
|
116
|
+
|
|
117
|
+
## Non-goals
|
|
118
|
+
|
|
119
|
+
Anycubic cloud accounts, file browsers, printing files, a custom panel or
|
|
120
|
+
card, filament cost ledgers, custom actions. Those belong to the separate
|
|
121
|
+
HACS integration.
|