# Migrate from NI TestStand to OpenHTF URL: /guides/migrate-from-teststand Map NI TestStand concepts to OpenHTF — sequences and step groups to tests and phase groups, numeric limit steps to measurements, preconditions to run_if and branches, station globals to configuration, report and database loggers to output callbacks — and plan a station-by-station transition. TestStand sequences and OpenHTF tests describe the same thing: ordered steps with limits, run against one unit, producing a report. The vocabulary differs; the shapes line up. TestStand OpenHTF Notes Sequence file (`.seq`) Python module with `htf.Test(...)` Text, diffable, in git MainSequence `htf.Test(*phases)` Setup / Main / Cleanup step groups `htf.PhaseGroup(setup=, main=, teardown=)` Cleanup semantics match: teardown runs if setup completed — Phase Groups Step (Action) Phase A Python function Numeric Limit Test step `@htf.measures(htf.Measurement(...).in_range(lo, hi).with_units(...))` GELE → `in_range`; GT/LT only → one bound; EQ → `.equals` Multiple Numeric Limit Test Several `Measurement`s on one phase, or a dimensioned measurement String Value Test `.equals("...")` or `.matches_regex(...)` Pass/Fail Test Boolean measurement `.equals(True)`, or `PhaseResult.FAIL_AND_CONTINUE` Step result "Skipped" `PhaseResult.SKIP` Recorded as SKIP Precondition expression `@htf.PhaseOptions(run_if=lambda: ...)` for static, `BranchSequence` for result-dependent `run_if` cannot see results; branches can — Checkpoints & Branching Sequence Call A phase list / `PhaseSequence` reused across tests; `Subtest` for an independent result Subtests Looping steps `force_repeat` + `repeat_limit`, or a Python loop building phases Step failure causes sequence failure Default OpenHTF behaviour (continue, record FAIL); `stop_on_first_failure` or `checkpoint()` to stop Test Options Station Globals / Sequence File Globals Configuration: `CONF.declare`, YAML per station Locals / FileGlobals passed between steps Measurements (`test.get_measurement`), plug state, `test.test_record.metadata` Process model (serial number prompt, report, loop) `test_start=prompt_for_test_start()`, output callbacks, `while True:` around `execute()` Device Under Test Operator Interface Built-in Operator UI or TofuPilot's Report (ATML / XML / HTML) JSON test record via `OutputToJSON` Database Logger An output callback — your SQL insert, or TofuPilot's `upload()` Code modules (LabVIEW VI, DLL, .NET) Plug methods in Python; call DLLs via `ctypes`, LabVIEW via its Python connectivity or a rewrite The main effort Deployment Utility Python packaging + a launcher — Production deployment Batch / parallel process models Not built in; one process per socket, or TofuPilot procedures A typical PCB sequence: Setup opens instruments; Main programs firmware, checks rails with numeric limits, runs an RF sub-sequence under a precondition; Cleanup powers down. This is where the time goes. Options, roughly in order of preference: **Rewrite in Python** against the instrument's SCPI or SDK — PyVISA, pyserial, pymodbus. Usually shorter than expected: a LabVIEW driver VI often wraps a handful of SCPI strings. **Call the existing DLL** from a plug with `ctypes` or a vendor Python binding. Keeps validated code; adds a Windows dependency. **Keep LabVIEW for one instrument** and expose it over a local TCP socket or CLI that a plug calls. A bridge, not a destination. **Pick one station** with a simple sequence and a Python-comfortable owner. Run OpenHTF alongside TestStand on the same units for a week and compare records. **Match limits exactly** from the sequence file; keep the same measurement names so historical comparisons hold. **Route both systems' results to one database** so quality does not lose visibility during the switch — an output callback for OpenHTF, the existing logger for TestStand. TofuPilot accepts both OpenHTF records and imported files. **Retire TestStand licences per station** as each converts; the savings fund the plug work.