Skip to content
Migrating from Legacy Systems

Migrate from LabVIEW to Python

A practical guide to replacing LabVIEW with Python for manufacturing test automation, with concept mappings, code examples, and TofuPilot integration.

JJulien Buteau
intermediate14 min readMarch 14, 2026

LabVIEW is powerful, but it comes with $3,000-5,000/seat licensing, Windows-only deployment, and binary files that don't version-control well. Python gives you the same instrument control capabilities with zero license cost, cross-platform support, and native Git integration. This guide maps LabVIEW concepts to Python equivalents and shows you how to rebuild your test system with OpenHTF and TofuPilot.

Why Teams Migrate

Pain PointLabVIEWPython
License cost$3,160-4,840/seat/year (depending on edition)Free
DeploymentWindows onlyLinux, macOS, Windows
Version controlBinary .vi files, merge conflictsText files, Git-native
HiringLabVIEW developers are scarce and expensivePython developers are everywhere
CI/CDDifficult to integrateNative (pytest, GitHub Actions, etc.)
Package ecosystemNI packages + limited communitypip, PyPI, 400K+ packages
Code reviewRequires LabVIEW to viewAny text editor
CollaborationOne person per VI at a timeStandard Git workflow

LabVIEW to Python Concept Map

LabVIEW ConceptPython EquivalentNotes
VI (Virtual Instrument)Python functionSame idea: reusable, callable unit
SubVIFunction or class methodImport from a module
Front panelNo equivalent (or: TofuPilot dashboard)Python doesn't need a GUI per function
Block diagramPython codeText instead of wires
Connector paneFunction signaturedef measure_voltage(channel: int) -> float
Error clusterException handlingtry/except instead of error wires
TypedefClass or dataclass@dataclass for structured data
Global variableModule-level variable or class attributeAvoid when possible
Property nodeProperty decorator@property on a class
State machineClass with methods or match/caseSee example below
TDMS fileJSON, CSV, or TofuPilotTofuPilot replaces file-based logging
DAQmx driverPyVISA + pyvisa-pyOr nidaqmx Python package
Instrument driverOpenHTF PlugBasePlug with setUp/tearDown
Test sequenceOpenHTF Testhtf.Test(phase1, phase2, ...)

Step 1: Set Up Your Python Environment

migration/setup.sh
python -m venv venv
source venv/bin/activate    # Linux/macOS
# venv\Scripts\activate     # Windows

pip install openhtf tofupilot pyvisa pyvisa-py

This gives you:

  • openhtf: Test framework (replaces LabVIEW test sequencer)
  • tofupilot: Cloud analytics (replaces TDMS file logging)
  • pyvisa: Instrument control (replaces LabVIEW instrument drivers)

Step 2: Convert SubVIs to Python Functions

A LabVIEW SubVI that reads voltage from a DMM becomes a Python function:

migration/instrument_functions.py
def measure_voltage(channel: int) -> float:
    """Equivalent of a LabVIEW SubVI that reads voltage from a DMM.

    In LabVIEW: SubVI with channel input (I32) and voltage output (DBL).
    In Python: function with type hints.
    """
    readings = {1: 3.31, 2: 5.02, 3: 1.81}  # Replace with real reads
    return readings.get(channel, 0.0)


# Call it like you'd wire a SubVI
rail_3v3 = measure_voltage(1)
rail_5v0 = measure_voltage(2)

The function signature replaces the connector pane. Type hints (int, float) replace the LabVIEW data types.

Step 3: Replace the Error Cluster

LabVIEW uses an error cluster (status, code, source) wired through every VI. Python uses exceptions.

migration/error_handling.py
class InstrumentError(Exception):
    """Replaces LabVIEW error cluster for instrument errors."""

    def __init__(self, code: int, source: str, message: str):
        self.code = code
        self.source = source
        super().__init__(f"[{code}] {source}: {message}")


# LabVIEW: check error cluster at every node
# Python: try/except catches errors from anywhere in the block
try:
    voltage = measure_voltage(1)
    if voltage < 0:
        raise InstrumentError(1001, "DMM", "Negative voltage reading")
except InstrumentError as e:
    print(f"Instrument error: {e}")
    # Handle or re-raise

This is cleaner than wiring error clusters through every node. Exceptions propagate automatically until caught.

Step 4: Replace the State Machine

LabVIEW state machines use a while loop + case structure. Python has several options.

