From 1f300589d179ff10a51f9e95264aebd704c12728 Mon Sep 17 00:00:00 2001 From: viewit Date: Sun, 14 Jun 2026 11:00:32 +0200 Subject: [PATCH] build: sources for v0.9.21 --- CHANGELOG.de.md | 19 +++++++++++++++++++ CHANGELOG.md | 19 +++++++++++++++++++ Dockerfile | 2 +- README.de.md | 16 +--------------- README.md | 16 +--------------- VERSION | 2 +- kobrax_client.py | 6 ++++-- kobrax_moonraker_bridge.py | 10 ++++++---- web/themes/default/app.js | 31 +++++++++++++++++-------------- 9 files changed, 69 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.de.md b/CHANGELOG.de.md index cea68e6..b7f203e 100644 --- a/CHANGELOG.de.md +++ b/CHANGELOG.de.md @@ -1,5 +1,24 @@ # Changelog +## [0.9.21] – 2026-06-14 + +### Behoben +- **Kamera-Stream auf Android (Chrome / Firefox) nicht sichtbar.** Android-Browser + unterstützen `multipart/x-mixed-replace` (MJPEG) nicht. Die UI erkennt Android + jetzt automatisch und fällt auf Snapshot-Polling mit 5 fps zurück + (`/api/camera/snapshot` alle 200 ms) — keine Server-Änderung nötig. + +### Geändert +- Docker-Image auf **Debian 12 (Bookworm)** gepinnt (`python:3.11-slim-bookworm`), + um Kompatibilitätsprobleme mit glibc 2.41 zu vermeiden, die das aktuell von + `python:3.11-slim` gezogene Debian 13 Basis-Image mitbringt. +- MQTT- und HTTP-Verbindungen erzwingen jetzt **IPv4** (`AF_INET`), um + Verbindungsfehler auf Hosts zu verhindern, bei denen der Drucker nur über IPv4 + erreichbar ist, das OS aber IPv6 bevorzugt. +- Extruder-Stub in der Moonraker-`configfile`-Antwort enthält jetzt `sensor_type` + und `filament_diameter` — behebt einen Mobileraker-Absturz + (`Null is not a subtype of Object`, Issue #48). + ## [0.9.20] – 2026-06-08 ### Neu diff --git a/CHANGELOG.md b/CHANGELOG.md index 7931b9a..d075487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [0.9.21] – 2026-06-14 + +### Fixed +- **Camera stream not visible on Android (Chrome / Firefox).** Android + browsers do not support `multipart/x-mixed-replace` (MJPEG). The UI + now detects Android and falls back to snapshot-polling at 5 fps + (`/api/camera/snapshot` every 200 ms) — no server-side change needed. + +### Changed +- Docker image now pinned to **Debian 12 (Bookworm)** (`python:3.11-slim-bookworm`) + to avoid glibc 2.41 compatibility issues introduced by the Debian 13 + base image that `python:3.11-slim` recently started pulling. +- MQTT and HTTP connections now **force IPv4** (`AF_INET`) to prevent + connection failures on hosts where the printer is only reachable via + IPv4 but the OS prefers IPv6. +- Extruder stub in the Moonraker `configfile` response now includes + `sensor_type` and `filament_diameter` — fixes a Mobileraker crash + (`Null is not a subtype of Object`, issue #48). + ## [0.9.20] – 2026-06-08 ### New diff --git a/Dockerfile b/Dockerfile index 2e34be3..553475a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-slim +FROM python:3.11-slim-bookworm WORKDIR /app diff --git a/README.de.md b/README.de.md index a6d9b27..e6931e4 100644 --- a/README.de.md +++ b/README.de.md @@ -105,20 +105,6 @@ Drucker → Verbindungstyp **Moonraker** → Host: `http://BRIDGE-IP:7125` --- -## 🐳 Portainer - -[`docker-compose.portainer.yml`](docker-compose.portainer.yml) in -**Portainer → Stacks → Add stack → Web editor** einfügen und auf *Deploy* klicken. - -Keine Konfiguration nötig — die Bridge startet im Offline-Modus. -**`http://HOST-IP:7125`** öffnen, den **Drucker-Tab** aufrufen und **+ Drucker hinzufügen** klicken. -Nur die Drucker-IP eingeben — Zugangsdaten werden automatisch geholt und gespeichert. - -Alle Daten (Konfiguration, GCode-Store, Datenbank) liegen in Named Docker Volumes -(`kx-bridge-config`, `kx-bridge-data`), die Portainer verwaltet. - ---- - ## 📺 Video-Tutorial [![KX-Bridge Setup & Usage](https://img.youtube.com/vi/1Ql4wfH27fM/hqdefault.jpg)](https://www.youtube.com/watch?v=1Ql4wfH27fM) @@ -144,7 +130,7 @@ Für sauberen AMS-Filament-Sync gibt es einen **gepatchten OrcaSlicer-Build**: - Vendor-Match wenn `tray_info_idx` gesetzt ist, das Preset aber inkompatibel - Zwei-Pass-Suche: erst kompatible Presets, dann alle sichtbaren -**Warum das zusammen wichtig ist:** ohne #13719 landen die AMS-Slots in OrcaSlicer alle auf `Generic PLA` / `Generic PETG`, obwohl die Bridge die konkrete Marke schon mitsendet (`name + vendor_name + gate_filament_name`). Mit dem KX-Build matched OrcaSlicer deine echten User-Presets — auch die, die du via [Eigene OrcaSlicer-Profile importieren](docs/filament-preset-bridge-guide.md) in die Bridge gezogen hast. +**Warum das zusammen wichtig ist:** ohne #13719 landen die AMS-Slots in OrcaSlicer alle auf `Generic PLA` / `Generic PETG`, obwohl die Bridge die konkrete Marke schon mitsendet (`name + vendor_name + gate_filament_name`). Mit dem KX-Build matched OrcaSlicer deine echten User-Presets — auch die, die du via [Eigene OrcaSlicer-Profile importieren](#-features) in die Bridge gezogen hast. Stock-Upstream-OrcaSlicer funktioniert für Slicing und Drucken weiterhin — nur das Per-Slot-Vendor-Matching beim AMS-Sync fällt dann weg. Material und Farbe pro Slot kannst du auch ohne den KX-Build über die Bridge ans Drucker-Display schreiben (das läuft über MQTT, nicht über den Slicer). diff --git a/README.md b/README.md index b11bd71..7bf3199 100644 --- a/README.md +++ b/README.md @@ -104,20 +104,6 @@ Printer → Connection type **Moonraker** → Host: `http://BRIDGE-IP:7125` --- -## 🐳 Portainer - -Paste [`docker-compose.portainer.yml`](docker-compose.portainer.yml) into -**Portainer → Stacks → Add stack → Web editor** and click *Deploy*. - -No configuration needed upfront — the bridge starts in offline mode. -Open **`http://HOST-IP:7125`**, go to the **Printers** tab and click **+ Add printer**. -Enter the printer's IP address — credentials are fetched and saved automatically. - -All data (config, GCode store, database) is stored in named Docker volumes -(`kx-bridge-config`, `kx-bridge-data`) managed by Portainer. - ---- - ## 📺 Video Tutorial [![KX-Bridge Setup & Usage](https://img.youtube.com/vi/1Ql4wfH27fM/hqdefault.jpg)](https://www.youtube.com/watch?v=1Ql4wfH27fM) @@ -143,7 +129,7 @@ For proper AMS filament-sync we ship a **patched OrcaSlicer build**: - Vendor match when `tray_info_idx` is set but its preset is incompatible - Two-pass lookup: first compatible presets, then all visible ones -**Why this matters:** without #13719 the AMS slots in OrcaSlicer all fall back to `Generic PLA` / `Generic PETG` even though the bridge already sends the concrete brand (`name + vendor_name + gate_filament_name`). With the KX build OrcaSlicer matches your actual user presets — including profiles you imported into the bridge via the [Import your own OrcaSlicer profiles](docs/filament-preset-bridge-guide.md) flow. +**Why this matters:** without #13719 the AMS slots in OrcaSlicer all fall back to `Generic PLA` / `Generic PETG` even though the bridge already sends the concrete brand (`name + vendor_name + gate_filament_name`). With the KX build OrcaSlicer matches your actual user presets — including profiles you imported into the bridge via the [Import your own OrcaSlicer profiles](#-features) flow. Stock upstream OrcaSlicer still works for slicing and printing — you just lose the per-slot brand matching on AMS sync. Slot material + colour can still be pushed bridge → printer either way (that goes over MQTT, not via the slicer). diff --git a/VERSION b/VERSION index bd758c9..b216023 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.20 +0.9.21 diff --git a/kobrax_client.py b/kobrax_client.py index fa50ce3..e7080fc 100644 --- a/kobrax_client.py +++ b/kobrax_client.py @@ -167,7 +167,8 @@ class KobraXClient: ctx.set_ciphers("DEFAULT:@SECLEVEL=0") ctx.load_cert_chain(CERT_FILE, KEY_FILE) - raw = socket.create_connection((self.host, self.port), timeout=5) + _ai = socket.getaddrinfo(self.host, self.port, socket.AF_INET, socket.SOCK_STREAM) + raw = socket.create_connection(_ai[0][4], timeout=5) self._sock = ctx.wrap_socket(raw) log.info("TLS connected cipher=%s", self._sock.cipher()[0]) @@ -596,7 +597,8 @@ class KobraXClient: # langsamerem WLAN am Drucker dauert das Schieben sonst >30 s und # würde den Connect-Timeout fälschlich auslösen. Read-Timeout danach # generös (Drucker verarbeitet die Datei bevor er antwortet). - sock = socket.create_connection((self.host, 18910), timeout=10) + _ai = socket.getaddrinfo(self.host, 18910, socket.AF_INET, socket.SOCK_STREAM) + sock = socket.create_connection(_ai[0][4], timeout=10) sock.settimeout(None) # blocking während Send sock.sendall(headers + body) sock.settimeout(180) diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 4c2f19e..8caf876 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -1906,10 +1906,12 @@ class KobraXBridge: "square_corner_velocity": 20.0, }, "extruder": { - "nozzle_diameter": 0.4, - "min_temp": 0, - "max_temp": 320, - "min_extrude_temp": 10, + "nozzle_diameter": 0.4, + "filament_diameter": 1.75, + "sensor_type": "ATC Semitec 104GT-2", + "min_temp": 0, + "max_temp": 320, + "min_extrude_temp": 10, }, "heater_bed": { "min_temp": 0, diff --git a/web/themes/default/app.js b/web/themes/default/app.js index 284dd89..a84ba72 100644 --- a/web/themes/default/app.js +++ b/web/themes/default/app.js @@ -7,6 +7,7 @@ var S={nozzle_temp:0,nozzle_target:0,bed_temp:0,bed_target:0, var tempHistory={n:[],b:[]}; var camOn=false; var camUserStopped=false; // user stopped camera manually — suppress auto-restart for this print +var _camPollInterval=null; // snapshot-polling interval for Android (no MJPEG support) var currentStep=1; var currentPanel='dashboard'; var aceAutoRefillPrefs=(function(){ @@ -1501,23 +1502,24 @@ function camStart(){ img.style.display='none'; sp.style.display='block'; post('/api/camera/start',{}).then(function(){ - img.onerror=function(){ - sp.style.display='none'; - img.style.display='none'; - ph.style.display='flex'; - camOn=false; - document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start'); - clog(tr('log_error')+' '+tr('cam_stream_unavailable'),'msg-err'); - }; - img.src='/api/camera/stream?t='+Date.now(); camOn=true; document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_stop'); clog(tr('log_cam_start'),'msg-ok'); - // MJPEG liefert kein onload – Spinner nach kurzem Timeout ausblenden - setTimeout(function(){ - sp.style.display='none'; - img.style.display='block'; - },1200); + setTimeout(function(){ sp.style.display='none'; img.style.display='block'; },1200); + if(/Android/i.test(navigator.userAgent)){ + // Android browsers don't support multipart/x-mixed-replace (MJPEG) — poll snapshots instead + _camPollInterval=setInterval(function(){ img.src='/api/camera/snapshot?t='+Date.now(); },200); + } else { + img.onerror=function(){ + sp.style.display='none'; + img.style.display='none'; + ph.style.display='flex'; + camOn=false; + document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start'); + clog(tr('log_error')+' '+tr('cam_stream_unavailable'),'msg-err'); + }; + img.src='/api/camera/stream?t='+Date.now(); + } }).catch(function(e){ sp.style.display='none'; ph.style.display='flex'; @@ -1527,6 +1529,7 @@ function camStart(){ function camStop(){ var img=document.getElementById('cam-img'); + if(_camPollInterval){clearInterval(_camPollInterval);_camPollInterval=null;} img.onerror=null; // deregister error handler before clearing src to avoid spurious error toast post('/api/camera/stop',{}).catch(function(){}); img.src='';