diff --git a/Makefile b/Makefile index a16a67f..a1cc8cf 100644 --- a/Makefile +++ b/Makefile @@ -79,7 +79,7 @@ help: @echo 'Recovery: keep a copy of the STOCK FlashForge .tgz on a spare stick.' @echo 'Flashing it restores every file the mod touches (see make test-recovery).' @echo - @echo 'Test -- four gates, no more:' + @echo 'Test:' @echo ' make test all of them' @echo ' make test-py pytest: the config gate, plus rootfs paths if extracted' @echo ' make test-install end-to-end: USB stick -> update -> reboot' diff --git a/bin/common.sh b/bin/common.sh index 5303172..f336646 100644 --- a/bin/common.sh +++ b/bin/common.sh @@ -43,8 +43,10 @@ MOD_WEB="${MOD_WEB:-1}" # downloaded on demand rather than vendored, so the repo carries no binaries # and no binary history. versions.env pins the version and the sha256; # bin/fetch-assets.sh puts the file in vendor/. Point MAINSAIL_ZIP / -# HELIX_TGZ / MOONRAKER_TGZ at your own build in config.env to override -- an -# explicit path is never overwritten. +# HELIX_TGZ / MOONRAKER_TGZ at your own build in config.env to override -- +# but an explicit path is checksummed like any other, and fetch-assets.sh +# overwrites it with the pinned release when the hash does not match. Put +# your file's own sha256 in versions.env to keep it. # shellcheck disable=SC1091 [ -f "$ROOT/versions.env" ] && . "$ROOT/versions.env" MAINSAIL_ZIP="${MAINSAIL_ZIP:-$ROOT/vendor/mainsail-${MAINSAIL_VERSION:-unpinned}.zip}" diff --git a/bin/pack.sh b/bin/pack.sh index 841eb92..f703355 100755 --- a/bin/pack.sh +++ b/bin/pack.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash # 3/3 -- repack work/software back into an installable USB package. # -# ./pack.sh software component only (DEFAULT, ~28MB) -# ./pack.sh --full also carry kernel / control / library -# ./pack.sh --plain no encryption: emits runFirmwareExe.sh + payload +# ./bin/pack.sh software component only (DEFAULT, ~28MB) +# ./bin/pack.sh --full also carry kernel / control / library +# ./bin/pack.sh --plain no encryption: emits runFirmwareExe.sh + payload # for the /mnt/runFirmwareExe.sh dev path # # Slim is the default because the stock installer skips any component that is @@ -28,7 +28,7 @@ for a in "$@"; do esac done -[ -d work/software ] || { echo "run ./unpack.sh first" >&2; exit 1; } +[ -d work/software ] || { echo "run ./bin/unpack.sh first" >&2; exit 1; } STOCK_SW_VER=$(cat work/.stock_sw_ver) OUT_VER="${SW_VER:-$STOCK_SW_VER}" diff --git a/bin/patch.sh b/bin/patch.sh index b2437aa..bdd43f5 100755 --- a/bin/patch.sh +++ b/bin/patch.sh @@ -18,7 +18,7 @@ skip() { printf ' (skip) %s\n' "$*"; } MODDIR=/usr/data/anvil # The mod payload is built OUTSIDE the software component on purpose. The # software component is extracted to /usr/prog/PROGRAM/software// -- the -# firmware partition, of which the installer keeps two versions. Mainsail and +# firmware partition, of which the installer keeps only one version. Mainsail and # HelixScreen are ~100MB and would overflow it. They ride in the outer package # instead, land in /usr/data/update/ (data partition), and are moved to # /usr/data/anvil from there. @@ -75,8 +75,9 @@ if [ "${BUILD_TOOLCHANGE:-1}" = "1" ]; then say "Toolchange: ff_*.py + configs" mkdir -p "$SW/klipper/extras" cp -f payload/klipper/extras/ff_*.py "$SW/klipper/extras/" - # .cfg files belong on the data partition; run.sh installs them without - # clobbering a config the user already tuned. + # .cfg files belong on the data partition. These are mod-owned: run.sh + # overwrites them on every update (test_config_ownership.py enforces it). + # User changes go in printer.cfg, which no flash ever writes. cp -f payload/klipper/config/ff-*.cfg "$MP/config/" # Our printer.base.cfg is FlashForge's with the chamber block replaced by # [include printer.chamber.cfg] -- Klipper can override an option but @@ -215,6 +216,8 @@ if [ "${BUILD_HELIX:-1}" = "1" ]; then mkdir -p "$MP/helixscreen/config/printer_database.d" cp -f payload/helixscreen/printer_database.d/*.json \ "$MP/helixscreen/config/printer_database.d/" + # Optional platform hook. No such file is in the repo, so this never fires + # on a stock checkout -- drop one in assets/ to have it shipped. [ -f assets/hooks-creator5.sh ] && \ cp -f assets/hooks-creator5.sh "$MP/helixscreen/assets/config/platform/" du -sh "$MP/helixscreen" | awk '{print " "$1}' @@ -338,4 +341,4 @@ echo echo "Patched." echo " software component: $(du -sh "$SW" | cut -f1) (-> /usr/prog, firmware partition)" echo " mod payload: $(du -sh "$MP" | cut -f1) (-> /usr/data/anvil, data partition)" -echo "Now run ./pack.sh" +echo "Now run ./bin/pack.sh" diff --git a/bin/unpack.sh b/bin/unpack.sh index 8f880b2..9b1c0d7 100755 --- a/bin/unpack.sh +++ b/bin/unpack.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # 1/3 -- decrypt the stock package and open the software component. # -# ./unpack.sh uses $STOCK_TGZ from config.env -# ./unpack.sh unpack some other package +# ./bin/unpack.sh uses $STOCK_TGZ from config.env +# ./bin/unpack.sh unpack some other package # # Result: # work/outer/ the 8 top-level files (runFirmwareExe.sh, *.tar.xz, imgs) @@ -75,6 +75,6 @@ if [ -f "$STOCK_BASE" ] && [ -f "$OURS" ]; then fi echo -echo "Unpacked. Edit work/software/ then run ./pack.sh" +echo "Unpacked. Edit work/software/ then run ./bin/pack.sh" echo " stock software version: $STOCK_SW_VER" echo " files: $(find work/software -type f | wc -l)" diff --git a/bin/verify.sh b/bin/verify.sh index dd4ae8d..5456087 100755 --- a/bin/verify.sh +++ b/bin/verify.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash # Simulate every check the printer performs, against a built package. -# ./verify.sh verify work/out/Creator5Pro-*.tgz -# ./verify.sh path/to/pkg.tgz +# ./bin/verify.sh verify work/out/Creator5Pro-*.tgz +# ./bin/verify.sh path/to/pkg.tgz set -uo pipefail . "$(dirname "$0")/common.sh" # Default to the package for the model currently selected, not a hardcoded # one -- otherwise a two-model release verifies the same file twice. PKG="${1:-$(ls -1 "work/out/${TARGET_MACHINE:-Creator5Pro}"-*.tgz 2>/dev/null | head -n1)}" -[ -f "${PKG:-}" ] || { echo "no package; run ./pack.sh" >&2; exit 1; } +[ -f "${PKG:-}" ] || { echo "no package; run ./bin/pack.sh" >&2; exit 1; } echo "verifying $PKG"; echo FAIL=0 diff --git a/config.env.example b/config.env.example index 48c82ba..e47d1e2 100644 --- a/config.env.example +++ b/config.env.example @@ -1,5 +1,5 @@ # creator5-fw-builder — build configuration -# Edit paths to match your machine, then run ./build.sh +# Edit paths to match your machine, then run `make build`. # ---- identity of the package you are building ------------------------------- MOD_NAME=anvil # goes into the .tgz filename @@ -37,14 +37,11 @@ DESKTOP="$HOME/Desktop" # carries a different firmwareExe and refuses to install on the other model. # The one matching TARGET_MACHINE (or MODEL=) is selected automatically. STOCK_TGZ_CREATOR5PRO="$DESKTOP/Creator5-1.9.7-1.2.9-20260810/Creator5Pro-1.9.7-1.2.9-20260810.tgz" -STOCK_TGZ_CREATOR5="$DESKTOP/Creator5-1.9.7-1.2.9-20260810/Creator5-1.9.7-1.2.9-20260810" +STOCK_TGZ_CREATOR5="$DESKTOP/Creator5-1.9.7-1.2.9-20260810/Creator5-1.9.7-1.2.9-20260810.tgz" # Set this only to force one specific package regardless of model. STOCK_TGZ="" -# Extracted stock software component -- the BASE our overlay is applied onto. -STOCK_SOFTWARE="$DESKTOP/software-1.9.8.tar_2026-08-21_7045c47d__md5-8e12dbbd961f0821bd7895f902563238/software-1.9.8" - # Your Klipper fork (branch creator5) -- klippy tree + prebuilt MIPS c_helper.so KLIPPER_FORK="$DESKTOP/klipper" @@ -56,8 +53,11 @@ KLIPPER_FORK="$DESKTOP/klipper" # Mainsail and the HelixScreen build for the Creator 5 (MIPS static) are NOT # set here. They are downloaded on demand into vendor/ by bin/fetch-assets.sh, # pinned by version and sha256 in versions.env, so the repo carries no -# binaries. Set either of these ONLY to build against your own local copy -- -# an explicit path is used as-is and is never downloaded or checksummed: +# binaries. Set either of these ONLY to build against your own local copy. +# NOTE: the path is still checksummed against versions.env, and on a mismatch +# fetch-assets.sh OVERWRITES it with the pinned release -- SKIP does not save +# you, it re-downloads too. To build against your own file, also put ITS +# sha256 in versions.env; only an exact match is left alone ("cached"): #HELIX_TGZ="$DESKTOP/helixscreen-creator5-v0.99.115.tar.gz" #MAINSAIL_ZIP="$DESKTOP/mainsail.zip" diff --git a/docs/building.md b/docs/building.md index a7f31ff..e77b908 100644 --- a/docs/building.md +++ b/docs/building.md @@ -91,12 +91,17 @@ anything whose sha256 does not match the pin. A cached file with the right hash is never re-downloaded, so it is a no-op on every build after the first. `bin/build.sh` calls it for you; `make vendor` pre-fetches everything. -To bump a version: edit `versions.env`, run `make vendor`, and paste the -sha256 it prints back into the file. +To bump a version: edit `versions.env` — for HelixScreen that means both +`HELIX_VERSION` and `HELIX_FILE`, which embeds the version in the filename — +set the matching sha256 to `SKIP`, and run `make vendor`. It downloads the new +file and prints its hash for you to paste back over the `SKIP`. With a stale +hash left in place `make vendor` fails instead of printing anything. -Setting `MAINSAIL_ZIP` or `HELIX_TGZ` in `config.env` overrides the download -and builds against your own local copy. An explicit path is used as-is and is -never checksummed. +Setting `MAINSAIL_ZIP` or `HELIX_TGZ` in `config.env` points the build at your +own local copy. It is still checksummed against `versions.env`, and on a +mismatch `fetch-assets.sh` **overwrites your file** with the pinned release — +`SKIP` re-downloads too. To build against your own file, put its sha256 in +`versions.env` as well; only an exact match is left alone. If the build asks for Mainsail or HelixScreen and the file is not there, it fails. It used to skip silently, which shipped a package with an empty diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 33d5544..c23b433 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -48,8 +48,9 @@ S80ui decides whether the UI runs; owns SAFE-MODE Everything the mod installs lives under `/usr/data/anvil` on the **data** partition, which a FlashForge OTA cannot delete. The software component itself -goes to `/usr/prog` on the firmware partition, which only holds two versions -and cannot fit ~100MB of web UI — that is why Mainsail and HelixScreen ride in +goes to `/usr/prog` on the firmware partition, which keeps only one version +(the installer wipes every other version directory) and cannot fit ~100MB of +web UI — that is why Mainsail and HelixScreen ride in the outer package instead. ## Moonraker diff --git a/docs/notes/00-overview.md b/docs/notes/00-overview.md index 34c5a4f..d07295c 100644 --- a/docs/notes/00-overview.md +++ b/docs/notes/00-overview.md @@ -30,7 +30,7 @@ firmwareExe (touchscreen app) │ ├─ mcu main board: X/Y/Z, bed │ ├─ eboard /dev/ttyS5 carriage extruder driver │ ├─ eheaterboard /dev/ttyS4 4 hotend heaters + 24V - │ └─ levelboard /dev/ttyS7 eddy-current probe + │ └─ levelboard /dev/ttyS7 fixed under-bed cylinder ├─ DryingService ──/dev/ttyS3──► filament drying box (own MCU, not Klipper) ├─ OrcaServer / MQTT cloud / camera └─ /usr/data/firmwareRes/config/*.json — per-unit calibration + state @@ -38,12 +38,16 @@ firmwareExe (touchscreen app) ## Printer facts -- 4-tool toolchanger: docked heads T0–T3 at X≈250–280, picked up by the X carriage +- 4-tool toolchanger: docked heads T0–T3 at X≈297 (250/280 are the staging and + approach waypoints, not the docks), picked up by the X carriage with a lock motor (current sensing via `temperature_sensor motor_value`). -- Bed ~250×250, 10×10 eddy bed mesh; eddy probe also used for nozzle XY/Z offset - calibration. -- 4 filament channels with feed hub (`manual_stepper gear_stepper`), presence sensors - `fd_ex0..3`, motion sensors `fm_ex0..3`. +- Bed ~250×250, 10×10 bed mesh from the carriage eddy (`[probe]`, `eboard:PG0`). + Nozzle XY/Z offset calibration uses a different sensor: the fixed inductive + cylinder under the bed (`[e_stop X/Y/Z]` on `levelboard:PD0`). +- 4 filament channels, each a direct-drive extruder on its own head — there is no + feed hub; `manual_stepper gear_stepper` is the tool LOCK motor (see + `47-filament-load-recovered.md`). Presence sensors `fd_ex0..3`, motion sensors + `fm_ex0..3`. - Chamber heater + 4 fans + LED, front/top door switches. ## Method diff --git a/docs/notes/10-hardware.md b/docs/notes/10-hardware.md index 80c29dc..409f39a 100644 --- a/docs/notes/10-hardware.md +++ b/docs/notes/10-hardware.md @@ -7,7 +7,7 @@ | `mcu` | (main board, see printer.base.cfg on device) | X/Y/Z steppers, bed heater `PD2`, bed thermistor `PC3` | | `eboard` | `/dev/ttyS5` | Carriage extruder board — ONE stepper driver shared by whichever head is mounted (`PB14/PB15/PB12`) | | `eheaterboard` | `/dev/ttyS4` | 4 hotend heaters `PC6..PC9`, 4 thermistors `PC0/PA4/PA5/PA7`, 24V rail `PA3` | -| `levelboard` | `/dev/ttyS7` | Eddy-current probe (bed leveling + nozzle offset sensing) | +| `levelboard` | `/dev/ttyS7` | The fixed inductive "cylinder" under the bed — all three `[e_stop X/Y/Z]` sit on `levelboard:PD0` and drive the nozzle-offset calibration. The *carriage* eddy used for G28 Z and bed mesh is `[probe]` on `eboard:PG0` — see `25-app-vs-klipper-ownership.md` | The app pokes ttyS4/S5/S7 (sends a char, waits for "Ready") before starting Klipper. Other serial: `/dev/ttyS3` = drying box (own protocol, not a Klipper MCU). @@ -88,11 +88,14 @@ sensors, max_temp 350. - Fans: `fan_generic fanM106` (part fan, via custom `SET_FAN_M106`), `chamber_fan`, `chamber_cool_fan`, `chamber_heat_fan`, `chamber_loop_fan`. - Buttons (`gcode_button`): `extruder_pos1..4` (head present in dock N), - `extruder_grab`, `extruder_grab1..4` (head on carriage), `extruder_check_pos`, - `servo_min`/`servo_max` (lock end positions), `frontDoor`, `topDoor`, `motor_pin`. + `extruder_grab1..4` (head on carriage), `frontDoor`, `topDoor`. That is the + whole family — confirmed against this printer's `printer.base.cfg` (see + `ff-toolchange.cfg`'s sensor note): there is no bare `extruder_grab`, no + `extruder_check_pos`, no `servo_min`/`servo_max`, and `motor_pin` is + commented out in `printer.motor.cfg`. - Filament: `filament_switch_sensor fd_ex0..3` (presence), custom motion sensors `fm_ex0..3`. -- Steppers: `manual_stepper gear_stepper` (feed hub; its endstop doubles as the lock - status signal — see `30-toolchange.md`). +- Steppers: `manual_stepper gear_stepper` (the tool LOCK motor, not a feeder; its + endstop doubles as the lock status signal — see `30-toolchange.md`). - Fork's `virtual_sdcard` extra status fields: `channel`, `refuelling`, `after_channel_g1`, `doingChangeEx`. diff --git a/docs/notes/45-tool-offset-calibration.md b/docs/notes/45-tool-offset-calibration.md index 97365e2..89d429a 100644 --- a/docs/notes/45-tool-offset-calibration.md +++ b/docs/notes/45-tool-offset-calibration.md @@ -12,20 +12,25 @@ per-tool sections, modelled on `[bed_mesh ]` / klipper-toolchanger's `[ | file | role | |---|---| -| `payload/klipper/extras/ff_tool.py` | `[ff_tool n]`: `dock_x/dock_y` hand-written; `nozzle_x/y/z` (measured) and `z_adjust` (user per-tool Z correction, `TOOL_Z_ADJUST`) autosaved | +| `payload/klipper/extras/ff_tool.py` | `[ff_tool n]`: `dock_x/dock_y` (autosaved by `FF_IMPORT_FIRMWARE_CONFIG`), `nozzle_x/y/z` (measured) and `z_adjust` (user per-tool Z correction, `TOOL_Z_ADJUST`) all autosaved | | `payload/klipper/extras/ff_tool_offset.py` | `TOOL_OFFSET_CALIBRATE`, `STATION_CALIBRATE`, `TOOL_OFFSET_STATUS`; `[ff_tool_offset]` holds `station_x/y/z` (autosaved) + probe geometry | | `payload/klipper/extras/ff_toolchange.py` | takes docks/offsets from the `ff_tool` objects; `refresh_offsets()`; `_station_z()`; JSON reader and `TOOLCHANGE_RELOAD` removed | | `payload/klipper/extras/ff_legacy.py` | `FF_IMPORT_FIRMWARE_CONFIG [DIR=] [APPLY=1]` — one-shot import of extruder/test/zoffset.json | -| `payload/klipper/config/ff-toolchange.cfg` | `[ff_tool 0..3]` with this unit's docks + `[ff_toolchange]` | +| `payload/klipper/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 | | `payload/klipper/config/ff-tool-offset.cfg`, `payload/klipper/config/ff-legacy.cfg` | sections for the other two extras | ## Storage layout +Everything per-unit lands in printer.cfg's `SAVE_CONFIG` block. The shipped +`ff-toolchange.cfg` sections stay empty. + ``` -# hand-written (ff-toolchange.cfg) # SAVE_CONFIG block of printer.cfg +# shipped, empty (ff-toolchange.cfg) # SAVE_CONFIG block of printer.cfg [ff_tool 1] #*# [ff_tool 1] -dock_x: 296.538940 #*# nozzle_x = 16.407789 -dock_y: 106.336197 #*# nozzle_y = 211.986710 + #*# dock_x = 296.538940 + #*# dock_y = 106.336197 + #*# nozzle_x = 16.407789 + #*# nozzle_y = 211.986710 #*# nozzle_z = 1.426736 [ff_tool_offset] #*# [ff_tool_offset] #cylinder_x: 28.5 #*# station_x = 28.791826 @@ -46,20 +51,25 @@ dock_y: 106.336197 #*# nozzle_y = 211.986710 ## Install / first run +A built package needs none of this — it installs the extras and +`printer.base.cfg` ships the whole `ff-*.cfg` include block at its end. By +hand, onto a printer that is already modded: + ``` cp payload/klipper/extras/ff_*.py /usr/prog/klipper/klippy/extras/ -# printer.cfg, after [virtual_sdcard]: -# [include ff-toolchange.cfg] [include ff-tool-offset.cfg] [include ff-legacy.cfg] +# Nothing to add to printer.cfg: printer.base.cfg already includes the +# ff-*.cfg set. Section order does not matter. RESTART -FF_IMPORT_FIRMWARE_CONFIG ; stages factory nozzle/station; prints dock/feed snippet +FF_IMPORT_FIRMWARE_CONFIG ; stages factory dock/nozzle/station SAVE_CONFIG TOOLCHANGE_STATUS ; all four tools calibrated, station_z present -# later, PEI off, homed: -STATION_CALIBRATE -TOOL_OFFSET_CALIBRATE TOOL=ALL +# later, PEI off, homed -- both commands REFUSE without PLATE_REMOVED=1: +STATION_CALIBRATE PLATE_REMOVED=1 +TOOL_OFFSET_CALIBRATE TOOL=ALL PLATE_REMOVED=1 SAVE_CONFIG ``` -Remove `ff-legacy.cfg` afterwards; it is only needed once. +`ff-legacy.cfg` stays included permanently: with `auto_import` (the default) +it re-imports on every startup until a tool has a saved nozzle position. ## Safety on an uncalibrated machine (added 2026-08-21, user request) @@ -95,9 +105,9 @@ points use the 3-decimal-rounded centre actually commanded; `fit_circle` is the Emitted G-code order matches the recovered list; fitted centre = virtual centre; autosave receives exactly `nozzle_x/y/z` for the calibrated tool and `station_x/y/z`; live offsets update without restart; `TOOLCHANGE_SET_PRINT_OFFSET` gives 3.240 for T0/220 °C/80 °C/0.25 mm -(OKF/62's value) and refuses without `station_z`; `TOOL_Z_ADJUST` applies live and stages; +(the value in docs/notes/40-offsets.md) and refuses without `station_z`; `TOOL_Z_ADJUST` applies live and stages; PLATE_REMOVED gate, bounded Z target, gap and residual guards all fire; `FF_IMPORT_FIRMWARE_CONFIG` against -`live/firmwareRes-config` stages 15 values and reproduces the OKF/33 offset table. +`live/firmwareRes-config` stages 15 values and reproduces the offset table in docs/notes/40-offsets.md. **Not yet run on the printer.** ## Not recoverable / open diff --git a/docs/notes/50b-nozzle-clean-port.md b/docs/notes/50b-nozzle-clean-port.md index f332c36..939b610 100644 --- a/docs/notes/50b-nozzle-clean-port.md +++ b/docs/notes/50b-nozzle-clean-port.md @@ -61,7 +61,7 @@ temperature per extruder is the same information from the other side. ## OrcaSlicer -`assets/orca/machine-start-gcode.txt` builds TOOLS/TEMPS from `is_extruder_used[n]` and +A slicer-side start-gcode snippet can build TOOLS/TEMPS from `is_extruder_used[n]` and `nozzle_temperature_initial_layer[n]`. Verify once in your Orca version that `is_extruder_used` resolves (it is a PrusaSlicer-lineage placeholder); fall back to `TOOLS=[initial_extruder]` if the G-code export complains. diff --git a/docs/notes/60-background.md b/docs/notes/60-background.md index c73b0ae..c8e058f 100644 --- a/docs/notes/60-background.md +++ b/docs/notes/60-background.md @@ -6,7 +6,8 @@ handles all of this. Kept as reference for future work. ## Filament system - 4 channels; per-channel presence (`fd_ex0..3`) and motion/clog (`fm_ex0..3`) - sensors; feed hub `manual_stepper gear_stepper`. Errors: E0162 runout, E0163 clog. + sensors; `manual_stepper gear_stepper` is the tool lock motor, not a feed hub. + Errors: E0162 runout, E0163 clog. - **Runout auto-swap** (the flagship app feature): on runout mid-print the app pauses, finds another channel with the same material/color, switches channel (possibly a head change), refeeds, purges, restores position and resumes. All app-side. diff --git a/docs/testing.md b/docs/testing.md index 295abc0..4127db5 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -8,8 +8,8 @@ is on. **Without firmware.** A synthetic fixture (`test/integration/make-stock-fixture.sh`) reproduces the package *structure*, so shell syntax, the bashism pass, the pytest config gate and the whole -packaging pipeline run in CI on a clean machine. This is roughly half the -gates, and it is the half that catches a chamber heater declared on a machine +packaging pipeline run in CI on a clean machine. This is the half that catches +a chamber heater declared on a machine that has no element for it — the replica cannot, because it never starts klippy. @@ -53,7 +53,7 @@ make test # everything below | `test-mcu` | Runs `ff-mcu-bringup.py` on the printer's **own** Python 3.8.2 in the exact environment `start.sh` sets — the only gate that executes our Python on the real interpreter, and the one that pins the `LD_LIBRARY_PATH` regression that shipped broken once | yes | | `test-recovery` | Installs the mod, then flashes the **stock** package, and asserts the machine is genuinely back to stock byte-for-byte and the leftover payload is inert | yes | -Four gates, deliberately. `test-install` boots the machine, so it independently +Few entry points, deliberately. `test-install` boots the machine, so it independently covers what `verify.sh` checks, parses every installed script with the same busybox that `test-ash` used, and reads `c_helper.so`'s ELF header the way `test-abi` did — the separate checks were the weaker copies. @@ -65,8 +65,8 @@ half with a loud message when no stock package is configured; `REQUIRE_PRINTER_SIM=1` turns that skip into a failure. **A skip is not a pass.** `run-tests.py` counts skips separately and exits -non-zero if any gate did not run. This matters more with four gates than it -did with twelve — and it is not hypothetical, see `test-abi` below. +non-zero if any gate did not run. This matters more the fewer gates there are +— and it is not hypothetical, see `test-abi` below. Accepting a gap is deliberate and has two forms: @@ -198,8 +198,8 @@ Tests that cannot fail are worse than no tests, because they read as coverage: honest but not useful enough to earn its place, and the whole check is gone. - **The hand-rolled bashism grep in `run-tests.sh`.** It knew five constructs; `shellcheck -s dash` knows the whole SC3xxx family and now stands in its - place. The same pass gave every replica launcher one shared - `test/integration/sim-image.sh` for the docker plumbing, which also fixed two `make + place. The same pass gave every replica launcher one shared home for the + docker plumbing — now `test/ffsim/replica.py` — which also fixed two `make test-ash` bugs: it ran without the docker socket (so it always silently skipped), and it never read `test.env`, so it ignored `PRINTER_IMAGE` and rebuilt the local sim image every run. diff --git a/payload/bin/ff-mcu-bringup.py b/payload/bin/ff-mcu-bringup.py index 8119b05..fe816ef 100755 --- a/payload/bin/ff-mcu-bringup.py +++ b/payload/bin/ff-mcu-bringup.py @@ -231,8 +231,8 @@ class Port(object): (bootSerialHeatMcu and bootSerialMainEboardMcu are the same function with a different device path and different printf strings; the only - logic difference anywhere in the three is in the ready phase, noted - on wait_for_banner.) + logic difference anywhere in the three is in the ready phase, handled + in Port.poll().) """ self.last_go = now for _ in range(ACK_TRIES): diff --git a/payload/helixscreen/printer_database.d/flashforge_creator5.json b/payload/helixscreen/printer_database.d/flashforge_creator5.json index 3204be1..ad4fdd7 100644 --- a/payload/helixscreen/printer_database.d/flashforge_creator5.json +++ b/payload/helixscreen/printer_database.d/flashforge_creator5.json @@ -7,7 +7,7 @@ "image": "generic-corexy.png", "ams_type": "tool_changer", "z_offset_calibration_strategy": "firmware_managed", - "notes": "4-tool toolchanger driven by the ff_toolchange Klipper extras (github: creator5-toolchange). The toolchanger/tool objects come from ff_toolchanger_compat; per-tool Z is managed by TOOL_Z_ADJUST and the print-start offset by TOOLCHANGE_SET_PRINT_OFFSET, so HelixScreen's probe-based Z-offset flow does not apply. Distinguished from the non-Pro only by the chamber heater: the two machines are otherwise the same hardware, and the stock firmwareExe builds differ in nothing else.", + "notes": "4-tool toolchanger driven by the ff_toolchange Klipper extra (github: creator5-custom-firmware). It registers the toolchanger and tool T0..T3 objects itself; per-tool Z is managed by TOOL_Z_ADJUST and the print-start offset by TOOLCHANGE_SET_PRINT_OFFSET, so HelixScreen's probe-based Z-offset flow does not apply. Distinguished from the non-Pro only by the chamber heater: the two machines are otherwise the same hardware, and the stock firmwareExe builds differ in nothing else.", "heuristics": [ { "type": "object_exists", diff --git a/payload/klipper/config/ff-chamber.cfg b/payload/klipper/config/ff-chamber.cfg index 95295f8..94455df 100644 --- a/payload/klipper/config/ff-chamber.cfg +++ b/payload/klipper/config/ff-chamber.cfg @@ -21,9 +21,11 @@ # ff-chamber.cfg -- the chamber: heater (gated by model) and light # # The Creator 5 Pro has a chamber heater; the plain Creator 5 does not. The -# two models ship the SAME Klipper tree -- printer.base.cfg declares -# [heater_generic chamber_heater] (heater_pin PE13 / sensor_pin PC1, both on -# the main mcu) on both. The whole difference lives in firmwareExe: the Pro +# declaration lives in printer.chamber.cfg, which differs by model (see the +# note further down): the Pro gets [heater_generic chamber_heater] +# (heater_pin PE13 / sensor_pin PC1, both on the main mcu), the plain +# Creator 5 gets only [temperature_sensor chamber]. On stock firmware the +# rest of the difference lives in firmwareExe: the Pro # binary carries "SET_HEATER_TEMPERATURE HEATER=chamber_heater TARGET=" in # CommMgr::heatManager case 5 and the non-Pro binary does not. The non-Pro app # still READS the chamber sensor and still runs the chamber fans -- it simply @@ -116,12 +118,12 @@ gcode: # --------------------------------------------------------------------------- # The chamber light, on at startup. # -# printer.base.cfg declares [led chamber_led] (white_pin PB5) and stops there. -# Klipper's led module defaults initial_WHITE to 0, so a freshly started -# Klipper leaves the light OFF and nothing ever turns it on: switching it was -# firmwareExe's job, from its own UI, and that binary is what this mod -# replaces. On stock firmware the app lit it; here the chamber simply stayed -# dark, which reads as a broken lamp rather than a missing command. +# Switching the light was firmwareExe's job, from its own UI, and that binary +# is what this mod replaces. Klipper's led module defaults initial_WHITE to 0, +# so without an explicit value the chamber simply stays dark, which reads as a +# broken lamp rather than a missing command. printer.base.cfg now sets +# initial_WHITE: 1.0 in its own [led chamber_led]; this file restates it so +# the light still comes up lit if that edit is ever reverted upstream. # # Overriding one option rather than restating the section: Klipper parses with # RawConfigParser(strict=False), so a second [led chamber_led] MERGES into the diff --git a/payload/klipper/config/ff-filament.cfg b/payload/klipper/config/ff-filament.cfg index ad401eb..5d27539 100644 --- a/payload/klipper/config/ff-filament.cfg +++ b/payload/klipper/config/ff-filament.cfg @@ -23,7 +23,7 @@ # # Port of the touchscreen app's FilamentLoad page (FilamentLoad::doLoad -> # startPrepareLoad -> pthreadFilamentLoad -> doLoadingOperation), recovered -# from MIPS machine code: firmwareExe-decompiled/recovered/filament-load.md. +# from MIPS machine code: docs/notes/47-filament-load-recovered.md. # # Facts that shape these macros (all address-verified there): # * Each tool has its own direct-drive extruder; "loading" is the tool's own @@ -54,9 +54,10 @@ # * TEMPERATURE_WAIT replaces the app's 1 s polling loop; it has no timeout # either. # -# REQUIRED -- this is not an optional drop-in; the mod does not work -# without it. Include from printer.cfg after ff-toolchange.cfg: -# [include ff-filament.cfg] +# REQUIRED -- this is not an optional drop-in; the mod does not work without +# it. Nothing to install: printer.base.cfg ships [include ff-filament.cfg] at +# its end, after ff-toolchange.cfg, along with the rest of the ff-*.cfg set. +# Do NOT add it to printer.cfg yourself -- that double-includes. # # HelixScreen: assign LOAD_FILAMENT / UNLOAD_FILAMENT / PURGE in # Settings -> Macro Buttons. With the tool-changer backend its default diff --git a/payload/klipper/config/ff-legacy.cfg b/payload/klipper/config/ff-legacy.cfg index 6dbe1e8..cb8f474 100644 --- a/payload/klipper/config/ff-legacy.cfg +++ b/payload/klipper/config/ff-legacy.cfg @@ -19,7 +19,7 @@ # --------------------------------------------------------------------------- # ff-legacy.cfg -- migration from firmwareExe's JSON. # -# Keep it included (klippy-extras/ff_legacy.py must be in +# Keep it included (payload/klipper/extras/ff_legacy.py must be in # /usr/prog/klipper/klippy/extras/). With auto_import (default) the stock # firmware's nozzle/station/dock numbers are loaded at every startup for as # long as no [ff_tool n] has a saved nozzle position, so a fresh install @@ -30,10 +30,12 @@ # FF_IMPORT_FIRMWARE_CONFIG ; reads /usr/data/firmwareRes/config # SAVE_CONFIG ; persists nozzle_*/station_*/z_adjust # -# The command also prints an [ff_tool n] / [ff_toolchange] / [ff_tool_offset] -# snippet with dock coordinates, feeds and the station start point -- paste -# that into ff-toolchange.cfg / ff-tool-offset.cfg by hand; those options are -# deliberately not autosaved. Afterwards the include can be removed. +# dock_x/dock_y and the station start point are staged for SAVE_CONFIG along +# with the rest -- nothing has to be pasted anywhere by hand. The command also +# prints an [ff_toolchange] snippet (x_correction, temp_offset, fast_feed, +# slow_feed, release_slow_feed), but only when the JSON disagrees with the +# running value; on a stock unit it never does. Keep this file included: see +# auto_import above. [ff_legacy] #auto_import: True diff --git a/payload/klipper/config/ff-print-macros.cfg b/payload/klipper/config/ff-print-macros.cfg index 7a309fa..5e71e0a 100644 --- a/payload/klipper/config/ff-print-macros.cfg +++ b/payload/klipper/config/ff-print-macros.cfg @@ -21,7 +21,7 @@ # ff-print-macros.cfg -- print-lifecycle macros for Mainsail/Moonraker prints # # The touchscreen app implements ALL print-lifecycle behavior itself (see -# OKF/61-print-lifecycle-verified.md for the address-verified sequences); the +# docs/notes/50-print-lifecycle.md for the address-verified sequences); the # Klipper fork ships no START/END/PAUSE/RESUME/CANCEL macros. These macros # reproduce the app's sequences for prints started from Mainsail. # @@ -31,13 +31,15 @@ # (the nozzle clean reuses its helpers and tunables) to EXIST, but not to come # first: nothing here reads their config at parse time. # -# [ff_print] (klippy-extras/ff_print.py) is declared at the end of this file: +# [ff_print] (payload/klipper/extras/ff_print.py) is declared at the end of +# this file: # it wraps SDCARD_PRINT_FILE and M23 so a print prepares the machine for # itself, with no slicer changes at all. It takes those commands over at # klippy:connect, once every section is loaded, so it does not care whether # [virtual_sdcard] was declared before or after this file. # -# Deliberate divergences from firmwareExe (documented in OKF/61): +# Deliberate divergences from firmwareExe (documented in +# docs/notes/50-print-lifecycle.md): # * resume reheats all tools in parallel, not e1+e2 then e3+e4 staged # * no dmesg/meminfo dumps, no drop_caches shell calls # * idle timeout after resume goes back to 864000 (app oddly uses 600000) @@ -55,8 +57,8 @@ # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- -# START_PRINT -- put in the slicer's machine start gcode, e.g. (OrcaSlicer, -# see orca/machine-start-gcode.txt for the full snippet): +# START_PRINT -- called for you by [ff_print] (see prepare, below). If you do +# put it in the slicer's machine start gcode instead, e.g. (OrcaSlicer): # START_PRINT BED=[bed_temperature_initial_layer_single] TOOL=[initial_extruder] \ # NOZZLE=[nozzle_temperature_initial_layer] LAYER=[layer_height] LEVEL=0 \ # TOOLS=0:220,2:240 diff --git a/payload/klipper/config/ff-runout.cfg b/payload/klipper/config/ff-runout.cfg index e9989ef..ac68a95 100644 --- a/payload/klipper/config/ff-runout.cfg +++ b/payload/klipper/config/ff-runout.cfg @@ -37,14 +37,14 @@ # path is the app's in-print feed), then RESUME. No endless-spool: another # tool is another head, not another spool. # -# INSTALL: include AFTER printer.base.cfg (which includes the stock -# printer.filament.cfg). Klipper merges repeated sections, later options win -# (configfile.py: RawConfigParser(strict=False)), so this file only has to -# restate the options it changes. REQUIRED -- not an optional drop-in: -# [include printer.base.cfg] -# ... -# [include ff-runout.cfg] -# Do not edit printer.filament.cfg -- the OTA overwrites it. +# INSTALL: nothing to do. printer.base.cfg ships [include ff-runout.cfg] at +# its end, along with the rest of the ff-*.cfg set -- do NOT add it to +# printer.cfg yourself, that double-includes. +# +# It lands AFTER the stock printer.filament.cfg, which is what makes this file +# work: Klipper merges repeated sections and later options win (configfile.py: +# RawConfigParser(strict=False)), so this file only has to restate the options +# it changes. Do not edit printer.filament.cfg -- the OTA overwrites it. # --------------------------------------------------------------------------- [filament_switch_sensor fd_ex0] @@ -70,6 +70,9 @@ insert_gcode: [filament_motion_sensor fm_ex1] pause_on_runout: False +# Stock binds all four motion sensors to `extruder` (extruder0), so a clog on +# T1..T3 was measured against T0's movement. Each sensor watches its own tool. +extruder: extruder1 runout_gcode: _FF_RUNOUT TOOL=1 KIND=motion insert_gcode: @@ -84,6 +87,7 @@ insert_gcode: [filament_motion_sensor fm_ex2] pause_on_runout: False +extruder: extruder2 runout_gcode: _FF_RUNOUT TOOL=2 KIND=motion insert_gcode: @@ -98,6 +102,7 @@ insert_gcode: [filament_motion_sensor fm_ex3] pause_on_runout: False +extruder: extruder3 runout_gcode: _FF_RUNOUT TOOL=3 KIND=motion insert_gcode: diff --git a/payload/klipper/config/ff-tool-offset.cfg b/payload/klipper/config/ff-tool-offset.cfg index 021b1bd..09a11c4 100644 --- a/payload/klipper/config/ff-tool-offset.cfg +++ b/payload/klipper/config/ff-tool-offset.cfg @@ -19,25 +19,29 @@ # --------------------------------------------------------------------------- # ff-tool-offset.cfg -- nozzle XY/Z offset calibration, Klipper-native. # -# Implementation: klippy-extras/ff_tool_offset.py (copy next to +# Implementation: payload/klipper/extras/ff_tool_offset.py (copy next to # ff_toolchange.py in /usr/prog/klipper/klippy/extras/). -# Recovered sequence: firmwareExe-decompiled/recovered/offset-calibration.md +# Recovered sequence: docs/notes/46-offset-calibration-recovered.md # # What it does # Both calibration commands REQUIRE PLATE_REMOVED=1: the station sits below # the bed plane and the Z probe cannot tell a build plate from air. # # TOOL_OFFSET_CALIBRATE TOOL=<0..3|ALL> per tool: T pickup, zero the -# G-code offset, go to (cylinder_x - 12.5, cylinder_y), ESTOP Z to -5, -# four ESTOP probes outward (+X +Y -X -Y, 14 mm) at Z+0.6, circle fit, -# second pass centred on that fit with Z re-probed, store the second -# fit as t_offset_x/y/z. Identical to the touchscreen's +# G-code offset, go to (cylinder_x - 12.5, cylinder_y), ESTOP Z to +# z_target (default -3; the app used -5), four ESTOP probes outward +# (+X +Y -X -Y, 14 mm) at Z+0.6, circle fit, second pass centred on that +# fit with Z re-probed, store the second fit as [ff_tool n]'s +# nozzle_x/nozzle_y/nozzle_z. Identical to the touchscreen's # "Four-point" (default) calibration, constants included. # STATION_CALIBRATE same with an EMPTY carriage from # (cylinder_x, cylinder_y); second Z probe to -3; stores -# x/y/z_station_pos. z_station_pos feeds TOOLCHANGE_SET_PRINT_OFFSET, -# so re-run this whenever the station or bed is disturbed. -# TOOL_OFFSET_STATUS saved vs firmware values side by side. +# station_x/y/z in [ff_tool_offset]. station_z feeds +# TOOLCHANGE_SET_PRINT_OFFSET, so re-run this whenever the station or +# bed is disturbed. +# TOOL_OFFSET_STATUS the saved values: cylinder start +# point, each tool's nozzle triple (or NOT CALIBRATED), the station +# triple, and whether a calibration is staged but unsaved. # # Where results go # The SAVE_CONFIG block of printer.cfg, like PID or input-shaper results: diff --git a/payload/klipper/config/ff-toolchange.cfg b/payload/klipper/config/ff-toolchange.cfg index 81d7cff..a79e4c6 100644 --- a/payload/klipper/config/ff-toolchange.cfg +++ b/payload/klipper/config/ff-toolchange.cfg @@ -19,15 +19,15 @@ # --------------------------------------------------------------------------- # ff-toolchange.cfg -- configuration for the native Creator 5 Pro toolchanger. # -# Implementation: klippy-extras/ff_tool.py + ff_toolchange.py. A built package -# installs them; see INSTALL below for the by-hand route. +# Implementation: payload/klipper/extras/ff_tool.py + ff_toolchange.py. +# A built package installs them; see INSTALL below for the by-hand route. # # It is a Python extra rather than a set of gcode_macros because firmwareExe's # sequence is a polling state machine -- it waits for the grab sensor to read # active BEFORE energising the lock motor, and retries the whole approach up to # three times if it never does. A gcode_macro renders its entire template # before executing any of it, so it cannot poll or retry. -# See OKF/31-recovered-toolchange-sequences.md. +# See docs/notes/30-toolchange.md. # # --------------------------------------------------------------------------- # Where the per-unit numbers live @@ -49,10 +49,12 @@ # The per-unit values belong in the "#*#" block at the end of printer.cfg, # like pid_* do. # -# First install on a machine that was calibrated from the touchscreen: -# include ff-legacy.cfg, run FF_IMPORT_FIRMWARE_CONFIG, SAVE_CONFIG -- it -# stages the factory dock/nozzle/station numbers. Check with -# TOOLCHANGE_STATUS before moving anything. +# First install on a machine that was calibrated from the touchscreen: nothing +# to include -- printer.base.cfg already ships ff-legacy.cfg, and its +# auto_import runs FF_IMPORT_FIRMWARE_CONFIG on every startup until a tool has +# a saved nozzle position. Run SAVE_CONFIG to keep the staged factory +# dock/nozzle/station numbers. Check with TOOLCHANGE_STATUS before moving +# anything. # # Tool-to-tool offsets are DIFFERENCES of nozzle_* against offset_base (T0), # exactly as CommMgr::setGrabGcodeOffsetMgr computes them: @@ -88,7 +90,7 @@ # [include] lines for all of them. Flashing is the whole install. # # By hand, onto a printer that is already modded: -# 1. cp klippy-extras/ff_*.py /usr/prog/klipper/klippy/extras/ +# 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.) @@ -106,8 +108,8 @@ # It does matter where one file overrides another's values: ff-runout.cfg # after printer.base.cfg's sensor sections, ff-chamber.cfg after # printer.chamber.cfg. -# 3. FF_IMPORT_FIRMWARE_CONFIG + SAVE_CONFIG (see README) fills in the -# dock and nozzle data. +# 3. FF_IMPORT_FIRMWARE_CONFIG + SAVE_CONFIG (see ff-legacy.cfg) fills in +# the dock and nozzle data. # 4. Run TOOLCHANGE_STATUS and confirm the sensor names AND the geometry # before moving anything. # --------------------------------------------------------------------------- @@ -209,7 +211,7 @@ stop_macro: MOTOR_STOP # --------------------------------------------------------------------------- # MOVED: Moonraker's print entry point used to be a SDCARD_PRINT_FILE wrapper -# here. It is now [ff_print] (klippy-extras/ff_print.py), which takes the +# here. It is now [ff_print] (payload/klipper/extras/ff_print.py), which takes # command over in Python at klippy:connect and calls FF_BEFORE_PRINT_START # (ff-print-macros.cfg) before chaining to the builtin -- two wrappers on one # command would fight over the rename, so only one may exist. The refusal to diff --git a/payload/klipper/extras/ff_legacy.py b/payload/klipper/extras/ff_legacy.py index 1b7fcba..88ebe06 100644 --- a/payload/klipper/extras/ff_legacy.py +++ b/payload/klipper/extras/ff_legacy.py @@ -1,8 +1,10 @@ # One-shot migration helpers from firmwareExe's per-unit JSON to Klipper config. # -# Nothing here runs during normal operation. Load it with an empty -# [ff_legacy] section when you need it, run the command once, SAVE_CONFIG, -# and you can remove the section again. +# [ff_legacy] is shipped permanently (printer.base.cfg includes ff-legacy.cfg). +# With auto_import -- the default -- _handle_ready re-imports on EVERY +# klippy:ready until some tool has a saved nozzle position, so a fresh install +# comes up with the calibration firmwareExe already made. Run SAVE_CONFIG once +# to persist it; after that the import finds a saved nozzle and does nothing. # # FF_IMPORT_FIRMWARE_CONFIG [DIR=/usr/data/firmwareRes/config] [APPLY=1] # @@ -25,7 +27,7 @@ # # Key names and the struct layout were read off the binary # (Config::loadExtruderConfig @0x64f7f8, loadTestConfig @0x654380, -# loadZOffsetConfig @0x65621c); see OKF/32-config-provenance.md. The files +# loadZOffsetConfig @0x65621c); see docs/notes/40-offsets.md. The files # are NOT strict JSON: each ends with a trailing C comment after the closing # brace, so we use raw_decode() and ignore the tail. diff --git a/payload/klipper/extras/ff_print.py b/payload/klipper/extras/ff_print.py index d8c9a05..59fa802 100644 --- a/payload/klipper/extras/ff_print.py +++ b/payload/klipper/extras/ff_print.py @@ -7,7 +7,7 @@ # that prepares the machine: something must home, clean and grab a tool # BEFORE the file's first line. The touchscreen app did exactly that -- it # ran its whole prepare sequence and only then sent M23/M24 (see -# OKF/61-print-lifecycle-verified.md). It never subscribed to a print-start +# docs/notes/50-print-lifecycle.md). It never subscribed to a print-start # event, because it *was* the thing starting the print. # # This module restores that ordering for prints started from Moonraker / diff --git a/payload/klipper/extras/ff_tool_offset.py b/payload/klipper/extras/ff_tool_offset.py index f6502d6..a60d777 100644 --- a/payload/klipper/extras/ff_tool_offset.py +++ b/payload/klipper/extras/ff_tool_offset.py @@ -7,7 +7,7 @@ # CommMgr::moveCylinderPos @0x789a1c / @0x78a610 # CommMgr::estopManager @0x788738 # BaseFunction::fitCircleStable / fitCircleByLeastSquares @0x6467f8 / @0x645b10 -# Full walkthrough: firmwareExe-decompiled/recovered/offset-calibration.md +# Full walkthrough: docs/notes/46-offset-calibration-recovered.md # # The physical setup: a fixed inductive "cylinder" station under the bed # (levelboard PD0, exposed to Klipper as [e_stop X]/[e_stop Y]/[e_stop Z] -- @@ -16,7 +16,8 @@ # -Y until the bore wall fires it. Four boundary points -> least-squares circle # -> the bore axis in this tool's nozzle frame. Done twice (the second pass is # centred on the first fit and re-probes Z there); the second result is stored -# as t_offset_x/y/z -- RAW machine coordinates with the G-code offset +# as [ff_tool n]'s nozzle_x/nozzle_y/nozzle_z -- RAW machine coordinates with +# the G-code offset # zeroed, nothing subtracted. Tool-to-tool differences of those are what the # toolchanger applies (ff_toolchange._derive_offsets). # diff --git a/payload/klipper/extras/ff_toolchange.py b/payload/klipper/extras/ff_toolchange.py index 6a06c32..96e7ed8 100644 --- a/payload/klipper/extras/ff_toolchange.py +++ b/payload/klipper/extras/ff_toolchange.py @@ -3,8 +3,7 @@ # A faithful port of firmwareExe's own sequences, recovered from the binary: # CommMgr::doGrabExtruderLatest @ 0x7a8190 # CommMgr::doReleaseExtruderLatest @ 0x7aa394 -# See OKF/31-recovered-toolchange-sequences.md and -# firmwareExe-decompiled/recovered/toolchange.c. +# See docs/notes/30-toolchange.md. # # This exists as a Python extra rather than a set of gcode_macros because the # original is a polling state machine: it waits for sensors, retries, and only @@ -187,7 +186,7 @@ class FFToolchange: # Per-tool G-code offsets -- differences against a base tool, exactly # as CommMgr::setGrabGcodeOffsetMgr computes them. See _derive_offsets - # and OKF/33-per-tool-offsets.md. + # and docs/notes/40-offsets.md. self.offset_base = config.getint('offset_base', 0, minval=0, maxval=EXTRUDER_COUNT - 1) self.off_x = self.off_y = self.off_z = [0.0] * EXTRUDER_COUNT @@ -1105,7 +1104,7 @@ class FFToolchange: 100.0 @0xedc950, cutoff 10 is the `slti 0xb` @0x9fc6c8 The JSON-sourced factors: tempOffset (testConfig+0xc, 0.00045 here), t*_offset_z / z_station_pos = nozzle-touch vs eddy-touch calibration - against the fixed under-bed sensor. Derivation: OKF/62. + against the fixed under-bed sensor. Derivation: docs/notes/40-offsets.md. The gap term (nozzle_z - station_z + z_adjust) is already the grab offset (off_z, applied by every T); this command re-asserts it and diff --git a/payload/start.sh b/payload/start.sh index 7e4df94..90638d2 100755 --- a/payload/start.sh +++ b/payload/start.sh @@ -6,12 +6,14 @@ # /usr/data/anvil/init.d/S60web, so there is exactly one place that starts # nginx and moonraker and one place to restart them from over ssh. # -# Two changes from stock: +# Three changes from stock: # * klipper_pri.sh is actually invoked. FlashForge ships that script but # never calls it, so klippy runs at normal priority. # * ff-mcu-bringup.py hands the heat and level boards over from their # bootloaders. Stock never needed it here because firmwareExe did all # three boards itself; replacing firmwareExe left two of them stranded. +# * the /tmp/uds idempotence guard below, so S70klipper's retry loop cannot +# start a second klippy against the same MCU. cmd_mcu write_firmware /usr/prog/libmcu-bare.bin cmd_mcu bootup diff --git a/test/integration/printer/Dockerfile b/test/integration/printer/Dockerfile index 7249c90..6937d38 100644 --- a/test/integration/printer/Dockerfile +++ b/test/integration/printer/Dockerfile @@ -4,7 +4,7 @@ # # It carries qemu-mipsel-static and nothing else of consequence: every command # the tests care about is executed by the printer's OWN busybox/coreutils out of -# the extracted rootfs.squashfs, under chroot. See test/integration/printer-exec.sh. +# the extracted rootfs.squashfs, under chroot. See test/integration/printer-exec.py. FROM debian:bookworm-slim RUN apt-get update -qq \ diff --git a/test/integration/printer/Dockerfile.full b/test/integration/printer/Dockerfile.full index 34fc262..e75be41 100644 --- a/test/integration/printer/Dockerfile.full +++ b/test/integration/printer/Dockerfile.full @@ -74,7 +74,7 @@ COPY --from=fetch /rootfs/ /rootfs/ COPY --from=fetch /parts/ /parts/ # The harness: binfmt registration, mount layout, prog checks, entrypoint. -# Build context is test/printer. +# Build context is test/integration/printer. COPY . /opt/printer/ RUN chmod +x /opt/printer/*.sh diff --git a/test/integration/printer/entrypoint.sh b/test/integration/printer/entrypoint.sh index 422e9d3..b174b13 100755 --- a/test/integration/printer/entrypoint.sh +++ b/test/integration/printer/entrypoint.sh @@ -21,7 +21,7 @@ FF_KEY="${FF_KEY:-FFP0331&*%root}" # USB_STICK=1 -- build a real FAT filesystem out of the packages before the # machine is assembled, so app_startup.sh can find and mount it itself. See -# assemble.sh for the loop device, and case-boot.sh for what uses it. +# assemble.sh for the loop device, and case-install.sh for what uses it. if [ "${USB_STICK:-0}" = 1 ]; then SZ=64 for spec in ${PKGS:-}; do diff --git a/test/integration/test_includes.py b/test/integration/test_includes.py index a33052f..ce18f32 100644 --- a/test/integration/test_includes.py +++ b/test/integration/test_includes.py @@ -29,7 +29,8 @@ import pytest INCLUDE = re.compile(r"^\[include\s+(\S+)\]\s*$") # Everything the mod ships, in the order printer.base.cfg must list them. -# ff-runout and ff-chamber are last because they override earlier sections. +# ff-runout and ff-chamber come after the stock sections they override; +# ff-legacy is last, so its import sees every [ff_tool] already declared. EXPECTED = [ "ff-toolchange.cfg", "ff-tool-offset.cfg",