migration/state_machine.py
class TestSequencer:
    """Replaces a LabVIEW state machine for test sequencing."""

    def __init__(self):
        self.results = {}
        self.state = "init"

    def run_step(self, name: str, func, limits: tuple) -> bool:
        """Run a test step and record the result."""
        value = func()
        passed = limits[0] <= value <= limits[1]
        self.results[name] = {
            "value": value,
            "limits": limits,
            "passed": passed,
        }
        return passed

    def run(self):
        """Execute the full test sequence."""
        self.run_step("rail_3v3", lambda: 3.31, (3.2, 3.4))
        self.run_step("rail_5v0", lambda: 5.02, (4.8, 5.2))

        all_passed = all(r["passed"] for r in self.results.values())
        return all_passed


seq = TestSequencer()
passed = seq.run()
print(f"Test {'PASSED' if passed else 'FAILED'}")

But you don't need to build this yourself. OpenHTF handles sequencing, measurements, and limits natively.

Step 5: Use OpenHTF Instead of Building a Sequencer

OpenHTF replaces both the LabVIEW test sequencer and the data logging (TDMS). Instrument drivers become Plugs.

migration/openhtf_test.py
import openhtf as htf
from openhtf.plugs import BasePlug
from openhtf.util import units
from tofupilot.openhtf import TofuPilot


class InstrumentPlug(BasePlug):
    """Replaces LabVIEW instrument driver VIs.

    setUp = equivalent of opening a VISA session in LabVIEW
    methods = equivalent of SubVIs for each measurement
    tearDown = equivalent of closing the session
    """

    def setUp(self):
        self._voltages = iter([3.31, 5.02])
        # Replace with: rm = pyvisa.ResourceManager("@py")
        # self.instr = rm.open_resource("TCPIP::192.168.1.100::INSTR")

    def read_voltage(self) -> float:
        return next(self._voltages)
        # Replace with: return float(self.instr.query(":MEAS:VOLT:DC?"))

    def tearDown(self):
        pass
        # Replace with: self.instr.close()


@htf.measures(
    htf.Measurement("rail_3v3")
    .in_range(3.2, 3.4)
    .with_units(units.VOLT)
    .doc("3.3V rail"),
    htf.Measurement("rail_5v0")
    .in_range(4.8, 5.2)
    .with_units(units.VOLT)
    .doc("5.0V rail"),
)
@htf.plug(instr=InstrumentPlug)
def test_power_rails(test, instr):
    """Replaces a LabVIEW test sequence with Numeric Limit Tests."""
    test.measurements.rail_3v3 = instr.read_voltage()
    test.measurements.rail_5v0 = instr.read_voltage()


def main():
    test = htf.Test(
        test_power_rails,
        procedure_id="FCT-001",
        part_number="PCBA-100",
    )
    with TofuPilot(test):
        test.execute(test_start=lambda: input("Scan serial number: "))


if __name__ == "__main__":
    main()

Step 6: Replace TDMS with TofuPilot

LabVIEW writes test data to TDMS files. You then need to build your own analytics tools to read them. TofuPilot replaces this entire pipeline.

LabVIEW (TDMS)TofuPilot
Write TDMS file after each testAutomatic upload (one line of code)
Build custom analytics toolsDashboard with FPY, Cpk, control charts
Manual data aggregation across stationsAutomatic multi-station aggregation
File server for TDMS storageCloud storage with API access
Custom report generationBuilt-in reports and exports

Migration Checklist

StepActionLabVIEW Equivalent
1Install Python + venvInstall LabVIEW
2pip install openhtf tofupilot pyvisaInstall NI packages
3Create Plug classes for each instrumentCreate instrument driver VIs
4Write phase functions with @htf.measuresCreate test sequence with limit checks
5Assemble htf.Test() with all phasesBuild test sequence in LabVIEW
6Add with TofuPilot(test)Configure TDMS logging
7Run and validate results match LabVIEWCompare measurement values

Common Gotchas

GotchaFix
"I miss the front panel"Use TofuPilot dashboard for real-time monitoring. For operator UI, OpenHTF has a built-in web interface.
"My NI hardware needs NI drivers"Use the nidaqmx Python package for DAQmx hardware. For GPIB, install NI-VISA runtime.
"LabVIEW handles threading automatically"Python has threading and asyncio. OpenHTF handles phase execution threading.
"My team doesn't know Python"Python has a gentler learning curve than LabVIEW. Most engineers learn it in days, not weeks.
"We have years of LabVIEW code"Migrate incrementally. Start with new tests in Python. Convert existing tests one at a time.

More Guides

Put this guide into practice