From f396dbde34d213e07523d1ac2ae7038a94577fc9 Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Mon, 3 Aug 2026 11:53:24 -0400 Subject: [PATCH] Remove history narrative and audit references from docs and tooling temp_calibrate now cross-checks the factory B-equation instead of the retired linear guess. --- .gitignore | 3 ++ BUILD.md | 4 +- docs/LIGHTBURN.md | 2 +- kas/README.md | 40 ++++++++----------- kas/forgefirm-glowforge.yml | 8 ++-- .../recipes-extended/ulfius/ulfius_2.7.15.bb | 6 +-- .../images/forgefirm-image-dev.bb | 5 +-- scripts/bench/README.md | 24 +++++------ scripts/bench/bench_phase2.py | 10 ++--- scripts/bench/check_pwm.py | 4 +- scripts/bench/feeder.c | 6 +-- scripts/bench/temp_calibrate.py | 35 +++++++++------- scripts/ffboot | 3 +- scripts/install-forgefirm.sh | 5 +-- 14 files changed, 76 insertions(+), 79 deletions(-) diff --git a/.gitignore b/.gitignore index 4634139..21ac90f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ # kas *.lock.yml.bak + +# Python bytecode +__pycache__/ diff --git a/BUILD.md b/BUILD.md index 9e3d3e7..24f85d1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,8 +1,8 @@ # Build ForgeFIRM is built with [**kas**](https://kas.readthedocs.io/), which manages -the Yocto layers and pins their versions. This replaces the old Google `repo` -+ `default.xml` workflow. The **forgefirm** repo is the base of the build. +the Yocto layers and pins their versions. The **forgefirm** repo is the base +of the build. Builds run on a Linux host, or on **WSL2** (officially supported by Yocto — keep the tree on the native ext4 filesystem, not `/mnt/c`). diff --git a/docs/LIGHTBURN.md b/docs/LIGHTBURN.md index d223efe..a875e93 100644 --- a/docs/LIGHTBURN.md +++ b/docs/LIGHTBURN.md @@ -1,6 +1,6 @@ # LightBurn setup & operation (ForgeFIRM, motion-only stage) -Status: written 2026-08-02 for the grblHAL-glowforge controller. At this +Status: motion-only. At this stage **the laser cannot fire** — the driver forces the hardware laser latch locked and never emits the laser bit in the pulse stream. A "job" runs every motion of the design (cuts at cut speed, travels at travel diff --git a/kas/README.md b/kas/README.md index 998ba5f..7dc97d5 100644 --- a/kas/README.md +++ b/kas/README.md @@ -3,8 +3,7 @@ The **forgefirm** repo is the base of the project: it controls the build, the resulting firmware images land here, and all build/install docs live here. It uses [**kas**](https://kas.readthedocs.io/) to manage Yocto layers and drive -the build, replacing the old Google `repo` + `default.xml` manifest and the -hand-maintained `base/conf/forgefirm-bblayers.conf`. +the build. ## Baseline @@ -88,25 +87,21 @@ kas auto-loads the lockfile on subsequent runs. Commit it; refresh deliberately. ## Push & release order (source-of-truth sequencing) The build is only reproducible when recipe pins, layer branches, and the kas -config move in the right order (audit findings M13/N14 — the old `AUTOREV` + -untracked-overlay setup meant a plain `kas build` fetched the wrong camera -code). The sequence, with current status: +config move in the right order. The sequence, with current status: -1. **Source repos pushed & pinned** — **DONE 2026-07-26.** +1. **Source repos pushed & pinned** — **DONE.** `kernel-module-glowforge` (`029dfb6`) and `python3-gfhardware` (`9bf31fd`) pushed to GitHub master; both recipes in meta-openglow pin those exact SRCREVs (no `AUTOREV` anywhere). Whenever either repo changes: push it, then bump the recipe `SRCREV` in meta-openglow deliberately. -2. **meta-openglow pushed** — **DONE 2026-07-26.** The Scarthgap port lives on +2. **meta-openglow pushed** — **DONE.** The Scarthgap port lives on the **`scarthgap` branch** (Yocto layer convention; the Dunfell-era `master` is untouched). Development continues on the local sibling checkout; push / fast-forward `scarthgap` as work lands. 3. **forgefirm pushed** with the kas config and a `kas lock` lockfile pinning the upstream layers (poky, meta-openembedded, meta-freescale, meta-freescale-distro). -4. **At release time** — gated on the audit **Phase 1 motion fixes** (do not - ship an image while the cnc-probe and SDMA-firmware-collision defects make - motion dead on arrival, AUDIT-REPORT B1/M1): +4. **At release time**: - flip `meta-openglow` in `forgefirm-glowforge.yml` from the local-sibling block to the pinned-remote block (commented FUTURE block in the file); - drop meta-openglow's `kernel-module-glowforge.bbappend` (externalsrc to @@ -117,13 +112,10 @@ code). The sequence, with current status: 5. **GitHub release**: upload the image asset under the exact name the installer downloads — Scarthgap emits `forgefirm-image-glowforge.rootfs.wic.gz`; align BUILD.md and - `install-forgefirm.sh` to one name before the first release (audit N13). + `install-forgefirm.sh` to one name before the first release. -Dev-overlay note: `kas/externalsrc-dev.yml` (an untracked overlay building -python3-gfhardware from a local checkout via `/mnt/c`) was removed when the -SRCREV was pinned. For future gfhardware development, either bump the pin per -iteration or add a tracked externalsrc bbappend mirroring the kernel-module -pattern. +For gfhardware development, either bump the recipe pin per iteration or add a +tracked externalsrc bbappend mirroring the kernel-module pattern. ## Scarthgap migration backlog @@ -138,7 +130,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until: 2. **Kernel forward-port (4.14 → linux-fslc 6.12.20)** — the factory NXP vendor kernel (linux-imx 4.14.98) carried 7 out-of-tree changes; these are re-derived against mainline 6.12 in `meta-glowforge-bsp/recipes-kernel/linux/linux-fslc_%.bbappend` - (the forward-port landing zone), **not** re-applied as the old 4.14 patches. + (the forward-port landing zone), **not** re-applied as the 4.14 patches. - **Foundation — DONE.** `linux-fslc` 6.12.20 builds for `glowforge` with a ported device tree (`glowforge.dts` + `openglow_common.dtsi` overlaid into `arch/arm/boot/dts/nxp/imx/`, registered via a Makefile patch) and deploys @@ -178,9 +170,9 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until: `glowforge.dtb` compiles with the full pipeline. **HW bring-up:** confirm the real supply rails, CSI-2 lane count/order and CAM_SEL polarity, then validate with `media-ctl` + a v4l2 capture. -3. **u-boot** — ~~rebuild for Scarthgap~~ **DONE.** The `glowforge` u-boot is now - a standalone `u-boot_2020.01.bb` (Scarthgap dropped the Dunfell-era poky base - recipe the old `.bbappend` extended). It reuses poky's +3. **u-boot** — **DONE.** The `glowforge` u-boot is + a standalone `u-boot_2020.01.bb` (Scarthgap's poky has no u-boot 2020.01 + base recipe to extend). It reuses poky's `u-boot-common.inc`/`u-boot.inc`, pins `SRCREV` to the upstream **v2020.01** tag with the matching `Licenses/README` md5, and overlays the glowforge board support + arch-Kconfig patch. **Builds clean under Scarthgap (GCC 13, no @@ -190,7 +182,7 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until: them. 4. **Device tree** — revalidate the `glowforge` `.dts` against the linux-fslc 6.12 DT bindings (paired with the kernel forward-port in #2). -5. **Real-time strategy — decided (2026-07-26, audit M9/N15).** The kernel runs +5. **Real-time strategy — decided.** The kernel runs `CONFIG_PREEMPT=y` (factory behavior; `imx_v6_v7_defconfig` alone gives only `PREEMPT_VOLUNTARY`). **PREEMPT_RT is not selectable on arm32 6.12** (no `ARCH_SUPPORTS_RT`) and is **not needed for the pulse feeder**: the SDMA @@ -199,10 +191,10 @@ Scarthgap, but the legacy (Dunfell/Gatesgarth) layers won't build clean until: a modest 1 MiB of queued data already rides out ~3–5 s of scheduling latency, orders of magnitude beyond anything PREEMPT exhibits. Deep buffering + `SCHED_FIFO` for the feeder is the design; revisit RT only if - the underrun bench ever contradicts this arithmetic. (The audit Phase 2 - bench: 5 s of continuous feed at a 1 s buffer depth, zero underruns.) + the underrun bench ever contradicts this arithmetic. (Bench: 5 s of + continuous feed at a 1 s buffer depth, zero underruns.) -6. **gfui-client → forgectrl** — the Glowforge **cloud client is now removed** +6. **gfui-client → forgectrl** — the Glowforge **cloud client is excluded** from `forgefirm-image` (`IMAGE_INSTALL:remove = "gfui-client"` in `meta-forgefirm/recipes-forgefirm/images/forgefirm-image.bb`) — it connected to Glowforge's servers, the dependency ForgeFIRM exists to cut. The grblHAL diff --git a/kas/forgefirm-glowforge.yml b/kas/forgefirm-glowforge.yml index eab15c9..6c1823b 100644 --- a/kas/forgefirm-glowforge.yml +++ b/kas/forgefirm-glowforge.yml @@ -9,8 +9,7 @@ # Machine: glowforge (i.MX6 Solo SOM inside Basic/Plus/Pro) # Distro : forgefirm # -# Replaces the old Google `repo` + default.xml manifest and the hand-maintained -# base/conf/forgefirm-bblayers.conf. kas generates bblayers.conf and local.conf. +# kas generates bblayers.conf and local.conf. # # Run from the forgefirm repo root so outputs land inside it: # cd forgefirm @@ -86,8 +85,7 @@ repos: meta-glowforge-bsp: # meta-openglow-bsp (separate OpenGlow_std board) intentionally excluded. # - # FUTURE — flip to this pinned-remote block at release time (the scarthgap - # branch is pushed as of 2026-07-26, so this now works; the flip is gated on + # FUTURE — flip to this pinned-remote block at release time (gated on # active BSP development settling — see kas/README.md "Push & release order". # When flipping, also drop meta-openglow's kernel-module-glowforge.bbappend # (externalsrc to the local sibling) so a fresh clone is self-contained; its @@ -110,7 +108,7 @@ local_conf_header: eula: | ACCEPT_FSL_EULA = "1" - # Kernel is now defaulted to linux-fslc 6.12 in conf/machine/glowforge.conf + # The kernel defaults to linux-fslc 6.12 in conf/machine/glowforge.conf # (with the factory drivers forward-ported — EPIT/SDMA/OV5648/glowforge.ko — # see kas/README.md backlog #2). This explicit pin is redundant but harmless. kernel: | diff --git a/meta-forgefirm/recipes-extended/ulfius/ulfius_2.7.15.bb b/meta-forgefirm/recipes-extended/ulfius/ulfius_2.7.15.bb index e55333b..49b454d 100644 --- a/meta-forgefirm/recipes-extended/ulfius/ulfius_2.7.15.bb +++ b/meta-forgefirm/recipes-extended/ulfius/ulfius_2.7.15.bb @@ -6,9 +6,9 @@ LIC_FILES_CHKSUM = "file://LICENSE;md5=40d2542b8c43a3ec2b7f5da31a697b88" # Pinned to the v2.7.15 release tag, together with orcania v2.3.3 and # yder v1.4.20 (its release-mate dependency versions). AUTOREV against master -# was unbuildable: master's CMake requires orcania/yder versions that have no -# release tags, and the git:// fetch never brought in the bundled-subproject -# fallback (audit M10). +# is unbuildable: master's CMake requires orcania/yder versions that have no +# release tags, and the git:// fetch never brings in the bundled-subproject +# fallback. SRC_URI = "git://github.com/babelouest/ulfius;protocol=https;branch=master" SRCREV = "a0603447d3ed63c0880db396b9c395fb4bf6b559" diff --git a/meta-forgefirm/recipes-forgefirm/images/forgefirm-image-dev.bb b/meta-forgefirm/recipes-forgefirm/images/forgefirm-image-dev.bb index baf6d53..8a1c520 100644 --- a/meta-forgefirm/recipes-forgefirm/images/forgefirm-image-dev.bb +++ b/meta-forgefirm/recipes-forgefirm/images/forgefirm-image-dev.bb @@ -2,9 +2,8 @@ require forgefirm-image.bb DESCRIPTION = "OpenGlow/ForgeFIRM development image for Glowforge" -# Strict superset of forgefirm-image (audit N12): everything the main image -# ships (incl. the gfui-client removal and v4l-utils), plus the forgectrl -# placeholder and debug tooling. +# Strict superset of forgefirm-image: everything the main image ships, plus +# the forgectrl placeholder and debug tooling. IMAGE_INSTALL += " \ forgectrl \ " diff --git a/scripts/bench/README.md b/scripts/bench/README.md index 7c76139..8480eb1 100644 --- a/scripts/bench/README.md +++ b/scripts/bench/README.md @@ -5,23 +5,23 @@ target board (dev image, python3 present) unless noted. | Tool | Purpose | |---|---| -| `feeder.c` | Spike-step-3 underrun proof: streams NOP pulse bytes to `/dev/glowforge` with wall-clock pacing, bounded queue depth, deadman flock, SCHED_FIFO. Usage: `feeder `. Passed 100 kHz × 120 s under full load with 0.2 ms worst write latency. Cross-compile with `build-feeder.sh` (WSL). | -| `bench_phase2.py` | End-of-data protocol bench (audit M2–M5): underrun detection/ack, parked no-replay guard, resume(0), continuous-feed stability, 20× run/underrun cycles. Motion-safe (motors locked, laser latched). 16/16 PASS on 2026-07-26. | -| `check_pwm.py` | Laser PWM register check (audit M8): reads PWM2 PWMCR/PWMPR via /dev/mem, expects divider 13 × ~127 counts ≈ 40 kHz. | -| `pwm_sweep.py` | LASER_PWM scope test (runs on the board): `check` = read-only safety readbacks + PWM2 dump; `sweep` = steps PWMSAR through 50/25/75/6/100 % duty with 4 s holds, then restores. Run only in the locked state (controller stopped, cnc disabled, latch locked). Waveform gate PASSED with it 2026-08-02: 40 kHz stable, duty tracks PWMSAR (6.4 % measured vs 6.3 % commanded at the low end). | +| `feeder.c` | Underrun proof: streams NOP pulse bytes to `/dev/glowforge` with wall-clock pacing, bounded queue depth, deadman flock, SCHED_FIFO. Usage: `feeder `. Proven envelope: 100 kHz × 120 s under full load, 0.2 ms worst write latency. Cross-compile with `build-feeder.sh` (WSL). | +| `bench_phase2.py` | End-of-data protocol bench: underrun detection/ack, parked no-replay guard, resume(0), continuous-feed stability, 20× run/underrun cycles. Motion-safe (motors locked, laser latched). | +| `check_pwm.py` | Laser PWM register check: reads PWM2 PWMCR/PWMPR via /dev/mem, expects divider 13 × ~127 counts ≈ 40 kHz. | +| `pwm_sweep.py` | LASER_PWM scope test (runs on the board): `check` = read-only safety readbacks + PWM2 dump; `sweep` = steps PWMSAR through 50/25/75/6/100 % duty with 4 s holds, then restores. Run only in the locked state (controller stopped, cnc disabled, latch locked). | | `pwm_hold.py` | Holds one PWMSAR value for a scope-measurement window (`pwm_hold.py `), then restores. Same locked-state rule. | -| `fire_test.py` | FIRE drop-timing scope test (runs on the board): A = latch locked (expects nothing on FIRE/LASER_ON), B = latch unlocked / normal end-of-data, U = true underrun. Duty 0 throughout; refuses to unlock if HV reports good. All gates PASSED with it 2026-08-02 (2.0000 s pulses exact, both paths). | -| `fan_test.py` | Fan/coolant bench (Windows-side): snapshots fan PWMs/tachs/temps, drives M8 → cut fans, M9 → cooldown → idle, verifying via tach readbacks. All-green 2026-08-02. | -| `flow_characterize.py` | Coolant flow characterization using the factory temperature curve: baseline → flow → no-flow → recovery, printing the ΔT bands and their separation. Takes the heater duty as an argument (`flow_characterize.py 30`); aborts if downstream passes 45 °C. This is what showed the 10 % scheme was unusable (0.04 °C gap) and sized the 30 % check. | +| `fire_test.py` | FIRE drop-timing scope test (runs on the board): A = latch locked (expects nothing on FIRE/LASER_ON), B = latch unlocked / normal end-of-data, U = true underrun. Duty 0 throughout; refuses to unlock if HV reports good. | +| `fan_test.py` | Fan/coolant bench (Windows-side): snapshots fan PWMs/tachs/temps, drives M8 → cut fans, M9 → cooldown → idle, verifying via tach readbacks. | +| `flow_characterize.py` | Coolant flow characterization using the factory temperature curve: baseline → flow → no-flow → recovery, printing the ΔT bands and their separation. Takes the heater duty as an argument (`flow_characterize.py 30`); aborts if downstream passes 45 °C. | | `flow_matrix.py` | **The flow-detection design matrix** (with `flow_sampler.py`, which lives on the board at `/data/`): duty × duration × flow/no-flow, every run from a common cooled baseline, interleaved repeats. One heating trace yields the metric at every candidate duration, so cost and precision come from the same 60 runs. Prints a cost table, a precision table (mean±sd, worst-case margin, d′) and a ranked shortlist. Env: `FM_DUTIES`, `FM_REPEATS`, `FM_RESULTS`. | -| `flow_sustained.py` | Long-run test of the real re-check cadence via M8: counts verdicts/false faults and tracks whether the loop accumulates heat. 40 min ⇒ 0 false faults, loop *cooled* 2 °C. | +| `flow_sustained.py` | Long-run test of the real re-check cadence via M8: counts verdicts/false faults and tracks whether the loop accumulates heat. | | `flow_warm_validate.py` | Runs the real check from a heater-warmed baseline. Note the ceiling: 100 % duty pushes the downstream sensor past 50 °C in 30 s while the bulk barely moves, so warm-loop validation above ~23 °C needs the laser, not the heater. | -| `flow_recheck_char.py` | Characterizes short in-run re-checks and the differential metric; documents two dead ends (over-temp cannot see a stopped pump; passive warming trends are ambiguous). | -| `temp_calibrate.py` | Coolant temperature calibration helper (`watch` / `point ` / `fit`) — pairs a measured temperature with averaged raw ADC readings and fits the line. Used to sanity-check the factory curve against a thermometer. | +| `flow_recheck_char.py` | Characterizes short in-run re-checks and the differential metric; shows why over-temp cannot see a stopped pump and why passive warming trends are ambiguous. | +| `temp_calibrate.py` | Coolant temperature spot-check helper (`watch` / `point ` / `fit`) — pairs a measured temperature with averaged raw ADC readings and fits a per-machine line to sanity-check the factory curve against a thermometer. | | `build-glowforge.sh` | Cross-compiles **grblHAL-glowforge** (the canonical driver repo, `../../../grblHAL-glowforge`) in the forge-yocto WSL distro. Run: `wsl -d forge-yocto -- bash /build-glowforge.sh` (from PowerShell; Git Bash mangles /mnt/c paths). This is the production controller build. | | `build-feeder.sh` | Cross-compiles `feeder.c` the same way. | -| `puls_profile.py` | Decodes factory `.puls` streams (raw or GF1-headered) into velocity/accel profiles: peak speeds, ramp-slope fits, per-move segments, Z cadence. Runs anywhere (stdlib only). Source of the factory-true grblHAL defaults (milestone 2): 700/590 mm/s² accel, 200 mm/s max rate, 28160 Hz travel tick. | -| `bench_m2.py` | Milestone-2 motion bench, runs against the board over TCP:23: bounded round-trip jogs (sanity, max-rate, diagonal) + feed-hold/resume mid-move, reporting peak feed, state transitions, and position drift. All-green 2026-08-02 at factory-true settings. | +| `puls_profile.py` | Decodes factory `.puls` streams (raw or GF1-headered) into velocity/accel profiles: peak speeds, ramp-slope fits, per-move segments, Z cadence. Runs anywhere (stdlib only). Source of the factory-true grblHAL defaults: 700/590 mm/s² accel, 200 mm/s max rate, 28160 Hz travel tick. | +| `bench_m2.py` | Motion-quality bench, runs against the board over TCP:23: bounded round-trip jogs (sanity, max-rate, diagonal) + feed-hold/resume mid-move, reporting peak feed, state transitions, and position drift. | The build scripts borrow the Yocto cross toolchain + sysroot from the ulfius 2.7.15 work directory in the WSL build tree; if that path ages out after a diff --git a/scripts/bench/bench_phase2.py b/scripts/bench/bench_phase2.py index 77d8e29..ef9ba11 100644 --- a/scripts/bench/bench_phase2.py +++ b/scripts/bench/bench_phase2.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 -"""Phase 2.8 bench test: the reworked end-of-data protocol (audit M2-M5). +"""End-of-data protocol bench. Motion-safe by construction: all four motors are locked via motor_lock (the SDMA script masks the STEP bits), the laser latch is locked (LASER_ON pin is Hi-Z and the hardware safety chain is open anyway), and only NOP (0x00) and laser-bit (0x10) bytes are streamed. -Exercises: normal completion, underrun detection/ack (M5), parked no-replay -guard (M4), alldone GPIO clear (M2, via GPIO2_DR readback - the data register -retains the last value the script wrote), resume(0) (M3), continuous-feed -stability, and 20x run/underrun cycling (M3 wedge check). +Exercises: normal completion, underrun detection/ack, parked no-replay +guard, alldone GPIO clear (via GPIO2_DR readback - the data register +retains the last value the script wrote), resume(0), continuous-feed +stability, and 20x run/underrun cycling (wedge check). """ import os, re, sys, time, mmap, struct diff --git a/scripts/bench/check_pwm.py b/scripts/bench/check_pwm.py index af28cb8..357e0ac 100644 --- a/scripts/bench/check_pwm.py +++ b/scripts/bench/check_pwm.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -"""Phase 3.2 software check: laser PWM carrier frequency from PWM2 registers. +"""Software check: laser PWM carrier frequency from PWM2 registers. Expected with the fsl,extra-prescale=<13> fix: PWMCR prescaler divider = 13, PWMPR ~125 (127 counts - 2), effective carrier = perclk / (13 * 127) ~= 40 kHz. -Without the fix: divider 1 -> ~520 kHz (audit M8). Safe: read-only register +Without the fix: divider 1 -> ~520 kHz. Safe: read-only register inspection; the laser PWM output feeds the PSU power input, firing stays gated by the hardware chain. The definitive gate remains the scope on LASER_PWM. """ diff --git a/scripts/bench/feeder.c b/scripts/bench/feeder.c index a35f7be..b4a9778 100644 --- a/scripts/bench/feeder.c +++ b/scripts/bench/feeder.c @@ -1,9 +1,9 @@ /* - * feeder.c - ForgeFIRM spike step 3: prove no-underrun continuous live - * feeding of the glowforge.ko SDMA pulse ring under load (audit 6.3). + * feeder.c - prove no-underrun continuous live feeding of the glowforge.ko + * SDMA pulse ring under load. * * Streams NOP (0x00) pulse bytes to /dev/glowforge, pacing by wall clock to - * hold a bounded queue depth (like a real grblHAL backend would - audit M6), + * hold a bounded queue depth (like a real grblHAL backend would), * with the deadman flock held. Motors must be locked and the laser latch * locked by the caller (bench_phase6.sh does this). * diff --git a/scripts/bench/temp_calibrate.py b/scripts/bench/temp_calibrate.py index 0dbb563..ed4c79a 100644 --- a/scripts/bench/temp_calibrate.py +++ b/scripts/bench/temp_calibrate.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 -"""Coolant temperature calibration helper (runs on Windows, reads the +"""Coolant temperature spot-check helper (runs on Windows, reads the board over ssh). -The raw->Celsius conversion in UAPI.md (C = raw * -0.09653 + 94) is an -unverified best guess. This tool collects reference points - a measured -real temperature paired with the machine's raw ADC readings - and fits -the actual line for THIS machine. +The raw->Celsius conversion in UAPI.md is the factory B-equation (10k +B3380 NTC in a 10k divider behind a 1.3x gain stage, 10-bit ADC). This +tool collects reference points - a measured real temperature paired with +the machine's raw ADC readings - and fits a per-machine line to +cross-check that curve against a thermometer. Usage: temp_calibrate.py watch live raw + current-formula C @@ -18,6 +19,7 @@ machine in the morning, and warm after a fan-off soak with the flow heater on). """ import json +import math import os import subprocess import sys @@ -26,8 +28,15 @@ import time HOST = '172.16.1.97' STORE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'temp_calibration.json') -# Current unverified guess, for comparison only. -GUESS_SLOPE, GUESS_OFFSET = -0.09653, 94.0 + +def uapi_c(raw): + """The UAPI.md factory conversion (B-equation NTC behind divider + gain).""" + adc_f = 1024.0 * 1.3 + if raw <= 0 or raw >= adc_f: + return float('nan') + rinf = 10000.0 * math.exp(-3380.0 / 298.15) + r = 10000.0 / (adc_f / raw - 1.0) + return 3380.0 / math.log(r / rinf) - 273.15 def board(cmd): @@ -79,12 +88,11 @@ def main(): mode = sys.argv[1] if len(sys.argv) > 1 else 'watch' if mode == 'watch': - print('raw1(down) raw2(up) guess-C down/up (ctrl-C to stop)') + print('raw1(down) raw2(up) uapi-C down/up (ctrl-C to stop)') while True: r1, r2 = raws(1, 0) print(' %6.1f %6.1f %.2f / %.2f' - % (r1, r2, r1 * GUESS_SLOPE + GUESS_OFFSET, - r2 * GUESS_SLOPE + GUESS_OFFSET)) + % (r1, r2, uapi_c(r1), uapi_c(r2))) time.sleep(2) elif mode == 'point': @@ -115,11 +123,10 @@ def main(): slope, offset = fit(pts, key) print('\n%s:' % label) print(' fitted: C = raw * %.6f + %.4f' % (slope, offset)) - print(' guess: C = raw * %.6f + %.4f' % (GUESS_SLOPE, GUESS_OFFSET)) for raw in (600, 650, 700, 750, 800): - print(' raw %3d -> fitted %6.2f C guess %6.2f C diff %+.2f' - % (raw, raw * slope + offset, raw * GUESS_SLOPE + GUESS_OFFSET, - (raw * slope + offset) - (raw * GUESS_SLOPE + GUESS_OFFSET))) + print(' raw %3d -> fitted %6.2f C uapi %6.2f C diff %+.2f' + % (raw, raw * slope + offset, uapi_c(raw), + (raw * slope + offset) - uapi_c(raw))) else: print(__doc__) return 1 diff --git a/scripts/ffboot b/scripts/ffboot index 907fe4c..be07369 100644 --- a/scripts/ffboot +++ b/scripts/ffboot @@ -73,8 +73,7 @@ esac # The env always lives on the eMMC (mmcblk2 0x80000/0x82000). On the factory # firmware and on the ForgeFIRM image, /etc/fw_env.config already points # there; the mmcblk2-specific override is honored if someone provisioned one, -# but nothing ships it, so fall back rather than failing on a missing file -# (audit N16). +# but nothing ships it, so fall back rather than failing on a missing file. if [ -f "/etc/fw_env_mmcblk2.config" ] && [ ! -d "/factory" ]; then FWCONFIG="/etc/fw_env_mmcblk2.config" else diff --git a/scripts/install-forgefirm.sh b/scripts/install-forgefirm.sh index f60dc0c..bd38a7b 100644 --- a/scripts/install-forgefirm.sh +++ b/scripts/install-forgefirm.sh @@ -15,9 +15,8 @@ BRIGHT="\033[1;39m" RESET="\033[0m" ASTERISK="${LIGHTRED}✺${RESET}" -# Abort loudly instead of completing silently broken: several steps below -# (image download, flash write, uEnv rewrite) used to fail without stopping -# the install (audit N13/M12/N16). +# Abort loudly instead of completing silently broken: a failed step (image +# download, flash write, uEnv rewrite) must stop the install. die () { echo echo -e "${LIGHTRED}!! INSTALL FAILED:${RESET} $1"