diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index fb2c09d..f6070a9 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -453,6 +453,7 @@ class KobraXBridge: self._ams_loaded_slot: int = -1 # global slot index of currently loaded slot self._pending_load_slot: int = -1 # global slot index requested via /api/ams/feed type=1 self._ace_box_ids: list[int] = [] # detected ACE unit IDs (0..3) + self._ace_auto_feed: dict[int, int] = {} # per-box auto_feed state (0/1) self._head_tools_model: int = -1 self._filament_mode: str = "toolhead" self._last_uploaded_file: str = "" @@ -821,6 +822,10 @@ class KobraXBridge: global_slots, global_loaded = self._aggregate_slots(boxes, self._filament_mode) self._ams_loaded_slot = global_loaded self._update_ace_drying_state(data, boxes) + for box in boxes: + bid = int(box.get("id", -1)) + if 0 <= bid <= 3 and "auto_feed" in box: + self._ace_auto_feed[bid] = int(box["auto_feed"]) if self._pending_load_slot >= 0 and global_loaded == self._pending_load_slot: self._pending_load_slot = -1 activity_map = self._slot_activity_map(boxes, global_loaded) @@ -3218,7 +3223,11 @@ function applyState(){ if(!show)continue; var ud=unitMap[i]||dry; var refillToggle=document.getElementById('ace-auto-refill-toggle-'+i); - if(refillToggle)refillToggle.checked=_aceAutoRefillGet(i); + var autoFeedMap=s.ace_auto_feed||{}; + if(refillToggle&&!_aceAutoFeedPending[i]){ + var afVal=autoFeedMap.hasOwnProperty(String(i))?Number(autoFeedMap[String(i)]):(_aceAutoRefillGet(i)?1:0); + refillToggle.checked=afVal===1; + } var dryToggle=document.getElementById('ace-dry-enable-toggle-'+i); if(dryToggle)dryToggle.checked=Number(ud.status||0)>0; var hh=document.getElementById('d-ace-dry-humidity-'+i); @@ -3755,11 +3764,19 @@ function aceDryStart(aceId){ .catch(function(e){clog('ACE-Fehler: '+e,'msg-err');}); } +var _aceAutoFeedPending={}; function aceAutoRefillToggle(aceId){ aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0; var on=!!((document.getElementById('ace-auto-refill-toggle-'+aceId)||{}).checked); - _aceAutoRefillSet(aceId,on); - clog('ACE '+(aceId+1)+' - '+(T.ace_dry_auto_refill||'Auto Refill')+': '+(on?'ON':'OFF')+' '+(T.ace_dry_ui_pending||'(UI only, backend next)'),'msg-info'); + _aceAutoFeedPending[aceId]=true; + fetch('/api/ace/auto_feed',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ace_id:aceId,on:on?1:0})}) + .then(function(r){return r.json();}) + .then(function(d){ + delete _aceAutoFeedPending[aceId]; + if(d.error){clog('Auto Refill error: '+d.error,'msg-err');var t=document.getElementById('ace-auto-refill-toggle-'+aceId);if(t)t.checked=!on;return;} + clog('ACE '+(aceId+1)+' - '+(T.ace_dry_auto_refill||'Auto Refill')+': '+(on?'ON':'OFF'),'msg-ok'); + }) + .catch(function(e){delete _aceAutoFeedPending[aceId];clog('Auto Refill error: '+e,'msg-err');var t=document.getElementById('ace-auto-refill-toggle-'+aceId);if(t)t.checked=!on;}); } function openAceDryDialog(aceId){ @@ -4764,6 +4781,36 @@ function loadPrinterTab(){ await loop.run_in_executor(None, _send) return web.json_response({"result": "ok"}) + async def handle_api_ace_auto_feed(self, request): + try: + body = await request.json() + except Exception: + body = {} + + ace_id_raw = body.get("ace_id", None) + on_raw = body.get("on", None) + if ace_id_raw is None or on_raw is None: + return web.json_response({"error": "ace_id and on are required"}, status=400) + try: + ace_id = int(ace_id_raw) + on = int(bool(on_raw)) + except Exception: + return web.json_response({"error": "invalid parameters"}, status=400) + if not (0 <= ace_id <= 3): + return web.json_response({"error": "ace_id must be 0-3"}, status=400) + + payload = {"multi_color_box": [{"id": ace_id, "auto_feed": on}]} + loop = asyncio.get_event_loop() + # Fire-and-forget: setAutoFeed ACK arrives via multiColorBox/report callback. + # Waiting for a response on that busy push topic causes false "code:0" rejections. + await loop.run_in_executor( + None, + lambda: self.client.publish("multiColorBox", "setAutoFeed", payload, timeout=0) + ) + self._ace_auto_feed[ace_id] = on + self._state_dirty = True + return web.json_response({"result": "ok", "ace_id": ace_id, "auto_feed": on}) + async def handle_api_ace_dry(self, request): try: body = await request.json() @@ -5087,6 +5134,7 @@ function loadPrinterTab(){ "filament_mode": s.get("filament_mode", self._filament_mode), "ace_drying": s.get("ace_drying", {"status": 0, "target_temp": 0, "duration": 0, "remain_time": 0, "humidity": None, "current_temp": None}), "ace_units": list(self._ace_box_ids), + "ace_auto_feed": dict(self._ace_auto_feed), "ace_dry_presets": self._ace_dry_presets, "thumbnail": self._thumbnail_b64, "connection_error": s["connection_error"], @@ -5822,6 +5870,7 @@ def build_app(bridge: KobraXBridge) -> web.Application: 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/ace/auto_feed", bridge.handle_api_ace_auto_feed) r.add_post("/api/ace/dry", bridge.handle_api_ace_dry) r.add_post("/api/axis", bridge.handle_api_axis) r.add_post("/api/temperature", bridge.handle_api_temperature)