Repository navigation
FEAT: Control tab under Print Hub (webcam, print panel, console) - #188
Conversation
|
If you like it, then I can also make the console send commands rather than be just read-only. I use it on my printer and it's pretty handy to have. |
decay71
left a comment
There was a problem hiding this comment.
Thanks, three points
-
Nozzle clamp: _heater_limits intersects the material ranges of every head with filament_detected, not just the active one (filament_detected is also true for an unloaded head with a spool at the ACE gate). PLA on one head and PETG on another gives 210-230 on both; PLA and PC inverts the range (240/230), so any nozzle target on the PLA head lands at 240. Please use only the toolhead whose idx matches the active extruder.
-
Homing gate: the 2 s status poll (_print_status) calls Moonraker directly. Please check _homing_active() first, like _query_state_gated does, and skip the poll or serve the last value while the flag is fresh. Polling during bed mesh/homing is what that gate prevents.
-
Swaps: /printer/gcode/script only returns once the command has run, and a multiACE swap holds the queue for 60-150 s. The 30 s timeout then reports 502 although the command is still queued and runs after the swap, so a second press sends it twice. Please treat a timeout as "queued" in the UI, or raise the timeout above a swap.
One question on top: would you like to add controls for the Snapmaker enclosure as well? Stock drives it through the purifier object (exhaust and inner fan, with its own attach detection), and the cavity temperature sensor gives the matching readout. A toggle for the chamber light (led cavity_led, part of the printer itself) would fit next to it and also fix your dark camera picture. Same pattern as the rest: fixed verbs, values clamped on the server.
|
Thanks for the review, I'll make those three changes and add the chamber light toggle. |
- Nozzle clamp: use only the material of the head that owns the active extruder. Other heads' spools no longer narrow or invert the range. - Homing gate: the 2 s status poll now serves the last value while ace.py's homing flag is fresh, like _query_state_gated. Klipper's config limits are cached and not queried during homing either. - Swaps: a read timeout on /printer/gcode/script is reported as queued, not as a 502. The UI keeps the same control disabled meanwhile so a second press cannot send it twice. A connect timeout is still a failure. - Chamber light: a fixed "light" verb (0-100 %, SET_LED LED=cavity_led), read back from the printer, shown as a toggle only when the printer has the LED.
|
Thank you, i will test that here! |
A bulb icon and "Light", with a toggle switch whose knob carries the ON / OFF text, instead of a button next to a status word. The switch has fixed dimensions so the phone rule that raises buttons to 44px leaves it alone, and it is a real role="switch" with aria-checked.
…rol tab Read-only: the cavity temperature sensor (part of the printer, it reads without an enclosure) and, when the purifier object reports an attached enclosure (power_detected), the exhaust and inner fan speeds. One control: "Fans off" sends SET_PURIFIER_MODE MODE=0, the command stock itself sends at the end of a print (PRINT_END, CANCEL_PRINT), so its meaning is known. There is no verb that starts the fans or sets a speed: no command for that could be found on the printer (help, config, logs, web UI), and it is not guessed. The verb is refused with 409 on a printer without the purifier object. Also, found while checking this on a real printer: - The console history was only fetched when the buffer was empty, so any live line arriving first meant it never loaded. It is now fetched once, and lines newer than the request are kept after it. - A WebRTC connection that comes up but delivers no video now fails visibly and retries, instead of staying silent.
POST /api/restart takes one of two fixed behaviours (firmware -> /printer/firmware_restart, printer -> /machine/reboot). It is refused while a print is running or paused, read fresh from Moonraker rather than from the homing cache, and refused under MULTIACE_MANAGED, where the platform owns restarts. A downed Klipper (503) does not block a firmware restart, which is when it is needed; an unreachable Moonraker does, rather than guessing. A connection that drops mid-request counts as sent. In the UI both buttons ask for confirmation, are disabled with an explanation while a print runs, are not rendered on a managed install, and the poll errors that follow a restart are held back until the printer answers again. Also: the webcam lookup failed once at first load with an empty error message and then stayed on "Camera unavailable" until Reload was pressed. The error now names the exception, and the tab looks again every ~10 s while no camera is found.
Found under load: the console seed test recorded its "early" line in the same clock tick as the request start, and the homing-gate test wrote a flag whose mtime could read slightly ahead of time.time() on Windows, so it counted as stale. Neither was a logic error in the code under test.
decay71
left a comment
There was a problem hiding this comment.
Thanks, this reads well and the safety design (fixed verbs, server-side clamps, print-state gate on restarts) is exactly right. Three things:
-
Heater clamp raises low targets. A nozzle target below the material range is lifted to the floor: PLA 150 becomes 170, PC 200 becomes 240. Someone setting a standby temperature gets a hotter nozzle than they typed. Please cap only the top (max_temp and the material maximum) and let values below the material floor through, the same way 0 already passes as "off".
-
MATERIAL_TEMP_LIMITS only knows base names. PLA-CF, PETG-CF, PETG-HF and similar find no entry and fall back to max_temp alone. That is safe, just inconsistent. Either match on the base material before the first "-" or leave it as is and say so in the comment.
-
The babystep allowance resets only when the filename changes. Printing the same file again keeps the used total from the previous job. Resetting it on print start would fit better than keying on the filename.
The restart buttons can stay: firmware_restart is what our own config save sends, the warning in index.html is about the bare Klipper restart, which you do not use.
On your enclosure question, this is what stock purifier.py (2.0.0) offers, take whatever you think fits:
- SET_PURIFIER FAN=exhaust|inner SPEED=<0..1> sets one fan directly. SPEED=0 starts the delayed turn-off (default 180 s) rather than stopping at once. Speed > 0 is refused without an attached enclosure.
- SET_PURIFIER_MODE MODE=0..3: 0 idle, 1 cool chamber (exhaust, FAN_SPEED default 0.6, DESIRE_TEMP default 42, ALARM_TEMP), 2 preheat chamber (inner fan, waits for DESIRE_TEMP), 3 hot chamber (inner fan, exhaust off). get_status reports the active mode and desired_chamber_temp, so the panel could show what is running.
- Your current "Fans off" (MODE=0) stops the fans only after that delay. MODE=0 EXHAUST_FAN_DELAY_OFF=0 INNER_FAN_DELAY_OFF=0 stops them now.
Things worth keeping in mind if you go further: in modes 1 to 3 stock drives the fans itself (dynamically in cool mode), so a direct speed control would compete with it; and during a print the mode usually comes from the sliced file, which is also what a power-loss resume restores. Direct fan control in idle and mode buttons outside a print would be one way, but it is your call.
I will test the enclosure readout, the fan controls and the chamber light here after the merge.
…t start - Cap only the top of the nozzle target (max_temp and the material maximum). A target below the material's printing range is a standby temperature set on purpose; it used to be lifted to the floor (PLA 150 became 170, PC 200 became 240). 0 still passes as off. - Match the material ceiling on the base name before the first "-", so PLA-CF, PETG-CF and PETG-HF get their base material's ceiling instead of only max_temp. A name with no entry still gets max_temp alone. - The Z babystep allowance now resets when a print starts, from the websocket's print-state change (pause and resume is the same print), and no longer depends on the filename, so printing the same file again starts over.
Using what stock purifier.py (2.0.0) offers: - Fans off now stops both fans at once (MODE=0 with EXHAUST_FAN_DELAY_OFF=0 and INNER_FAN_DELAY_OFF=0). Plain MODE=0 only starts stock's delayed turn-off (180 s by default). - Two sliders set the exhaust and inner fan directly (SET_PURIFIER FAN=... SPEED=0..1, clamped as a percentage). The server refuses it unless an enclosure is attached, no print is running (the mode usually comes from the sliced file) and stock is not running a mode (it drives the fans itself, and in cool mode dynamically, so a direct speed would compete). The sliders are disabled with the reason. Fans off stays available in a mode: it is the way out. - The active mode and its target temperature are read back and shown. The modes themselves (cool, preheat, hot chamber) are not driven from here: they take extra parameters and stock sets them during a print. None of this could be run against an enclosure; it is covered by unit tests and mock mode.
|
Pushed two more commits. The first one targets your 3 points. The nozzle target is now only capped at the top, so lower targets pass through like 0 does. The material ceiling matches on the base name, so The second and last one instead features the following:
I left the modes themselves (cool, preheat, hot chamber) out, since they take extra parameters and stock sets them during a print. I couldn't run any of the enclosure part without an enclosure, so it's covered by unit tests and mock mode only. I'd appreciate your test on yours. I also updated the PR description to reflect the new entries. Mock-mode screenshots of the enclosure sliders are below. I couldn't take screenshots from the real frontend because I have no enclosure. |
decay71
left a comment
There was a problem hiding this comment.
Thanks, the three fixes are in and the enclosure commands match stock purifier.py. Three notes:
- Light off during a print: stock turns cavity_led on itself at DEFECT_DETECTION_START because the AI camera check needs it. Switching it off from the tab during a print with defect detection enabled leaves the camera dark, so the detection can misfire or go blind. Please refuse (or at least warn) light-off while printing or paused with defect detection on.
- One more on the material table from my last review: the ceilings are stricter than stock. PLA stops at 230 here, while the Snapmaker filament database gives PLA a load temperature of 250 and stock loads and unloads at that, so heating up to clear a clog is not possible from the tab. Please cap at max_temp only, or use more generous ceilings.
- Restart Printer fails on my U1: /machine/reboot answers "500 Error running shell command: 'sudo systemctl reboot'". Did you try the button on your printer?
The per-material ceilings were stricter than stock: PLA stopped at 230 while stock loads and unloads PLA at 250, so heating up to clear a clog was not possible from the tab. The heater limits now come from Klipper's own config (min_temp and max_temp) alone. A target below a printing range is still not raised, and 0 is still off. This removes the material table and the base-name matching added in the previous round, which only existed to feed it.
…runs Stock turns cavity_led on itself at DEFECT_DETECTION_START because the AI camera check needs it. Switching it off from the tab during a print with defect detection enabled would leave the camera dark, so the check could misfire or go blind. The server now refuses dimming or switching off the light (409) while a print is running or paused and defect detection is enabled (its master switch plus at least one of its checks). Keeping it fully on is still allowed, and outside a print, without detection, or without the object, nothing changes. The state reports a "detection" flag, and the switch is disabled with the reason.
Moonraker's JSON error carries a full Python traceback (about 2 KB). A failed Restart Printer put all of it into the panel. The restart and print-control endpoints now pass on only error.message, and fall back to the raw body when the response is not Moonraker's JSON. Found by pressing Restart Printer on a real printer, where it fails with "Error running shell command: 'sudo systemctl reboot'".
Moonraker's /machine/reboot runs `sudo systemctl reboot`. The Snapmaker U1 has neither sudo nor systemd (busybox init), so Restart Printer always failed there with "Error running shell command: 'sudo systemctl reboot'". Moonraker stays the first choice, so printers where it works are unchanged. Only if it fails does the backend run reboot itself, and only when it runs as root on the same machine as Moonraker, so a backend on a dev box talking to a remote printer (or on Windows) can never reboot the wrong machine. The print, managed-install and mock-mode guards still come first, the firmware restart never takes this path, and reboot is looked up in /usr/sbin as well, because the service's own PATH (.local/bin:/usr/bin:/bin) does not include it. Verified on a real Snapmaker U1 (idle, root, no sudo/systemd): the fallback ran reboot, the printer went down and came back with Klipper ready and multiACE intact (uptime reset from 20742 s to 48 s). The first attempt on the printer did not reboot, which is how the PATH problem above was found. The fallback is also unit-tested, including that case.
|
Pushed four more commits for your three notes. Light during a print ( Ceilings ( Restart Printer ( It now tries Moonraker first, so printers where that works are unchanged. Only if it fails, and only when the backend runs as root on the same machine as Moonraker, it runs I ran the new code on my U1 from a scratch copy, without touching the installed service. The printer went down and came back with Klipper ready and multiACE intact (uptime reset from 20742 s to 48 s). I haven't been able to try it on yours, so it relies on your web service running as root, which is the default.
|
decay71
left a comment
There was a problem hiding this comment.
Merged, thanks for the careful work and for testing it on your U1. I will run the enclosure controls and the remaining commands on my printer and report back here if anything turns up.







Adds Control, the first tab of the Print Hub group from #184. Print Hub was hidden until a feature gave it a child, so this is the PR that makes it appear.
Nothing here is new functionality. The camera, the console and the print controls already exist in the printer's Fluidd/Moonraker interface. Control only routes them into the multiACE page, so everything is handy from one place. It replaces nothing in Fluidd.
What it adds
GET /api/webcam/info,/stream(MJPEG) and/snapshot, plusPOST /api/webcam/webrtc-signalfor WebRTC cameras such as camera-streamer. The camera is looked up through Moonraker, and our own Fluidd iframe entry is skipped. The browser never sees the camera's address: media goes browser-to-camera, and only the SDP handshake goes through us, because camera-streamer sends no CORS headers. The lookup retries on its own while no camera is found, and a connection that comes up without delivering video fails visibly and retries.led cavity_led, shown only when the printer has that LED. While a print is running or paused with defect detection enabled, the light stays on: stock turns it on itself because the AI camera check needs it, so the server refuses dimming or switching it off then, and the switch is disabled with the reason.power_detected) the tab also shows the active mode and its target temperature, two sliders for the exhaust and inner fan, and "Fans off". See below.since_id, using the Moonraker websocket the backend already listens to, so there is no extra polling. History is loaded once from Moonraker's gcode store.POST /api/restartwith one of two fixed behaviours. See below.Safety.
POST /api/print-controltakes a fixed verb and a number, never a G-code string. Values are clamped on the server:max_temp(read from its config) and nothing tighter: stock loads and unloads above a material's printing range, so a per-material cap would stop someone heating up to clear a clog. A target below a printing range is not raised, and 0 means off;_query_state_gated, and a command that times out on its way back is reported as queued, with the matching control kept disabled, so a swap that holds Klipper's queue cannot make a second press send it twice.Unknown verbs return 400 and send nothing. The nozzle follows the active extruder. When Moonraker rejects a command, the panel shows its error message, not the traceback that comes with it.
Restarts are refused while a print is running or paused (read fresh from Moonraker) and under
MULTIACE_MANAGED, where the buttons are hidden. A downed Klipper (503) does not block a firmware restart, which is when it is needed; an unreachable Moonraker does.firmware_restartis what the config save already sends.Restart Printer asks Moonraker (
/machine/reboot) first. That runssudo systemctl reboot, and the Snapmaker U1 has neithersudonorsystemd(busybox init), so it always fails there. If it fails, and only when the backend runs as root on the same machine as Moonraker, the backend runsrebootitself. A backend on a dev box talking to a remote printer, one that is not root, or one on Windows never takes that path, and the firmware restart never does.rebootis looked up in/usr/sbinas well, because the service's ownPATHdoes not include it.Enclosure controls use the stock commands:
SET_PURIFIER FAN=exhaust|inner SPEED=0..1andSET_PURIFIER_MODE MODE=0 EXHAUST_FAN_DELAY_OFF=0 INNER_FAN_DELAY_OFF=0for "Fans off". The server refuses the fan speeds unless an enclosure is attached, no print is running and no mode is running, because stock drives the fans itself in modes 1–3 and sets the mode from the sliced file during a print. "Fans off" stays available in a mode as the way out. The modes themselves (cool, preheat, hot chamber) are not driven from here.Navigation. One
navItemsentry withgroup: "printhub",controladded to_validTabs, andui.tabs.controlplusui.control.*andui.restart.*in en, de and zh. The top bar and.subnavrules are untouched. Panel mode is unaffected.Not included. A console input box, the enclosure modes, and Retry controls. Polling and the camera connection only run while the Control tab is showing.
Mock mode. Fixtures:
mock_webcam.json(shows "camera unavailable"),mock_console.json,mock_print_control.json. The stream, snapshot and signalling endpoints and the restarts refuse or skip Moonraker in mock mode, and?mock=still only works while mock mode is on.Testing
pytest tests/unit: 242 passed, 226 of them new (camera lookup, stream proxy, WebRTC relay, console buffer, every clamp and guard, restarts including the local reboot fallback, enclosure, light and defect detection).Screenshots. See the comments.