Migrating from Legacy Systems

Document a Test Station for Hand-Over

Learn how to document a test station so anyone on the team can rebuild it and change a limit without its author, with a checklist and a README template.

JJulien Buteau
intermediate8 min readSeptember 23, 2026

A test station is documented when anyone on the team can rebuild it on a new PC and change a limit without calling the person who built it. That's the whole test, and most stations fail it. This guide gives the hand-over checklist, a README template, and the reason a text-based station layout does most of the work for you.

The Goal

Two questions decide whether a station is handed over. Can a colleague rebuild it on the spare PC from what's in the repo? Can they change one limit, release it, and prove afterwards which units ran the old version and which ran the new?

Everything else is nice to have. A wiring photo helps. A 40-page validation report helps less than the two answers above, because nobody opens it at 2 a.m. when the fixture is down.

Bus factor is the score across the plant. This guide is how you raise it for one station, before or after the person who built it leaves.

The Hand-Over Checklist

Work down the table. "Where it lives" is one place, not "the share drive and also Marc's laptop".

ItemWhere it livesDone when
Sequence of phases and their orderprocedure.yaml, under main:A colleague reads the list and can say what the station does without opening a .py file
Limits and units per measurementprocedure.yaml, under validators:Every limit has a comment naming its source (spec section, datasheet table, or the engineer and the date)
Instrument list, VISA addresses, firmwareplugs/*.py defaults, listed again in README.md*IDN? from each plug matches the README line
Wiringdocs/wiring.pdf and a bench photo in docs/, referenced from the READMEThe spare PC can be cabled from the picture alone
FixtureDrawing number, revision, pogo pin map in docs/The fixture on the bench carries the same revision as the README
Station PC setupREADME.md, "How to run"A fresh Windows install reaches a green run from the README, no phone call
Release processREADME.md, "How to release", plus Git tagsThe tag on the station PC matches version: in procedure.yaml
Results historyTofuPilot, per unit and per stationAnyone can open a serial number and see every run with the procedure version that produced it
Calibration datesREADME.md, one tableEvery next-due date is in the future
Known failure modesREADME.md, one tableThe top three failures from the TofuPilot Pareto are listed with their usual cause

Station README Template

Copy this into the station repo. Fill it in one sitting; a half-finished README ages badly.

README.md
58 lines
# FCT Station 3, PCBA-100Owner: test engineering (the team, not a person). Last reviewed: 2026-09-15.## WiringSee `docs/wiring.pdf`, revision C. Bench photo in `docs/bench.jpg`.| From | To | Cable ||---|---|---|| DMM HI / LO | Fixture J3 pins 1, 2 | 1 m banana, red / black || PSU CH1 | Fixture J1 | 18 AWG, 0.5 m || Station PC | DUT console | USB to UART, COM3 |## Instruments| Instrument | VISA address | Firmware | Calibration due ||---|---|---|---|| Keysight 34465A DMM | TCPIP::192.168.1.100::INSTR | A.03.03 | 2027-02-01 || Keysight E36313A PSU | TCPIP::192.168.1.101::INSTR | 1.0.6 | 2027-02-01 || DUT console | ASRL3::INSTR (COM3, 115200 8N1) | n/a | n/a |## FixtureDrawing FIX-0042 rev B. Pogo pin map in `docs/FIX-0042-pinmap.pdf`.Spare pogo pins: drawer 2, part P-0910.## LimitsAll limits live in `procedure.yaml`. Each one carries a comment with itssource. Don't change a limit without changing the comment.| Measurement | Limit | Source ||---|---|---|| rail_3v3 | 3.2 to 3.4 V | PCBA-100 spec rev 4, section 5.1 || idle_current | <= 45 mA | MCU datasheet table 12, plus 20 % margin |## How to run    curl -fsSL https://www.tofupilot.app/install | sh    git clone <repo url> && cd fct-station-3    tofupilot run ./procedure.yaml --uploadRuns land in TofuPilot under the procedure "PCBA-100 FCT", one page per unit.## How to release1. Branch, change, open a pull request. One reviewer from test engineering.2. Bump `version:` in `procedure.yaml`.3. Merge, tag `vX.Y.Z`, pull the tag on the station PC.4. Run three golden units. Compare their runs in TofuPilot with the previous tag.## Known failure modes| Symptom | Usual cause | Fix ||---|---|---|| rail_3v3 reads 0.00 V | Pogo pin 7 worn | Replace, part P-0910 || DMM timeout on the first unit of the day | DMM in standby | Power cycle the DMM |

The README is short on purpose. Four other things carry the rest.

Why the Framework Layout Does Most of This

A TofuPilot Framework station is a folder: procedure.yaml, phases/*.py, plugs/*.py. All text, all under Git. Each part carries exactly one kind of information, so the README only has to cover what none of them can.

CarrierWhat it holdsWhat that replaces
procedure.yamlThe sequence, the unit fields, every measurement with its unit and limitsThe test spec spreadsheet and the "which step runs first" question
plugs/*.pyVISA addresses, SCPI strings, timeouts, close()The instrument list scribbled on the fixture
GitWho changed which limit, when, reviewed by whom; git blame on the lineThe change log tab nobody updates
TofuPilotEvery run per unit and per station, with the procedure version, FPY, Cpk, ParetoThe TDMS folder on the station's local disk

What's left for the README is physical: wiring, fixture, calibration, the failure modes an operator sees. That's why the template above fits on one screen.

procedure.yaml as Living Documentation

Treat the procedure as the spec. A reviewer who can't read Python can still read this file, and a limit change is a one-line diff with a comment.

procedure.yaml
39 lines
# The procedure is the spec. Every limit names its source in a comment.name: PCBA-100 FCTversion: 2.3.0unit:  serial_number:    default_value: "SN-000001"  part_number:    default_value: "PCBA-100"plugs:  - name: dmm    python: plugs.dmm:Multimeter       # 34465A at 192.168.1.100  - name: psu    python: plugs.psu:PowerSupply      # E36313A at 192.168.1.101main:  - key: power_rails    name: Power Rails    python: phases.power_rails    measurements:      - name: rail_3v3        unit: V        validators:          - operator: ">="            expected_value: 3.2        # PCBA-100 spec rev 4, section 5.1          - operator: "<="            expected_value: 3.4        # same  - key: idle_current    name: Idle Current    python: phases.idle_current    depends_on: [power_rails]    measurements:      - name: idle_current        unit: mA        validators:          - operator: "<="            expected_value: 45         # MCU datasheet table 12, plus 20 % margin

When quality asks "why 45 mA", the answer is on the line. When they ask "since when", git log -p procedure.yaml answers. When they ask "which units shipped with 50 mA", the run pages in TofuPilot carry the procedure version, so the answer is a filter, not an archaeology project.

What a LabVIEW Project Makes Hard Here

None of this is impossible in LabVIEW. It's more work, for three concrete reasons.

VIs are binary. A diff needs NI's compare tool and a licensed seat, so reviews happen by opening both versions side by side, and git blame on a limit isn't available. Most teams skip the review.

Limits tend to live inside VIs, or in an Excel file next to the sequence. Either way the spec and the code drift, and the comment naming the datasheet section has nowhere natural to go.

Results tend to land in TDMS or CSV on the station's local disk. Per-unit history means finding the right file on the right PC. When the PC is replaced, the history usually isn't.

A LabVIEW station can still meet the checklist. Someone has to write the README by hand, keep the limits table in sync by hand, and copy the results somewhere shared by hand. The Python layout makes those three automatic, which is the difference between a hand-over that happens and one that's planned.

A Five-Minute Review Before Every Release

Run this before tagging. Five minutes, every time, by the reviewer and not the author.

  1. Open the pull request diff. Every changed limit has a changed comment on the same line.
  2. version: in procedure.yaml went up.
  3. The README instrument table still matches *IDN? from each plug. Run them.
  4. Three golden units passed on the branch, and their runs in TofuPilot sit within the previous version's histograms.
  5. The tag name matches version:. Pull it on the station PC, not a copy of the folder.

If step 4 fails, the change is real and needs a conversation with quality before it ships. That's the review doing its job.

Start With One Station

Pick the station with the highest volume, or the one only one person can open. Rebuild it in Python with the TofuPilot Framework, then run it side by side with the LabVIEW version on the same units for two weeks. Write the README on day one and keep it true; the onboarding guide uses that README as the new engineer's first task.

station-setup.sh
# Install the CLI, run a procedure locally, then run it with upload.curl -fsSL https://www.tofupilot.app/install | shtofupilot run ./procedure.yamltofupilot run ./procedure.yaml --upload

The LabVIEW migration guide covers the rebuild step by step, and the Framework page covers the layout. The Lab tier is free, and tofupilot run works with no account.

More Guides

Put this guide into practice