#!/usr/bin/env python3 """Record camera frames and HID mouse reports from the Hama myScan side by side. Saves every raw MJPEG frame plus a timeline (frames.csv, hid.csv) into an output directory and prints a once-per-second summary, so we can see how the camera reacts to buttons, movement and surfaces. """ import argparse import csv import os import struct import sys import threading import time # libjpeg prints "Corrupt JPEG data" warnings straight to fd 2 for every # truncated frame; keep Python's stderr but silence the C-level noise. sys.stderr = os.fdopen(os.dup(2), "w") os.dup2(os.open(os.devnull, os.O_WRONLY), 2) import cv2 # noqa: E402 import numpy as np # noqa: E402 def hid_reader(path, out, stop, t0): # Report layout (from the report descriptor): 1 byte buttons (5 bits), # int16 X, int16 Y, int8 wheel, int8 AC pan. fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK) while not stop.is_set(): try: r = os.read(fd, 64) except BlockingIOError: time.sleep(0.002) continue if len(r) >= 7: b, x, y, w, p = struct.unpack("= 1.0: new = hid[hid_seen:] hid_seen = len(hid) btn = sorted({h[1] for h in new}) dx, dy = sum(h[2] for h in new), sum(h[3] for h in new) recent = [fr for fr in frames if fr[0] > t - 1.0] print(f"t={t:5.1f}s fps={len(recent):2d} size={np.mean([r[2] for r in recent]):7.0f}B " f"rows={np.mean([r[3] for r in recent]):5.0f} bright={np.mean([r[4] for r in recent]):6.1f} " f"hid={len(new):3d} buttons={btn} dx={dx:+d} dy={dy:+d}", flush=True) last_print = t stop.set() th.join() cap.release() with open(f"{a.out}/frames.csv", "w", newline="") as fh: w = csv.writer(fh) w.writerow(["t", "frame", "bytes", "decoded_rows", "brightness"]) w.writerows(frames) with open(f"{a.out}/hid.csv", "w", newline="") as fh: w = csv.writer(fh) w.writerow(["t", "buttons", "dx", "dy", "wheel", "pan", "raw"]) w.writerows(hid) print(f"done: {n} frames, {len(hid)} HID reports -> {a.out}/") if __name__ == "__main__": main()