Skip to content
Scaling & Monitoring

Video and Log Sync for Hardware Tests

Learn how to attach video recordings, log files, and supplementary data to hardware test runs in TofuPilot for synchronized review.

JJulien Buteau
intermediate9 min readMarch 14, 2026

Video and Log Synchronization for Hardware Tests

Measurements tell you what happened. Videos and logs tell you why. A failed vibration test makes more sense when you can watch the unit rattle loose. A firmware crash is easier to debug when the serial console log is right next to the test result. TofuPilot stores attachments alongside measurements.

Why Attachments Matter

A measurement value of "FAIL" tells you something broke. But it doesn't tell you:

  • What the unit physically looked like during the test
  • What the firmware logged before the crash
  • What the oscilloscope waveform looked like
  • What the operator saw on their screen

Attachments bridge this gap. They turn a test result from a number into a complete record of what happened.

Types of Test Attachments

Attachment typeUse caseFormat
Video recordingVisual inspection, mechanical testsMP4, AVI
Serial console logFirmware debug, boot sequenceTXT, LOG
Oscilloscope captureWaveform analysis, timingPNG, CSV
Thermal imageHot spot detectionJPEG, PNG
Test station logTest software debugTXT, LOG
Configuration fileDUT or fixture settingsJSON, YAML
PhotoPhysical defect documentationJPEG, PNG

Attaching Files to Test Runs

With the Python Client

test_with_attachments.py
from tofupilot import TofuPilotClientclient = TofuPilotClient()client.create_run(    procedure_id="VIBRATION-SCREENING",    unit_under_test={"serial_number": "UNIT-7832"},    run_passed=False,    steps=[{        "name": "Random Vibration 20-2000Hz",        "step_type": "measurement",        "status": False,        "measurements": [{            "name": "resonance_freq_hz",            "value": 847,            "unit": "Hz",            "limit_low": 900,            "limit_high": 1500,        }],    }],    attachments=[        "recordings/unit-7832-vibration.mp4",        "logs/unit-7832-accel.csv",        "captures/unit-7832-spectrum.png",    ],)

Capturing Video Automatically

Record video during the test and attach it to the run.

video_capture.py
import subprocessimport osdef start_recording(output_path):    """Start recording from a USB camera."""    proc = subprocess.Popen([        "ffmpeg", "-f", "v4l2", "-i", "/dev/video0",        "-t", "120",  # max 2 minutes        "-y", output_path,    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)    return procdef stop_recording(proc):    """Stop the recording."""    proc.terminate()    proc.wait()# Usage in a testvideo_path = f"/tmp/test_{serial}.mp4"recorder = start_recording(video_path)# Run the test...run_test()stop_recording(recorder)# Attach to TofuPilot runclient.create_run(    procedure_id="MECHANICAL-TEST",    unit_under_test={"serial_number": serial},    run_passed=passed,    steps=steps,    attachments=[video_path],)

Capturing Serial Console Logs

serial_logger.py
import serialimport threadingclass SerialLogger:    def __init__(self, port, baudrate=115200):        self.ser = serial.Serial(port, baudrate, timeout=1)        self.log = []        self.running = False    def start(self):        self.running = True        self.log = []        self.thread = threading.Thread(target=self._read_loop)        self.thread.start()    def stop(self):        self.running = False        self.thread.join()        self.ser.close()    def _read_loop(self):        while self.running:            line = self.ser.readline().decode("utf-8", errors="replace")            if line:                self.log.append(line)    def save(self, path):        with open(path, "w") as f:            f.writelines(self.log)# Usagelogger = SerialLogger("/dev/ttyUSB0")logger.start()# Run the test...run_test()logger.stop()log_path = f"/tmp/console_{serial}.log"logger.save(log_path)# Attach to TofuPilotclient.create_run(    procedure_id="FIRMWARE-VALIDATION",    unit_under_test={"serial_number": serial},    run_passed=passed,    steps=steps,    attachments=[log_path],)

Synchronized Review

When you open a test run in TofuPilot, all attachments are available alongside the measurements. This synchronized view lets you:

  1. See that resonance_freq_hz failed at 847 Hz (below the 900 Hz limit)
  2. Watch the video to see the enclosure flexing during the test
  3. Open the accelerometer CSV to see the raw vibration data
  4. Check the spectrum plot to identify the resonant mode

All in one place, for one run, with one click.

When to Attach What

Not every test needs video and logs. Attachments add storage and complexity. Use them where they add diagnostic value.

Test typeRecommended attachments
Vibration/shockVideo, accelerometer data
Firmware validationSerial console log
Visual inspectionPhoto of DUT
Power testingOscilloscope captures
Environmental testingTemperature/humidity logs
Burn-inAll logs (long-duration, hard to reproduce)

For routine production tests (ICT, basic functional), measurements alone are usually sufficient. Save attachments for tests where visual or log context helps debugging.

Storage Considerations

PracticeWhy
Compress videos before attachingReduce storage costs
Keep attachments under 50MB per runPractical upload/download speed
Use PNG for plots, JPEG for photosPNG for clarity, JPEG for size
Only attach to failing runs (optionally)Save storage on passing runs where attachments are rarely reviewed
Include timestamps in log filesCorrelate log events with test step timing

More Guides

Put this guide into practice