diff --git a/tools/pbhw.py b/tools/pbhw.py index 2976130..02bdeeb 100755 --- a/tools/pbhw.py +++ b/tools/pbhw.py @@ -132,18 +132,28 @@ class Suite: got = erased.read_bytes() if erased.exists() else b"" self.check("EEPROM erase leaves 0xff", got == b"\xff" * size, f"{len(got)} B") - def application(self, info, app: pathlib.Path, marker: str) -> None: + def application(self, info, app: pathlib.Path, marker: str, + marker_wait: float = 2.5) -> None: rc, out = self.rig.pureboot("--flash", str(app), "--verify-flash", str(app)) self.check(f"application flash + verify ({app.name})", rc == 0, self._brief(out)) if marker: # The tool hands over as it ends its session, so the application is - # already running; opening the port does not reset a board whose DTR - # is unwired, so this simply listens. - data = self.rig.capture(seconds=2.5) + # already running — but only on a board whose DTR is unwired, where + # opening a port simply listens. Where DTR *is* wired to reset (an + # Arduino, most USB-serial dev boards), this open resets the part + # and the activation window comes first, so a marker emitted once at + # startup happens on the far side of a wait this cannot know the + # length of: the window is a compile-time constant and nothing on + # the wire reports it. Hence --marker-wait, and a fixture that + # repeats its banner (PUREBOOT_HEARTBEAT) rather than saying it once. + data = self.rig.capture(seconds=marker_wait) seen = marker.encode() in data sample = "".join(chr(b) if 32 <= b < 127 else "." for b in data[:40]) - self.check(f"application runs (emits {marker!r})", seen, f"|{sample}|") + self.check(f"application runs (emits {marker!r})", seen, + f"|{sample}|" if seen or data else + f"nothing in {marker_wait:g} s — if this board resets when its port " + f"opens, that wait has to outlast the activation window") back = self.work / "app-back.bin" rc, out = self.rig.pureboot("--read-flash", str(back)) @@ -190,7 +200,7 @@ class Suite: # ------------------------------------------------------------------- run def run(self, app: pathlib.Path | None, loader_image: pathlib.Path | None, - marker: str) -> int: + marker: str, marker_wait: float = 2.5) -> int: print("identity") info = self.identity() if info is None: @@ -206,7 +216,7 @@ class Suite: if app: print("\napplication") - self.application(info, app, marker) + self.application(info, app, marker, marker_wait) else: print("\nskip application checks (pass --app )") @@ -232,6 +242,10 @@ def main(argv: list[str] | None = None) -> int: help="the resident loader's .bin, to prove the slot survives an erase") parser.add_argument("--marker", default="", help="text the application emits when it runs, e.g. APP") + parser.add_argument("--marker-wait", type=float, default=2.5, + help="seconds to listen for it. On a board whose DTR is wired to " + "reset, opening the port resets the part, so this must outlast " + "the activation window (default 2.5)") args = parser.parse_args(argv) rig = pbrig.Rig(pbrig.Deployment.from_args(args)) @@ -239,7 +253,8 @@ def main(argv: list[str] | None = None) -> int: f"{' (autobaud)' if args.autobaud else ''}") print("this overwrites the application flash and EEPROM\n") with tempfile.TemporaryDirectory(prefix="pbhw-") as temporary: - return Suite(rig, pathlib.Path(temporary)).run(args.app, args.loader, args.marker) + return Suite(rig, pathlib.Path(temporary)).run(args.app, args.loader, args.marker, + args.marker_wait) if __name__ == "__main__":