feat: add per-second GCP-format trials with history, watch and HTTP access (FPGA v10)

This commit is contained in:
randogoth 2026-10-25 18:23:18 +02:00
parent d821b77d3b
commit 6bee626b7f
12 changed files with 475 additions and 29 deletions

View file

@ -99,6 +99,25 @@ def main() -> None:
device.serial.reset_input_buffer()
check(trials == sorted(trials) and len(set(trials)) == 3, f"watch stats pushed trials {trials}")
check(device.json("watch gcp") == {"ok": True}, "watch gcp")
seconds = [json.loads(device.serial.readline()) for _ in range(3)]
device.json("watch off")
device.serial.reset_input_buffer()
numbers = [r.get("second") for r in seconds]
last = seconds[-1]
check(numbers == list(range(numbers[0], numbers[0] + 3)), f"watch gcp pushed consecutive seconds {numbers}")
check(last["healthy"] == 15 and last["sample"]["bits"] == 200
and all(0 <= s <= 200 for s in last["sample"]["sums"]),
f"gcp sample: sums {last['sample']['sums']}, Stouffer {last['sample']['stouffer']}")
check(abs(last["full"]["bits"] - 24_000_000) <= 1,
f"gcp full: {last['full']['bits']} bits, z {last['full']['z']}, Stouffer {last['full']['stouffer']}")
device.entropy_bin(65536)
time.sleep(1.1)
records = device.json(f"gcp {numbers[-1]}").get("records", [])
after = [r["second"] for r in records]
check(len(after) >= 1 and after == list(range(numbers[-1] + 1, numbers[-1] + 1 + len(after))),
f"gcp {numbers[-1]}: no gap across an entropy transfer, seconds {after}")
if args.onod.exists():
sample = Path("captures/api_entropy.bin")
sample.parent.mkdir(exist_ok=True)

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Checks the STRIX firmware's Bluetooth LE API (arduino/strix/API.md) from this computer:
finds the device, sends commands over the command characteristic, reads the replies from the
reply notifications, and reads and streams the entropy characteristic."""
reply notifications (including `watch gcp` output), and reads and streams the entropy characteristic."""
import argparse
import asyncio
@ -57,6 +57,15 @@ async def main() -> None:
print(f"{'stats':<12} -> trial {stats.get('trial')}, combined total z "
f"{stats['channels'][-1]['total']['z'] if 'channels' in stats else None}")
print(f"{'watch gcp':<12} -> {await command(client, replies, 'watch gcp')}")
records = [json.loads(await asyncio.wait_for(replies.lines.get(), timeout=3)) for _ in range(2)]
await client.write_gatt_char(COMMAND, b"watch off", response=True)
await asyncio.sleep(1.5)
while not replies.lines.empty():
replies.lines.get_nowait() # the watch reply and any second pushed before it
print(f"{'gcp seconds':<12} -> {[r['second'] for r in records]}, healthy {records[-1]['healthy']}, "
f"sample sums {records[-1]['sample']['sums']}")
first = await client.read_gatt_char(ENTROPY)
second = await client.read_gatt_char(ENTROPY)
print(f"entropy read -> {len(first)} bytes, then {len(second)} bytes, "

View file

@ -82,8 +82,8 @@ class Board:
while self.serial.readline():
pass
self.serial.timeout = 200
if not identity.startswith("STRX v9"):
sys.exit(f"unexpected board identity: {identity!r} (needs the v9 FPGA image)")
if not identity.startswith("STRX v10"):
sys.exit(f"unexpected board identity: {identity!r} (needs the v10 FPGA image)")
self.generators = self.read(REG_GENERATORS)
self.rings = self.read(REG_RINGS)
print(f"board: {identity}, {self.generators} generators x {self.rings} rings")