Files
dragon-q6a/tests/check_backend.py
T
ykcheungab a723e96dcc 支持传感器独立校准文件与批量配置启动
保留逐台添加,并通过显式配置一次连接全组;失败时撤销本批新增连接。通过 35 项回归测试、真实 SDK 校准与 1–4 路验证、Ruff 和文档检查。
2026-10-14 16:11:15 +08:00

124 lines
5.2 KiB
Python

"""Exercise real SDK instances for 1-4 recorded inputs, without UART/hardware."""
import argparse
import itertools
import json
import os
import sys
from functools import partial
from pathlib import Path
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "serial"))
from finger_api import FingerSensorBank
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--recording", required=True, type=Path)
parser.add_argument("--scratch", required=True, type=Path)
parser.add_argument("--calibration", type=Path, help="explicit calibration used for all offline test sources")
args = parser.parse_args()
scratch = args.scratch.resolve()
scratch.mkdir(parents=True, exist_ok=True)
os.environ["NUMBA_CACHE_DIR"] = str(scratch / "numba")
import cv2
import numpy as np
import orisys
from orisys.finger import FingerRuntime
print(f"SDK: {orisys.__file__}", flush=True)
capture = cv2.VideoCapture(str(args.recording.resolve()))
frames = []
try:
for _ in range(12):
ok, frame = capture.read()
if not ok:
raise RuntimeError("recording must contain at least 12 frames")
frames.append(frame)
finally:
capture.release()
height, width = frames[0].shape[:2]
sources = []
calibrations = []
for sensor_id in range(1, 5):
path = scratch / f"sensor-{sensor_id}.avi"
writer = cv2.VideoWriter(str(path), cv2.VideoWriter_fourcc(*"MJPG"), 30, (width, height))
try:
if not writer.isOpened():
raise RuntimeError("cannot create test recording")
for frame in frames:
writer.write(np.roll(frame, sensor_id, axis=1))
finally:
writer.release()
sources.append(str(path))
if args.calibration:
calibration = scratch / f"sensor-{sensor_id}-calibration.json"
calibration.write_bytes(args.calibration.read_bytes())
calibrations.append(str(calibration))
# Keep the SDK implementation intact; redirect only its generated UV cache.
open_runtime = partial(FingerRuntime.open, map_cache=str(scratch / "uv-map.npz"))
with patch.object(FingerRuntime, "open", side_effect=open_runtime):
for count in range(1, 5):
bank = FingerSensorBank()
try:
for index, source in enumerate(sources[:count]):
existing = dict(bank.runtimes)
bank.add(source, cal_path=calibrations[index] if calibrations else None)
assert all(bank.runtimes[i] is runtime for i, runtime in existing.items())
assert len(bank.read_all()) == len(bank.runtimes)
if calibrations:
for sensor_id, runtime in bank.runtimes.items():
calibration = runtime.tactile.raw_sensor.calibration
assert calibration is not None
assert calibration.source_path.resolve() == Path(calibrations[sensor_id - 1])
assert runtime.tactile.force_units == ("N", "N", "N")
runtimes = list(bank.runtimes.values())
for left, right in itertools.combinations(runtimes, 2):
assert left.capture is not right.capture
assert left.tactile.raw_sensor is not right.tactile.raw_sensor
assert not np.shares_memory(left.tactile.flow, right.tactile.flow)
for _ in range(3):
readings = bank.read_all()
assert [r.sensor_id for r in readings] == list(range(1, count + 1))
assert all(np.isfinite([r.normal, r.shear_x, r.shear_y]).all() for r in readings)
print(f"PASS {count} sources: incremental add/read, independent states, 3 final rounds", flush=True)
finally:
bank.close()
assert all(runtime.capture is None for runtime in runtimes)
setup = scratch / "sensors.json"
setup.write_text(json.dumps({"sensors": [
{"source": source, **({"calibration": calibrations[i]} if calibrations else {})}
for i, source in enumerate(sources)
]}), encoding="utf-8")
bank = FingerSensorBank()
try:
assert bank.add_setup(setup) == (1, 2, 3, 4)
assert len(bank.read_all()) == 4
bad_calibration = scratch / "invalid-calibration.json"
bad_calibration.write_text("{}", encoding="utf-8")
finally:
bank.close()
bank = FingerSensorBank()
try:
bank.add(sources[0])
first = bank.runtimes[1]
try:
bank.add(sources[1], cal_path=str(bad_calibration))
except ValueError:
pass
else:
raise AssertionError("invalid explicit calibration was accepted")
assert bank.runtimes == {1: first}
assert len(bank.read_all()) == 1
finally:
bank.close()
print("PASS 4-source JSON setup; invalid calibration rejected without fallback", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())