157 lines
9.0 KiB
Markdown
157 lines
9.0 KiB
Markdown
# Anycubic-Stack: wie Druck, Abbruch und ACE wirklich laufen
|
||
|
||
Stand **15.10.2026**. Quellen: Anycubic [Klipper-go](https://github.com/ANYCUBIC-3D/Klipper-go), [K3-klipper-mcu](https://github.com/ANYCUBIC-3D/K3-klipper-mcu); Rinkhals `kobra.py`, `mmu_ace.py`, `mmu_ace_metadata.py`, Docs `firmware/mqtt.md` und `firmware/goklipper.md` (lokal unter `D:\Projekte\_ref\`). Nicht geraten — am Quellcode und an der Rinkhals-Doku gelesen.
|
||
|
||
## Schichten
|
||
|
||
```mermaid
|
||
flowchart TB
|
||
subgraph host [PC]
|
||
Grotti[Grotti]
|
||
end
|
||
subgraph printer [Kobra 3 / S1]
|
||
Moonraker[Moonraker + kobra.py]
|
||
MmuAce[mmu_ace + mmu_ace_metadata]
|
||
Gkapi[gkapi MQTT Broker]
|
||
Gklib[gklib GoKlipper]
|
||
Ui[K3SysUi Touch]
|
||
Mcu[MCU Firmware]
|
||
AceHw[ACE Hardware]
|
||
end
|
||
Grotti -->|"HTTP :80 upload / print/start / cancel"| Moonraker
|
||
Grotti -->|"MQTTS :9883 print start/stop"| Gkapi
|
||
Grotti -->|"HTTP :18910 info/ctrl Credentials"| Gkapi
|
||
Moonraker -->|"bei print/start oft MQTT lokal :2883"| Gkapi
|
||
Moonraker --> Gklib
|
||
MmuAce --> Moonraker
|
||
Gkapi --> Gklib
|
||
Ui --> Gkapi
|
||
Gklib --> Mcu
|
||
Gklib --> AceHw
|
||
```
|
||
|
||
| Komponente | Offen? | Rolle |
|
||
| --- | --- | --- |
|
||
| **gklib** (GoKlipper) | teilweise ([Klipper-go](https://github.com/ANYCUBIC-3D/Klipper-go)) | Klipper-Host: Motion, Heater, `virtual_sdcard`, ACE-G-codes |
|
||
| **MCU** | teilweise ([K3-klipper-mcu](https://github.com/ANYCUBIC-3D/K3-klipper-mcu)) | Stepper/Sensoren |
|
||
| **gkapi** | nein (proprietär) | MQTT-Broker, Druck-Tasks, Cloud/LAN, Credentials `:18910` |
|
||
| **K3SysUi** | nein | Touch-UI |
|
||
| **Rinkhals Moonraker** | ja | Vanilla Moonraker + `kobra.py` + `mmu_ace*` |
|
||
|
||
Anycubic schreibt in den READMEs: proprietäre Features sind gekapselt. MQTT-Taskverwaltung und Cloud stecken nicht in Klipper-go.
|
||
|
||
## Zwei Welten beim Druckstart
|
||
|
||
1. **Klipper-Pfad** (Moonraker `POST /printer/print/start` → gklib `virtual_sdcard`): setzt `print_stats`, lädt G-code in gklib.
|
||
2. **Anycubic-Task** (MQTT `type: print`, `action: start` an Topic `…/slicer/printer/{modelId}/{deviceId}/print`): legt den Job an, den Display und Cloud kennen; steuert LeviQ/`auto_leveling`, ACE-Map in der Payload.
|
||
|
||
Rinkhals `kobra.py` `patch_mqtt_print` hängt den Moonraker-`print/start` oft auf MQTT um (`mqtt_print_file`), lokal `127.0.0.1:2883`. Grotti kann denselben Start von außen über **MQTTS `:9883`** mit Credentials aus `:18910` machen — dann mit eigener `task_settings` und `ams_settings` (siehe [Senden-Queue](Senden-Queue)).
|
||
|
||
### MQTT Start (belegt)
|
||
|
||
Aus Rinkhals-Doku `firmware/mqtt.md` und `kobra.py`:
|
||
|
||
- Publish: `anycubic/anycubicCloud/v1/slicer/printer/{modelId}/{deviceId}/print`
|
||
- Report: `…/printer/public/{modelId}/{deviceId}/print/report`
|
||
- Pflichtfelder Start: `filename`, `filetype` (1), `taskid`
|
||
- Optional: `ams_settings.ams_box_mapping`, `task_settings.auto_leveling` (0/1 Integer)
|
||
- Rinkhals und Grotti setzen `taskid` auf eine Zufallszahl; die Doc-Minimalbeispiel nutzt `"-1"` — beides kommt vor
|
||
|
||
### MQTT Stop (belegt) — warum 10101
|
||
|
||
```json
|
||
{ "type": "print", "action": "stop", "data": { "taskid": "-1" } }
|
||
```
|
||
|
||
Moonraker `/printer/print/cancel` trifft nur gklib (`print_stats` → `cancelled`). Der Anycubic-Task in gkapi bleibt → nächster MQTT-Start mit **code 10101** („Print task already exists“ / chinesische Meldung). Rinkhals `mqtt_print_file` kennt 10101 und **retryt einmal**; Grotti sendet bei Abbruch Moonraker-cancel **und** MQTT-stop.
|
||
|
||
### Task-Hygiene (wie Rinkhals)
|
||
|
||
Vorbild: `kobra.py` `mqtt_print_file` + `firmware/mqtt.md`. **Kobra 3 und Kobra S1** — dieselbe gkapi-API (`:18910` / MQTTS `:9883`). Gemessen 15.10.2026: S1 `modelId` 20025 / `state: free`, K3 `modelId` 20024; `project: null` zählt nicht als busy. Abräumen-Ablauf bleibt nur Kobra 3.
|
||
|
||
| Schritt | Verhalten in Grotti |
|
||
| --- | --- |
|
||
| Vor MQTT-Start | `ensure_task_free`: `info/query`; wenn `state` nicht free oder `project` gesetzt → MQTT-stop, bis zu 3× pollen |
|
||
| Vor Moonraker-`print/start` | dieselbe Hygiene best-effort (Rinkhals wandelt Start oft intern zu MQTT um) |
|
||
| gkapi kurz weg | `print_info` → `None`, negativer Cache ~45 s; Start über Moonraker läuft weiter |
|
||
| Report **10101** | einmal cancel + 1,5 s Pause + erneuter Start |
|
||
| Report **10107** | klare Filament-Meldung |
|
||
| Live | Snapshot-Feld `anycubic_task` (`state`, `task_id`, `filename`) aus gecachtem `print_info` (TTL ~10 s); Wand zeigt die Zeile |
|
||
|
||
Codes und Retry: `grotti/anycubic_lan.py` (`message_for_code`, `ensure_task_free`, `start_print`).
|
||
|
||
### Report-Timeout
|
||
|
||
`kobra.py` wartet max. 30 s auf `print/report`. Nach Bettnetz/`auto_leveling` kann der Report später kommen, der Druck trotzdem laufen (`info/query` → `project.task_id`). Grotti prüft bei Timeout deshalb `print_info` (MQTT `info/query`).
|
||
|
||
## FIRMWARE_RESTART
|
||
|
||
In **Klipper-go** ist der Befehl implementiert wie bei Klipper:
|
||
|
||
- `Cmd_FIRMWARE_RESTART` → `Request_restart("firmware_restart")` (`gcode.go`)
|
||
- Nach dem Reactor-Ende: Event `project:firmware_restart` → MCU `_firmware_restart` (`k3c.go`, `Mcu.go`)
|
||
|
||
Auf dem Gerät **deadlockt gklib** trotzdem (beobachtet, Rinkhals #32): Log zeigt Annahme des Befehls, danach keine weitere Verarbeitung, Moonraker bleibt in `startup`. Rinkhals `patch_klipper_restart` **lehnt** `RESTART` / `FIRMWARE_RESTART` unter GoKlipper ab. Grotti ebenso. Config: Display-Reboot oder Strom. Hängenden Fehler: **Fehler löschen** (`SDCARD_RESET_FILE`), nicht Firmware-Restart.
|
||
|
||
Der Open-Source-Code erklärt den Intent des Befehls; die Deadlock-Ursache steckt vermutlich im ausgelieferten Binary / der Anbindung an gkapi — nicht im veröffentlichten Happy Path.
|
||
|
||
## ACE
|
||
|
||
### In gklib (Klipper-go `extras_ace.go`)
|
||
|
||
G-codes: `ACE_CHANGE_TOOL`, `ACE_FEED`, `ACE_RETRACT`, `ACE_SET_SLOT`, Trocknen, Endless Spool, … Kommunikation über Seriell (`extras_ace_commun.go`). `Get_status` liefert ACE-`info` + Endless-Spool-Flags — **nicht** das Moonraker-Objekt `mmu` mit `gate_color`.
|
||
|
||
### In Rinkhals Moonraker (`mmu_ace.py`)
|
||
|
||
- Emuliert Happy-Hare-ähnliches **`mmu`**-Objekt (`gate_color`, `gate_material`, …) für Clients
|
||
- Beim MQTT-Start: `register_print_data_patcher` → wenn keine `ams_settings` in der Payload, baut Identity-/TTG-Map aus den Gates und hängt `ams_settings` an
|
||
- Liest/schreibt Sidecar **`basename.acm`** neben dem G-code; ohne ACE kann sie nach `.acm.disabled` umbenannt werden
|
||
- Auto-Feed-Logik vor Start, wenn nichts im Toolhead ist (Issue #464)
|
||
|
||
### Upload-Metadaten (`mmu_ace_metadata.py`)
|
||
|
||
Beim Moonraker-Upload: ganze Datei lesen, ggf. umschreiben. Fingerprint `; processed by MmuAcePatcher`. Trailer `; referenced_tools = 0,2,…`. Ohne Trailer: gklib „skipping auto-feed (no trailer found)“. Grotti setzt Fingerprint + Trailer vor dem Upload, damit der teure Durchlauf entfällt ([Senden-Queue](Senden-Queue)).
|
||
|
||
### `.acm` vs MQTT-`ams_settings`
|
||
|
||
Zwei Kanäle für dieselbe Zuordnung:
|
||
|
||
| Kanal | Wann |
|
||
| --- | --- |
|
||
| Datei `name.gcode.acm` / `name.acm` auf dem Drucker | Stock/gklib liest Sidecar beim Start |
|
||
| MQTT-Feld `data.ams_settings` | gkapi / Rinkhals-Patcher legt Map in die Payload |
|
||
|
||
Ohne Map: Rinkhals füllt oft Identity T0→Platz0 … — deshalb Grotti bei eigener ACE-Zuordnung per MQTT mit expliziter Map startet und beide `.acm`-Varianten hochlädt ([ACE-Live](ACE-Live)).
|
||
|
||
## Was Grotti bewusst nutzt
|
||
|
||
| Aktion | Weg | Beleg |
|
||
| --- | --- | --- |
|
||
| Einfacher Start | Upload + Moonraker `print/start` → Rinkhals MQTT | `kobra.py` `patch_mqtt_print` |
|
||
| Start mit Bettnetz oder ACE-Map | Upload (+ `.acm`) + Grotti-MQTT `:9883` | `anycubic_lan.start_print`, mqtt.md |
|
||
| Abbruch | Moonraker cancel + MQTT stop `taskid:-1` | `mqtt_stop_print`, mqtt.md |
|
||
| Credentials | `:18910/info` + `/ctrl` AES | `anycubic_lan.credentials` |
|
||
| Slots anzeigen | Moonraker `mmu` (Rinkhals) | `mmu_ace.py` |
|
||
| Kein Firmware-Restart | abgelehnt | Rinkhals #32, `patch_klipper_restart` |
|
||
|
||
## History nach Filament-Pause
|
||
|
||
Runout, Rolle tauschen, Fortsetzen: Anycubic schließt den History-Job als `cancelled` und öffnet eine neue Job-ID. Beobachtet am 2026-10-25 auf Kobra 3 A1 (`000040` → `000041`):
|
||
|
||
- Der neue `start_time` liegt etwa 0,04 s nach dem `end_time` des alten.
|
||
- `print_duration` und `total_duration` sind an beiden Einträgen gleich und laufen weiter. Der Zähler beginnt nicht bei 0.
|
||
- `/server/history/list` liefert neueste zuerst. Grotti sortiert pro Drucker nach `start_time`, sonst kommt die Fortsetzung vor ihrem Vorgänger dran (`jobs.history_in_order`).
|
||
- Nach dem Zusammenlegen hängt die alte Job-ID in `run_jobs` am Lauf. `store.history_job_retired` überspringt sie, sonst schreibt sie `cancelled` zurück.
|
||
- Zusammenlegen nur mit Beweis: `print_duration` fortgesetzt und Lücke ≤ 5 s, oder Watcher `paused→printing` (`docs/internal/Lauf-Identitaet.md`).
|
||
|
||
## Offene Grenzen
|
||
|
||
- Quellcode von **gkapi** (Task-State-Machine, exakte 10101-Bedingungen) ist nicht öffentlich.
|
||
- Deadlock in **FIRMWARE_RESTART** ist beobachtet und in Rinkhals abgefangen, Root Cause im Binary nicht freigelegt.
|
||
- Kobra X / K4-Stack ist ein anderes System (C++-Klipper), nicht GoKlipper — für unsere Drucker irrelevant.
|
||
|
||
## Siehe auch
|
||
|
||
- [Makros-Kobra](Makros-Kobra.md) — Nicht-tun, Vanilla-Alltag, ACE, LeviQ/Wipe je Druckertyp
|
||
|