diff --git a/VERSION b/VERSION index 058f0fe..3e81aae 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.1-beta2 +0.9.1-beta3 diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index 02e618c..56d510e 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -83,6 +83,7 @@ class KobraXBridge: "light_on": False, "light_brightness": 80, "taskid": "-1", + "print_speed_mode": 2, } self._ams_slots: list[dict] = [] self._ams_loaded_slot: int = -1 @@ -128,6 +129,9 @@ class KobraXBridge: self._state["total_layers"] = d["total_layers"] if "taskid" in d: self._state["taskid"] = str(d["taskid"]) + settings = d.get("settings") or {} + if "print_speed_mode" in settings: + self._state["print_speed_mode"] = int(settings["print_speed_mode"]) self._push_status_update() def _on_info(self, payload: dict): @@ -152,6 +156,9 @@ class KobraXBridge: fan = d.get("fan_speed_pct") if fan is not None: self._state["fan_speed"] = int(fan) + speed_mode = d.get("print_speed_mode") + if speed_mode is not None: + self._state["print_speed_mode"] = int(speed_mode) self._push_status_update() def _on_file(self, payload: dict): @@ -637,6 +644,14 @@ main{flex:1;overflow-y:auto;padding:20px} .btn-cancel{background:#2d0d0d;color:var(--err);border:1px solid var(--err)} .btn-accent{background:var(--accent);color:#001a24} .btn-sm{padding:7px 12px;font-size:12px} +.spd-btn{flex:1;border:1.5px solid var(--border);background:var(--raised);color:var(--txt); + border-radius:10px;padding:14px 8px;font-size:13px;font-weight:600;cursor:pointer; + transition:all .15s;display:flex;flex-direction:column;align-items:center;gap:4px} +.spd-btn:hover{border-color:var(--accent);color:var(--accent)} +.spd-btn.spd-active{border-color:var(--accent);background:rgba(0,200,255,.12);color:var(--accent)} +.spd-btn .spd-icon{font-size:22px} +.spd-bar{height:4px;border-radius:2px;background:var(--border);margin-top:10px;overflow:hidden} +.spd-bar-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,var(--accent2),var(--accent));transition:width .3s} /* ── TEMPS ── */ .temp-pair{display:grid;grid-template-columns:1fr 1fr;gap:12px} @@ -887,9 +902,9 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
-
📷 Kamera
+
📷 Kamera
- 💡 Licht + 💡 Licht
-
Bett
+
Bett
°C
@@ -1005,6 +1020,28 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0;
Schrittweite: 1 mm
+ +
+
🏎 Druckgeschwindigkeit
+
+ + + +
+
+
+
+
+
🌀 Lüfter
@@ -1036,8 +1073,8 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0; Slot 1
- - + +
@@ -1064,7 +1101,7 @@ nav.bottom-nav{display:none;position:fixed;bottom:0;left:0;right:0; var S={nozzle_temp:0,nozzle_target:0,bed_temp:0,bed_target:0, print_state:'standby',filename:'',progress:0,print_duration:0, curr_layer:0,total_layers:0,printer_name:'Kobra X',firmware_version:'–', - camera_url:'',fan_speed:0,light_on:false,light_brightness:80,ams_slots:[]}; + camera_url:'',fan_speed:0,print_speed_mode:2,light_on:false,light_brightness:80,ams_slots:[]}; var tempHistory={n:[],b:[]}; var camOn=false; var currentStep=1; @@ -1083,7 +1120,9 @@ var LANG_DE={ header_status_standby:'Bereit',header_status_printing:'Druckt',header_status_complete:'Fertig',header_status_error:'Fehler', kobra_free:'Bereit',kobra_busy:'Beschäftigt',kobra_printing:'Druckt',kobra_preheating:'Aufheizen',kobra_auto_leveling:'Nivellierung',kobra_checking:'Prüfung',kobra_updated:'Aktualisierung',kobra_init:'Initialisierung',kobra_finished:'Abgeschlossen',kobra_failed:'Fehler',kobra_canceled:'Abgebrochen',kobra_offline:'Offline', nav_dashboard:'Dashboard',nav_print:'Druck',nav_temps:'Temperaturen',nav_motion:'Achsen',nav_ams:'AMS',nav_extras:'Licht / Lüfter',nav_console:'Konsole', - card_progress:'Fortschritt',card_temps:'Temperaturen',card_light_fan:'Lüfter', + card_progress:'Fortschritt',card_temps:'Temperaturen',card_light_fan:'Lüfter',card_speed:'Druckgeschwindigkeit',card_cam:'Kamera', + speed_silent:'🐢 Leise',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport', + lbl_light:'💡 Licht',lbl_feed:'Einziehen',lbl_unload:'Ausziehen', cam_placeholder:'📷 Kamera nicht gestartet',btn_cam_start:'▶ Kamera',btn_cam_stop:'◼ Kamera', btn_pause:'⏸ Pause',btn_resume:'▶ Weiter',btn_cancel:'✕ Stopp', label_nozzle:'Nozzle',label_bed:'Bett',label_fan:'🌀 Lüfter',label_light:'💡 Licht',label_on_off:'Ein / Aus',label_speed:'Geschwindigkeit', @@ -1106,7 +1145,9 @@ var LANG_EN={ header_status_standby:'Ready',header_status_printing:'Printing',header_status_complete:'Complete',header_status_error:'Error', kobra_free:'Ready',kobra_busy:'Busy',kobra_printing:'Printing',kobra_preheating:'Preheating',kobra_auto_leveling:'Auto Leveling',kobra_checking:'Checking',kobra_updated:'Updating',kobra_init:'Initializing',kobra_finished:'Finished',kobra_failed:'Error',kobra_canceled:'Cancelled',kobra_offline:'Offline', nav_dashboard:'Dashboard',nav_print:'Print',nav_temps:'Temperatures',nav_motion:'Motion',nav_ams:'AMS',nav_extras:'Light / Fan',nav_console:'Console', - card_progress:'Progress',card_temps:'Temperatures',card_light_fan:'Fan', + card_progress:'Progress',card_temps:'Temperatures',card_light_fan:'Fan',card_speed:'Print Speed',card_cam:'Camera', + speed_silent:'🐢 Silent',speed_normal:'⚡ Normal',speed_sport:'🚀 Sport', + lbl_light:'💡 Light',lbl_feed:'Load',lbl_unload:'Unload', cam_placeholder:'📷 Camera not started',btn_cam_start:'▶ Camera',btn_cam_stop:'◼ Camera', btn_pause:'⏸ Pause',btn_resume:'▶ Resume',btn_cancel:'✕ Stop', label_nozzle:'Nozzle',label_bed:'Bed',label_fan:'🌀 Fan',label_light:'💡 Light',label_on_off:'On / Off',label_speed:'Speed', @@ -1146,7 +1187,11 @@ function applyLang(){ setText('d-card-progress',T.card_progress); setText('d-card-temps',T.card_temps); setText('d-card-lightfan',T.card_light_fan); + setText('d-card-speed',T.card_speed); + setText('d-card-cam',T.card_cam); setText('d-card-ams',T.panel_ams_title); + setText('d-lbl-light',T.lbl_light); + setText('d-lbl-bed',T.label_bed); // Dashboard buttons setText('d-btn-pause',T.btn_pause); setText('d-btn-resume',T.btn_resume); @@ -1181,6 +1226,13 @@ function applyLang(){ setText('lbl-mode-id',T.settings_mode_id); setText('lbl-update-check',T.update_check); setText('lbl-update-apply',T.update_apply); + // Speed buttons + setText('d-spd-lbl-1',T.speed_silent.replace(/^\S+\s/,'')); + setText('d-spd-lbl-2',T.speed_normal.replace(/^\S+\s/,'')); + setText('d-spd-lbl-3',T.speed_sport.replace(/^\S+\s/,'')); + // AMS feed/unload + document.querySelectorAll('.lbl-feed').forEach(e=>e.textContent=T.lbl_feed); + document.querySelectorAll('.lbl-unload').forEach(e=>e.textContent=T.lbl_unload); } function setText(id,txt){var el=document.getElementById(id);if(el)el.textContent=txt;} (function(){ @@ -1271,6 +1323,15 @@ function applyState(){ var dfan=document.getElementById('d-fan');if(dfan)dfan.value=s.fan_speed; var dfanval=document.getElementById('d-fan-val');if(dfanval)dfanval.textContent=s.fan_speed; + // speed mode buttons + var spdWidths={1:25,2:55,3:90}; + [1,2,3].forEach(function(m){ + var b=document.getElementById('d-spd-'+m); + if(b) b.classList.toggle('spd-active', s.print_speed_mode===m); + }); + var spdBar=document.getElementById('d-spd-bar'); + if(spdBar) spdBar.style.width=(spdWidths[s.print_speed_mode]||55)+'%'; + // AMS if(s.ams_slots&&s.ams_slots.length){ var html=''; @@ -1478,7 +1539,7 @@ function setNozzle(){ function setBed(){ var v=parseFloat(document.getElementById('p-bed-inp').value||0); post('/api/temperature',{nozzle:S.nozzle_target,bed:v}) - .then(function(){clog('Bett → '+v+'°C','msg-ok')}) + .then(function(){clog(T.label_bed+' → '+v+'°C','msg-ok')}) .catch(function(e){clog('Temp-Fehler: '+e,'msg-err')}); } @@ -1490,6 +1551,17 @@ function setLight(){ .catch(function(e){clog('Licht-Fehler: '+e,'msg-err')}); } +// ── Print Speed ── +function setSpeed(mode){ + S.print_speed_mode=mode; + [1,2,3].forEach(function(m){ + var b=document.getElementById('d-spd-'+m); + if(b) b.classList.toggle('spd-active',m===mode); + }); + post('/api/speed',{mode:mode}) + .catch(function(e){clog('Speed-Fehler: '+e,'msg-err')}); +} + // ── Fan ── function setFan(){ var v=parseInt(document.getElementById('d-fan').value); @@ -1510,7 +1582,7 @@ function quickFan(v){ function amsFeed(type){ var slot=parseInt(document.getElementById('ams-slot-sel').value); post('/api/ams/feed',{slot_index:slot,type:type}) - .then(function(){clog((type===1?'Einziehen':'Ausziehen')+' Slot '+(slot+1),'msg-ok')}) + .then(function(){clog((type===1?T.lbl_feed:T.lbl_unload)+' Slot '+(slot+1),'msg-ok')}) .catch(function(e){clog('AMS-Fehler: '+e,'msg-err')}); } @@ -1600,6 +1672,21 @@ function toggleCam(){if(camOn)camStop();else camStart()} self._state["fan_speed"] = speed return web.json_response({"result": "ok"}) + async def handle_api_speed(self, request): + try: + body = await request.json() + except Exception: + body = {} + mode = int(body.get("mode", 2)) + loop = asyncio.get_event_loop() + taskid = self._state.get("taskid", "-1") + await loop.run_in_executor(None, lambda: self.client.publish_web( + "print", "update", + {"taskid": taskid, "settings": {"print_speed_mode": mode}}, + )) + self._state["print_speed_mode"] = mode + return web.json_response({"result": "ok"}) + async def handle_api_ams_feed(self, request): try: body = await request.json() @@ -1799,6 +1886,7 @@ function toggleCam(){if(camOn)camStop();else camStart()} "filename": s["filename"], "camera_url": s["camera_url"], "fan_speed": s["fan_speed"], + "print_speed_mode": s["print_speed_mode"], "light_on": s["light_on"], "light_brightness": s["light_brightness"], "ams_slots": self._ams_slots, @@ -2255,6 +2343,7 @@ def build_app(bridge: KobraXBridge) -> web.Application: # New API endpoints r.add_post("/api/light", bridge.handle_api_light) r.add_post("/api/fan", bridge.handle_api_fan) + r.add_post("/api/speed", bridge.handle_api_speed) r.add_post("/api/ams/feed", bridge.handle_api_ams_feed) r.add_post("/api/axis", bridge.handle_api_axis) r.add_post("/api/temperature", bridge.handle_api_temperature)