diff --git a/README.md b/README.md index e6227bf..b210fa1 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,8 @@ keypresses silently stop working. Run the test after touching that code; uvk5_stream.py the MJPEG-style frame pump behind /stream uvk5_supervisor.py starts, stops and recovers the emulator process test_kill_emulator.sh cleanup never kills an unrelated process - (plus ad-hoc probe scripts -- scan_trace.sh, gpio_watch.py and friends -- + uvk5_elf.sh where the probe scripts find a firmware (env, then the checkout) +(plus ad-hoc probe scripts -- scan_trace.sh, gpio_watch.py and friends -- kept because they are quick to reach for, not because they are polished) harness/, stubs/, shim/, tests/ host build of the CW timing chain (stage A) diff --git a/README.zh-CN.md b/README.zh-CN.md index 504ed16..00f9aea 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -102,6 +102,7 @@ VFO 重算了状态。在真机上你只看到"什么都没发生",在这里 uvk5_stream.py /stream 背后的抓帧泵 uvk5_supervisor.py 启动、停止、以及故障后恢复模拟器进程 test_kill_emulator.sh 清理逻辑绝不会杀掉无关进程 +uvk5_elf.sh 探针脚本从哪里找固件(环境变量,然后本仓库) (另有一批临时探针脚本 —— scan_trace.sh、gpio_watch.py 等 —— 留着是因为随手就能用,不是因为它们打磨过) harness/, stubs/, shim/, tests/ CW 时序链的宿主机构建(阶段 A) diff --git a/tools/col_probe.sh b/tools/col_probe.sh index 488672d..fa68fc2 100755 --- a/tools/col_probe.sh +++ b/tools/col_probe.sh @@ -6,7 +6,9 @@ # probe only read IDR, which cannot tell those two apart. set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } KEY="${1:-MENU}" BASE=0x50000400 IDR=$((BASE + 0x10)) diff --git a/tools/delay_rate.sh b/tools/delay_rate.sh index 5eba071..48584a1 100755 --- a/tools/delay_rate.sh +++ b/tools/delay_rate.sh @@ -5,7 +5,9 @@ # both twice gives the rate and an estimate of how long the delay will take. set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } GAP="${1:-4}" SCRIPT=$(mktemp --suffix=.gdb) trap 'rm -f "$SCRIPT"' EXIT diff --git a/tools/gpiob_dump.sh b/tools/gpiob_dump.sh index 5712628..a27ef75 100755 --- a/tools/gpiob_dump.sh +++ b/tools/gpiob_dump.sh @@ -5,7 +5,9 @@ # IDR 0x10, ODR 0x14. set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } BASE=0x50000400 SCRIPT=$(mktemp --suffix=.gdb) trap 'rm -f "$SCRIPT"' EXIT diff --git a/tools/key_result.sh b/tools/key_result.sh index d063d07..af316ec 100755 --- a/tools/key_result.sh +++ b/tools/key_result.sh @@ -6,7 +6,9 @@ # the debounce in app.c accept it? set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } KEY="${1:-MENU}" python3 - "$KEY" <<'PY' diff --git a/tools/keypad_probe.sh b/tools/keypad_probe.sh index 7eac96f..dd985d7 100755 --- a/tools/keypad_probe.sh +++ b/tools/keypad_probe.sh @@ -9,7 +9,9 @@ # BSRR: low half sets a pin, high half resets it (py32f071xB.h). set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } KEY="${1:-MENU}" BASE=0x50000400 BSRR=$((BASE + 0x18)) diff --git a/tools/keytest.sh b/tools/keytest.sh index bc7f3a5..05177cf 100755 --- a/tools/keytest.sh +++ b/tools/keytest.sh @@ -6,7 +6,7 @@ # in the matrix wiring or the debounce. set -uo pipefail -TOOLS="$HOME/uvk5-port/sim/tools" +TOOLS="$(cd "$(dirname "$0")" && pwd)" KEY="${1:-MENU}" python3 - "$KEY" <<'PY' diff --git a/tools/pc.sh b/tools/pc.sh index 17507df..e95fbf5 100755 --- a/tools/pc.sh +++ b/tools/pc.sh @@ -6,7 +6,9 @@ # (different PC, or r1 reset for a new call). set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } SAMPLES="${1:-6}" GAP="${2:-2}" SCRIPT=$(mktemp --suffix=.gdb) diff --git a/tools/poll_watch.sh b/tools/poll_watch.sh index f452780..6316fa3 100755 --- a/tools/poll_watch.sh +++ b/tools/poll_watch.sh @@ -6,7 +6,9 @@ # this only observes; the firmware does the driving. set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } KEY="${1:-MENU}" STEPS="${2:-400}" diff --git a/tools/run.sh b/tools/run.sh index b5c286a..1770272 100755 --- a/tools/run.sh +++ b/tools/run.sh @@ -7,9 +7,13 @@ # Usage: run.sh [firmware.elf] set -euo pipefail -QEMU="$HOME/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm" -ELF="${1:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" -FLASH="$HOME/uvk5-port/sim/assets/flash.img" +QEMU="${QEMU:-$(command -v qemu-system-arm || true)}" +[ -n "$QEMU" ] || { echo "SKIP no qemu-system-arm; set QEMU or put it on PATH" >&2; exit 0; } +ELF="${1:-}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } +FLASH="${UVK5_FLASH_IMAGE:-$(cd "$(dirname "$0")/.." && pwd)/assets/flash.img}" QMP=/tmp/uvk5-qmp.sock # Never `pkill -f 'M uv-k5-v3'` here: see tools/lib_kill_emulator.sh for why that diff --git a/tools/scan_trace.sh b/tools/scan_trace.sh index 41df089..b84f10c 100755 --- a/tools/scan_trace.sh +++ b/tools/scan_trace.sh @@ -6,7 +6,9 @@ # the firmware looks, versus the rows going low but the debounce rejecting them. set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } KEY="${1:-MENU}" SAMPLES="${2:-10}" diff --git a/tools/scan_watch.sh b/tools/scan_watch.sh index eb0e442..126743d 100755 --- a/tools/scan_watch.sh +++ b/tools/scan_watch.sh @@ -7,7 +7,9 @@ # go low". set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } KEY="${1:-MENU}" HITS="${2:-12}" diff --git a/tools/test_kill_emulator.sh b/tools/test_kill_emulator.sh index 7b739d1..38e8489 100755 --- a/tools/test_kill_emulator.sh +++ b/tools/test_kill_emulator.sh @@ -46,8 +46,9 @@ kill "$decoy" "$decoy2" 2>/dev/null || true wait 2>/dev/null || true # And a real emulator must actually be terminated. -QEMU="$HOME/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm" -ELF="$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf" +QEMU="${QEMU:-$(command -v qemu-system-arm || true)}" +. "$HERE/uvk5_elf.sh" +ELF=$(uvk5_find_elf || true) IMG=$(mktemp /tmp/kill-test-XXXX.img) gzip -dc "$HERE/../assets/pristine/flash-pristine.img.gz" > "$IMG" diff --git a/tools/test_serial_capture.py b/tools/test_serial_capture.py index 8de630e..45f2ea3 100755 --- a/tools/test_serial_capture.py +++ b/tools/test_serial_capture.py @@ -18,18 +18,25 @@ import time HERE = os.path.dirname(os.path.abspath(__file__)) SIM = os.path.dirname(HERE) -QEMU = os.path.expanduser("~/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm") -ELF = os.path.expanduser("~/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf") +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import uvk5_testenv + +QEMU = uvk5_testenv.qemu() +ELF = uvk5_testenv.firmware() FLASH = os.path.join(SIM, "assets", "flash.img") LOG = "/tmp/uvk5-serial-test.log" QMP = "/tmp/uvk5-serial-test.sock" def main(): - for path, what in ((QEMU, "QEMU binary"), (ELF, "firmware ELF"), - (FLASH, "flash image")): - if not os.path.exists(path): - sys.exit(f"missing {what}: {path}") + missing = uvk5_testenv.missing([ + (QEMU, "QEMU binary", "set QEMU=/path/to/qemu-system-arm, or put it on PATH"), + (ELF, "firmware", "run tools/fetch_firmware.py, or set ELF=..."), + (FLASH, "flash image", "run tools/make_flash.py"), + ]) + if missing: + print("SKIP: %s" % missing) + return 0 if os.path.exists(QMP): os.unlink(QMP) diff --git a/tools/test_webui_e2e.py b/tools/test_webui_e2e.py index d808dcd..485c9b8 100755 --- a/tools/test_webui_e2e.py +++ b/tools/test_webui_e2e.py @@ -25,8 +25,11 @@ HERE = os.path.dirname(os.path.abspath(__file__)) SIM = os.path.dirname(HERE) sys.path.insert(0, HERE) -QEMU = os.path.expanduser("~/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm") -ELF = os.path.expanduser("~/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf") +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import uvk5_testenv + +QEMU = uvk5_testenv.qemu() +ELF = uvk5_testenv.firmware() FLASH = os.path.join(SIM, "assets", "flash.img") QMP = "/tmp/uvk5-webui-e2e.sock" HTTP_PORT = 8099 diff --git a/tools/trace_run.sh b/tools/trace_run.sh index b15d45a..3686a14 100755 --- a/tools/trace_run.sh +++ b/tools/trace_run.sh @@ -6,9 +6,13 @@ # - does the GPIO input callback run? (TRACE gpio... set_input) set -uo pipefail -QEMU="$HOME/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm" -ELF="${1:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" -FLASH="$HOME/uvk5-port/sim/assets/flash.img" +QEMU="${QEMU:-$(command -v qemu-system-arm || true)}" +[ -n "$QEMU" ] || { echo "SKIP no qemu-system-arm; set QEMU or put it on PATH" >&2; exit 0; } +ELF="${1:-}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } +FLASH="${UVK5_FLASH_IMAGE:-$(cd "$(dirname "$0")/.." && pwd)/assets/flash.img}" LOG=/tmp/uvk5-trace.log QMP=/tmp/uvk5-qmp.sock @@ -26,7 +30,7 @@ sleep 1 -kernel "$ELF" -gdb tcp::1234 >"$LOG" 2>&1 & sleep 12 -python3 "$HOME/uvk5-port/sim/tools/key.py" MENU >/dev/null 2>&1 || true +python3 "$(dirname "$0")/key.py" MENU >/dev/null 2>&1 || true sleep 2 echo "== keypad row drives ==" diff --git a/tools/uvk5_elf.sh b/tools/uvk5_elf.sh new file mode 100644 index 0000000..8f61693 --- /dev/null +++ b/tools/uvk5_elf.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +# Where the firmware is, for the ad-hoc probe scripts. +# +# Every one of them used to name the same hardcoded path -- one developer's CW build -- +# so none of them ran on a machine that did not have that file. The order here matches +# tools/uvk5_testenv.py, so the scripts and the tests agree about what is available. +# +# ELF, UVK5_FIRMWARE, UVK5_MULTIBOOT_IMAGE the environment wins +# assets/firmware/*, work/* then whatever the checkout has +# +# Returns non-zero when there is nothing, so a caller can skip with a reason rather +# than run against a path that does not exist. +uvk5_find_elf() { + local var value + for var in ELF UVK5_FIRMWARE UVK5_MULTIBOOT_IMAGE; do + eval "value=\${$var:-}" + if [ -n "$value" ] && [ -e "$value" ]; then + printf '%s' "$value" + return 0 + fi + done + local root cand + root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) + for cand in "$root"/assets/firmware/*.elf "$root"/assets/firmware/*.bin \ + "$root"/work/*.elf "$root"/work/*.bin; do + if [ -e "$cand" ]; then + printf '%s' "$cand" + return 0 + fi + done + return 1 +} diff --git a/tools/uvk5_testenv.py b/tools/uvk5_testenv.py index 78feb0a..a15553a 100644 --- a/tools/uvk5_testenv.py +++ b/tools/uvk5_testenv.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 """Where the emulator tests find a QEMU and a firmware, and what to say when they cannot. -The emulator tests used to name one developer's build directory: - - QEMU = os.path.expanduser("~/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm") - ELF = os.path.expanduser("~/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf") +The emulator tests used to name one developer's build directory -- a QEMU under their +home, and one firmware build from a source tree outside this repository -- which is fine on the machine they were written on and useless anywhere else -- and a missing file used to be a hard failure, so a checkout without that build could never diff --git a/tools/webui.py b/tools/webui.py index 84c8284..bb704b1 100644 --- a/tools/webui.py +++ b/tools/webui.py @@ -1164,6 +1164,29 @@ pollFirmware(); """ +def _default_qemu(): + """A QEMU to spawn: the environment, then PATH. Never one developer's build dir.""" + try: + import uvk5_testenv + return str(uvk5_testenv.qemu() or "qemu-system-arm") + except Exception: + return "qemu-system-arm" + + +def _default_firmware(): + """Whatever firmware this checkout has, or nothing. + + Nothing is the normal case now: a firmware can be uploaded from the page, and the page + says when one is not loaded. tools/uvk5_testenv.py looks in assets/firmware and work. + """ + try: + import uvk5_testenv + firmware = uvk5_testenv.firmware() + return str(firmware) if firmware else None + except Exception: + return None + + def main() -> int: ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--qmp", default="/tmp/uvk5-qmp.sock") @@ -1178,12 +1201,14 @@ def main() -> int: help="attach to an emulator started elsewhere (run.sh) " "instead of managing one. Off is then refused, since " "this server did not start that process.") - ap.add_argument("--qemu", default=os.path.expanduser( - "~/qemu-build/qemu-7.2+dfsg/build/qemu-system-arm")) + # A path or a bare name: Popen resolves a bare name through PATH. The machine is + # built from this checkout, not from one developer's build directory. + ap.add_argument("--qemu", default=_default_qemu()) # Named --elf for history; any .bin or .elf works, and its shape is read out # of the file rather than assumed. More can be uploaded from the page. - ap.add_argument("--elf", "--firmware", dest="elf", default=os.path.expanduser( - "~/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf")) + # Nothing local by default: upload a firmware from the page, which says so when + # none is loaded. tools/uvk5_testenv.py finds one in assets/firmware or work. + ap.add_argument("--elf", "--firmware", dest="elf", default=_default_firmware()) ap.add_argument("--flash", default=os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets", "flash.img")) diff --git a/tools/where.sh b/tools/where.sh index fe9f734..692e05e 100755 --- a/tools/where.sh +++ b/tools/where.sh @@ -4,7 +4,9 @@ # Usage: where.sh [samples] set -uo pipefail -ELF="${ELF:-$HOME/uvk5-port/uvk5-sat/build/CW/nr7y.cw.elf}" +# shellcheck source=tools/uvk5_elf.sh +. "$(dirname "$0")/uvk5_elf.sh" +ELF=$(uvk5_find_elf) || { echo "SKIP no firmware found; set ELF or put one in assets/firmware" >&2; exit 0; } SAMPLES="${1:-1}" SCRIPT=$(mktemp --suffix=.gdb) trap 'rm -f "$SCRIPT"' EXIT