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)