Compare commits

..

16 Commits

Author SHA1 Message Date
9279036c51 enable http communication with printer to fetch user and password 2026-05-07 12:49:10 +02:00
ce63cc5e7a fix: YouTube Thumbnail auf hqdefault 2026-05-04 14:39:00 +02:00
5c83cc6df0 docs: Video Tutorial in README 2026-05-04 14:37:10 +02:00
be11217896 release: v0.9.6.1 2026-05-02 21:31:54 +02:00
0292785fd8 release: v0.9.6.1 2026-05-02 21:27:19 +02:00
50419fb487 release: v0.9.6 2026-05-02 20:58:40 +02:00
f196b8d29a release: v0.9.5 2026-05-01 18:09:24 +02:00
1d3c5a7e1b release: v0.9.4 2026-05-01 11:24:08 +02:00
c22296d880 chore: sync v0.9.3 – alle Fixes, CHANGELOG, README, VERSION 2026-05-01 10:36:09 +02:00
d9d3581e22 fix: VERSION ins Dockerfile, STABLE_RELEASE_API fix, Version im Header (#14) 2026-05-01 10:11:54 +02:00
966d421016 fix: Dockerfile für flache Release-Struktur angepasst, config.ini.example hinzugefügt 2026-04-30 09:51:17 +02:00
2a12ecca51 chore: sync v0.9.2 – README/CHANGELOG DE+EN, config_loader, aktuelle Bridge-Quelldateien
- README.md (EN), README.de.md (DE) – README.en.md entfernt
- CHANGELOG.md (EN), CHANGELOG.de.md (DE)
- config_loader.py neu (config.ini statt .env)
- kobrax_moonraker_bridge.py, kobrax_client.py, env_loader.py aktualisiert
- Dockerfile, docker-compose.yml, VERSION auf 0.9.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 14:53:23 +02:00
ae4777187f docs: Netzwerkhinweis auf Host-IP statt 0.0.0.0 verbessert 2026-04-26 20:30:55 +02:00
8ccafb96c4 docs: Logo in englische README eingefügt 2026-04-26 20:25:37 +02:00
21f340271b docs: Projektname auf 'Klipper Bridge', Version auf 0.9.1-beta15 2026-04-26 20:24:17 +02:00
2f56a1f056 feat: Logo ins README hinzugefügt 2026-04-26 20:20:44 +02:00
18 changed files with 2057 additions and 695 deletions

View File

@@ -21,3 +21,4 @@ DEVICE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Modell-ID (Kobra X Standard: 20030)
MODE_ID=20030

265
CHANGELOG.de.md Normal file
View File

@@ -0,0 +1,265 @@
# Changelog
## [0.9.6.1] 2026-05-02
### Fixes
- **Upload-Banner:** Banner wird nach Stopp/Abbruch nicht mehr erneut angezeigt — `file_ready` und Thumbnail werden jetzt gecleared wenn der Drucker `stoped` oder `canceled` meldet
---
## [0.9.6] 2026-05-02
### Neu
- **Fortschritts-Karte:** Verstrichen / Slicer-Schätzung / Restzeit als Mini-Cards (gleicher Stil wie Temperaturkarten)
- **Layer-Mini-Card:** Layerzahl als Mini-Card neben der Fortschrittsleiste
### Fixes
- **Slicer-Schätzzeit:** OrcaSlicer schreibt die geschätzte Zeit ans Ende der GCode-Datei — Bridge liest jetzt auch die letzten 64 KB (vorher nur die ersten 16 KB)
- **start.sh:** `config/`-Verzeichnis wird jetzt automatisch erstellt und `config.ini.example` wird beim ersten Start hineinkopiert (Issue #15)
---
## [0.9.5] 2026-05-01
### Neu
- **Upload-Banner:** Nach „Nur hochladen" erscheint ein grüner Banner mit Dateiname — „▶ Druck starten" startet den Druck direkt, „✕ Abbrechen" schließt den Banner
### Fixes
- **Auto-Print:** `auto_print` wurde nach dem Multipart-Loop immer auf `False` zurückgesetzt — OrcaSlicer „Hochladen und drucken" startete den Druck nie automatisch
- **Thumbnail:** Vorschaubild wird jetzt auch bei „Nur hochladen" angezeigt — Bridge fragt `fileDetails` direkt nach dem Upload an
- **Log Auto-Scroll:** Scroll-Position bleibt erhalten wenn Auto-Scroll deaktiviert ist — kein ungewollter Sprung nach oben mehr
---
## [0.9.4] 2026-05-01
### Neu
- **AMS-Slot-Editor:** Slot im AMS-Panel anklicken → Dialog mit Farbpicker und Material-Auswahl (Schnellbuttons: PLA/PETG/ABS/ASA/TPU/PA/PC/HIPS oder Freitext) direkt im Browser
- **Verbessertes Log-Panel:** Vollständige MQTT-Payloads (keine Kürzung mehr), Richtungsfilter (Alle/RX/TX) und Topic-Schnellfilter (AMS / print / info / status)
### Fixes
- **i18n:** Kamera-Placeholder-Text und Log-Richtungs-Button „Alle" werden jetzt korrekt beim Sprachwechsel übersetzt
---
## [0.9.3] 2026-05-01
### Fixes
- **Update-Check:** Stable-User erhalten keine Dev-Pre-Releases mehr — `STABLE_RELEASE_API` hatte `pre-release=true`, wodurch stabile Installationen Dev-Builds statt stabiler Releases fanden (Issue #14)
- **Version nach Update:** `VERSION`-Datei wird jetzt im Docker-Image mitgeliefert (`COPY VERSION .`) — `_write_version()` benötigt eine vorhandene Datei, ohne die wurde die Version nach dem Self-Update nie aktualisiert (Issue #14)
### Neu
- **Version im Header:** Laufende Version wird im Web-UI-Header neben dem Druckernamen angezeigt — kein Öffnen der Einstellungen nötig (Issue #14)
---
## [0.9.2] 2026-04-29
### ⚠️ Breaking Change: Konfiguration wechselt von `.env` zu `config/config.ini`
**Migration erfolgt automatisch** beim ersten Start — keine manuelle Aktion nötig.
- Einstellungen werden ab sofort aus `config/config.ini` gelesen statt aus `.env`
- Beim ersten Start ohne `config.ini` wird die Datei automatisch aus `.env` erstellt
- **Docker:** Volume `./config:/app/config` in `docker-compose.yml` ist der persistente Speicherort — Einstellungen überleben `docker-compose restart` und Updates
- **Standalone:** `config/config.ini` liegt neben der Binary und wird bei Updates nicht überschrieben
- `.env` bleibt als read-only Fallback gemountet — kann liegen bleiben
- Zum manuellen Anlegen einer `config.ini`: Vorlage unter `config/config.ini.example`
### Neu
- **Persistente Einstellungen:** `config/config.ini` ersetzt `.env` — Einstellungen gehen nach `docker-compose restart` nicht mehr verloren (Issue #9)
- **Verbindungsfehler-Banner:** Roter Banner oben in der Web-UI wenn MQTT-Verbindung fehlschlägt (z.B. falsches Passwort, Drucker nicht erreichbar) (Issue #11)
- **Slicer-Schätzzeit:** Geschätzte Gesamtdruckzeit aus dem GCode-Header wird im Fortschritts-Panel angezeigt
### Fixes
- README: OrcaSlicer-Verbindung explizit mit `http://` und Port `:7125` dokumentiert (Issue #12)
- README: Direkter Download-Link für `extract_credentials` auf Gitea-Releases (Issue #13)
---
## [0.9.1-dev] laufend (dev-Branch)
### Neu
- **Dev-Branch-Infrastruktur:** Versionsschema `0.9.1-dev+<hash>` — jeder Build eindeutig identifizierbar
- **Separater Update-Kanal:** Dev-Versionen prüfen auf Gitea Pre-Releases mit `-dev+` im Tag
- **AMS-Slot-Auswahl:** Einstellung „Standard-Slot (Einfarbdruck)" im Settings-Modal — fixiert einen bestimmten AMS-Kanal oder Auto (alle belegten Slots)
- **Auto-Leveling:** Checkbox im Settings-Modal — steuert `task_settings.auto_leveling` beim Druckstart
- **MQTT-Logging:** Strukturiertes TX/RX-Log mit Duplikat-Filter (`kobrax.mqtt` Logger)
- **Server-Log im Browser:** Live-Stream via SSE (`/api/log/stream`) — alle Server-Logs erscheinen im Log-Tab der UI
- **Log-Tab Verbesserungen:**
- Auto-Scroll ein/aus — deaktiviert sich beim manuellen Hochscrollen, Button zum Reaktivieren
- Textfilter — Live-Filterung der Log-Einträge
- Error-Badge — roter Zähler im Tab-Button bei Fehlern/Warnungen
- Clear-Button — Buffer leeren
- Download-Button — letzte 500 Einträge als `kx-bridge.log`
- Log-Fenster füllt den gesamten verfügbaren Platz (statt fester Höhe 160px)
- **Log-Buffer:** 500 Einträge (Server + Browser vereinheitlicht)
- **Changelog im Update-Dialog:** Release-Notes aus Gitea werden direkt im Update-Dialog angezeigt
- **Slicer-Schätzzeit:** Geschätzte Gesamtdruckzeit aus dem GCode-Header im Fortschritts-Panel
---
## [0.9.1-beta15] 2026-04-26
### Fixes
- AMS: Leere Slots werden beim Druckstart übersprungen — kein `filament runout` mehr bei unbelegten Kanälen (Issue #5)
- AMS: Material-Typ wird jetzt korrekt aus dem Drucker-Protokoll gelesen (Feld `type` statt `material_type`)
- AMS UI: Leere Slots werden grau/transparent mit „Leer"-Label dargestellt
---
## [0.9.1-beta14] 2026-04-26
### Fixes
- Z-Achse: ▲ fährt jetzt aufwärts (Z+), ▼ abwärts (Z) — Pfeile waren vertauscht (Issue #4)
- Home All: korrekter Achsen-Code 5 — homed alle Achsen XYZ (Issue #4)
- Neuer Button „Home XY" (axis=4) in der UI
- Neuer Button „Motoren aus" (axis turnOff) in der UI
---
## [0.9.1-beta13] 2026-04-26
### Fixes (Windows)
- Self-Update / Settings-Neustart: `os.execv` funktioniert jetzt korrekt in der PyInstaller-Binary
- Kamera: `ffmpeg nicht gefunden` crasht nicht mehr — saubere 503-Antwort wenn ffmpeg fehlt
- Reconnect-Loop: Kurze leere TCP-Reads unter Windows lösen keine sofortigen Reconnects mehr aus
### Struktur
- `bridge/`: Bridge-Dateien aus `05_scripts/` herausgelöst
- `tools/`: `extract_credentials.py` als eigenständiges Tool mit eigenem README
- `_archive/`: RE-Forschungsordner, Analyse-Tools und alte Release-Checksums archiviert
- README komplett neu: klarer 3-Schritte-Schnellstart
---
## [0.9.1-beta12] 2026-04-25
### Fixes
- Falsche MQTT-Zugangsdaten zeigen jetzt eine verständliche Fehlermeldung statt des kryptischen `CONNACK failed: 20020005`
---
## [0.9.1-beta11] 2026-04-25
### Fixes
- Drucker-IP wird automatisch bereinigt wenn der Nutzer versehentlich den Port miteingibt (z.B. `192.168.1.102:9883``192.168.1.102`)
- Settings-Modal: Hinweis erscheint wenn ein `:` in der IP erkannt wird
- `docker-compose.yml`: `.env` als Volume gemountet — Einstellungen bleiben nach `docker-compose restart` erhalten
---
## [0.9.1-beta10] 2026-04-25
### Neu
- `start.sh` — startet die Bridge per Docker, baut das Image automatisch beim ersten Aufruf
- Tests: pytest-Suite (19 Tests) für API-State, Moonraker-Endpunkte, Settings; Installations-Smoke-Test (`test_install.sh`)
- Settings-Modal öffnet sich beim ersten Start automatisch wenn keine Zugangsdaten hinterlegt sind
### Geändert
- README: Schnellstart zeigt jetzt `./start.sh` statt manuellem `docker build`
- README: LAN-Modus korrekt als Drucker-Menüoption beschrieben
- README: Versionsnummer wird ab jetzt automatisch bei jedem Release aktualisiert
- `extract_credentials`: `--write-env` nicht mehr empfohlen — Werte im ⚙-Menü eintragen
- Dockerfile im Release-Repo: Pfade ohne `05_scripts/`-Präfix
- `release.sh`: Dockerfile für Release-Repo automatisch per `sed` angepasst
### Fixes
- Restdruckzeit (`remain_time`) wird jetzt korrekt aus `print/report` übernommen und in der UI angezeigt
- Übersetzungen: „Schrittweite" und „Ziel"-Placeholder in Temperatureingaben korrekt übersetzt
---
## [0.9.1-beta9] 2026-04-25
### Neu
- OrcaSlicer-Profil (`kobra_x_orcaslicer_preset.zip`) als Release-Asset
- `release.sh`: OrcaSlicer-Profil wird automatisch ins Release-Repo kopiert und hochgeladen
### Geändert
- README: `extract_credentials` ohne `--write-env`, Werte manuell im ⚙-Menü eintragen
- README: Docker-Schnellstart vereinfacht
---
## [0.9.1-beta8] 2026-04-25
### Neu
- Restdruckzeit-Anzeige in der UI (≈ Xh Ym verbleibend) aus `remain_time`-Feld
- Settings-Modal: Verbindungseinstellungen und Self-Update direkt im Browser
- Self-Update: Bridge prüft Gitea-Release-API auf neue Versionen und aktualisiert sich selbst
### Geändert
- Bridge startet im Offline-Modus wenn Drucker nicht erreichbar (kein Absturz)
- Verbinden/Trennen-Button im Header
---
## [0.9.1-beta7] 2026-04-22
### Neu
- Offline-Start: Bridge läuft auch ohne MQTT-Verbindung, verbindet automatisch sobald Drucker erreichbar
- Verbinden/Trennen-Button im Header
---
## [0.9.1-beta6] 2026-04-20
### Neu
- Release-ZIPs: `kx-bridge-linux.zip`, `kx-bridge-windows.zip`, `anycubic-certs.zip` mit Zertifikaten
### Fixes
- PyInstaller frozen-Binary: `__file__` durch `sys.executable`-Pfad ersetzt (Cert-Pfad-Fix)
---
## [0.9.1-beta5] 2026-04-19
### Neu
- `kx-bridge.exe` (Windows) wird automatisch via GitHub Actions gebaut
---
## [0.9.1-beta4] 2026-04-18
### Neu
- `release.sh`: baut Linux-Binary und Windows-EXE, lädt alle Assets auf Gitea hoch
- Englische README (`README.en.md`)
### Fixes
- `progress` und `filename` werden bei `stoped`/`canceled` korrekt auf 0 zurückgesetzt
---
## [0.9.1-beta3] 2026-04-17
### Neu
- Druckgeschwindigkeit-Karte (Leise / Normal / Sport)
- Übersetzungen (DE/EN) vervollständigt
---
## [0.9.1-beta2] 2026-04-17
### Fixes
- Temperatursteuerung während eines laufenden Drucks
---
## [0.9.1-beta1] 2026-04-17
### Neu
- UI-Komplettüberarbeitung: Settings-Modal, Self-Update, Dashboard, Responsive Design
- Neue Drucker-Zustände: `pausing`, `paused`, `resuming`, `resumed`, `stopping`
- `release.sh`: Version-Bump und Release-Sync Skript
---
## [0.9.0-beta1] 2026-04-10
### Neu
- Erster öffentlicher Release
- Docker-Deployment, Linux-Binary, `extract_credentials`-Tool
- Moonraker-kompatible HTTP/WebSocket-Bridge für den Anycubic Kobra X
- AMS Einziehen/Ausziehen, Licht- und Lüftersteuerung
- Web-UI mit Dashboard, Temperaturkarten, Achsensteuerung

View File

@@ -1,166 +1,265 @@
# Changelog
## [0.9.6.1] 2026-05-02
### Fixes
- **Upload banner:** Banner is no longer shown again after print stop/cancel — `file_ready` and thumbnail are now cleared when the printer reports `stoped` or `canceled`
---
## [0.9.6] 2026-05-02
### New
- **Progress card:** Elapsed / Slicer estimate / Remaining time shown as mini-cards (same style as temperature cards)
- **Layer mini-card:** Layer count displayed as mini-card next to the progress bar
### Fixes
- **Slicer estimate time:** OrcaSlicer writes the estimated time at the end of the GCode file — bridge now also scans the last 64 KB (previously only the first 16 KB were checked)
- **start.sh:** `config/` directory is now created automatically and `config.ini.example` is copied into it on first run (Issue #15)
---
## [0.9.5] 2026-05-01
### New
- **Upload banner:** After "Upload only", a green banner appears with the filename — "▶ Start Print" starts the print directly, "✕ Cancel" dismisses the banner
### Fixes
- **Auto-print:** `auto_print` was always reset to `False` after the multipart loop — OrcaSlicer "Upload and print" never started the print automatically
- **Thumbnail:** Preview image is now shown after "Upload only" — bridge requests `fileDetails` immediately after upload
- **Log auto-scroll:** Scroll position is preserved when auto-scroll is disabled — no more unwanted jump to top
---
## [0.9.4] 2026-05-01
### New
- **AMS slot editor:** Click any slot in the AMS panel to open an edit dialog — set color (color picker) and material (preset buttons: PLA/PETG/ABS/ASA/TPU/PA/PC/HIPS or free text) directly from the browser
- **Improved log panel:** Full MQTT payloads (no truncation), direction filter (All/RX/TX) and topic quick-filter buttons (AMS / print / info / status)
### Fixes
- **i18n:** Camera placeholder text and log direction "All" button now correctly translated on language switch
---
## [0.9.3] 2026-05-01
### Fixes
- **Update check:** Stable users no longer receive dev pre-releases — `STABLE_RELEASE_API` had `pre-release=true` which caused stable installs to find dev builds instead of stable releases (Issue #14)
- **Version after update:** `VERSION` file is now included in the Docker image (`COPY VERSION .`) — `_write_version()` requires the file to exist, without it the version was never updated after self-update (Issue #14)
### New
- **Version in header:** Running version shown in the Web-UI header next to the printer name — no need to open Settings to check (Issue #14)
---
## [0.9.2] 2026-04-29
### ⚠️ Breaking Change: Configuration moves from `.env` to `config/config.ini`
**Migration is automatic** on first start — no manual action required.
- Settings are now read from `config/config.ini` instead of `.env`
- On first start without `config.ini`, the file is created automatically from `.env`
- **Docker:** Volume `./config:/app/config` in `docker-compose.yml` is the persistent storage — settings survive `docker-compose restart` and updates
- **Standalone:** `config/config.ini` sits next to the binary and is not overwritten on update
- `.env` stays mounted read-only as a migration source — you can leave it in place
- To create a `config.ini` manually: copy `config/config.ini.example`
### New
- **Persistent settings:** `config/config.ini` replaces `.env` — settings no longer lost after `docker-compose restart` (Issue #9)
- **Connection error banner:** Red banner at the top of the Web-UI when MQTT connection fails (e.g. wrong password, printer unreachable) (Issue #11)
- **Slicer estimated time:** Estimated total print time from GCode header shown in the progress panel
### Fixes
- README: OrcaSlicer connection documented explicitly with `http://` and port `:7125` (Issue #12)
- README: Direct download link for `extract_credentials` pointing to Gitea releases (Issue #13)
---
## [0.9.1-dev] ongoing (dev branch)
### New
- **Dev branch infrastructure:** Version scheme `0.9.1-dev+<hash>` — every build uniquely identifiable
- **Separate update channel:** Dev versions check for Gitea pre-releases with `-dev+` in the tag
- **AMS slot selection:** Setting "Default slot (single color)" in the Settings modal — pins a specific AMS channel or Auto (all loaded slots)
- **Auto-leveling:** Checkbox in Settings modal — controls `task_settings.auto_leveling` on print start
- **MQTT logging:** Structured TX/RX log with duplicate filter (`kobrax.mqtt` logger)
- **Server log in browser console:** Live stream via SSE (`/api/log/stream`) — all server logs appear in the Log tab
- **Log tab improvements:**
- Auto-scroll on/off — disables automatically on manual scroll-up, button to re-enable
- Text filter — live filtering of log entries
- Error badge — red counter on the tab button when errors/warnings occur while on another tab
- Clear button — empty the buffer
- Download button — last 500 entries as `kx-bridge.log`
- Log window now fills all available space (instead of fixed 160px height)
- **Log buffer:** 500 entries (server + browser unified)
- **Changelog in update dialog:** Release notes from Gitea loaded and shown directly in the update dialog
- **Slicer estimated time:** Estimated total print time from GCode header shown in the progress panel
---
## [0.9.1-beta15] 2026-04-26
### Fixes
- AMS: Leere Slots werden beim Druckstart übersprungen kein `filament runout` mehr bei unbelegten Kanälen (Issue #5)
- AMS: Material-Typ wird jetzt korrekt aus dem Drucker-Protokoll gelesen (Feld `type` statt `material_type`)
- AMS UI: Leere Slots werden grau und transparent dargestellt mit „Leer"-Label
- AMS: Empty slots are skipped on print start — no more `filament runout` for unloaded channels (Issue #5)
- AMS: Material type is now correctly read from the printer protocol (field `type` instead of `material_type`)
- AMS UI: Empty slots shown grey/transparent with "Empty" label
---
## [0.9.1-beta14] 2026-04-26
### Fixes
- Z-Achse: ▲ fährt jetzt aufwärts (Z+), ▼ abwärts (Z) Pfeile waren vertauscht (Issue #4)
- Home All: korrekter axis-Code 5 homed alle Achsen XYZ (Issue #4)
- Neuer Button „Home XY" (axis=4) in der UI
- Neuer Button „Motors Off" (axis turnOff) in der UI
- Z axis: ▲ now moves up (Z+), ▼ moves down (Z) — arrows were reversed (Issue #4)
- Home All: correct axis code 5 homes all axes XYZ (Issue #4)
- New "Home XY" button (axis=4) in the UI
- New "Motors Off" button (axis turnOff) in the UI
---
## [0.9.1-beta13] 2026-04-26
### Fixes (Windows)
- Self-Update / Settings-Neustart: `os.execv` funktioniert jetzt korrekt in der PyInstaller-Binary (kein doppelter Pfad als Argument mehr)
- Kamera: `ffmpeg nicht gefunden` crasht nicht mehr saubere 503-Antwort wenn ffmpeg nicht installiert ist
- Reconnect-Loop: Kurzeitige leere TCP-Reads unter Windows führen nicht mehr sofort zu Reconnects
- Self-update / Settings restart: `os.execv` now works correctly in PyInstaller binary
- Camera: `ffmpeg not found` no longer crashes — clean 503 response when ffmpeg is not installed
- Reconnect loop: Short empty TCP reads on Windows no longer trigger immediate reconnects
### Struktur
- `bridge/`: Bridge-Dateien aus `05_scripts/` herausgelöst
- `tools/`: `extract_credentials.py` als eigenständiges Tool mit eigenem README
- `_archive/`: RE-Forschungsordner, Analyse-Tools und alte Release-Checksums archiviert
- README komplett neu: klarer 3-Schritte-Schnellstart
### Structure
- `bridge/`: Bridge files extracted from `05_scripts/`
- `tools/`: `extract_credentials.py` as standalone tool with its own README
- `_archive/`: RE research folders, analysis tools and old release checksums archived
- README fully rewritten: clear 3-step quick start
---
## [0.9.1-beta12] 2026-04-25
### Fixes
- Fehlermeldung bei falschen MQTT-Zugangsdaten ist jetzt verständlich: `Falsche MQTT-Zugangsdaten (falscher Benutzername, Passwort oder Device-ID)` statt kryptischem `CONNACK failed: 20020005`
- Wrong MQTT credentials now shows a human-readable error instead of cryptic `CONNACK failed: 20020005`
---
## [0.9.1-beta11] 2026-04-25
### Fixes
- Drucker-IP wird automatisch bereinigt wenn der Nutzer versehentlich den Port miteingibt (z.B. `192.168.1.102:9883``192.168.1.102`)
- Settings-Modal: Hinweis erscheint wenn ein `:` in der IP erkannt wird
- `docker-compose.yml`: `.env` wird als Volume in den Container gemountet Einstellungen bleiben nach `docker-compose restart` erhalten
- Printer IP is automatically cleaned if the user accidentally includes the port (e.g. `192.168.1.102:9883``192.168.1.102`)
- Settings modal: hint shown when `:` is detected in the IP field
- `docker-compose.yml`: `.env` mounted as volume into the container — settings persist after `docker-compose restart`
---
## [0.9.1-beta10] 2026-04-25
### Neu
- `start.sh` startet die Bridge per Docker, baut das Image automatisch beim ersten Aufruf
- Tests: pytest-Suite (19 Tests) für API-State, Moonraker-Endpunkte, Settings; Installations-Smoke-Test (`test_install.sh`)
- Settings-Modal öffnet sich beim ersten Start automatisch wenn keine Zugangsdaten hinterlegt sind
### New
- `start.sh` starts the bridge via Docker, builds the image automatically on first run
- Tests: pytest suite (19 tests) for API state, Moonraker endpoints, settings; install smoke test (`test_install.sh`)
- Settings modal opens automatically on first start when no credentials are configured
### Geändert
- README (DE + EN): Schnellstart zeigt jetzt `./start.sh` statt manuellem `docker build`
- README: LAN-Modus korrekt als Drucker-Menüoption beschrieben (kein WLAN-Bezug)
- README: Versionsnummer wird ab jetzt automatisch bei jedem Release aktualisiert
- `extract_credentials`: kein `--write-env` mehr empfohlen Werte im ⚙-Menü eintragen
- Dockerfile im Release-Repo: Pfade ohne `05_scripts/`-Präfix (direkt aus Repo-Root)
- `release.sh`: Dockerfile für Release-Repo automatisch per `sed` angepasst
### Changed
- README: Quick start now shows `./start.sh` instead of manual `docker build`
- README: LAN mode correctly described as a printer menu option
- README: Version number now updated automatically on each release
- `extract_credentials`: `--write-env` no longer recommended — enter values in the ⚙ menu
- Dockerfile in release repo: paths without `05_scripts/` prefix
- `release.sh`: Dockerfile for release repo automatically patched via `sed`
### Fixes
- Restdruckzeit (`remain_time`) wird jetzt korrekt aus `print/report` übernommen und in der UI angezeigt
- Übersetzung: „Schrittweite" und „Ziel"-Placeholder in Temperatureingaben werden jetzt korrekt übersetzt
- Remaining print time (`remain_time`) now correctly taken from `print/report` and shown in UI
- Translation: "Step size" and "Target" placeholders in temperature inputs now correctly translated
---
## [0.9.1-beta9] 2026-04-25
### Neu
- OrcaSlicer-Profil (`kobra_x_orcaslicer_preset.zip`) als Release-Asset
- `release.sh`: OrcaSlicer-Profil wird automatisch ins Release-Repo kopiert und hochgeladen
### New
- OrcaSlicer profile (`kobra_x_orcaslicer_preset.zip`) as release asset
- `release.sh`: OrcaSlicer profile automatically copied to release repo and uploaded
### Geändert
- README: `extract_credentials` ohne `--write-env`, Werte manuell ins ⚙-Menü eintragen
- README: Docker-Schnellstart vereinfacht (kein `.env` anlegen vor dem Start nötig)
### Changed
- README: `extract_credentials` without `--write-env`, values entered manually in the ⚙ menu
- README: Docker quick start simplified
---
## [0.9.1-beta8] 2026-04-25
### Neu
- Restdruckzeit-Anzeige in der UI (≈ Xh Ym verbleibend) aus `remain_time`-Feld des Druckers
- Settings-Modal: Verbindungseinstellungen und Self-Update direkt im Browser
- Self-Update: Bridge prüft Gitea-Release-API auf neue Versionen und aktualisiert sich selbst
### New
- Remaining print time display in UI (≈ Xh Ym remaining) from `remain_time` field
- Settings modal: connection settings and self-update directly in the browser
- Self-update: bridge checks Gitea release API for new versions and updates itself
### Geändert
- Bridge startet im Offline-Modus wenn Drucker nicht erreichbar (kein Absturz)
- Verbinden/Trennen-Button im Header
### Changed
- Bridge starts in offline mode when printer is unreachable (no crash)
- Connect/Disconnect button in header
---
## [0.9.1-beta7] 2026-04-22
### Neu
- Offline-Start: Bridge läuft auch ohne MQTT-Verbindung, verbindet automatisch sobald Drucker erreichbar
- Verbinden/Trennen-Button im Header
### New
- Offline start: bridge runs without MQTT connection, reconnects automatically when printer is reachable
- Connect/Disconnect button in header
---
## [0.9.1-beta6] 2026-04-20
### Neu
- Release-ZIPs: `kx-bridge-linux.zip`, `kx-bridge-windows.zip`, `anycubic-certs.zip` mit Zertifikaten
### New
- Release ZIPs: `kx-bridge-linux.zip`, `kx-bridge-windows.zip`, `anycubic-certs.zip` with certificates
### Fixes
- PyInstaller frozen-Binary: `__file__` durch `sys.executable`-Pfad ersetzt (Cert-Pfad-Fix)
- PyInstaller frozen binary: `__file__` replaced with `sys.executable` path (cert path fix)
---
## [0.9.1-beta5] 2026-04-19
### Neu
- `kx-bridge.exe` (Windows) wird automatisch via GitHub Actions gebaut
### New
- `kx-bridge.exe` (Windows) built automatically via GitHub Actions
---
## [0.9.1-beta4] 2026-04-18
### Neu
- `release.sh`: baut Linux-Binary und Windows-EXE, lädt alle Assets auf Gitea hoch
- Englische README (`README.en.md`)
### New
- `release.sh`: builds Linux binary and Windows EXE, uploads all assets to Gitea
- English README (`README.en.md`)
### Fixes
- `progress` und `filename` werden bei `stoped`/`canceled` korrekt auf 0 zurückgesetzt
- `progress` and `filename` correctly reset to 0 on `stoped`/`canceled`
---
## [0.9.1-beta3] 2026-04-17
### Neu
- Print-Speed-Card (Leise / Normal / Sport)
- Übersetzungen (DE/EN) vervollständigt
### New
- Print speed card (Silent / Normal / Sport)
- Translations (DE/EN) completed
---
## [0.9.1-beta2] 2026-04-17
### Fixes
- Temperatursteuerung während eines laufenden Drucks
- Temperature control during an active print
---
## [0.9.1-beta1] 2026-04-17
### Neu
- UI-Komplettüberarbeitung: Settings-Modal, Self-Update, Dashboard, Responsive Design
- Neue Drucker-Zustände: `pausing`, `paused`, `resuming`, `resumed`, `stopping`
- `release.sh`: Version-Bump und Release-Sync Skript
### New
- Complete UI overhaul: Settings modal, self-update, dashboard, responsive design
- New printer states: `pausing`, `paused`, `resuming`, `resumed`, `stopping`
- `release.sh`: version bump and release sync script
---
## [0.9.0-beta1] 2026-04-10
### Neu
- Erster öffentlicher Release
- Docker-Deployment, Linux-Binary, `extract_credentials`-Tool
- Moonraker-kompatible HTTP/WebSocket-Bridge für den Anycubic Kobra X
- AMS Einziehen/Ausziehen, Licht- und Lüftersteuerung
- Web-UI mit Dashboard, Temperaturkarten, Achsensteuerung
### New
- First public release
- Docker deployment, Linux binary, `extract_credentials` tool
- Moonraker-compatible HTTP/WebSocket bridge for the Anycubic Kobra X
- AMS load/unload, light and fan control
- Web-UI with dashboard, temperature cards, motion control

View File

@@ -6,10 +6,17 @@ COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY kobrax_moonraker_bridge.py .
COPY config_loader.py .
COPY env_loader.py .
COPY kobrax_client.py .
COPY VERSION .
COPY anycubic_slicer.crt .
COPY anycubic_slicer.key .
COPY config/config.ini.example /app/config/config.ini.example
# config/ ist ein Volume-Mountpoint beim Start wird config.ini aus .env migriert
# falls noch keine config.ini vorhanden ist.
RUN mkdir -p /app/config
EXPOSE 7125

143
README.de.md Normal file
View File

@@ -0,0 +1,143 @@
<p align="center"><img src="knlogo.png" alt="KX-Bridge Logo" width="180"/></p>
# KX-Bridge Anycubic Kobra X
**Version:** 0.9.6.1
Steuere deinen **Anycubic Kobra X** mit OrcaSlicer — ohne Klipper, ohne Raspberry Pi.
KX-Bridge ist eine Moonraker-kompatible Bridge die direkt mit dem Drucker kommuniziert.
---
## Schnellstart in 3 Schritten
### Schritt 1 Drucker vorbereiten
Den Kobra X in den LAN-Modus versetzen:
**Drucker-Display → Einstellungen → LAN-Modus einschalten**
### Schritt 2 Credentials holen
Die MQTT-Zugangsdaten sind druckerspezifisch. So holst du sie:
1. **AnycubicSlicerNext** öffnen und Drucker verbinden (bis Status angezeigt wird)
2. **`extract_credentials.exe`** (Windows) oder **`extract_credentials`** (Linux) ausführen — gibt Username, Password, Device-ID und Drucker-IP aus
3. Werte merken / kopieren
> **Download:** [gitea.it-drui.de/viewit/KX-Bridge-Release/releases](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases) → `extract_credentials.exe` (Windows) / `extract_credentials` (Linux) im jeweiligen Release-Asset
### Schritt 3 Bridge starten
```bash
./start.sh
```
Das Skript baut das Docker-Image automatisch beim ersten Aufruf.
**Web-UI öffnen:** `http://BRIDGE-IP:7125`
→ Das ⚙-Menü öffnet sich beim ersten Start automatisch
→ Credentials aus Schritt 2 eintragen → **Speichern & Neustart**
**OrcaSlicer verbinden:**
Drucker → Verbindungstyp **Moonraker** → Host: `http://BRIDGE-IP:7125`
> **Wichtig:** Verbindungstyp muss **Moonraker** sein (nicht „Bambu" oder „Klipper").
> Im Host-Feld vollständige URL mit `http://` und Port `:7125` angeben.
---
## 📺 Video Tutorial
[![KX-Bridge Setup & Bedienung](https://img.youtube.com/vi/1Ql4wfH27fM/hqdefault.jpg)](https://www.youtube.com/watch?v=1Ql4wfH27fM)
---
## ⚠️ Update von 0.9.1 oder älter
Ab **0.9.2** speichert KX-Bridge Einstellungen in `config/config.ini` statt in `.env`.
**Migration erfolgt automatisch** — keine manuelle Aktion nötig:
- Beim ersten Start nach dem Update liest die Bridge die vorhandene `.env` und erstellt `config/config.ini` automatisch
- Einstellungen bleiben ab sofort nach `docker-compose restart` und zukünftigen Updates erhalten
- Die `.env`-Datei bleibt read-only gemountet als Migrationsquelle — kann liegen bleiben
- Zum manuellen Anlegen einer `config.ini`: Vorlage unter `config/config.ini.example` kopieren
---
## Was wird unterstützt?
| Funktion | Details |
|----------|---------|
| Druckerstatus | Temperatur, Fortschritt, Zustand, Restzeit |
| Drucksteuerung | Start, Pause, Fortsetzen, Abbrechen |
| Temperaturregelung | Nozzle und Bett während des Drucks |
| Druckgeschwindigkeit | Leise / Normal / Sport |
| AMS-Farbwechsel | Filament einziehen / ausziehen |
| Licht & Lüfter | Drucklicht und Lüfterdrehzahl |
| Web-UI | Dashboard, Achsensteuerung, Kameraansicht |
| Self-Update | Neue Versionen direkt im Browser installieren |
| OrcaSlicer | Moonraker-Protokoll (HTTP + WebSocket) |
---
## Alternativen zu Docker
**Linux Binary** (kein Docker nötig):
```bash
chmod +x kx-bridge
./kx-bridge
```
**Python direkt:**
```bash
pip install aiohttp
python bridge/kobrax_moonraker_bridge.py
```
Web-UI jeweils unter `http://localhost:7125` — ⚙-Menü führt durch die Erstkonfiguration.
---
## Nützliche Befehle
```bash
# Logs anzeigen
docker-compose logs -f
# Bridge stoppen
docker-compose down
# Bridge neu starten (nach Update)
./start.sh
```
---
## Fehlerbehebung
**„Falsche MQTT-Zugangsdaten"** beim Start:
- AnycubicSlicerNext neu starten, Drucker verbinden, `extract_credentials` erneut ausführen
- Nur die IP-Adresse ins Feld eintragen, keinen Port (✗ `192.168.1.102:9883` → ✓ `192.168.1.102`)
**Drucker nicht gefunden / kein LAN-Modus:**
- Am Drucker-Display: Einstellungen → LAN-Modus einschalten
- Drucker und Bridge müssen im selben Netzwerk sein
**Docker: Permission denied:**
```bash
sudo usermod -aG docker $USER # dann neu einloggen
```
---
## Sicherheitshinweise
- Die Bridge ist im lokalen Netzwerk erreichbar unter `http://<Host-IP>:7125` — nicht ins Internet freigeben
- `config/config.ini` enthält Drucker-Credentials — nicht öffentlich teilen
- Credentials haben keinen Zugang zu Anycubic-Cloud-Diensten
---
## Lizenz & Rechtliches
Interoperabilitätsforschung gem. §69e UrhG — ausschließlich private, nicht-kommerzielle Nutzung.

View File

@@ -1,256 +0,0 @@
# KX-Bridge Anycubic Kobra X Moonraker Bridge
**Version:** 0.9.1-beta10
**Status:** Public Beta suitable for home users, feedback welcome
KX-Bridge is a Moonraker-compatible HTTP/WebSocket bridge for the **Anycubic Kobra X** 3D printer. It allows you to control the printer through OrcaSlicer and other Moonraker-compatible software — no Klipper, no Raspberry Pi required.
---
## What's supported?
- Printer status (temperature, progress, state)
- File transfer and print start
- Print control: pause, resume, cancel
- Temperature control during an active print
- Print speed (Silent / Normal / Sport)
- AMS filament change (load / unload)
- Light and fan control
- Web UI with dashboard, temperature cards, axis control, and camera view
- Settings and self-update directly in the browser (⚙ menu)
- OrcaSlicer connection (Moonraker protocol)
---
## Requirements
- Anycubic Kobra X on your local network, with **LAN mode enabled** (printer menu → enable LAN mode)
- Printer MQTT credentials (→ see [Extracting credentials](#extracting-credentials))
- Docker **or** Python 3.9+ **or** the pre-built Linux binary
---
## Quick start Docker (recommended)
```bash
# 1. Start the bridge
./start.sh
```
`start.sh` builds the Docker image automatically on first run and starts the bridge.
```
# 2. Open the web UI: http://BRIDGE-IP:7125
# → Settings (⚙) open automatically on first start
# → Enter your credentials (→ see Extracting credentials)
# 3. In OrcaSlicer: add printer → "Moonraker" → http://BRIDGE-IP:7125
```
Check logs:
```bash
docker-compose logs -f
```
Stop:
```bash
docker-compose down
```
---
## Quick start Binary (Linux)
```bash
chmod +x kx-bridge
./kx-bridge
```
Open the web UI: `http://localhost:7125`
→ Settings (⚙) open automatically and guide you through the initial setup.
---
## Quick start Python directly
```bash
pip install aiohttp
python kobrax_moonraker_bridge.py
```
Open the web UI: `http://localhost:7125`
→ Settings (⚙) open automatically on first start.
---
## Extracting credentials
The MQTT credentials are printer-specific and are generated on first connection with AnycubicSlicerNext. The `extract_credentials` tool reads them from the memory of the running slicer.
**Requirement:** AnycubicSlicerNext must be running and connected to the printer (printer status is shown).
### Windows
```
extract_credentials.exe
```
### Linux
```bash
chmod +x extract_credentials
./extract_credentials
```
### Output
```
[*] Process found: AnycubicSlicerNext.exe (PID 1234)
[*] 1986 memory segments read (738.8 MB)
[*] Analyzing ... 100% (739 MB)
=======================================================
RESULTS
=======================================================
Username userXXXXXXXXXX (hits: 47)
Password *************** (hits: 1046)
Device-ID xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (hits: 3504)
Printer IP 192.168.x.x (hits: 3036)
=======================================================
```
Enter the displayed values in the bridge settings:
Open web UI → **⚙ Settings** → fill in the fields → **Save & Restart**
> If the result looks uncertain: `--verbose` shows all found candidates.
All credentials are **processed locally only** — nothing is sent to external servers.
---
## Configuration (.env)
```env
PRINTER_IP=192.168.x.x # Printer IP address
MQTT_PORT=9883 # Default, do not change
MQTT_USERNAME=userXXXXXXXX # Starts with "user"
MQTT_PASSWORD=XXXXXXXXXXXXXX # ~15 characters, mixed case
DEVICE_ID=xxxxxxxx... # 32-character hex string
MODE_ID=20030 # Kobra X default
```
---
## OrcaSlicer setup
1. Add printer → **Anycubic Kobra X** (or generic Klipper printer)
2. Connection type: **Moonraker**
3. Host: `http://BRIDGE-HOST:7125`
4. Test connection → should show "Online"
---
## Web UI
The bridge serves a web interface at `http://BRIDGE-HOST:7125`:
| Section | Function |
|---------|----------|
| Dashboard | Printer status, progress, temperature overview |
| Temperatures | Set nozzle and bed temperature directly |
| Motion | X/Y/Z movement, motor release |
| Print Speed | Silent / Normal / Sport |
| Fan / Light | Fan speed and work light |
| AMS | Load / unload filament |
| Camera | Live preview (if supported by printer) |
| ⚙ Settings | MQTT credentials, poll interval, self-update |
### Self-update
The ⚙ menu in the web UI lets you check for new versions and update the bridge in place — no reinstallation needed. After the download the bridge restarts automatically with the new version.
---
## bridge.sh (Linux service manager)
```bash
./bridge.sh start # Start bridge in background
./bridge.sh stop # Stop bridge
./bridge.sh restart # Restart
./bridge.sh status # Check status and port
./bridge.sh log 50 # Show last 50 log lines
```
---
## Printer states
The bridge translates internal Kobra states into Moonraker-compatible states:
| Kobra state | Meaning |
|-------------|---------|
| free | Ready |
| printing / busy | Printing |
| pausing / paused | Paused |
| resuming / resumed | Resuming |
| stopping / stoped | Stopping |
| finished | Complete |
| canceled | Cancelled |
| failed | Error |
---
## Troubleshooting
**Port 7125 already in use:**
```bash
./bridge.sh stop # or: fuser -k 7125/tcp
./bridge.sh start
```
**Invalid credentials / connection refused:**
- Start AnycubicSlicerNext, connect to the printer, then run `extract_credentials` again
- If the result looks uncertain: `extract_credentials --verbose` shows all candidates
- Manually enter the correct candidate in `.env` and restart the bridge
**Temperature changes are ignored:**
- During an active print, temperature changes are sent via a separate channel — this is normal and the bridge handles it automatically.
**Docker: Permission denied:**
```bash
sudo usermod -aG docker $USER
# Log out and back in
```
**Docker: .env not found:**
```bash
# .env must be in the same directory as docker-compose.yml
cp .env.example .env && nano .env
```
---
## Logs
```bash
# Docker
docker compose logs -f kx-bridge
# Binary / Python
tail -f /tmp/bridge.log # when using bridge.sh
```
---
## Security notes
- The bridge binds to `0.0.0.0:7125` by default — use on your local network only
- `.env` contains printer credentials — do not share publicly
- The credentials are printer-specific and have no access to Anycubic cloud services
---
## License & legal
This project was created through interoperability research under §69e UrhG (German copyright law).
For private, non-commercial use only.

285
README.md
View File

@@ -1,256 +1,143 @@
# KX-Bridge Anycubic Kobra X Moonraker Bridge
<p align="center"><img src="knlogo.png" alt="KX-Bridge Logo" width="180"/></p>
**Version:** 0.9.1-beta10
**Status:** Public Beta für Heimanwender geeignet, Feedback willkommen
# KX-Bridge Anycubic Kobra X
KX-Bridge ist eine Moonraker-kompatible HTTP/WebSocket-Bridge für den **Anycubic Kobra X** 3D-Drucker. Sie ermöglicht die Steuerung des Druckers über OrcaSlicer und andere Moonraker-kompatible Software, ohne dass Klipper oder ein Raspberry Pi benötigt wird.
**Version:** 0.9.6.1
Control your **Anycubic Kobra X** with OrcaSlicer — no Klipper, no Raspberry Pi.
KX-Bridge is a Moonraker-compatible bridge that communicates directly with the printer.
---
## Was wird unterstützt?
## Quick Start in 3 Steps
- Druckerstatus (Temperatur, Fortschritt, Zustand)
- Dateiübertragung und Druckstart
- Drucksteuerung: Pause, Fortsetzen, Abbrechen
- Temperaturregelung während des laufenden Drucks
- Druckgeschwindigkeit (Leise / Normal / Sport)
- AMS-Farbwechsel (Einziehen / Ausziehen)
- Licht- und Lüftersteuerung
- Web-UI mit Dashboard, Temperaturkarten, Achsensteuerung und Kameraansicht
- Einstellungen und Self-Update direkt im Browser (⚙-Menü)
- OrcaSlicer-Verbindung (Moonraker-Protokoll)
### Step 1 Prepare the printer
---
Enable LAN mode on the Kobra X:
**Printer display → Settings → Enable LAN mode**
## Voraussetzungen
### Step 2 Get credentials
- Anycubic Kobra X im lokalen Netzwerk, mit aktiviertem **LAN-Modus** (Drucker-Menü → LAN-Modus einschalten)
- MQTT-Credentials des Druckers (→ siehe [Credentials extrahieren](#credentials-extrahieren))
- Docker **oder** Python 3.9+ **oder** direkt die Linux-Binary
The MQTT credentials are printer-specific. Here's how to get them:
---
1. Open **AnycubicSlicerNext** and connect the printer (wait until status is shown)
2. Run **`extract_credentials.exe`** (Windows) or **`extract_credentials`** (Linux) — outputs Username, Password, Device ID and printer IP
3. Note / copy the values
## Schnellstart Docker (empfohlen)
> **Download:** [gitea.it-drui.de/viewit/KX-Bridge-Release/releases](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases) → `extract_credentials.exe` (Windows) / `extract_credentials` (Linux) in the release assets
### Step 3 Start the bridge
```bash
# 1. Bridge starten
./start.sh
```
`start.sh` baut das Docker-Image automatisch beim ersten Aufruf und startet die Bridge.
The script builds the Docker image automatically on first run.
```
# 2. Web-UI öffnen: http://BRIDGE-IP:7125
# → Einstellungen (⚙) öffnen sich automatisch beim ersten Start
# → Zugangsdaten eintragen (→ siehe Credentials extrahieren)
**Open Web-UI:** `http://BRIDGE-IP:7125`
→ The ⚙ menu opens automatically on first start
→ Enter credentials from Step 2 → **Save & Restart**
# 3. In OrcaSlicer: Drucker → "Moonraker" → http://BRIDGE-IP:7125
```
**Connect OrcaSlicer:**
Printer → Connection type **Moonraker** → Host: `http://BRIDGE-IP:7125`
Logs prüfen:
```bash
docker-compose logs -f
```
Stoppen:
```bash
docker-compose down
```
> **Important:** Connection type must be **Moonraker** (not "Bambu" or "Klipper").
> Enter the full URL including `http://` and port `:7125` in the host field.
---
## Schnellstart Binary (Linux)
## 📺 Video Tutorial
[![KX-Bridge Setup & Usage](https://img.youtube.com/vi/1Ql4wfH27fM/hqdefault.jpg)](https://www.youtube.com/watch?v=1Ql4wfH27fM)
---
## ⚠️ Upgrading from 0.9.1 or earlier
Starting with **0.9.2**, KX-Bridge stores settings in `config/config.ini` instead of `.env`.
**Migration is automatic** — no manual action required:
- On first start after upgrade, the bridge reads your existing `.env` and creates `config/config.ini` automatically
- Settings now survive `docker-compose restart` and future updates
- The `.env` file stays mounted read-only as a migration source — you can keep it in place
- If you want to create a `config.ini` manually: copy `config/config.ini.example`
---
## What's supported?
| Feature | Details |
|---------|---------|
| Printer status | Temperature, progress, state, remaining time |
| Print control | Start, pause, resume, cancel |
| Temperature control | Nozzle and bed during print |
| Print speed | Silent / Normal / Sport |
| AMS filament change | Load / unload filament |
| Light & fan | Print light and fan speed |
| Web-UI | Dashboard, motion control, camera view |
| Self-update | Install new versions directly in the browser |
| OrcaSlicer | Moonraker protocol (HTTP + WebSocket) |
---
## Alternatives to Docker
**Linux binary** (no Docker needed):
```bash
chmod +x kx-bridge
./kx-bridge
```
Web-UI öffnen: `http://localhost:7125`
→ Einstellungen (⚙) öffnen sich automatisch und führen durch die Erstkonfiguration.
---
## Schnellstart Python direkt
**Python directly:**
```bash
pip install aiohttp
python kobrax_moonraker_bridge.py
python bridge/kobrax_moonraker_bridge.py
```
Web-UI öffnen: `http://localhost:7125`
→ Einstellungen (⚙) öffnen sich automatisch beim ersten Start.
Web-UI available at `http://localhost:7125` — the ⚙ menu guides through initial setup.
---
## Credentials extrahieren
Die MQTT-Zugangsdaten sind druckerspezifisch und werden beim ersten Verbindungsaufbau mit dem AnycubicSlicerNext generiert. Das Tool `extract_credentials` liest sie aus dem RAM des laufenden Slicers aus.
**Voraussetzung:** AnycubicSlicerNext muss gestartet und mit dem Drucker verbunden sein (Drucker-Status wird angezeigt).
### Windows
```
extract_credentials.exe
```
### Linux
## Useful commands
```bash
chmod +x extract_credentials
./extract_credentials
```
# Show logs
docker-compose logs -f
### Ausgabe
# Stop bridge
docker-compose down
```
[*] Prozess gefunden: AnycubicSlicerNext.exe (PID 1234)
[*] 1986 Speichersegmente gelesen (738.8 MB)
[*] Analysiere ... 100% (739 MB)
=======================================================
ERGEBNISSE
=======================================================
Username userXXXXXXXXXX (Treffer: 47)
Password *************** (Treffer: 1046)
Device-ID xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (Treffer: 3504)
Drucker-IP 192.168.x.x (Treffer: 3036)
=======================================================
```
Die angezeigten Werte in die Bridge-Einstellungen übertragen:
Web-UI öffnen → **⚙ Einstellungen** → Felder ausfüllen → **Speichern & Neustart**
> Falls das Ergebnis unsicher wirkt: `--verbose` zeigt alle gefundenen Kandidaten.
Alle Credentials werden **ausschließlich lokal verarbeitet** — keine Übertragung an externe Server.
---
## Konfiguration (.env)
```env
PRINTER_IP=192.168.x.x # IP des Druckers
MQTT_PORT=9883 # Standard, nicht ändern
MQTT_USERNAME=userXXXXXXXX # Beginnt mit "user"
MQTT_PASSWORD=XXXXXXXXXXXXXX # ~15 Zeichen, gemischt
DEVICE_ID=xxxxxxxx... # 32-stelliger Hex-String
MODE_ID=20030 # Kobra X Standard
# Restart bridge (after update)
./start.sh
```
---
## OrcaSlicer verbinden
## Troubleshooting
1. Drucker hinzufügen → **Anycubic Kobra X** (oder generischer Klipper-Drucker)
2. Verbindungstyp: **Moonraker**
3. IP: `http://BRIDGE-HOST:7125`
4. Verbindung testen → sollte "Online" anzeigen
**"Wrong MQTT credentials"** on start:
- Restart AnycubicSlicerNext, reconnect the printer, run `extract_credentials` again
- Enter only the IP address, no port (✗ `192.168.1.102:9883` → ✓ `192.168.1.102`)
---
## Web-UI
Die Bridge stellt unter `http://BRIDGE-HOST:7125` eine Web-Oberfläche bereit:
| Bereich | Funktion |
|---------|----------|
| Dashboard | Druckerstatus, Fortschritt, Temperaturübersicht |
| Temperaturen | Nozzle und Bett direkt setzen |
| Achsen | X/Y/Z-Bewegung, Motorfreigabe |
| Druckgeschwindigkeit | Leise / Normal / Sport |
| Lüfter / Licht | Lüfterdrehzahl und Drucklicht |
| AMS | Filament einziehen / ausziehen |
| Kamera | Live-Vorschau (falls Drucker unterstützt) |
| ⚙ Einstellungen | MQTT-Zugangsdaten, Poll-Intervall, Self-Update |
### Self-Update
Über das ⚙-Menü in der Web-UI kann die Bridge auf neue Versionen prüfen und sich selbst aktualisieren — ohne Neuinstallation. Nach dem Download startet die Bridge automatisch mit der neuen Version neu.
---
## bridge.sh (Linux Service-Manager)
```bash
./bridge.sh start # Bridge im Hintergrund starten
./bridge.sh stop # Bridge beenden
./bridge.sh restart # Neustarten
./bridge.sh status # Status und Port prüfen
./bridge.sh log 50 # Letzte 50 Log-Zeilen
```
---
## Druckerzustände
Die Bridge übersetzt die internen Kobra-Zustände in Moonraker-kompatible Zustände:
| Kobra-Zustand | Bedeutung |
|---------------|-----------|
| free | Bereit |
| printing / busy | Druckt |
| pausing / paused | Pausiert |
| resuming / resumed | Wird fortgesetzt |
| stopping / stoped | Wird gestoppt |
| finished | Abgeschlossen |
| canceled | Abgebrochen |
| failed | Fehler |
---
## Fehlerbehebung
**Port 7125 bereits belegt:**
```bash
./bridge.sh stop # oder: fuser -k 7125/tcp
./bridge.sh start
```
**Credentials ungültig / Verbindung abgelehnt:**
- AnycubicSlicerNext starten, mit Drucker verbinden, `extract_credentials` erneut ausführen
- Falls das Ergebnis unsicher wirkt: `extract_credentials --verbose` zeigt alle Kandidaten an
- Den richtigen Kandidaten manuell in `.env` eintragen und Bridge neu starten
**Temperaturänderungen werden ignoriert:**
- Während eines laufenden Drucks werden Temperaturänderungen über einen separaten Kanal gesendet — das ist normal und wird von der Bridge automatisch erkannt.
**Printer not found / no LAN mode:**
- On the printer display: Settings → Enable LAN mode
- Printer and bridge must be on the same network
**Docker: Permission denied:**
```bash
sudo usermod -aG docker $USER
# Neu einloggen
```
**Docker: .env nicht gefunden:**
```bash
# .env muss im gleichen Verzeichnis wie docker-compose.yml liegen
cp .env.example .env && nano .env
sudo usermod -aG docker $USER # then log out and back in
```
---
## Logs
## Security
```bash
# Docker
docker compose logs -f kx-bridge
# Binary / Python
tail -f /tmp/bridge.log # bei Nutzung von bridge.sh
```
- The bridge is accessible on the local network at `http://<host-IP>:7125` — do not expose to the internet
- `config/config.ini` contains printer credentials — do not share publicly
- Credentials do not grant access to Anycubic cloud services
---
## Sicherheitshinweise
## License
- Die Bridge bindet standardmäßig auf `0.0.0.0:7125` — nur im lokalen Netzwerk nutzen
- `.env` enthält Drucker-Credentials — nicht öffentlich teilen
- Die Credentials sind druckerspezifisch und haben keinen Zugang zu Anycubic-Cloud-Diensten
---
## Lizenz & Rechtliches
Dieses Projekt entstand durch Interoperabilitätsforschung gem. §69e UrhG.
Ausschließlich für private, nicht-kommerzielle Nutzung.
Interoperability research under §69e UrhG — private, non-commercial use only.

View File

@@ -1 +1 @@
0.9.1-beta15
0.9.6.1

34
config.ini.example Normal file
View File

@@ -0,0 +1,34 @@
# KX-Bridge Konfigurationsdatei
# Kopiere diese Datei nach config.ini und trage deine Werte ein:
# cp config.ini.example config.ini
#
# Credentials mit extract_credentials.exe (Windows) oder
# extract_credentials (Linux) aus dem laufenden AnycubicSlicerNext auslesen.
[connection]
# IP-Adresse des Druckers im lokalen Netzwerk
printer_ip = 192.168.x.x
# MQTT-Port (Anycubic Kobra X Standard: 9883)
mqtt_port = 9883
# MQTT-Zugangsdaten (druckerspezifisch, beginnt mit "user")
username = userXXXXXXXXXX
password = XXXXXXXXXXXXXXX
# Geräte-ID (32-stelliger Hex-String, druckerspezifisch)
device_id = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Modell-ID (Kobra X Standard: 20030)
mode_id = 20030
[print]
# Standard-AMS-Slot für Einfarbdruck (auto = alle belegten Slots, 0-3 = fixer Slot)
default_ams_slot = auto
# Auto-Leveling vor jedem Druck (1 = an, 0 = aus)
auto_leveling = 1
[bridge]
# Poll-Intervall in Sekunden
poll_interval = 3

143
config_loader.py Normal file
View File

@@ -0,0 +1,143 @@
"""
config_loader.py lädt Verbindungsparameter aus config/config.ini (primär)
oder .env (Fallback / Migration).
Umgebungsvariablen haben immer Vorrang.
"""
import os
import sys
import pathlib
import configparser
_BASE = pathlib.Path(sys.executable).parent if getattr(sys, "frozen", False) else pathlib.Path(__file__).parent
CONFIG_SECTION_CONNECTION = "connection"
CONFIG_SECTION_PRINT = "print"
CONFIG_SECTION_BRIDGE = "bridge"
def _find_config_file() -> pathlib.Path | None:
for base in (_BASE, _BASE.parent):
p = base / "config" / "config.ini"
if p.is_file():
return p
return None
def _find_env_file() -> pathlib.Path | None:
for base in (_BASE, _BASE.parent):
p = base / ".env"
if p.is_file():
return p
return None
def _load_env_file(path: pathlib.Path):
"""Lädt .env-Datei als Fallback setzt nur Keys die noch nicht in os.environ sind."""
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, val = line.partition("=")
key = key.strip()
val = val.strip()
if key and key not in os.environ:
os.environ[key] = val
def _load_config_file(path: pathlib.Path):
"""Lädt config.ini und setzt Keys in os.environ (nur wenn nicht bereits gesetzt)."""
cfg = configparser.ConfigParser()
cfg.read(path, encoding="utf-8")
mapping = {
"PRINTER_IP": (CONFIG_SECTION_CONNECTION, "printer_ip"),
"MQTT_PORT": (CONFIG_SECTION_CONNECTION, "mqtt_port"),
"MQTT_USERNAME": (CONFIG_SECTION_CONNECTION, "username"),
"MQTT_PASSWORD": (CONFIG_SECTION_CONNECTION, "password"),
"MODE_ID": (CONFIG_SECTION_CONNECTION, "mode_id"),
"DEVICE_ID": (CONFIG_SECTION_CONNECTION, "device_id"),
"DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"),
"AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"),
}
for env_key, (section, option) in mapping.items():
if env_key not in os.environ:
try:
val = cfg.get(section, option)
if val:
os.environ[env_key] = val
except (configparser.NoSectionError, configparser.NoOptionError):
pass
def migrate_env_to_config(env_path: pathlib.Path, config_path: pathlib.Path):
"""Einmalige Migration: .env → config.ini anlegen."""
env_vals: dict[str, str] = {}
with open(env_path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, _, v = line.partition("=")
env_vals[k.strip()] = v.strip()
config_path.parent.mkdir(parents=True, exist_ok=True)
cfg = configparser.ConfigParser()
cfg[CONFIG_SECTION_CONNECTION] = {
"printer_ip": env_vals.get("PRINTER_IP", ""),
"mqtt_port": env_vals.get("MQTT_PORT", "9883"),
"username": env_vals.get("MQTT_USERNAME", ""),
"password": env_vals.get("MQTT_PASSWORD", ""),
"mode_id": env_vals.get("MODE_ID", ""),
"device_id": env_vals.get("DEVICE_ID", ""),
}
cfg[CONFIG_SECTION_PRINT] = {
"default_ams_slot": env_vals.get("DEFAULT_AMS_SLOT", "auto"),
"auto_leveling": env_vals.get("AUTO_LEVELING", "1"),
}
cfg[CONFIG_SECTION_BRIDGE] = {
"poll_interval": "3",
}
with open(config_path, "w", encoding="utf-8") as f:
f.write("# KX-Bridge Konfigurationsdatei\n")
f.write("# Automatisch migriert aus .env\n\n")
cfg.write(f)
def find_config_path() -> pathlib.Path:
"""Gibt den Pfad zur config.ini zurück (auch wenn sie noch nicht existiert)."""
for base in (_BASE, _BASE.parent):
config_dir = base / "config"
if config_dir.is_dir():
return config_dir / "config.ini"
return _BASE / "config" / "config.ini"
# ─── Laden ───────────────────────────────────────────────────────────────────
_config_path = _find_config_file()
_env_path = _find_env_file()
if _config_path:
_load_config_file(_config_path)
elif _env_path:
# Kein config.ini vorhanden → aus .env migrieren
_target = find_config_path()
migrate_env_to_config(_env_path, _target)
_load_config_file(_target)
_config_path = _target
def get(key: str, default: str = "") -> str:
return os.environ.get(key, default)
# Häufig verwendete Shortcuts
PRINTER_IP = get("PRINTER_IP", "")
MQTT_PORT = int(get("MQTT_PORT", "9883"))
USERNAME = get("MQTT_USERNAME", "")
PASSWORD = get("MQTT_PASSWORD", "")
MODE_ID = get("MODE_ID", "")
DEVICE_ID = get("DEVICE_ID", "")
DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto")
AUTO_LEVELING = int(get("AUTO_LEVELING","1"))

View File

@@ -2,9 +2,9 @@ services:
kx-bridge:
image: kx-bridge:latest
build: .
env_file: .env
volumes:
- ./.env:/app/.env
- ./config:/app/config
- ./.env:/app/.env:ro
ports:
- "7125:7125"
restart: unless-stopped

View File

@@ -46,3 +46,5 @@ USERNAME = get("MQTT_USERNAME", "")
PASSWORD = get("MQTT_PASSWORD", "")
MODE_ID = get("MODE_ID", "")
DEVICE_ID = get("DEVICE_ID", "")
DEFAULT_AMS_SLOT = get("DEFAULT_AMS_SLOT", "auto")
AUTO_LEVELING = int(get("AUTO_LEVELING", "1"))

345
fetch_credentials.py Normal file
View File

@@ -0,0 +1,345 @@
#!/usr/bin/env python3
"""
Decrypt printer configuration data using AES-256-CBC
This script decrypts encrypted printer configuration data from a 3D printer using AES-256-CBC.
The method was reverse engineered from the vue project embedded in libWorkbench.so,
which is part of Anycubic Slicer Next.
The decryption key is derived from the device token,
and the IV is obtained from the response token in the encrypted data.
- Algorithm: AES-256-CBC
- Key: Characters 16-32 of the token from info.json
- IV: The response token from ctrl.json
- Mode: CBC with PKCS7 padding
Usage:
python3 fetch_credentials.py --ip 192.168.2.32
python3 fetch_credentials.py --ip 192.168.2.32 --port 18910
python3 fetch_credentials.py --ctrl ctrl.json --info info.json --output config.json
"""
import json
import sys
import base64
import hashlib
import argparse
import time
import random
import string
import requests
from pathlib import Path
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def evp_bytes_to_key(password, salt, key_len, iv_len):
"""
Derive key and IV from password and salt using OpenSSL EVP_BytesToKey
This mimics the CryptoJS default key derivation
"""
m = []
i = 0
while len(b''.join(m)) < (key_len + iv_len):
md5 = hashlib.md5()
data = password + salt
if i > 0:
data = m[i - 1] + password + salt
md5.update(data)
m.append(md5.digest())
i += 1
ms = b''.join(m)
return ms[:key_len], ms[key_len:key_len + iv_len]
def generate_signature(token, ts, nonce):
"""
Generate MD5 signature for /ctrl endpoint
Signature = md5(md5(token[0:16]) + ts + nonce)
"""
# First MD5: token.slice(0, 16)
first_md5 = hashlib.md5(token[:16].encode('utf-8')).hexdigest()
# Second MD5: first_md5 + ts + nonce
signature_data = first_md5 + str(ts) + nonce
signature = hashlib.md5(signature_data.encode('utf-8')).hexdigest()
return signature
def generate_nonce(length=6):
"""Generate a random alphanumeric nonce"""
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
def fetch_from_http(ip, port, endpoint, token=None, did="random", verbose=False):
"""
Fetch data from HTTP endpoint on the printer
Args:
ip (str): IP address of the printer
port (int): Port number (default 18910)
endpoint (str): Either 'info' or 'ctrl'
token (str): Device token (required for /ctrl endpoint)
did (str): Device ID (required for /ctrl endpoint)
verbose (bool): Print debug information
Returns:
dict: JSON response data
"""
try:
if endpoint == 'info':
url = f"http://{ip}:{port}/info"
if verbose:
print(f"Fetching: {url}")
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
elif endpoint == 'ctrl':
if not token:
raise ValueError("Token is required for /ctrl endpoint")
# Generate signature parameters
ts = int(time.time() * 1000) # Current timestamp in ms
nonce = generate_nonce(6)
signature = generate_signature(token, ts, nonce)
url = f"http://{ip}:{port}/ctrl"
params = {
'ts': ts,
'nonce': nonce,
'sign': signature,
'did': did
}
if verbose:
print(f"Fetching: {url}")
print(f" Parameters:")
print(f" ts: {ts}")
print(f" nonce: {nonce}")
print(f" sign: {signature}")
print(f" did: {did}")
response = requests.post(url, params=params, timeout=10)
response.raise_for_status()
return response.json()
else:
raise ValueError(f"Unknown endpoint: {endpoint}")
except requests.exceptions.RequestException as e:
raise Exception(f"HTTP request failed for {endpoint}: {e}")
except json.JSONDecodeError as e:
raise Exception(f"Invalid JSON response from {endpoint}: {e}")
def decrypt_text(encrypted_data, key, iv):
"""
Decrypt data using AES-256-CBC
Handles CryptoJS-style encrypted data (OpenSSL format with salt)
Args:
encrypted_data (str): Encrypted data string (CryptoJS format)
key (str): Decryption key string
iv (str): Initialization vector string
Returns:
dict: Decrypted JSON data
"""
try:
# Convert key and IV to bytes
key_bytes = key.encode('utf-8')
iv_bytes = iv.encode('utf-8')
# Decrypt using direct key and IV (as per the original code)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
# The encrypted_data might be base64 or hex encoded
# Try base64 first
try:
encrypted_bytes = base64.b64decode(encrypted_data)
except:
try:
# Try as hex
encrypted_bytes = bytes.fromhex(encrypted_data)
except:
# If all else fails, encode as UTF-8
encrypted_bytes = encrypted_data.encode('utf-8')
# Decrypt
decrypted = cipher.decrypt(encrypted_bytes)
# Try to unpad
try:
unpadded = unpad(decrypted, AES.block_size)
except ValueError:
# If unpadding fails, use as-is
unpadded = decrypted
plaintext = unpadded.decode('utf-8')
# Parse JSON
return json.loads(plaintext)
except Exception as e:
return {"error": str(e), "error_type": type(e).__name__}
def main():
"""Main function to decrypt printer data"""
# Parse command-line arguments
parser = argparse.ArgumentParser(
description='Decrypt Anycubic printer configuration data',
)
# HTTP mode
parser.add_argument('--ip', help='IP address of the printer (fetch via HTTP)')
parser.add_argument('--port', type=int, default=18910, help='Printer port (default: 18910)')
# File mode
parser.add_argument('--ctrl', default='ctrl.json', help='Path to ctrl.json (default: ctrl.json)')
parser.add_argument('--info', default='info.json', help='Path to info.json (default: info.json)')
parser.add_argument('--output', default='decrypted_printer_config.json', help='Output file (default: decrypted_printer_config.json)')
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output')
args = parser.parse_args()
# Determine mode: HTTP or file
if args.ip:
# HTTP mode: fetch from printer
if args.verbose:
print("=" * 70)
print("Fetching configuration from printer via HTTP")
print("=" * 70)
print(f"Printer IP: {args.ip}:{args.port}")
print()
try:
# Fetch info.json
if args.verbose:
print("Step 1: Fetching device info...")
info = fetch_from_http(args.ip, args.port, 'info', verbose=args.verbose)
# Get token from info
token = info.get('token')
if not token:
print("Error: No token found in /info response", file=sys.stderr)
return 1
# Fetch data.json (encrypted config) from /ctrl endpoint
if args.verbose:
print("\nStep 2: Fetching encrypted configuration from /ctrl...")
data = fetch_from_http(args.ip, args.port, 'ctrl', token=token, verbose=args.verbose)
if args.verbose:
print("\nData fetched successfully!")
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
else:
# File mode: load from disk
if args.verbose:
print("=" * 70)
print("Loading configuration from files")
print("=" * 70)
# Check if input files exist
if not Path(args.ctrl).exists():
print(f"Error: {args.ctrl} not found", file=sys.stderr)
return 1
if not Path(args.info).exists():
print(f"Error: {args.info} not found", file=sys.stderr)
return 1
# Read data.json
try:
with open(args.data, 'r') as f:
data = json.load(f)
except json.JSONDecodeError as e:
print(f"Error reading {args.data}: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error reading {args.data}: {e}", file=sys.stderr)
return 1
# Read info.json
try:
with open(args.info, 'r') as f:
info = json.load(f)
except json.JSONDecodeError as e:
print(f"Error reading {args.info}: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error reading {args.info}: {e}", file=sys.stderr)
return 1
# Extract values
try:
encrypted_info = data['data']['info']
response_token = data['data']['token']
full_token = info['token']
except KeyError as e:
print(f"Error: Missing required key {e}", file=sys.stderr)
return 1
# Generate decryption key and IV
key_part = full_token[16:32]
if args.verbose:
print("=" * 70)
print("Printer Configuration Decryption")
print("=" * 70)
print(f"Input data file: {args.ctrl}")
print(f"Input info file: {args.info}")
print(f"Output file: {args.output}")
print()
print("Decryption Parameters:")
print(f" Encrypted data length: {len(encrypted_info)} bytes")
print(f" Full token: {full_token}")
print(f" Full token length: {len(full_token)} characters")
print(f" Response token (IV): {response_token}")
print(f" Decryption key: {key_part}")
print(f" Key length: {len(key_part)} characters")
print(f" IV length: {len(response_token)} characters")
print()
# Decrypt
if args.verbose:
print("Decrypting...")
result = decrypt_text(encrypted_info, key_part, response_token)
if 'error' in result:
print(f"Error during decryption: {result.get('error')}", file=sys.stderr)
return 1
# Pretty print result
if args.verbose:
print("=" * 70)
print("Decrypted Configuration:")
print("=" * 70)
print(json.dumps(result, indent=2))
print()
# Save to file
try:
with open(args.output, 'w') as f:
json.dump(result, f, indent=2)
if args.verbose:
print("=" * 70)
print(f"Successfully saved decrypted config to: {args.output}")
print("=" * 70)
else:
print(f"Decrypted config saved to: {args.output}")
except Exception as e:
print(f"Error writing to {args.output}: {e}", file=sys.stderr)
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

BIN
knlogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@@ -16,7 +16,9 @@ Verwendung:
client.disconnect()
"""
import hashlib
import json
import logging
import os
import socket
import ssl
@@ -28,6 +30,8 @@ from datetime import datetime
import env_loader
log = logging.getLogger("kobrax.mqtt")
_SCRIPT_DIR = os.path.dirname(sys.executable) if getattr(sys, "frozen", False) else os.path.dirname(os.path.abspath(__file__))
CERT_FILE = os.path.join(_SCRIPT_DIR, "anycubic_slicer.crt")
KEY_FILE = os.path.join(_SCRIPT_DIR, "anycubic_slicer.key")
@@ -119,6 +123,13 @@ class KobraXClient:
# Optional callbacks: topic_suffix → callable(payload_dict)
self.callbacks: dict[str, callable] = {}
# Dedup: last hash per topic suffix to suppress repeated identical messages
self._last_rx_hash: dict[str, str] = {}
# Fields that change every tick and should be stripped before dedup-hashing
_VOLATILE = {"timestamp", "msgid", "progress", "curr_layer",
"curr_nozzle_temp", "curr_hotbed_temp",
"target_nozzle_temp", "target_hotbed_temp"}
# -- Topics --------------------------------------------------------------
def _pub_topic(self, msg_type: str) -> str:
@@ -144,18 +155,19 @@ class KobraXClient:
raw = socket.create_connection((self.host, self.port), timeout=5)
self._sock = ctx.wrap_socket(raw)
print(f"[kobrax] TLS: {self._sock.cipher()[0]}")
log.info("TLS connected cipher=%s", self._sock.cipher()[0])
self._sock.sendall(_build_connect(self.client_id, self.username, self.password))
self._sock.settimeout(3)
r = self._sock.recv(64)
if len(r) < 4 or r[0] != 0x20 or r[3] != 0:
raise RuntimeError(f"CONNACK failed: {r.hex()}")
print(f"[kobrax] CONNACK rc=0")
log.info("CONNACK rc=0")
self._sock.settimeout(0.2)
self._buf = b""
self._subscribe(self._sub_topic())
log.debug("MQTT connected to %s:%s", self.host, self.port)
def connect(self):
self._do_connect()
@@ -172,7 +184,7 @@ class KobraXClient:
pass
def _reconnect(self):
print("[kobrax] Verbindung verloren reconnect…")
log.warning("Verbindung verloren reconnect…")
try:
self._sock.close()
except Exception:
@@ -180,10 +192,10 @@ class KobraXClient:
for delay in [2, 4, 8, 15, 30]:
try:
self._do_connect()
print("[kobrax] Reconnect erfolgreich")
log.info("Reconnect erfolgreich")
return True
except Exception as e:
print(f"[kobrax] Reconnect fehlgeschlagen ({e}), warte {delay}s…")
log.warning("Reconnect fehlgeschlagen (%s), warte %ss…", e, delay)
time.sleep(delay)
return False
@@ -192,7 +204,7 @@ class KobraXClient:
pid = self._pid
self._pid += 1
self._sock.sendall(_build_subscribe(topic, pid))
print(f"[kobrax] SUB {topic}")
log.info("SUB %s", topic)
# -- Read loop -----------------------------------------------------------
@@ -227,7 +239,7 @@ class KobraXClient:
continue
except Exception as e:
if self._running:
print(f"[kobrax] reader error: {e}")
log.warning("reader error: %s", e)
if not self._reconnect():
break
last_ping = time.time()
@@ -266,9 +278,40 @@ class KobraXClient:
self._buf = buf[idx:]
def _dedup_hash(self, suffix: str, payload: dict) -> str:
"""Hash payload ignoring volatile per-tick fields for dedup check."""
stable = {k: v for k, v in payload.items()
if k not in {"timestamp", "msgid", "progress", "curr_layer",
"curr_nozzle_temp", "curr_hotbed_temp",
"target_nozzle_temp", "target_hotbed_temp"}}
return hashlib.md5(json.dumps(stable, sort_keys=True).encode(), usedforsecurity=False).hexdigest()
def _dispatch(self, topic: str, payload: dict):
# Resolve by report topic suffix (e.g. "info/report")
suffix = "/".join(topic.split("/")[-2:])
# Structured RX log with dedup suppression
h = self._dedup_hash(suffix, payload)
is_dup = self._last_rx_hash.get(suffix) == h
self._last_rx_hash[suffix] = h
if is_dup:
log.debug("RX [dup] %-25s state=%-12s", suffix, payload.get("state", ""))
else:
data = payload.get("data") or {}
state = payload.get("state", "")
if "progress" in data:
log.info("RX %-25s state=%-12s progress=%s%% layer=%s/%s",
suffix, state, data["progress"],
data.get("curr_layer", "?"), data.get("total_layers", "?"))
elif "curr_nozzle_temp" in data:
log.info("RX %-25s nozzle=%s°C/%s°C bed=%s°C/%s°C",
suffix,
data["curr_nozzle_temp"], data.get("target_nozzle_temp", 0),
data.get("curr_hotbed_temp", "?"), data.get("target_hotbed_temp", 0))
else:
log.info("RX %-25s state=%-12s data=%s",
suffix, state, json.dumps(payload.get("data"), ensure_ascii=False))
# Resolve by report topic suffix (e.g. "info/report")
if suffix in self._pending_report:
entry = self._pending_report[suffix]
entry["result"] = payload
@@ -282,19 +325,18 @@ class KobraXClient:
entry["event"].set()
# User callbacks by topic suffix (last two path components)
suffix = "/".join(topic.split("/")[-2:])
if suffix in self.callbacks:
try:
self.callbacks[suffix](payload)
except Exception as e:
print(f"[kobrax] callback error for {suffix}: {e}")
log.error("callback error for %s: %s", suffix, e)
# Generic wildcard callback
if "*" in self.callbacks:
try:
self.callbacks["*"](topic, payload)
except Exception as e:
print(f"[kobrax] wildcard callback error: {e}")
log.error("wildcard callback error: %s", e)
# -- Publish + request/response ------------------------------------------
@@ -322,11 +364,14 @@ class KobraXClient:
report_registered = True
topic = self._pub_topic(msg_type)
log.info("TX %-25s action=%-12s data=%s",
f"{msg_type}/request", action,
json.dumps(data, ensure_ascii=False) if data else "null")
try:
with self._lock:
self._sock.sendall(_build_publish(topic, payload))
except Exception as e:
print(f"[kobrax] send error: {e}, reconnecting…")
log.error("send error: %s, reconnecting…", e)
self._pending_msgid.pop(msgid, None)
if report_registered:
self._pending_report.pop(report_key, None)
@@ -367,11 +412,14 @@ class KobraXClient:
"data": data,
}, separators=(",", ":"))
topic = self._web_topic(msg_type)
log.info("TX(web) %-23s action=%-12s data=%s",
f"{msg_type}/request", action,
json.dumps(data, ensure_ascii=False) if data else "null")
try:
with self._lock:
self._sock.sendall(_build_publish(topic, payload))
except Exception as e:
print(f"[kobrax] web send error: {e}")
log.error("web send error: %s", e)
# -- High-level commands -------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,4 @@
aiohttp>=3.9
imageio-ffmpeg>=0.4.9
requests>=2.30.0
pycryptodome>=3.20.0

View File

@@ -15,6 +15,15 @@ if [[ ! -f .env ]]; then
fi
fi
# config/ Verzeichnis und config.ini.example anlegen falls nicht vorhanden
mkdir -p config
if [[ ! -f config/config.ini ]] && [[ ! -f config/config.ini.example ]]; then
if [[ -f config.ini.example ]]; then
cp config.ini.example config/config.ini.example
echo "[start] config/config.ini.example aus config.ini.example erstellt"
fi
fi
# Docker verfügbar?
if ! docker info > /dev/null 2>&1; then
echo "[start] Docker nicht gefunden bitte Docker installieren."