diff --git a/README.md b/README.md index 373e61e..3495c7e 100644 --- a/README.md +++ b/README.md @@ -102,10 +102,13 @@ installs the same way — stick in, power on. A few things are deliberately built in so a bad flash cannot cost you the machine: -* **A crashing UI cannot lock you out.** If HelixScreen cannot start or - crashes repeatedly, the printer latches SAFE-MODE and boots *headless* — - no UI at all — instead of looping. Delete `/usr/data/anvil/SAFE-MODE` over - ssh to try again. +* **A crashing UI cannot lock you out.** The screen is not how you reach the + printer: ssh and Mainsail come up from their own services, before and + independently of the UI, so a dead screen costs you the screen and nothing + else — you can still print. It is not repairable on the machine, though: + HelixScreen is the only UI there is, and getting a working screen back means + flashing a package again. `MOD_UI=0` in `/usr/data/anvil/anvil.conf` stops + it being started at all. * **ssh stays up** even when the screen does not, so you can get in and look. * **Your `printer.cfg` is never overwritten** — it is not a file this firmware ships, and it is where your changes belong. The configs the mod diff --git a/bin/common.sh b/bin/common.sh index f336646..408995d 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -27,10 +27,10 @@ fi # above -- still overrides any of them. # # FlashForge's firmwareExe is REPLACED, not kept: HelixScreen is the only UI. -# If it crash-loops, S80ui latches SAFE-MODE after 3 failed boots and the -# printer boots headless instead of looping. ssh and Mainsail are your -# recovery path if the screen is dark, and a USB stick with the STOCK -# FlashForge package on it is the uninstall (proven by `make test-recovery`). +# If it will not start, set MOD_UI=0 in /usr/data/anvil/anvil.conf to boot +# headless. ssh and Mainsail are your recovery path if the screen is dark, and +# a USB stick with the STOCK FlashForge package on it is the uninstall (proven +# by `make test-recovery`). BUILD_KLIPPER="${BUILD_KLIPPER:-fork}" BUILD_TOOLCHANGE="${BUILD_TOOLCHANGE:-1}" BUILD_MAINSAIL="${BUILD_MAINSAIL:-1}" @@ -38,6 +38,7 @@ BUILD_MOONRAKER="${BUILD_MOONRAKER:-1}" BUILD_HELIX="${BUILD_HELIX:-1}" MOD_SSH="${MOD_SSH:-1}" MOD_WEB="${MOD_WEB:-1}" +MOD_UI="${MOD_UI:-1}" # Third-party payload pieces (Mainsail, HelixScreen, Moonraker). They are # downloaded on demand rather than vendored, so the repo carries no binaries diff --git a/bin/patch.sh b/bin/patch.sh index bdd43f5..c7735aa 100755 --- a/bin/patch.sh +++ b/bin/patch.sh @@ -288,6 +288,7 @@ cp -f payload/init.d/S* "$MP/init.d/" chmod +x "$MP/init.d"/S* sed -e "s/^MOD_WEB=.*/MOD_WEB=${MOD_WEB:-1}/" \ -e "s/^MOD_CAM=.*/MOD_CAM=${MOD_CAM:-1}/" \ + -e "s/^MOD_UI=.*/MOD_UI=${MOD_UI:-1}/" \ -e "s/^MOD_SSH=.*/MOD_SSH=${MOD_SSH:-1}/" \ -e "s/^MOD_WIFI=.*/MOD_WIFI=${MOD_WIFI:-1}/" \ payload/anvil.conf > "$MP/anvil.conf" diff --git a/docs/hardware-testing.md b/docs/hardware-testing.md index 6283030..9a58f5b 100644 --- a/docs/hardware-testing.md +++ b/docs/hardware-testing.md @@ -102,9 +102,10 @@ screen and there is nothing to fall back to: `firmwareExe` — not any init script — that ran `/usr/prog/klipper/start.sh`. Without this the printer would boot to a working screen and be unable to move. -- If the UI dies repeatedly, `SAFE-MODE` latches after 3 boots and the printer - boots **headless** — no UI started at all — rather than crash-looping. ssh - and Mainsail stay up. Delete `/usr/data/anvil/SAFE-MODE` to try again. +- If the UI misbehaves, set `MOD_UI=0` in `/usr/data/anvil/anvil.conf` and + reboot: the printer comes up **headless** — no UI started at all. ssh and + Mainsail are unaffected either way. There is no automatic latch; nothing + stops the UI unless you do. **Go/no-go, in this order — with the emergency stop within reach:** @@ -223,14 +224,22 @@ every flash. Each of those files opens with a header saying as much. `moonraker.conf` is the one exception — it has no include-and-override seam, so an edited copy is kept and the new version lands beside it as `.mod-new`. -**Recovering from a bad UI, in increasing severity:** +**A bad UI is not repairable on the printer.** There is no fallback interface: +FlashForge's binary is replaced and HelixScreen is the only thing that draws on +the screen. ssh tells you *why* it is dark, but nothing you can set on the +machine will make it light up again: + ```sh ssh root@PRINTER /usr/data/anvil/init.d/S80ui status # what did it choose, and why -touch /usr/data/anvil/SAFE-MODE # boot headless next time (ssh + Mainsail only) -reboot +# chose "none" -> MOD_UI=0, or HelixScreen is not installed +# chose "helix" -> it was started and failed on its own; see its log ``` -If ssh is gone too, flash the stock FlashForge package for your model. + +`MOD_UI=0` only stops it being started at all — useful to silence a UI that +interferes with something else, not a repair. The actual fix is to flash a +package again: the mod, if you have a corrected build, or the stock FlashForge +package for your model to go back to FlashForge's own interface. --- @@ -238,7 +247,7 @@ If ssh is gone too, flash the stock FlashForge package for your model. | Symptom | Do this | |---|---| -| Printer boots, screen blank | ssh in; `/usr/data/anvil/init.d/S80ui status`; `touch /usr/data/anvil/SAFE-MODE`; reboot | +| Printer boots, screen blank | ssh in; `/usr/data/anvil/init.d/S80ui status` says whether the UI was even started. No on-device repair — reflash the mod, or flash the stock package to get FlashForge's UI back. ssh, Mainsail and printing are unaffected | | No ssh, no screen | flash the stock package for your model | | Recovery stick does not help | try a newer stock FlashForge package for your model | | Still broken | factory package (`Creator5Pro-factory-*.tgz` **plus** the separate `Creator5Pro-factory.tar.xz` on the same stick; needs 800 MB free) | diff --git a/docs/how-it-works.md b/docs/how-it-works.md index c23b433..7f09556 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -43,7 +43,7 @@ S50wifi wlan0 + wpa_supplicant + udhcpc S60web nginx (Mainsail) + moonraker S65camera mjpg-streamer on :8080 (nginx proxies it at /webcam/) S70klipper Klipper -S80ui decides whether the UI runs; owns SAFE-MODE +S80ui decides whether the UI runs (MOD_UI, HelixScreen installed?) ``` Everything the mod installs lives under `/usr/data/anvil` on the **data** diff --git a/docs/testing.md b/docs/testing.md index 4127db5..03376cf 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -234,9 +234,9 @@ Tests that cannot fail are worse than no tests, because they read as coverage: to it. Its allowlist had drifted to nine entries that excused nothing. The absolute-path half survives, because `S50wifi`'s binaries sit on a branch no simulation reaches. - - `lint-danger`'s structural greps were satisfiable by comments — - `firmwareExe:44` and `S80ui:16` *mention* `helix` and `SAFE-MODE`, so the - checks passed with the logic deleted. + - `lint-danger`'s structural greps were satisfiable by comments — the + comments in `firmwareExe` and `S80ui` *mention* `helix` and (at the time) + `SAFE-MODE`, so the checks passed with the logic deleted. - `case-ui` went with the helix-only decision. ## Bugs these caught diff --git a/payload/anvil.conf b/payload/anvil.conf index d24f0fb..32bee03 100644 --- a/payload/anvil.conf +++ b/payload/anvil.conf @@ -14,6 +14,10 @@ MOD_WEB=1 #MOD_CAM_PORT=8080 MOD_CAM=1 +# Touchscreen UI: HelixScreen, started by the firmwareExe wrapper. +# 0 = boot headless (blank screen); ssh and Mainsail are unaffected. +MOD_UI=1 + # SSH: dropbear on :22 MOD_SSH=1 diff --git a/payload/firmwareExe b/payload/firmwareExe index 31c8332..297481c 100755 --- a/payload/firmwareExe +++ b/payload/firmwareExe @@ -40,8 +40,8 @@ if [ -d $MODDIR/init.d ]; then fi # ---- the UI, in the foreground so the stock watchdog can see us ---------- -# S80ui already handled SAFE-MODE and crash counting and wrote its verdict -# here: "helix" to start the UI, "none" to stay headless. +# S80ui already decided and wrote its verdict here: "helix" to start the UI, +# "none" to stay headless (MOD_UI=0, or HelixScreen not installed). UI_CHOICE=`cat $MODDIR/.ui-choice 2>/dev/null || echo none` echo "ui: choice is $UI_CHOICE" diff --git a/payload/init.d/S80ui b/payload/init.d/S80ui index 2d4ed44..d81835b 100755 --- a/payload/init.d/S80ui +++ b/payload/init.d/S80ui @@ -8,19 +8,14 @@ # foreground, so it only chooses. # # HelixScreen is the only UI: FlashForge's firmwareExe is retired and there is -# nothing to fall back to. So the safety rules encoded here are what stands -# between a crash-looping UI and losing the machine: -# * ssh comes from the stock /etc/init.d/S50dropbear, which busybox init -# already ran long before this script. It is up regardless of what -# happens here, and so are S60web (Mainsail) and S70klipper. -# * A UI that crash-loops latches SAFE-MODE after MAX_UI_FAILURES boots and -# the printer then boots HEADLESS -- no UI at all -- rather than looping. -# * Delete /usr/data/anvil/SAFE-MODE (over ssh) to try the UI again. +# nothing to fall back to. There is no crash counter and no automatic headless +# latch -- the UI is started whenever it is installed and MOD_UI is not 0. +# To stop it, set MOD_UI=0 in /usr/data/anvil/anvil.conf and reboot; ssh comes +# from the stock /etc/init.d/S50dropbear, which busybox init ran long before +# this script, so it is up regardless of what happens here, and so are S60web +# (Mainsail) and S70klipper. MODDIR=/usr/data/anvil [ -f $MODDIR/anvil.conf ] && . $MODDIR/anvil.conf -MAX_UI_FAILURES="${MOD_MAX_UI_FAILURES:-3}" -FAILFILE=$MODDIR/ui-failures -SAFEFILE=$MODDIR/SAFE-MODE CHOICE=$MODDIR/.ui-choice have_helix() { [ -x $MODDIR/helixscreen/bin/helix-launcher.sh ] || [ -x $MODDIR/helixscreen/bin/helix-screen ]; } @@ -28,20 +23,8 @@ have_helix() { [ -x $MODDIR/helixscreen/bin/helix-launcher.sh ] || [ -x $MODDIR/ start() { want=helix - if [ -f "$SAFEFILE" ]; then - echo "ui: SAFE-MODE latched -- staying headless (delete $SAFEFILE to retry)" - want=none - fi - - failures=`cat "$FAILFILE" 2>/dev/null || echo 0` - case "$failures" in ''|*[!0-9]*) failures=0 ;; esac - - # Every boot increments the counter; the UI clears it once it has been - # alive for a while (see the settle check below). A crash-loop therefore - # walks the counter up until SAFE-MODE latches. - if [ "$failures" -ge "$MAX_UI_FAILURES" ]; then - echo "ui: $failures consecutive short boots -- latching SAFE-MODE" - : > "$SAFEFILE" + if [ "${MOD_UI:-1}" = 0 ]; then + echo "ui: MOD_UI=0 in anvil.conf -- staying headless" want=none fi @@ -56,12 +39,6 @@ start() { echo "$want" > "$CHOICE" echo "ui: chose $want" - - # Arm the failure counter, then clear it in the background once the UI has - # survived long enough to be considered healthy. - echo `expr $failures + 1` > "$FAILFILE" - ( sleep 90 - if [ -s "$CHOICE" ]; then rm -f "$FAILFILE"; fi ) & } stop() { killall helix-screen helix-watchdog helix-splash 2>/dev/null @@ -72,7 +49,6 @@ case "$1" in stop) stop ;; restart) stop; sleep 2; start ;; status) echo "ui choice: `cat $CHOICE 2>/dev/null || echo unset`"; - echo "failures: `cat $FAILFILE 2>/dev/null || echo 0`"; - [ -f "$SAFEFILE" ] && echo "SAFE-MODE: latched" || echo "SAFE-MODE: clear" ;; + echo "MOD_UI: ${MOD_UI:-1}" ;; *) echo "usage: $0 {start|stop|restart|status}"; exit 1 ;; esac diff --git a/payload/klipper/config/ff-runout.cfg b/payload/klipper/config/ff-runout.cfg index ac68a95..1648456 100644 --- a/payload/klipper/config/ff-runout.cfg +++ b/payload/klipper/config/ff-runout.cfg @@ -155,7 +155,8 @@ gcode: # Called by the sensors' insert_gcode (Klipper only fires it while idle, so # this never interrupts a moving print). The app logged "insert" and, for # the motion sensors, cleared its clog flag; we just tell the user what to -# do next. +# do next. A switch insert means filament is present again; a motion insert +# means it is MOVING again, which is what clearing a clog looks like. # --------------------------------------------------------------------------- [gcode_macro _FF_INSERT] gcode: @@ -167,4 +168,8 @@ gcode: {% elif not printer.pause_resume.is_paused %} { action_respond_info("Filament detected in T%d: LOAD_FILAMENT TOOL=%d to feed it." % (tool, tool)) } {% endif %} + {% else %} + {% if printer.pause_resume.is_paused and tool == printer.ff_toolchange.current_tool %} + { action_respond_info("T%d is moving filament again: RESUME to continue (or CANCEL_PRINT)." % tool) } + {% endif %} {% endif %} diff --git a/payload/klipper/extras/ff_tool_offset.py b/payload/klipper/extras/ff_tool_offset.py index a60d777..831d58d 100644 --- a/payload/klipper/extras/ff_tool_offset.py +++ b/payload/klipper/extras/ff_tool_offset.py @@ -545,12 +545,17 @@ class FFToolOffset: self.last['t%d' % tool] = (cx, cy, zp) gcmd.respond_info("T%d: offset = (%.4f, %.4f, %.4f)" % (tool, cx, cy, zp)) - if save: - self.tools[tool].set_nozzle(cx, cy, zp) if release: self._run('TOOLCHANGE_PARK') self._wait_moves() + # Staged only once every tool has passed its guards, so that a failure + # part-way through TOOL=ALL really does leave nothing saved -- which is + # what the guard messages above promise. + if save: + for tool, (cx, cy, zp) in results.items(): + self.tools[tool].set_nozzle(cx, cy, zp) + # the app's exit block: heater off for the tool(s), Z15 for tool in tools: self._run('M104 S0 T%d' % tool)