From 1d3c5a7e1b97bb1544c538f7f5a164a71de2793a Mon Sep 17 00:00:00 2001 From: viewit Date: Fri, 1 May 2026 11:24:08 +0200 Subject: [PATCH] release: v0.9.4 --- .env.example | 1 + CHANGELOG.de.md | 11 +++ CHANGELOG.md | 11 +++ Dockerfile | 16 ++-- README.de.md | 2 +- README.md | 2 +- VERSION | 2 +- kobrax_client.py | 6 +- kobrax_moonraker_bridge.py | 183 +++++++++++++++++++++++++++++++++++-- 9 files changed, 212 insertions(+), 22 deletions(-) diff --git a/.env.example b/.env.example index 6bfe60b..981ec89 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,4 @@ DEVICE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # Modell-ID (Kobra X Standard: 20030) MODE_ID=20030 + diff --git a/CHANGELOG.de.md b/CHANGELOG.de.md index b8dc7fd..c03b3cd 100644 --- a/CHANGELOG.de.md +++ b/CHANGELOG.de.md @@ -1,5 +1,16 @@ # Changelog +## [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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 962b596..b34c738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## [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 diff --git a/Dockerfile b/Dockerfile index 2926150..f1657dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,17 +2,17 @@ FROM python:3.11-slim WORKDIR /app -COPY bridge/requirements.txt . +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -COPY bridge/kobrax_moonraker_bridge.py . -COPY bridge/config_loader.py . -COPY bridge/env_loader.py . -COPY bridge/kobrax_client.py . +COPY kobrax_moonraker_bridge.py . +COPY config_loader.py . +COPY env_loader.py . +COPY kobrax_client.py . COPY VERSION . -COPY bridge/anycubic_slicer.crt . -COPY bridge/anycubic_slicer.key . -COPY bridge/config/config.ini.example /app/config/config.ini.example +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. diff --git a/README.de.md b/README.de.md index 75d4f2c..3b7d76c 100644 --- a/README.de.md +++ b/README.de.md @@ -2,7 +2,7 @@ # KX-Bridge – Anycubic Kobra X -**Version:** 0.9.3 +**Version:** 0.9.4 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. diff --git a/README.md b/README.md index 8ea2956..0033a62 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # KX-Bridge – Anycubic Kobra X -**Version:** 0.9.3 +**Version:** 0.9.4 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. diff --git a/VERSION b/VERSION index 965065d..a602fc9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.3 +0.9.4 diff --git a/kobrax_client.py b/kobrax_client.py index a6689ac..f05f20e 100644 --- a/kobrax_client.py +++ b/kobrax_client.py @@ -309,7 +309,7 @@ class KobraXClient: 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)[:120]) + 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: @@ -366,7 +366,7 @@ class KobraXClient: 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)[:120] if data else "null") + json.dumps(data, ensure_ascii=False) if data else "null") try: with self._lock: self._sock.sendall(_build_publish(topic, payload)) @@ -414,7 +414,7 @@ class KobraXClient: 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)[:120] if data else "null") + json.dumps(data, ensure_ascii=False) if data else "null") try: with self._lock: self._sock.sendall(_build_publish(topic, payload)) diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index ef9d559..ab73ab9 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -1102,6 +1102,34 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0; + + +
-
📷 Kamera nicht gestartet
+
📷 Kamera nicht gestartet
-
+
@@ -1317,7 +1345,18 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
-
+
+ Dir: + + + + Topic: + + + + +
+
@@ -1374,7 +1413,11 @@ var LANG_DE={ update_check:'Auf Updates prüfen',update_checking:'Prüfe...',update_available:'verfügbar',update_none:'Bereits aktuell', update_apply:'Jetzt installieren',update_applying:'Lade herunter...',update_restarting:'Starte neu...',update_error:'Fehler', btn_connect:'⚡ Verbinden',btn_disconnect:'✕ Trennen', - lbl_conn_error:'Verbindungsfehler:' + lbl_conn_error:'Verbindungsfehler:', + slot_edit_title:'Slot bearbeiten',slot_edit_color:'Farbe',slot_edit_material:'Material', + slot_edit_save:'💾 Speichern',slot_edit_custom:'z.B. PLA, PETG, ABS…', + slot_edit_ok:'AMS Slot', + log_dir_all:'Alle' }; var LANG_EN={ header_status_standby:'Ready',header_status_printing:'Printing',header_status_complete:'Complete',header_status_error:'Error', @@ -1402,7 +1445,11 @@ var LANG_EN={ update_check:'Check for Updates',update_checking:'Checking...',update_available:'available',update_none:'Already up to date', update_apply:'Install Now',update_applying:'Downloading...',update_restarting:'Restarting...',update_error:'Error', btn_connect:'⚡ Connect',btn_disconnect:'✕ Disconnect', - lbl_conn_error:'Connection error:' + lbl_conn_error:'Connection error:', + slot_edit_title:'Edit Slot',slot_edit_color:'Color',slot_edit_material:'Material', + slot_edit_save:'💾 Save',slot_edit_custom:'e.g. PLA, PETG, ABS…', + slot_edit_ok:'AMS Slot', + log_dir_all:'All' }; var currentLang='de'; var T=LANG_DE; @@ -1479,6 +1526,12 @@ function applyLang(){ document.querySelectorAll('.lbl-unload').forEach(e=>e.textContent=T.lbl_unload); // conn-btn text (nur wenn nicht im Übergangszustand) updateConnBtn(); + // Slot-Edit-Dialog + setText('lbl-slot-color',T.slot_edit_color); + setText('lbl-slot-material',T.slot_edit_material); + setText('btn-slot-edit-save',T.slot_edit_save); + var mi=document.getElementById('slot-edit-mat');if(mi)mi.setAttribute('placeholder',T.slot_edit_custom); + setText('logdir-all',T.log_dir_all); } function setText(id,txt){var el=document.getElementById(id);if(el)el.textContent=txt;} (function(){ @@ -1510,6 +1563,8 @@ function showPanel(id){ var consoleLogs=[]; var logAutoScroll=true; var logBadgeCount=0; +var logDirFilter='all'; // 'all'|'rx'|'tx' +var logTopicFilter=''; // '' = no topic filter function clog(msg,cls){ cls=cls||'msg-info'; @@ -1535,12 +1590,37 @@ function _appendLog(entry,forceCls){ } renderLog(); } +function setLogDir(dir){ + logDirFilter=dir; + document.querySelectorAll('.log-dir-btn').forEach(function(b){ + b.style.background=b.id==='logdir-'+dir?'var(--accent)':'var(--raised)'; + b.style.color=b.id==='logdir-'+dir?'#fff':'var(--txt2)'; + }); + renderLog(); +} +function setLogTopic(topic){ + var inp=document.getElementById('log-filter'); + var active=inp.value===topic; + inp.value=active?'':topic; + document.querySelectorAll('.log-topic-btn').forEach(function(b){ + var on=!active&&b.getAttribute('data-topic')===topic; + b.style.background=on?'var(--accent)':'var(--raised)'; + b.style.color=on?'#fff':'var(--txt2)'; + }); + renderLog(); +} function renderLog(){ var el=document.getElementById('console-log'); if(!el)return; var filter=(document.getElementById('log-filter')||{}).value||''; var fl=filter.toLowerCase(); - var rows=fl?consoleLogs.filter(l=>l.msg.toLowerCase().includes(fl)):consoleLogs; + var rows=consoleLogs.filter(function(l){ + var m=l.msg; + if(logDirFilter==='rx'&&!/ RX[ (]/.test(m))return false; + if(logDirFilter==='tx'&&!/ TX[ (]/.test(m))return false; + if(fl&&!m.toLowerCase().includes(fl))return false; + return true; + }); el.innerHTML=rows.map(l=>`
${l.ts}${escHtml(l.msg)}
`).join(''); if(logAutoScroll)el.scrollTop=el.scrollHeight; } @@ -1657,6 +1737,7 @@ function applyState(){ // AMS if(s.ams_slots&&s.ams_slots.length){ + window._amsSlots=s.ams_slots; var html=''; s.ams_slots.forEach(function(slot,i){ var empty=slot.status!==5; @@ -1664,11 +1745,13 @@ function applyState(){ var col='rgb('+rgb[0]+','+rgb[1]+','+rgb[2]+')'; var active=slot.status===1||slot.active; var pct=empty?T.ams_empty:(slot.consumables_percent!=null?slot.consumables_percent+'%':'–'); - html+='
' + var idx=slot.index!=null?slot.index:i; + html+='
' +'
' +'
'+(empty?'–':(slot.type||slot.material_type||'–'))+'
' - +'
Slot '+(slot.index!=null?slot.index+1:i+1)+'
' + +'
Slot '+(idx+1)+'
' +'
'+pct+'
' + +'
' +'
'; }); document.getElementById('ams-slots').innerHTML=html; @@ -1767,6 +1850,60 @@ function openSettings(){ function closeSettings(){ document.getElementById('settings-modal').classList.remove('open'); } + +// ── AMS Slot Edit ── +var _slotEditIndex=-1; +var _MAT_PRESETS=['PLA','PETG','ABS','ASA','TPU','PA','PC','HIPS']; +function openSlotEdit(i){ + var slot=(window._amsSlots||[])[i]||{}; + var index=slot.index!=null?slot.index:i; + _slotEditIndex=index; + document.getElementById('slot-edit-title').textContent=T.slot_edit_title+' '+(index+1); + var rgb=Array.isArray(slot.color)?slot.color:[128,128,128]; + var hex='#'+rgb.map(function(v){return('0'+Math.min(255,v).toString(16)).slice(-2)}).join(''); + var ci=document.getElementById('slot-edit-color'); + ci.value=hex; + document.getElementById('slot-edit-preview').style.background=hex; + var mat=(slot.type||'PLA').toUpperCase(); + document.getElementById('slot-edit-mat').value=mat; + var btns=document.getElementById('slot-mat-btns'); + btns.innerHTML=_MAT_PRESETS.map(function(m){ + return ''; + }).join(''); + document.getElementById('slot-edit-modal').classList.add('open'); +} +function closeSlotEdit(){ + document.getElementById('slot-edit-modal').classList.remove('open'); +} +function selectMatPreset(m){ + document.getElementById('slot-edit-mat').value=m; + highlightMatBtn(m); +} +function highlightMatBtn(val){ + document.querySelectorAll('.mat-preset-btn').forEach(function(b){ + var on=b.getAttribute('data-mat')===val.toUpperCase(); + b.style.background=on?'var(--accent)':'var(--raised)'; + b.style.color=on?'#fff':'var(--txt2)'; + }); +} +function hexToRgb(hex){ + var r=parseInt(hex.slice(1,3),16),g=parseInt(hex.slice(3,5),16),b=parseInt(hex.slice(5,7),16); + return[r,g,b]; +} +function saveSlotEdit(){ + var hex=document.getElementById('slot-edit-color').value; + var mat=document.getElementById('slot-edit-mat').value.trim().toUpperCase()||'PLA'; + var color=hexToRgb(hex); + post('/api/ams/set_slot',{index:_slotEditIndex,type:mat,color:color}) + .then(function(r){return r.json();}) + .then(function(r){ + closeSlotEdit(); + clog((T.slot_edit_ok||'AMS Slot')+' '+(_slotEditIndex+1)+': '+mat+' '+hex,'msg-ok'); + }) + .catch(function(e){clog('Fehler: '+e,'msg-err');}); +} document.addEventListener('DOMContentLoaded',function(){ document.getElementById('s-printer-ip').addEventListener('input',function(){ var hint=document.getElementById('lbl-ip-hint'); @@ -2084,6 +2221,35 @@ function toggleCam(){if(camOn)camStop();else camStart()} self._state["print_speed_mode"] = mode return web.json_response({"result": "ok"}) + async def handle_api_ams_set_slot(self, request): + try: + body = await request.json() + except Exception: + body = {} + index = int(body.get("index", 0)) + mat = str(body.get("type", "PLA")).upper() + color = body.get("color", [255, 255, 255]) + if not (isinstance(color, list) and len(color) == 3): + return web.json_response({"error": "color must be [r,g,b]"}, status=400) + loop = asyncio.get_event_loop() + def _send(): + resp = self.client.publish( + "multiColorBox", "setInfo", + {"multi_color_box": [{"id": -1, "slots": [{"index": index, "type": mat, "color": color}]}]}, + timeout=5 + ) + log.info(f"setInfo slot={index} type={mat} color={color} → {resp}") + return resp + resp = await loop.run_in_executor(None, _send) + if resp and resp.get("code") == 200: + # Update cached slot immediately + for s in self._ams_slots: + if s.get("index") == index: + s["type"] = mat + s["color"] = color + break + return web.json_response({"result": "ok"}) + async def handle_api_ams_feed(self, request): try: body = await request.json() @@ -2850,6 +3016,7 @@ def build_app(bridge: KobraXBridge) -> web.Application: r.add_post("/api/disconnect", bridge.handle_api_disconnect) r.add_post("/api/speed", bridge.handle_api_speed) r.add_post("/api/ams/feed", bridge.handle_api_ams_feed) + r.add_post("/api/ams/set_slot", bridge.handle_api_ams_set_slot) r.add_post("/api/axis", bridge.handle_api_axis) r.add_post("/api/temperature", bridge.handle_api_temperature) r.add_get("/api/camera", bridge.handle_api_camera)