Skip to content
Scaling & Monitoring

Offline Test Data Sync with TofuPilot

Learn how to collect hardware test data in offline or air-gapped environments and sync it to TofuPilot when connectivity is restored.

JJulien Buteau
intermediate9 min readMarch 14, 2026

Offline Test Data Sync with TofuPilot

Not every test station has an internet connection. Cleanrooms, field deployments, secure facilities, and factory floors with spotty WiFi all need to capture test data reliably. TofuPilot's Python client handles offline scenarios by letting you store results locally and sync when connectivity returns.

When Offline Testing Happens

ScenarioWhy it's offline
Air-gapped facilitySecurity requirements prohibit internet access
Field testingRemote location with no connectivity
CleanroomNo network access inside the controlled environment
Factory floorUnreliable WiFi, can't depend on it for every test
Mobile test stationTraveling between sites

In all these cases, the test must run and record data regardless of network status. You can't tell a production line to stop because the WiFi is down.

Architecture for Offline-First Testing

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Test Station │────▶│ Local Store │────▶│ TofuPilot │ │ (runs tests) │ │ (JSON files) │ │ (cloud) │ └──────────────┘ └──────────────┘ └──────────────┘ Offline When connected

The test station writes results to local storage. A sync process uploads them to TofuPilot when a connection is available.

Step 1: Store Results Locally

Write test results to JSON files on the local machine. Each file represents one test run.

offline_test.py
import json
import os
from datetime import datetime

OFFLINE_DIR = "/data/test-results/pending"
os.makedirs(OFFLINE_DIR, exist_ok=True)

def run_test(serial_number):
    """Run the test and store results locally."""
    # Run your actual test measurements
    vcc = measure_voltage(channel=1)
    current = measure_current()

    result = {
        "procedure_id": "BOARD-FUNCTIONAL-V3",
        "unit_under_test": {"serial_number": serial_number},
        "run_passed": 3.25 <= vcc <= 3.35 and 30 <= current <= 60,
        "tested_at": datetime.utcnow().isoformat(),
        "steps": [{
            "name": "Power Rail Check",
            "step_type": "measurement",
            "status": 3.25 <= vcc <= 3.35,
            "measurements": [{
                "name": "vcc_3v3",
                "value": vcc,
                "unit": "V",
                "limit_low": 3.25,
                "limit_high": 3.35,
            }],
        }, {
            "name": "Current Draw",
            "step_type": "measurement",
            "status": 30 <= current <= 60,
            "measurements": [{
                "name": "idle_current_ma",
                "value": current,
                "unit": "mA",
                "limit_low": 30,
                "limit_high": 60,
            }],
        }],
    }

    filename = f"{serial_number}_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.json"
    filepath = os.path.join(OFFLINE_DIR, filename)
    with open(filepath, "w") as f:
        json.dump(result, f, indent=2)

    print(f"Result saved locally: {filepath}")
    return result

# Run tests regardless of network status
run_test("UNIT-5501")
run_test("UNIT-5502")

Step 2: Sync When Connected

A separate sync script uploads pending results to TofuPilot and moves them to a "synced" directory.

sync_results.py
import json
import os
import shutil
from tofupilot import TofuPilotClient

PENDING_DIR = "/data/test-results/pending"
SYNCED_DIR = "/data/test-results/synced"
os.makedirs(SYNCED_DIR, exist_ok=True)

def sync_pending_results():
    """Upload all pending results to TofuPilot."""
    client = TofuPilotClient()
    pending_files = sorted(os.listdir(PENDING_DIR))

    if not pending_files:
        print("No pending results to sync.")
        return

    print(f"Syncing {len(pending_files)} pending results...")

    for filename in pending_files:
        filepath = os.path.join(PENDING_DIR, filename)
        with open(filepath) as f:
            result = json.load(f)

        try:
            client.create_run(
                procedure_id=result["procedure_id"],
                unit_under_test=result["unit_under_test"],
                run_passed=result["run_passed"],
                steps=result["steps"],
            )
            # Move to synced directory
            shutil.move(filepath, os.path.join(SYNCED_DIR, filename))
            print(f"Synced: {filename}")
        except Exception as e:
            print(f"Failed to sync {filename}: {e}")
            # Leave in pending for retry

    remaining = len(os.listdir(PENDING_DIR))
    print(f"Sync complete. {remaining} files still pending.")

sync_pending_results()

Step 3: Automate the Sync

Run the sync script automatically when connectivity is available.

Option A: Cron Job

crontab
# Try to sync every 15 minutes
*/15 * * * * /usr/bin/python3 /opt/test-station/sync_results.py >> /var/log/test-sync.log 2>&1

Option B: Network Event Trigger

network_watcher.py
import subprocess
import time

def has_internet():
    try:
        subprocess.check_call(
            ["curl", "-s", "--max-time", "5", "https://app.tofupilot.com/api/health"],
            stdout=subprocess.DEVNULL,
        )
        return True
    except subprocess.CalledProcessError:
        return False

while True:
    if has_internet():
        subprocess.run(["python3", "sync_results.py"])
    time.sleep(300)  # Check every 5 minutes

Data Integrity

Offline sync introduces a risk: what if a file gets corrupted or a sync partially fails?

RiskMitigation
File corruptionWrite to temp file first, then rename (atomic write)
Duplicate uploadTofuPilot deduplicates by procedure + serial + timestamp
Partial sync failureFiles stay in pending until successfully uploaded
Clock drift on offline machinesUse monotonic timestamps for ordering, UTC for absolute time

When to Use This Pattern

Use offline-first testing when:

  • Your test station can't guarantee network connectivity
  • Network latency would slow down test cycle time
  • You need tests to run even during network outages
  • Security requirements prevent direct cloud access from the test floor

For stations with reliable connectivity, push results directly to TofuPilot from within the test script. The offline pattern adds complexity that's only justified when connectivity is unreliable.

More Guides

Put this guide into practice