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 TofuPilotClient

client = 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 subprocess
import os

def 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 proc

def stop_recording(proc):
    """Stop the recording."""
    proc.terminate()
    proc.wait()

# Usage in a test
video_path = f"/tmp/test_{serial}.mp4"
recorder = start_recording(video_path)

# Run the test...
run_test()

stop_recording(recorder)

# Attach to TofuPilot run
client.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 serial
import threading

class 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)

# Usage
logger = 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 TofuPilot
client.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