mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-19 03:49:50 -05:00
anvil-core owns firmwareExe and start.sh; kill the 3.8.2 myth
Three things, all downstream of klippy having moved to $MODDIR and our own
CPython 3.13.
SECTIONS 8 AND 9 ARE GONE. Both files install at $MODDIR/prog/ and
anvil-link-prog.sh points the stock paths at them -- from run-append.sh on a
flash and from anvil-core's postinst on `opkg upgrade anvil-core`. Nothing
execs either between run.sh distributing the component and link-prog running,
so the component copy bought no ordering. It cost, though: our firmwareExe
with no payload behind it logs "no anvil-env.sh", finds no s6-svscan and then
`while true; do sleep 3600; done` -- a printer that boots to a blank screen for
ever and never reaches app_startup.sh's own recovery. Absent, that recovery
restores a firmwareExe from a version directory. So patch.sh now REMOVES both
from the component: the stock component carries them, and deleting the two cp
lines alone would have shipped FlashForge's originals.
SECTION 5c IS GONE. It did no work -- two du prints and a comment whose first
line was the falsehood below.
THE FALSEHOOD. "klippy runs on the stock 3.8.2, started by
/usr/prog/klipper/start.sh" had spread well past patch.sh, and it had reached
things people follow:
- docs/toolchange.md told owners to scp the ff_*.py extras to
/usr/prog/klipper/klippy/extras/ -- the wrong partition -- and warned that
an OTA would delete them there. Same wrong path in three ff-*.cfg headers
and in ff_toolchange.py.
- versions.env labelled our CPython 3.13 and its eighteen packages
"SHIPPED BUT NOT USED".
- docs/how-it-works.md said Moonraker runs on python3.8, contradicting line
126 of the same file.
All corrected. Historical notes keep their history and get a [Superseded: ...]
marker instead -- including 80-s6-migration.md's "s6-rc-compile is not
shipped", which pkgs/3rdparty/s6-rc/build.sh contradicts deliberately.
Found while reviewing: every docs/ reference to the ff-*.cfg pointed at
pkgs/anvil-core/payload/config/, which does not exist -- they are
anvil-klipper-config's. 28 references across 9 files, several of them broken
markdown links on the published site. All 24 relative links under docs/ now
resolve.
Gates: verify.sh syntax-checks prog/start.sh and prog/firmwareExe out of the
payload (firmwareExe was never syntax-checked before) and fails if the
component carries either. A new replica test asserts both stock paths are
symlinks into $MODDIR/prog and do not dangle. One existing replica test stubbed
firmwareExe by redirecting into $FE, which now writes THROUGH the symlink into
the packaged wrapper; it moves the link aside instead.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01La5MaDKqTW675k5fsXsP6S
This commit is contained in:
1 parent
b25a19683d
commit
214a4e58b9
28 files changed
+175
-124
No files matched your search
+11
-29
@@ -176,18 +176,6 @@ case "$S6RC_SHEBANG" in
|
||||
esac
|
||||
say "s6-rc: database $S6RC_DB_NAME compiled -- $(ls "$S6RC_SRC" | wc -l) definitions"
|
||||
|
||||
# --- 5c. CPython 3.13 (shipped)
|
||||
# FF_PYTHON points here -- but not for klippy, which runs on the stock 3.8.2.
|
||||
# That 3.8.2 has no _sqlite3, which is what pins MOONRAKER_VERSION to 2023.
|
||||
# The dev half is never installed, so verify.sh's "no headers" check passes.
|
||||
|
||||
# Each python-* recipe asserts its own extension: an aggregate count cannot say
|
||||
# WHICH one fell back to a pure-python wheel. A gate in build.sh does not run
|
||||
# on a cache hit.
|
||||
du -sh "$PAYLOAD_DIR/lib/python$PY_MM" | awk '{print " "$1"\tlib/python'"$PY_MM"'/"}'
|
||||
du -sh "$PAYLOAD_DIR/lib/python$PY_MM/site-packages" \
|
||||
| awk '{print " "$1"\tlib/python'"$PY_MM"'/site-packages/"}'
|
||||
|
||||
# --- 6. SSH
|
||||
# Nothing to install: the stock rootfs ships dropbear and an enabled
|
||||
# S50dropbear, so ssh is already listening. Only the password is missing.
|
||||
@@ -209,23 +197,17 @@ else
|
||||
skip "root password (set ROOT_PW_HASH)"
|
||||
fi
|
||||
|
||||
# --- 8. start.sh (web stack on)
|
||||
# From the installed payload, but still carried in the component: that is what
|
||||
# FlashForge's run.sh copies to /usr/prog, and what keeps a printer bootable
|
||||
# when the payload is not there. anvil-link-prog.sh then swaps it for a link.
|
||||
say "start.sh: enabling nginx + moonraker"
|
||||
cp -f "$PAYLOAD_DIR/prog/start.sh" "$SOFTWARE_DIR/start.sh"
|
||||
chmod +x "$SOFTWARE_DIR/start.sh"
|
||||
|
||||
# --- 9. firmwareExe -> our wrapper script
|
||||
# The stock chain is rcS -> S99factory_test_shell -> app_startup.sh ->
|
||||
# firmwareExe, which also starts Klipper: this one file owns the whole
|
||||
# userspace boot and leaves the init chain stock. The binary is not kept
|
||||
# aside -- flashing the stock package is the uninstall. From the payload, as
|
||||
# in section 8.
|
||||
say "firmwareExe: installing wrapper (replaces the stock binary)"
|
||||
cp -f "$PAYLOAD_DIR/prog/firmwareExe" "$SOFTWARE_DIR/firmwareExe"
|
||||
chmod +x "$SOFTWARE_DIR/firmwareExe"
|
||||
# --- 8. firmwareExe + start.sh -- anvil-core's, and NOT in the component
|
||||
# Both install at $MODDIR/prog/ and anvil-link-prog.sh points the stock paths
|
||||
# at them, on the flash path (run-append.sh) and on `opkg upgrade anvil-core`
|
||||
# (postinst). Nothing execs either between run.sh copying the component and
|
||||
# link-prog running, so the component copy bought no ordering -- and it cost:
|
||||
# our wrapper with no payload never starts a UI, it logs and sleeps for ever,
|
||||
# turning a failed install into an unbootable printer. Absent, app_startup.sh
|
||||
# restores a firmwareExe from a version directory and the stock recovery works.
|
||||
# Stock run.sh is set -x, not set -e, so its two cp lines are noisy no-ops.
|
||||
say "firmwareExe + start.sh: dropped from the component (anvil-core owns both)"
|
||||
rm -f "$SOFTWARE_DIR/firmwareExe" "$SOFTWARE_DIR/start.sh"
|
||||
|
||||
# --- 10. anvil.conf
|
||||
# anvil.conf is not in anvil-core and is the whole of what is left here:
|
||||
|
||||
+22
-5
@@ -62,7 +62,9 @@ if [ -n "$SOFTWARE_TAR" ]; then
|
||||
fi
|
||||
|
||||
# --- 5. shell syntax of scripts
|
||||
for s in "$WORK/runFirmwareExe.sh" "$WORK/sw/run.sh" "$WORK/sw/start.sh" "$WORK/sw/app_startup.sh"; do
|
||||
# start.sh and firmwareExe are the payload's now, not the component's -- section
|
||||
# 7 extracts and checks them there.
|
||||
for s in "$WORK/runFirmwareExe.sh" "$WORK/sw/run.sh" "$WORK/sw/app_startup.sh"; do
|
||||
[ -f "$s" ] || continue
|
||||
if sh -n "$s" 2>/dev/null; then ok "syntax OK: $(basename "$s")"
|
||||
else bad "SYNTAX ERROR in $(basename "$s")"; sh -n "$s" 2>&1 | head -3 | sed 's/^/ /'; fi
|
||||
@@ -84,14 +86,20 @@ fi
|
||||
# chelper.tar that carried its .so are both gone. Unconditional, because
|
||||
# v20260824 passed here by having every check below conditional on files the
|
||||
# broken build did not contain. ./klipper alone, not the whole payload --
|
||||
# section 8 answers the rest from a listing. Paths carry a leading ./ because
|
||||
# bin/pack.sh tars `.` from inside $PAYLOAD_DIR.
|
||||
# section 8 answers the rest from a listing. ./prog rides along because the two
|
||||
# stock-path scripts moved there and still need a syntax gate. Paths carry a
|
||||
# leading ./ because bin/pack.sh tars `.` from inside $PAYLOAD_DIR.
|
||||
PAYLOAD_TAR=$(ls -1 "$WORK"/anvil.tar.xz 2>/dev/null | head -n1)
|
||||
if [ -n "$PAYLOAD_TAR" ]; then
|
||||
mkdir -p "$WORK/pl"
|
||||
xz -dc "$PAYLOAD_TAR" 2>/dev/null | tar -xf - -C "$WORK/pl" ./klipper 2>/dev/null \
|
||||
|| tar -xf "$PAYLOAD_TAR" -C "$WORK/pl" ./klipper 2>/dev/null
|
||||
xz -dc "$PAYLOAD_TAR" 2>/dev/null | tar -xf - -C "$WORK/pl" ./klipper ./prog 2>/dev/null \
|
||||
|| tar -xf "$PAYLOAD_TAR" -C "$WORK/pl" ./klipper ./prog 2>/dev/null
|
||||
fi
|
||||
for s in "$WORK/pl/prog/start.sh" "$WORK/pl/prog/firmwareExe"; do
|
||||
[ -f "$s" ] || { bad "the payload carries no prog/$(basename "$s") -- link-prog has nothing to point the stock path at"; continue; }
|
||||
if sh -n "$s" 2>/dev/null; then ok "syntax OK: prog/$(basename "$s")"
|
||||
else bad "SYNTAX ERROR in prog/$(basename "$s")"; sh -n "$s" 2>&1 | head -3 | sed 's/^/ /'; fi
|
||||
done
|
||||
if [ -f "$WORK/pl/klipper/klippy/chelper/__init__.py" ]; then
|
||||
ok "payload carries the fork klippy tree"
|
||||
else
|
||||
@@ -125,6 +133,15 @@ fi
|
||||
[ -e "$WORK/sw/klipper/klippy" ] \
|
||||
&& bad "the software component still carries a klippy tree -- bin/patch.sh section 1 is staging one again" \
|
||||
|| ok "no klippy tree in the software component (the payload is the only one)"
|
||||
# Same rule for the two stock-path scripts: anvil-core owns them at
|
||||
# $MODDIR/prog/ and link-prog symlinks them into place. A component copy of the
|
||||
# wrapper is worse than none -- with no payload it never starts a UI, where an
|
||||
# absent one lets app_startup.sh restore the stock binary.
|
||||
for f in firmwareExe start.sh; do
|
||||
[ -e "$WORK/sw/$f" ] \
|
||||
&& bad "the software component still carries $f -- bin/patch.sh section 8 is staging one again" \
|
||||
|| ok "no $f in the software component (anvil-core's \$MODDIR/prog is the only one)"
|
||||
done
|
||||
|
||||
# --- 8. mod payload
|
||||
if [ -f "$WORK/anvil.tar.xz" ]; then
|
||||
|
||||
+3
-4
@@ -233,12 +233,11 @@ Which is to say:
|
||||
```
|
||||
pkgs/anvil-core/ what makes the machine ours, and nothing else
|
||||
payload/ anvil-env.sh PATH/LD_LIBRARY_PATH/FF_PYTHON -- sourced
|
||||
anvil-service.sh the start/stop/status/liveness shape services share
|
||||
init.d/ S40s6, S50wifi, S60nginx, S62moonraker, S65camera,
|
||||
S70klipper, S80ui
|
||||
bin/ ff-startup.py, ff_mcu_bringup.py, ffscreen.py,
|
||||
wifi-action.sh
|
||||
etc/s6/ the s6 scandir, one directory per service
|
||||
etc/s6-rc/source/ one directory per service: wifi, nginx, moonraker,
|
||||
camera, klipper, ui, mcu-bringup, ff-startup,
|
||||
ok-all
|
||||
nginx/nginx.conf
|
||||
bin/anvil-link-prog.sh links the two below, and the configs, into
|
||||
the absolute paths the stock scripts read
|
||||
|
||||
@@ -125,7 +125,7 @@ START_PRINT TOOLS=0:220,2:240 BED=60 LEVEL=1 SOAK=300
|
||||
|
||||
The macros are on the printer at `/usr/data/config/ff-print-macros.cfg`, and
|
||||
in this repo at
|
||||
[`payload/klipper/config/ff-print-macros.cfg`](../payload/klipper/config/ff-print-macros.cfg).
|
||||
[`pkgs/klipper-config/payload/config/ff-print-macros.cfg`](../pkgs/klipper-config/payload/config/ff-print-macros.cfg).
|
||||
They are ordinary Klipper macros with comments explaining why each step is
|
||||
what it is.
|
||||
|
||||
|
||||
@@ -130,9 +130,9 @@ in `anvil-env.sh` names a CPython 3.13 of our own, cross-built for mipsel by
|
||||
recipe and one `.ipk` each — and libsodium in `$MODDIR/lib`; none of it
|
||||
borrowed from `/usr/prog`. Measured through the
|
||||
real boot path (the scandir, the moonraker service, readiness gating on `:7125`
|
||||
actually listening, a `kill -9` respawn, a stop that stays stopped). klippy is not part of
|
||||
this: it stays on FlashForge's 3.8.2, started independently by
|
||||
`/usr/prog/klipper/start.sh`.
|
||||
actually listening, a `kill -9` respawn, a stop that stays stopped). klippy is on it
|
||||
too: the `klipper` service execs `$FF_PYTHON` against the klippy tree in
|
||||
`$MODDIR/klipper`, so both halves are ours.
|
||||
|
||||
**The Moonraker commit pin is still a 2023 one, though the reason it exists
|
||||
has changed.** FlashForge built python 3.8.2 without the `_sqlite3` module,
|
||||
@@ -163,10 +163,10 @@ the old one aside first and restoring it if the copy does not complete.
|
||||
|
||||
`make test` covers this end to end on the printer replica: that the swap
|
||||
happened, that the installed `webcam.py` has the `enabled` field, that the
|
||||
shipped tree runs on the printer's own python3.8 **and is still running after a
|
||||
settle**, and that its log has no import errors. The settle matters — a build
|
||||
that dies on a missing module is alive for a second or two first, and checking
|
||||
only "did it start" is what let the sqlite3 failure through.
|
||||
shipped tree runs on our own CPython 3.13 (`$FF_PYTHON`) **and is still running
|
||||
after a settle**, and that its log has no import errors. The settle matters — a
|
||||
build that dies on a missing module is alive for a second or two first, and
|
||||
checking only "did it start" is what let the sqlite3 failure through.
|
||||
`BUILD_MOONRAKER=0` builds a package that leaves the stock server alone.
|
||||
|
||||
## Five things the stock firmware does that will surprise you
|
||||
|
||||
@@ -9,9 +9,9 @@ print is driven from Mainsail unless `ff_*` re-provides it.
|
||||
|---|---|---|---|
|
||||
| Heaters | `M104 S.. T0..T3`, `M140`, `SET_HEATER_TEMPERATURE HEATER=chamber_heater`; **never M109/M190** — waiting is app-side polling; staged heating e1+e2 then e3+e4; 5-min bed soak; chamber heater coupled to `chamber_heat_fan` 0.9 / `chamber_loop_fan` 0.3 in the app only | `[verify_heater]` (fork hardcodes gain times, caps concurrent extruder heating at 2, loosens PID settle so M109 returns early); `min_extrude_temp: -200` on all extruders = cold-extrude guard effectively off. The chamber is **ported**: `ff-chamber.cfg` wraps `SET_HEATER_TEMPERATURE` and adds `M141`/`M191`, gated on the model that actually has an element | `CommMgr::heatManager` @0x76… (CommMgr.c:2576-2700) |
|
||||
| Material temps | from `filament.json` via `getFilamentOperationTemp` | none | CommMgr.c, LoadFilamentPrint.c |
|
||||
| Runout / clog | app state machine on `filament_switch_sensor fd_ex*` `filament_detected` + `"wheel runout:Tn"` text; arms/disarms with `SET_FILAMENT_SENSOR … ENABLE=0/1`; endless-spool auto-swap (E0162/E0163) | all eight sensors `pause_on_runout: False`; motion sensors' `runout_gcode` only `action_respond_info` — **ported** (mounted-tool arming in `ff_toolchange.py`, pause via `pkgs/anvil-core/payload/config/ff-runout.cfg`; no endless spool), see `49-runout-recovered.md` | `printer.filament.cfg`, `dealFilamentWheelStatus`, `checkAndAutoFeed` |
|
||||
| Runout / clog | app state machine on `filament_switch_sensor fd_ex*` `filament_detected` + `"wheel runout:Tn"` text; arms/disarms with `SET_FILAMENT_SENSOR … ENABLE=0/1`; endless-spool auto-swap (E0162/E0163) | all eight sensors `pause_on_runout: False`; motion sensors' `runout_gcode` only `action_respond_info` — **ported** (mounted-tool arming in `ff_toolchange.py`, pause via `pkgs/klipper-config/payload/config/ff-runout.cfg`; no endless spool), see `49-runout-recovered.md` | `printer.filament.cfg`, `dealFilamentWheelStatus`, `checkAndAutoFeed` |
|
||||
| Doors | `openDoorPause` toggle in general.json | `[gcode_button topDoor/frontDoor]` with **empty** `press_gcode` | `printer.base.cfg:278-287` |
|
||||
| Load / unload / purge | app sequences (`FilamentLoad::doLoad/doUnload`, `clearNozzlePrint`) — grab tool, purge chute X275 Y254, material+30 °C, `G1 E150`+`E145`; "unload" is the same push | nothing (extruders are direct drive; `gear_stepper` is the tool lock) — **ported** to `pkgs/anvil-core/payload/config/ff-filament.cfg`, see `48-filament-load-port.md` | `47-filament-load-recovered.md` |
|
||||
| Load / unload / purge | app sequences (`FilamentLoad::doLoad/doUnload`, `clearNozzlePrint`) — grab tool, purge chute X275 Y254, material+30 °C, `G1 E150`+`E145`; "unload" is the same push | nothing (extruders are direct drive; `gear_stepper` is the tool lock) — **ported** to `pkgs/klipper-config/payload/config/ff-filament.cfg`, see `48-filament-load-port.md` | `47-filament-load-recovered.md` |
|
||||
| Print lifecycle | prepare (14 steps), preamble, pause (**all hotends off**), resume (staged reheat + re-grab), exit block | no START/END/PAUSE/RESUME macros in stock config | 50-print-lifecycle.md |
|
||||
| Z frame | absolute print-start Z offset (~+3.2 mm) computed in `BuildPage::startPrint`; per-tool XY/Z diffs applied on every grab | nothing; eddy `G28 Z` is **not** nozzle zero | 40-offsets.md |
|
||||
| Mesh / leveling | app triggers `BED_MESH_CALIBRATE` / `BED_MESH_PROFILE LOAD=…` | executes | 50-print-lifecycle.md |
|
||||
@@ -117,5 +117,5 @@ Two details that bite: `libjpeg.so.9` lives in that directory, so without the LD
|
||||
binary dies on a missing shared object (the same failure moonrakerDaemon had, which is why
|
||||
`anvil-env.sh` is the one place the library path is written down); and the
|
||||
plugin `.so` names resolve against the working directory, hence the `cd`. nginx proxies it at
|
||||
`/webcam/` and `pkgs/anvil-core/payload/config/moonraker.conf` carries the matching `[webcam]` entry with relative URLs,
|
||||
`/webcam/` and `pkgs/moonraker/payload/config/moonraker.conf` carries the matching `[webcam]` entry with relative URLs,
|
||||
so one forwarded port is enough.
|
||||
@@ -16,8 +16,8 @@ per-tool sections, modelled on `[bed_mesh <profile>]` / klipper-toolchanger's `[
|
||||
| `pkgs/klipper/payload/klipper/klippy/extras/ff_tool_offset.py` | `TOOL_CALIBRATE_TOOL_OFFSET`, `TOOL_LOCATE_SENSOR`, `TOOL_OFFSET_STATUS`; `[ff_tool_offset]` holds `station_x/y/z` (autosaved) + probe geometry |
|
||||
| `pkgs/klipper/payload/klipper/klippy/extras/ff_toolchange.py` | takes docks/offsets from the `ff_tool` objects; `refresh_offsets()`; `_station_z()`; JSON reader and `TOOLCHANGE_RELOAD` removed |
|
||||
| `pkgs/klipper/payload/klipper/klippy/extras/ff_legacy.py` | `FF_IMPORT_FIRMWARE_CONFIG [DIR=] [APPLY=1]` — one-shot import of extruder/test/zoffset.json |
|
||||
| `pkgs/anvil-core/payload/config/ff-toolchange.cfg` | four deliberately empty `[ff_tool 0..3]` sections + `[ff_toolchange]`. Per-unit values must NOT go here — `SAVE_CONFIG` refuses to autosave an option an include already sets |
|
||||
| `pkgs/anvil-core/payload/config/ff-tool-offset.cfg`, `pkgs/anvil-core/payload/config/ff-legacy.cfg` | sections for the other two extras |
|
||||
| `pkgs/klipper-config/payload/config/ff-toolchange.cfg` | four deliberately empty `[ff_tool 0..3]` sections + `[ff_toolchange]`. Per-unit values must NOT go here — `SAVE_CONFIG` refuses to autosave an option an include already sets |
|
||||
| `pkgs/klipper-config/payload/config/ff-tool-offset.cfg`, `pkgs/klipper-config/payload/config/ff-legacy.cfg` | sections for the other two extras |
|
||||
|
||||
## Storage layout
|
||||
|
||||
@@ -57,7 +57,7 @@ A built package needs none of this — it installs the extras and
|
||||
hand, onto a printer that is already modded:
|
||||
|
||||
```
|
||||
cp pkgs/klipper/payload/klipper/klippy/extras/ff_*.py /usr/prog/klipper/klippy/extras/
|
||||
cp pkgs/klipper/payload/klipper/klippy/extras/ff_*.py /usr/data/anvil/klipper/klippy/extras/
|
||||
# Nothing to add to printer.cfg: printer.base.cfg already includes the
|
||||
# ff-*.cfg set. Section order does not matter.
|
||||
RESTART
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Source of truth: [`47-filament-load-recovered.md`](47-filament-load-recovered.md), recovered 2026-08-21
|
||||
from MIPS code (Ghidra bodies were 36-byte stubs).
|
||||
Port: `pkgs/anvil-core/payload/config/ff-filament.cfg` — `LOAD_FILAMENT`, `UNLOAD_FILAMENT`, `PURGE`, tunables in
|
||||
Port: `pkgs/klipper-config/payload/config/ff-filament.cfg` — `LOAD_FILAMENT`, `UNLOAD_FILAMENT`, `PURGE`, tunables in
|
||||
`[gcode_macro _FF_FILAMENT]`.
|
||||
|
||||
## What the app really does
|
||||
|
||||
@@ -87,7 +87,7 @@ error):
|
||||
* `FF_RUNOUT_ARM [TOOL=]` / `FF_RUNOUT_DISARM`; status `runout_armed` (tool or −1),
|
||||
`runout_sensors` (object names); `TOOLCHANGE_STATUS` prints the same.
|
||||
|
||||
`pkgs/anvil-core/payload/config/ff-runout.cfg` (include after `printer.base.cfg`; Klipper merges repeated sections,
|
||||
`pkgs/klipper-config/payload/config/ff-runout.cfg` (include after `printer.base.cfg`; Klipper merges repeated sections,
|
||||
later options win — the stock `printer.filament.cfg` is left untouched):
|
||||
|
||||
* the eight sections get `runout_gcode: _FF_RUNOUT TOOL=n KIND=switch|motion` and
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Print lifecycle, address-verified
|
||||
|
||||
Everything below lives in the APP, not in Klipper: the fork ships only bare
|
||||
`[pause_resume]` and `[virtual_sdcard]`. `pkgs/anvil-core/payload/config/ff-print-macros.cfg` reproduces the
|
||||
`[pause_resume]` and `[virtual_sdcard]`. `pkgs/klipper-config/payload/config/ff-print-macros.cfg` reproduces the
|
||||
sequences for Mainsail-started prints.
|
||||
|
||||
**How they are entered now.** `START_PRINT` is no longer something the slicer has to
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Recovered 2026-08-22 from the MIPS code of `BuildPage::clearNozzlePrint` @0x9f46f4,
|
||||
`clearNozzleCheckTemp` @0x9f6ce0, `clearNozzleProbez` @0x9f4020 and the presence check in
|
||||
`prepareForEddy` @0x9f1068 (the Ghidra bodies are 36-byte stubs, cut at the first `ErrorBak`
|
||||
constructor). Port: `_FF_NOZZLE_CLEAN` / `_FF_PREFLIGHT` in `pkgs/anvil-core/payload/config/ff-print-macros.cfg`,
|
||||
constructor). Port: `_FF_NOZZLE_CLEAN` / `_FF_PREFLIGHT` in `pkgs/klipper-config/payload/config/ff-print-macros.cfg`,
|
||||
see [`50b-nozzle-clean-port.md`](50b-nozzle-clean-port.md).
|
||||
|
||||
## Where it sits in the prepare flow (prepareForEddy)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Pre-print nozzle clean and tool gate — Klipper port
|
||||
|
||||
Source of truth: [`50a-nozzle-clean-recovered.md`](50a-nozzle-clean-recovered.md).
|
||||
Port: `pkgs/anvil-core/payload/config/ff-print-macros.cfg` — `_FF_PREFLIGHT`, `_FF_NOZZLE_CLEAN`, and the
|
||||
Port: `pkgs/klipper-config/payload/config/ff-print-macros.cfg` — `_FF_PREFLIGHT`, `_FF_NOZZLE_CLEAN`, and the
|
||||
`TOOLS=` / `TEMPS=` / `CLEAN=` / `SOAK=` parameters of `START_PRINT`; tunables
|
||||
`clean_*` in `[gcode_macro _FF_FILAMENT]` (`pkgs/anvil-core/payload/config/ff-filament.cfg`);
|
||||
`clean_*` in `[gcode_macro _FF_FILAMENT]` (`pkgs/klipper-config/payload/config/ff-filament.cfg`);
|
||||
`docked_tools` in `printer.ff_toolchange` status.
|
||||
|
||||
## START_PRINT now
|
||||
|
||||
@@ -63,6 +63,8 @@ Get them wrong and the phase fails on a printer, not in CI.
|
||||
* We do **not** install Klipper. `bin/patch.sh` stages a *software component*
|
||||
and FlashForge's own stock `run.sh` copies it onto `/usr/prog/klipper`. That
|
||||
is why Klipper sits on the firmware partition -- inherited, not chosen.
|
||||
**Superseded by phase 7:** `anvil-klipper` installs the tree at
|
||||
`$MODDIR/klipper` and the `klipper` service runs it from there.
|
||||
* `pkgs/klipper/prog/start.sh` already **is** `/usr/prog/klipper/start.sh`; the mod
|
||||
replaces it. What we do not own are the FlashForge pieces it calls:
|
||||
`klipperDaemon`, `klipper_pri.sh`, `checkEboard`, `libmcu-bare.bin`.
|
||||
@@ -504,7 +506,9 @@ So s6-rc roughly DOUBLES what supervision costs on the data partition, and it
|
||||
does so to buy a boot order that is three real edges wide (see the section
|
||||
above) and was previously spelled as filenames. That is the honest trade and it should be read next to the
|
||||
free-space unknown phase 6 records: nobody has ever run `df` on a real printer.
|
||||
`s6-rc-compile` (131KB) is host-side only and is not shipped.
|
||||
`s6-rc-compile` (131KB) is host-side only and is not shipped. [Superseded: it
|
||||
ships. `pkgs/3rdparty/s6-rc/build.sh` keeps it deliberately -- it is the only
|
||||
way to fix a printer whose database is wrong without a laptop.]
|
||||
|
||||
**Both new packages must be configured `--prefix=/usr/data/anvil`, for a reason
|
||||
one level worse than the `s6-ftrigrd` trap.** execline's `--shebangdir`
|
||||
|
||||
@@ -373,7 +373,9 @@ The package installs the tree under `$MODDIR/klipper`, where nothing reads it
|
||||
yet: klippy is still started by the stock `/usr/prog/klipper/klipperDaemon`,
|
||||
so `bin/patch.sh` stages the recipe's output into the SOFTWARE component as
|
||||
before. Phase 7 of `docs/notes/80-s6-migration.md` is what makes the installed
|
||||
copy the live one.
|
||||
copy the live one. [Superseded: phase 7 landed. The `klipper` s6-rc service
|
||||
execs `$FF_PYTHON` against `$MODDIR/klipper/klippy`, and the software-component
|
||||
copy is gone.]
|
||||
|
||||
`patch.sh` keeps staging the payload from those same trees, so the shipped
|
||||
tarball does not change. The work is mechanical; the risk is in the two builds
|
||||
|
||||
@@ -476,8 +476,9 @@ again, and should know two things it had learnt. `make_fixture` staged into
|
||||
mounts host-side, so a path under the build container's `/tmp` does not exist
|
||||
as far as the daemon is concerned. And the shape it reproduced -- the
|
||||
software component's `run.sh` ending in `tar -xf
|
||||
$WORK_DIR/klipper/chelper.tar` -- is what `bin/patch.sh` section 4 still
|
||||
depends on. Recover both from git history rather than from a fresh reading of
|
||||
$WORK_DIR/klipper/chelper.tar` -- is what `bin/patch.sh` depended on while the
|
||||
klippy tree still went out on the firmware partition; the tree lives at
|
||||
`$MODDIR/klipper` now and neither that section nor the `chelper.tar` exists. Recover both from git history rather than from a fresh reading of
|
||||
a stock package.
|
||||
|
||||
### What survives in test/
|
||||
|
||||
+22
-21
@@ -19,23 +19,23 @@ documented in the file headers.
|
||||
|
||||
| File | Goes to (on the printer) | What it does |
|
||||
|---|---|---|
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_toolchange.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_toolchange.py) | `/usr/prog/klipper/klippy/extras/` | The toolchanger: `T0..T3`, dock/grab state machine with sensor polling and retries, per-tool G-code offsets (the absolute ~3.2 mm bed-frame Z is applied at every grab), `TOOLCHANGE_SET_PRINT_OFFSET` (the print-start thermal/bed/layer Z terms), `TOOL_Z_ADJUST` (per-tool babystep, live, saved on request), `TOOLCHANGE_STATUS`, `TOOLCHANGE_PARK` |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_toolchange.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_toolchange.py) | `/usr/data/anvil/klipper/klippy/extras/` | The toolchanger: `T0..T3`, dock/grab state machine with sensor polling and retries, per-tool G-code offsets (the absolute ~3.2 mm bed-frame Z is applied at every grab), `TOOLCHANGE_SET_PRINT_OFFSET` (the print-start thermal/bed/layer Z terms), `TOOL_Z_ADJUST` (per-tool babystep, live, saved on request), `TOOLCHANGE_STATUS`, `TOOLCHANGE_PARK` |
|
||||
| [`pkgs/helixscreen/payload/helixscreen/config/printer_database.d/flashforge_creator5.json`](../pkgs/helixscreen/payload/helixscreen/config/printer_database.d/flashforge_creator5.json) | HelixScreen `config/printer_database.d/` | Printer-database entry so HelixScreen auto-detects both Creator 5 models as tool changers (the Pro and the non-Pro differ only by the chamber heater) |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_print.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_print.py) | `/usr/prog/klipper/klippy/extras/` | `[ff_print]` — takes over `SDCARD_PRINT_FILE` and `M23`, reads bed/nozzle/initial tool/first-layer height out of the file itself, and calls `FF_BEFORE_PRINT_START` before the file's first line and `FF_AFTER_PRINT_END` once the job leaves the printing state. Declared in `ff-print-macros.cfg`; holds no policy of its own |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_tool.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_tool.py) | `/usr/prog/klipper/klippy/extras/` | `[ff_tool n]` — one section per tool; `dock_x/dock_y`, `nozzle_x/y/z` and `z_adjust` are all autosaved (import or calibration + `SAVE_CONFIG`) |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_tool_offset.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_tool_offset.py) | `/usr/prog/klipper/klippy/extras/` | `TOOL_CALIBRATE_TOOL_OFFSET` / `TOOL_LOCATE_SENSOR` / `TOOL_OFFSET_STATUS` — the touchscreen's nozzle XY/Z offset calibration, recovered from the binary and reimplemented in Klipper |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_legacy.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_legacy.py) | `/usr/prog/klipper/klippy/extras/` | `FF_IMPORT_FIRMWARE_CONFIG` — one-shot import of the factory/touchscreen JSON into Klipper config. The command and nothing else: no startup behaviour. `bin/ff-startup.py` is what runs it on the first boot |
|
||||
| [`pkgs/anvil-core/payload/config/ff-toolchange.cfg`](../pkgs/anvil-core/payload/config/ff-toolchange.cfg) | `/usr/data/config/` | empty `[ff_tool 0..3]` sections (the per-unit dock/nozzle data is autosaved, nothing unit-specific ships), `[ff_toolchange]` feeds/geometry, the `G28` dock-first wrapper |
|
||||
| [`pkgs/anvil-core/payload/config/ff-tool-offset.cfg`](../pkgs/anvil-core/payload/config/ff-tool-offset.cfg) | `/usr/data/config/` | `[ff_tool_offset]` — probe geometry and guards for the calibration commands |
|
||||
| [`pkgs/anvil-core/payload/config/ff-legacy.cfg`](../pkgs/anvil-core/payload/config/ff-legacy.cfg) | `/usr/data/config/` | `[ff_legacy]` — stays included permanently; declares the section and, optionally, `firmware_config_dir` |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_print.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_print.py) | `/usr/data/anvil/klipper/klippy/extras/` | `[ff_print]` — takes over `SDCARD_PRINT_FILE` and `M23`, reads bed/nozzle/initial tool/first-layer height out of the file itself, and calls `FF_BEFORE_PRINT_START` before the file's first line and `FF_AFTER_PRINT_END` once the job leaves the printing state. Declared in `ff-print-macros.cfg`; holds no policy of its own |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_tool.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_tool.py) | `/usr/data/anvil/klipper/klippy/extras/` | `[ff_tool n]` — one section per tool; `dock_x/dock_y`, `nozzle_x/y/z` and `z_adjust` are all autosaved (import or calibration + `SAVE_CONFIG`) |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_tool_offset.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_tool_offset.py) | `/usr/data/anvil/klipper/klippy/extras/` | `TOOL_CALIBRATE_TOOL_OFFSET` / `TOOL_LOCATE_SENSOR` / `TOOL_OFFSET_STATUS` — the touchscreen's nozzle XY/Z offset calibration, recovered from the binary and reimplemented in Klipper |
|
||||
| [`pkgs/klipper/payload/klipper/klippy/extras/ff_legacy.py`](../pkgs/klipper/payload/klipper/klippy/extras/ff_legacy.py) | `/usr/data/anvil/klipper/klippy/extras/` | `FF_IMPORT_FIRMWARE_CONFIG` — one-shot import of the factory/touchscreen JSON into Klipper config. The command and nothing else: no startup behaviour. `bin/ff-startup.py` is what runs it on the first boot |
|
||||
| [`pkgs/klipper-config/payload/config/ff-toolchange.cfg`](../pkgs/klipper-config/payload/config/ff-toolchange.cfg) | `/usr/data/config/` | empty `[ff_tool 0..3]` sections (the per-unit dock/nozzle data is autosaved, nothing unit-specific ships), `[ff_toolchange]` feeds/geometry, the `G28` dock-first wrapper |
|
||||
| [`pkgs/klipper-config/payload/config/ff-tool-offset.cfg`](../pkgs/klipper-config/payload/config/ff-tool-offset.cfg) | `/usr/data/config/` | `[ff_tool_offset]` — probe geometry and guards for the calibration commands |
|
||||
| [`pkgs/klipper-config/payload/config/ff-legacy.cfg`](../pkgs/klipper-config/payload/config/ff-legacy.cfg) | `/usr/data/config/` | `[ff_legacy]` — stays included permanently; declares the section and, optionally, `firmware_config_dir` |
|
||||
| [`pkgs/anvil-core/payload/bin/ffscreen.py`](../pkgs/anvil-core/payload/bin/ffscreen.py) | `/usr/data/anvil/bin/` | A few lines of text and a progress bar drawn straight onto `/dev/fb0`, geometry read from sysfs. The framebuffer is **portrait 480×800@32** and the panel is that buffer turned 90° clockwise (landscape 800×480) — established from FlashForge's own `/usr/prog/start.img`, 1536000 bytes, which only decodes into a picture read that way. Drawing is done in landscape coordinates and each rectangle is rotated on the way into the buffer, so it costs arithmetic per rectangle and nothing per pixel. All of the first boot happens before HelixScreen starts, so without it the panel is black for the longest wait of the install — which reads as a brick and invites a power cut mid-`SAVE_CONFIG`. `make boot-screen` renders every frame to PNG on the host; `make boot-screen-sim` renders the same frames inside the replica using FlashForge's own python3 on MIPS (they come out byte-identical); `qa/replica/test_boot_screen.py` is the gate |
|
||||
| [`pkgs/anvil-core/payload/bin/ff-startup.py`](../pkgs/anvil-core/payload/bin/ff-startup.py) | `/usr/data/anvil/bin/` | Everything before HelixScreen. **Every boot** it hands the toolhead boards over from their bootloaders (calling `ff_mcu_bringup.py` directly — it owns when klippy opens the ports, so it owns doing this first), starts klipper, and waits for klipper + moonraker to be ready, naming the board or service holding things up and re-handing the boards over on each retry. It runs as two s6-rc oneshots (`mcu-bringup`, then `ff-startup`), and the UI depends on the second — starting the UI before that is what produces a screen reporting a disconnected printer with no clue which board is missing. **First boot only**, once that has happened, it sends `FF_IMPORT_FIRMWARE_CONFIG` and `SAVE_CONFIG` over the moonraker API and stamps `/usr/data/anvil/.firmware-config-imported`. Only a verified save stamps, so a slow boot retries. `MOD_STARTUP=0` skips the lot; `MOD_IMPORT=0` keeps the wait and skips the migration |
|
||||
| [`pkgs/anvil-core/payload/config/ff-print-macros.cfg`](../pkgs/anvil-core/payload/config/ff-print-macros.cfg) | `/usr/data/config/` | `START_PRINT` / `END_PRINT` / `PAUSE` / `RESUME` / `CANCEL_PRINT`, reconstructed from the app's sequences, plus the `_FF_PREFLIGHT` calibration and tool-presence gate; declares `[ff_print]` and the `FF_BEFORE_PRINT_START` / `FF_AFTER_PRINT_END` entry points it calls |
|
||||
| [`pkgs/anvil-core/payload/config/ff-filament.cfg`](../pkgs/anvil-core/payload/config/ff-filament.cfg) | `/usr/data/config/` | `LOAD_FILAMENT` / `UNLOAD_FILAMENT` / `PURGE` — the touchscreen's filament-load sequence (grab tool, purge chute, feed) recovered from the binary; unload is a designed retract (the stock app has none) |
|
||||
| [`pkgs/anvil-core/payload/config/ff-runout.cfg`](../pkgs/anvil-core/payload/config/ff-runout.cfg) | `/usr/data/config/` | Runout / clog handling: gives the stock `fd_ex*` / `fm_ex*` sensors a `runout_gcode` that pauses a Mainsail print when the **mounted** tool runs out or clogs (the app's E0162 / E0163, reported here in plain words); `ff_toolchange` arms only the mounted tool's sensors |
|
||||
| [`pkgs/klipper-config/payload/config/ff-print-macros.cfg`](../pkgs/klipper-config/payload/config/ff-print-macros.cfg) | `/usr/data/config/` | `START_PRINT` / `END_PRINT` / `PAUSE` / `RESUME` / `CANCEL_PRINT`, reconstructed from the app's sequences, plus the `_FF_PREFLIGHT` calibration and tool-presence gate; declares `[ff_print]` and the `FF_BEFORE_PRINT_START` / `FF_AFTER_PRINT_END` entry points it calls |
|
||||
| [`pkgs/klipper-config/payload/config/ff-filament.cfg`](../pkgs/klipper-config/payload/config/ff-filament.cfg) | `/usr/data/config/` | `LOAD_FILAMENT` / `UNLOAD_FILAMENT` / `PURGE` — the touchscreen's filament-load sequence (grab tool, purge chute, feed) recovered from the binary; unload is a designed retract (the stock app has none) |
|
||||
| [`pkgs/klipper-config/payload/config/ff-runout.cfg`](../pkgs/klipper-config/payload/config/ff-runout.cfg) | `/usr/data/config/` | Runout / clog handling: gives the stock `fd_ex*` / `fm_ex*` sensors a `runout_gcode` that pauses a Mainsail print when the **mounted** tool runs out or clogs (the app's E0162 / E0163, reported here in plain words); `ff_toolchange` arms only the mounted tool's sensors |
|
||||
| [`pkgs/klipper-config/payload/config/printer.base.cfg`](../pkgs/klipper-config/payload/config/printer.base.cfg) | `$MODDIR/config/` -> symlinked to `/usr/data/config/printer.base.cfg` | FlashForge's `printer.base.cfg` with the chamber block replaced by `[include printer.chamber.cfg]`. Klipper can override an option but cannot un-declare a section, and the plain Creator 5 has no chamber heating element, so its heater must be **absent** rather than neutralised. `bin/unpack.sh` compares this against each stock package it unpacks and warns if FlashForge's has changed |
|
||||
| [`Creator5.cfg`](../pkgs/klipper-config/payload/config/chamber/Creator5.cfg) · [`Creator5Pro.cfg`](../pkgs/klipper-config/payload/config/chamber/Creator5Pro.cfg) | `$MODDIR/config/chamber/<Machine>.cfg` -> symlinked to `/usr/data/config/printer.chamber.cfg` | The one per-model difference: the Pro gets `[heater_generic chamber_heater]` + `[verify_heater]` verbatim from FlashForge, the Creator 5 gets only `[temperature_sensor chamber]` on the same pin. **Both ship in `anvil-klipper-config`** and `anvil-link-prog.sh` links whichever the printer asks for, reading `MACHINE=` out of FlashForge's own `app_startup.sh`. They used to be a package per model, which Conflicted — each owned `config/printer.chamber.cfg`, so opkg refused the pair and the build had to choose. Nothing is edited at build time, and the payload is no longer model-specific |
|
||||
| [`pkgs/anvil-core/payload/config/ff-chamber.cfg`](../pkgs/anvil-core/payload/config/ff-chamber.cfg) | `/usr/data/config/` | `M141` / `M191` for the chamber heater (Klipper has neither, and the stock app drove the chamber only from its own UI), plus the gate: the macros ask Klipper whether `heater_generic chamber_heater` exists, so a non-zero chamber target is refused on a machine that does not declare one. Nothing to keep in sync; identical in every package |
|
||||
| [`pkgs/klipper-config/payload/config/ff-chamber.cfg`](../pkgs/klipper-config/payload/config/ff-chamber.cfg) | `/usr/data/config/` | `M141` / `M191` for the chamber heater (Klipper has neither, and the stock app drove the chamber only from its own UI), plus the gate: the macros ask Klipper whether `heater_generic chamber_heater` exists, so a non-zero chamber target is refused on a machine that does not declare one. Nothing to keep in sync; identical in every package |
|
||||
| [`docs/notes/`](notes/) | (reference only) | Condensed reverse-engineering notes: what the stock app actually does, with binary addresses |
|
||||
|
||||
## ⚠️ Before you start
|
||||
@@ -62,16 +62,17 @@ documented in the file headers.
|
||||
* **Still never edit the JSON under `/usr/data/firmwareRes/config/`.** This
|
||||
module no longer reads it, but the touchscreen app still does and rewrites
|
||||
those files wholesale — leave it alone.
|
||||
* **Klipper lives on the firmware partition.** A FlashForge OTA update will
|
||||
overwrite `/usr/prog/klipper/`, deleting the extras. Keep this repo and
|
||||
re-run step 1 after every firmware update. The `#*#` block in
|
||||
`printer.cfg` is on the data partition and survives.
|
||||
* **Klipper lives on the data partition.** The extras install under
|
||||
`/usr/data/anvil/klipper/klippy/extras/`, which a FlashForge OTA cannot
|
||||
delete — but an OTA rewrites the software component the mod boots from, so
|
||||
re-flash the mod after one. The `#*#` block in `printer.cfg` survives either
|
||||
way.
|
||||
|
||||
## Install
|
||||
|
||||
A package built from this repo needs no config editing at all. It ships:
|
||||
|
||||
* the `ff_*.py` extras, into the Klipper tree it flashes (`bin/patch.sh`)
|
||||
* the `ff_*.py` extras, inside the klippy tree `anvil-klipper` installs
|
||||
* the `ff-*.cfg`, to `/usr/data/config/` (`run-append.sh`, keeping any you
|
||||
edited and leaving the new one as `.mod-new`)
|
||||
* the `[include]` lines for all seven, at the end of `printer.base.cfg` —
|
||||
@@ -93,12 +94,12 @@ root/ssh access is covered in the community
|
||||
[Discord](https://discord.gg/tYs3eNEDq)):
|
||||
|
||||
```sh
|
||||
# 1. the klippy extras (firmware partition — may need remount rw)
|
||||
# 1. the klippy extras (data partition — the tree the klipper service runs)
|
||||
scp pkgs/klipper/payload/klipper/klippy/extras/ff_*.py \
|
||||
pwned@PRINTER:/usr/prog/klipper/klippy/extras/
|
||||
pwned@PRINTER:/usr/data/anvil/klipper/klippy/extras/
|
||||
|
||||
# 2. the config files (data partition — survives OTA)
|
||||
scp pkgs/anvil-core/payload/config/ff-*.cfg \
|
||||
scp pkgs/klipper-config/payload/config/ff-*.cfg \
|
||||
pwned@PRINTER:/usr/data/config/
|
||||
```
|
||||
|
||||
@@ -371,7 +372,7 @@ dialog. Drop `pkgs/helixscreen/payload/helixscreen/config/printer_database.d/fla
|
||||
`config/printer_database.d/` for auto-detection (`ams_type: tool_changer`,
|
||||
`z_offset_calibration_strategy: firmware_managed`). Its default Load/Unload buttons only mount/unmount the tool (`SELECT_TOOL` /
|
||||
`UNSELECT_TOOL`); to actually feed or pull filament assign `LOAD_FILAMENT`,
|
||||
`UNLOAD_FILAMENT` and `PURGE` from [`pkgs/anvil-core/payload/config/ff-filament.cfg`](../pkgs/anvil-core/payload/config/ff-filament.cfg)
|
||||
`UNLOAD_FILAMENT` and `PURGE` from [`pkgs/klipper-config/payload/config/ff-filament.cfg`](../pkgs/klipper-config/payload/config/ff-filament.cfg)
|
||||
in Settings → Macro Buttons — a user-assigned macro outranks the backend, and
|
||||
the parameter dialog picks up `TOOL` / `TEMP` / `PURGE_TEMP` from the macros.
|
||||
|
||||
|
||||
+4
-3
@@ -60,9 +60,10 @@ your copy, landing the new version beside it as `.mod-new`.
|
||||
|
||||
## A FlashForge OTA update is a different thing
|
||||
|
||||
overwrite `/usr/prog/klipper/`, deleting the extras. Keep this repo and
|
||||
re-run step 1 after every firmware update. The `#*#` block in
|
||||
`printer.cfg` is on the data partition and survives.
|
||||
Everything the mod installs is under `/usr/data/anvil` on the data partition,
|
||||
the klippy tree and its extras included, so an OTA cannot delete them. What it
|
||||
does rewrite is the software component the mod boots from — so re-flash the mod
|
||||
after a firmware update. The `#*#` block in `printer.cfg` survives either way.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -263,10 +263,10 @@ sync
|
||||
# Overriding from printer.cfg AFTER the include survives every flash, while an
|
||||
# edit here is reverted by the next one. Each file says so in its header.
|
||||
#
|
||||
# printer.base.cfg is on the same footing and needs no rule here: it installs
|
||||
# to /usr/prog/klipper/config with the software component, which a flash
|
||||
# replaces wholesale. printer.chamber.cfg used to be beside it and is not any
|
||||
# more -- see the case below.
|
||||
# printer.base.cfg is on the same footing and needs no rule here: it is
|
||||
# anvil-klipper-config's, and anvil-link-prog.sh symlinks $MODDIR/config into
|
||||
# /usr/data/config, so an upgrade repoints the link rather than editing a file.
|
||||
# printer.chamber.cfg goes the same way -- see the case below.
|
||||
#
|
||||
# moonraker.conf is KEPT when edited. There is no include-and-override seam for
|
||||
# it, and a printer reached through a tuned trusted_clients or cors_domains
|
||||
|
||||
@@ -52,8 +52,8 @@
|
||||
#
|
||||
# NOT libsodium either: the only importer of libnacl is Moonraker, which runs
|
||||
# on our 3.13, whose libnacl resolves $MODDIR/lib/libsodium.so by absolute path
|
||||
# with no library path entry at all. klippy runs on 3.8 and has no libnacl
|
||||
# import.
|
||||
# with no library path entry at all. klippy is on that same 3.13 and has no
|
||||
# libnacl import.
|
||||
ANVIL_LIBS="/usr/prog/Python-3.8.2/lib
|
||||
/usr/prog/openssl-1.0.2d/lib
|
||||
/usr/prog/libffi-3.4.4/lib"
|
||||
|
||||
@@ -10,12 +10,15 @@
|
||||
# the only place anything is installed and `opkg upgrade` is enough to change
|
||||
# what the printer runs.
|
||||
#
|
||||
# ORDERING. The software component carries firmwareExe and start.sh and stock
|
||||
# run.sh copies them into place at its lines 107-179, before run-append.sh
|
||||
# extracts the payload at line 180. So the component cannot ship the symlinks
|
||||
# itself: on a first install they would dangle, and on an upgrade they would
|
||||
# resolve to the payload being replaced. This runs after extraction, and again
|
||||
# from anvil-core's postinst.
|
||||
# ORDERING. Stock run.sh distributes the software component at its lines
|
||||
# 107-179, before run-append.sh extracts the payload at line 180. So the
|
||||
# component cannot ship these symlinks itself: on a first install they would
|
||||
# dangle, and on an upgrade they would resolve to the payload being replaced.
|
||||
# This runs after extraction, and again from anvil-core's postinst. The
|
||||
# component no longer carries firmwareExe or start.sh at all -- nothing execs
|
||||
# either before this point, and a wrapper without a payload behind it is worse
|
||||
# than no wrapper (app_startup.sh restores the stock binary from a version
|
||||
# directory; the wrapper would just sit there and never start a UI).
|
||||
set -e
|
||||
|
||||
MODDIR=${MODDIR:-/usr/data/anvil}
|
||||
@@ -30,8 +33,9 @@ if [ ! -f /usr/prog/app_startup.sh ]; then
|
||||
fi
|
||||
|
||||
# $1 is relative to $MODDIR, $2 is the absolute path the printer reads. The
|
||||
# two destinations under /usr/prog mirror what stock run.sh does with the same
|
||||
# files, so this stays true as long as that does:
|
||||
# two destinations under /usr/prog are the ones stock run.sh copies to, so this
|
||||
# stays true as long as that does -- its two cp lines are no-ops now that the
|
||||
# component ships neither file:
|
||||
# cp $WORK_DIR/firmwareExe /usr/prog/PROGRAM/software/
|
||||
# cp $WORK_DIR/start.sh /usr/prog/klipper/start.sh
|
||||
link_one() {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# ff-legacy.cfg -- migration from firmwareExe's JSON.
|
||||
#
|
||||
# Keep it included (payload/klipper/extras/ff_legacy.py must be in
|
||||
# /usr/prog/klipper/klippy/extras/). It registers ONE command and does
|
||||
# /usr/data/anvil/klipper/klippy/extras/). It registers ONE command and does
|
||||
# nothing at startup:
|
||||
#
|
||||
# FF_IMPORT_FIRMWARE_CONFIG ; reads /usr/data/firmwareRes/config
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# ff-tool-offset.cfg -- nozzle XY/Z offset calibration, Klipper-native.
|
||||
#
|
||||
# Implementation: payload/klipper/extras/ff_tool_offset.py (copy next to
|
||||
# ff_toolchange.py in /usr/prog/klipper/klippy/extras/).
|
||||
# ff_toolchange.py in /usr/data/anvil/klipper/klippy/extras/).
|
||||
# Recovered sequence: docs/notes/46-offset-calibration-recovered.md
|
||||
#
|
||||
# What it does
|
||||
|
||||
@@ -111,16 +111,16 @@
|
||||
#
|
||||
# ---------------------------------------------------------------------------
|
||||
# INSTALL
|
||||
# A package built from this repo needs none of this: bin/patch.sh puts the
|
||||
# ff_*.py extras into the Klipper tree it flashes, the stock run.sh copies
|
||||
# A package built from this repo needs none of this: the ff_*.py extras
|
||||
# ride inside the klippy tree anvil-klipper installs, the stock run.sh copies
|
||||
# the ff-*.cfg to /usr/data/config/, and printer.base.cfg ships the
|
||||
# [include] lines for all of them. Flashing is the whole install.
|
||||
#
|
||||
# By hand, onto a printer that is already modded:
|
||||
# 1. cp payload/klipper/extras/ff_*.py /usr/prog/klipper/klippy/extras/
|
||||
# (Klipper lives on the firmware partition -- klipper_pri.sh runs
|
||||
# /usr/prog/klipper/klippy/klippy.py. It may be mounted read-only, and an
|
||||
# OTA will overwrite it, so keep a copy and a re-apply script.)
|
||||
# 1. cp payload/klipper/extras/ff_*.py /usr/data/anvil/klipper/klippy/extras/
|
||||
# (that is the tree the klipper service runs, on the data partition, so an
|
||||
# OTA cannot delete it -- but an OTA does rewrite the software component
|
||||
# the mod boots from, so re-flash the mod after one.)
|
||||
# 2. cp the ff-*.cfg to /usr/data/config/ and make sure something includes
|
||||
# them -- printer.base.cfg does, at its end. All of them: the set is
|
||||
# mandatory, not a menu of optional drop-ins. Without it Klipper comes up
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# Driving the same MOTOR_* macros therefore inherits that behaviour unchanged.
|
||||
#
|
||||
# Install: copy ff_tool.py and ff_toolchange.py to
|
||||
# /usr/prog/klipper/klippy/extras/, then see config/ff-toolchange.cfg.
|
||||
# /usr/data/anvil/klipper/klippy/extras/, then see config/ff-toolchange.cfg.
|
||||
#
|
||||
# Klipper calls Tn only for lines that reach the gcode engine. We ship upstream
|
||||
# virtual_sdcard, which passes tool lines straight through, so a bare "Tn" from
|
||||
|
||||
@@ -55,6 +55,8 @@ pytestmark = pytest.mark.replica
|
||||
|
||||
MODDIR = "/usr/data/anvil"
|
||||
FE = "/usr/prog/PROGRAM/software/firmwareExe"
|
||||
# The other stock path anvil-link-prog.sh owns. klipperDaemon starts from here.
|
||||
START = "/usr/prog/klipper/start.sh"
|
||||
APP = "/usr/prog/app_startup.sh"
|
||||
SOURCE = MODDIR + "/etc/s6-rc/source"
|
||||
DB = MODDIR + "/etc/s6-rc/compiled/current"
|
||||
@@ -141,6 +143,30 @@ def test_the_wrapper_starts_the_supervisor(box):
|
||||
"would start")
|
||||
|
||||
|
||||
def test_the_stock_paths_are_symlinks_into_the_payload(box):
|
||||
"""The property the whole seam rests on since the software component
|
||||
stopped carrying firmwareExe and start.sh.
|
||||
|
||||
anvil-core installs both at $MODDIR/prog/ and anvil-link-prog.sh points the
|
||||
stock paths at them -- from run-append.sh on a flash and from the postinst
|
||||
on `opkg upgrade anvil-core`. A REGULAR FILE at either path means the link
|
||||
step did not run, and the printer would go on executing whatever the last
|
||||
install happened to leave there while an upgrade quietly rewrote $MODDIR.
|
||||
"""
|
||||
for path in (FE, START):
|
||||
target = box.sh("readlink %s 2>/dev/null" % path).out.strip()
|
||||
assert target, (
|
||||
"%s is not a symlink -- anvil-link-prog.sh did not run, so an "
|
||||
"`opkg upgrade anvil-core` would not change what this printer "
|
||||
"executes" % path)
|
||||
assert target.startswith(MODDIR + "/prog/"), (
|
||||
"%s -> %r, which is not under %s/prog -- anvil-core is not what "
|
||||
"this printer runs" % (path, target, MODDIR))
|
||||
assert box.file(path).exists, (
|
||||
"%s -> %r dangles: the link is there but the payload is not"
|
||||
% (path, target))
|
||||
|
||||
|
||||
def test_every_installed_script_parses_under_the_printers_busybox(box):
|
||||
"""Every shell script the mod put on the machine, with the shell that will
|
||||
actually run it. A bashism here is a service that dies at boot."""
|
||||
@@ -389,15 +415,20 @@ def test_a_boot_with_no_stick_does_not_try_to_update(box):
|
||||
firmwareExe is neutered for the run, because the real one holds the
|
||||
foreground forever by design. What is under test is the update block's
|
||||
decision, not the boot that follows it.
|
||||
|
||||
The stub goes in by MOVING THE SYMLINK ASIDE, not by writing to $FE: that
|
||||
path is a link into $MODDIR/prog now, so a redirection would write through
|
||||
it and leave the packaged wrapper stubbed for whatever runs next. `mv`
|
||||
moves the link itself, and moving it back restores it exactly.
|
||||
"""
|
||||
box.sh("cp %s /tmp/fe.real && printf '#!/bin/sh\\nexit 0\\n' > %s"
|
||||
% (FE, FE))
|
||||
box.sh("mv -f %s /tmp/fe.link && printf '#!/bin/sh\\nexit 0\\n' > %s "
|
||||
"&& chmod +x %s" % (FE, FE, FE))
|
||||
try:
|
||||
box.sh("sh %s > /tmp/boot-nostick.log 2>&1; echo rc=$?" % APP,
|
||||
timeout=300)
|
||||
log = box.file("/tmp/boot-nostick.log").text
|
||||
finally:
|
||||
box.sh("cp /tmp/fe.real %s && chmod +x %s" % (FE, FE))
|
||||
box.sh("rm -f %s && mv -f /tmp/fe.link %s" % (FE, FE))
|
||||
|
||||
assert log.strip(), "app_startup.sh produced no output at all"
|
||||
assert "find update file" not in log, (
|
||||
|
||||
@@ -225,10 +225,10 @@ libsodium is ours -- and packaging and integration followed:
|
||||
the replica.
|
||||
4. ~~Then the switch itself.~~ **Done**: `FF_PYTHON` in `anvil-env.sh` and
|
||||
`/usr/prog/libsodium/lib` off `ANVIL_LIBS`, in the same commit.
|
||||
5. klippy is **not** blocked by packages, and was never part of this switch:
|
||||
it keeps running on FlashForge's 3.8.2, started independently by
|
||||
`/usr/prog/klipper/start.sh` (see `init.d/S70klipper`), not by
|
||||
`FF_PYTHON`. cffi, greenlet, jinja2, markupsafe
|
||||
5. klippy is **not** blocked by packages, and was not part of this switch --
|
||||
though it has since made the same move: the `klipper` s6-rc service execs
|
||||
`$FF_PYTHON` against `$MODDIR/klipper/klippy`, so it is on 3.13 as
|
||||
well. cffi, greenlet, jinja2, markupsafe
|
||||
and pyserial all build, and `c_helper.so` binds. `numpy` is the only gap,
|
||||
wanted solely by `extras/stepper_resonance_tester.py`, which Klipper loads
|
||||
on demand. numpy on 3.13 means numpy >= 2.1 and a Meson cross-file, and
|
||||
|
||||
@@ -252,6 +252,10 @@ FlashForge's binary.
|
||||
|
||||
## It ships. Nothing runs it.
|
||||
|
||||
*[Superseded: `FF_PYTHON` is `$MODDIR/bin/python3.13` now, and Moonraker,
|
||||
klippy, `ff-startup.py`, `ffscreen.py` and the MCU bring-up all run on it. What
|
||||
follows is the state this investigation ended in.]*
|
||||
|
||||
`payload/anvil-env.sh` still points `FF_PYTHON` at
|
||||
`/usr/prog/Python-3.8.2/bin/python3`. That is timing, not preference: klippy,
|
||||
Moonraker and `ff-startup.py` import third-party C extensions -- tornado,
|
||||
|
||||
+13
-8
@@ -156,9 +156,9 @@ S6RC_SHA256="bf5b8ce0da5a4ee70d642b818b61d9916a7a9b64a457595f388113e54a188688"
|
||||
# toolchain like everything else, and this tree has one libc again.
|
||||
|
||||
# ---------------------------------------------------------------- CPython
|
||||
# A SECOND Python for the printer, cross-compiled here and SHIPPED BUT NOT
|
||||
# USED (bin/patch.sh section 5c). FlashForge's own /usr/prog/Python-3.8.2 is
|
||||
# still what everything runs on -- see payload/anvil-env.sh.
|
||||
# A SECOND Python for the printer, cross-compiled here and SHIPPED AND USED:
|
||||
# $FF_PYTHON names it, so Moonraker, klippy, ff-startup.py, ffscreen.py and the
|
||||
# MCU bring-up all run on it -- see payload/anvil-env.sh.
|
||||
#
|
||||
# WHY IT EXISTS AT ALL. FlashForge built 3.8.2 without _sqlite3 and shipped no
|
||||
# libsqlite3 anywhere on the rootfs, which is the single fact that pins
|
||||
@@ -168,7 +168,12 @@ S6RC_SHA256="bf5b8ce0da5a4ee70d642b818b61d9916a7a9b64a457595f388113e54a188688"
|
||||
# rather than a 3.8 with one module added because a from-source build costs
|
||||
# the same either way. Measured on the replica: the stock 3.8.2 cannot import
|
||||
# sqlite3 and this one creates a database, writes it, closes it and reads it
|
||||
# back. test/integration/printer/case-python.sh is that measurement, kept.
|
||||
# back. That standalone measurement was retired with the case scripts; what is
|
||||
# run on every replica now is qa/replica/test_web.py, which brings real
|
||||
# Moonraker up on this interpreter and waits for s6's `ready`. It exercises the
|
||||
# interpreter harder and says nothing about sqlite3 directly -- the pinned
|
||||
# Moonraker predates the sqlite database, which is the whole reason it is
|
||||
# pinned.
|
||||
#
|
||||
# EVERY VERSION BELOW IS PINNED WITH A sha256 FOR THE SAME REASON THE REST OF
|
||||
# THIS FILE IS: nothing binary is vendored, so the only thing standing between
|
||||
@@ -241,10 +246,10 @@ EXPAT_SHA256="fd03b7172b3bd7427a3e7a812063f74754f24542429b634e0db6511b53fb2278"
|
||||
|
||||
# ------------------------------------------- CPython's third-party packages
|
||||
# The 18 sdists that become $MODDIR/lib/python3.13/site-packages -- what
|
||||
# Moonraker and klippy import, cross-built for mipsel by bin/patch.sh section
|
||||
# 5c against the interpreter pinned above. SHIPPED BUT NOT USED, exactly like
|
||||
# the interpreter itself: payload/anvil-env.sh still points FF_PYTHON at
|
||||
# FlashForge's 3.8.2 and the switch is a later commit.
|
||||
# Moonraker and klippy import, cross-built for mipsel against the interpreter
|
||||
# pinned above -- one pkgs/3rdparty/python-* recipe and one .ipk each. They are
|
||||
# live, exactly like the interpreter itself: payload/anvil-env.sh points
|
||||
# FF_PYTHON at it and klippy is among its callers.
|
||||
#
|
||||
# THE LIST IS NOT moonraker-requirements.txt, and that is a decision rather
|
||||
# than an omission. That file installs every OPTIONAL component's
|
||||
|
||||
Reference in new issue
Block a user