One test station, multiple products: configure your OpenHTF test to detect the SKU at runtime, load SKU-specific limits and phases from a config file, and let TofuPilot track yield per part number automatically.
Why Multi-SKU Stations Matter
Running separate stations per SKU wastes floor space and fixtures. A single station that handles a product family reduces:
- Fixture count (shared bed-of-nails or probe card)
- Operator training (one workflow, not five)
- Maintenance surface (one station to calibrate)
SKU Detection Methods
| Method | When to Use | Reliability |
|---|---|---|
| Barcode scan | Operator-scanned label, no electrical contact needed | High |
| Resistor ID | PCB has a resistor divider encoding SKU | Medium |
| I2C EEPROM | PCB stores part number in onboard memory | High |
Barcode Scan
import openhtf as htfclass BarcodePlug(htf.plugs.BasePlug): """Reads SKU from operator barcode scan.""" def setUp(self): pass def scan(self) -> str: raw = input("Scan barcode: ").strip() if not raw: raise ValueError("Empty barcode scan") return raw def tearDown(self): passResistor ID
import openhtf as htf_VOLTAGE_MAP = { (0.0, 0.4): "SKU-A", (0.4, 0.8): "SKU-B", (0.8, 1.2): "SKU-C", (1.2, 1.6): "SKU-D",}class ResistorIdPlug(htf.plugs.BasePlug): """Identifies SKU from resistor divider voltage.""" def setUp(self): pass def read_sku(self, adc_voltage: float) -> str: for (low, high), sku in _VOLTAGE_MAP.items(): if low <= adc_voltage < high: return sku raise ValueError(f"Unrecognised voltage: {adc_voltage:.3f} V") def tearDown(self): passConfiguration-Driven Test Selection
Store SKU definitions in a YAML file. This separates limits from test logic.
SKU-A: part_number: "PCB-001-A" description: "Standard 5 V variant" phases: - power_on - voltage_check - current_check limits: supply_voltage: min: 4.85 max: 5.15 supply_current: min: 0.080 max: 0.120SKU-B: part_number: "PCB-001-B" description: "Low-power 3.3 V variant" phases: - power_on - voltage_check - current_check - sleep_current_check limits: supply_voltage: min: 3.2 max: 3.4 supply_current: min: 0.030 max: 0.060 sleep_current: min: 0 max: 0.000050Load the config at station startup:
from pathlib import Pathimport yamldef load_sku_config(path: str = "station/config/skus.yaml") -> dict: config_path = Path(path) if not config_path.exists(): raise FileNotFoundError(f"SKU config not found: {config_path}") with config_path.open() as f: return yaml.safe_load(f)SKU-Specific Measurement Limits with OpenHTF
Use a phase factory to bake SKU-specific limits into OpenHTF measurements:
import openhtf as htffrom openhtf.util import unitsdef make_voltage_phase(min_v: float, max_v: float): """Returns a voltage phase with baked-in limits.""" @htf.measures( htf.Measurement("supply_voltage") .in_range(minimum=min_v, maximum=max_v) .with_units(units.VOLT), ) def voltage_check(test): test.measurements.supply_voltage = read_voltage() return voltage_checkdef make_current_phase(min_a: float, max_a: float): """Returns a current phase with baked-in limits.""" @htf.measures( htf.Measurement("supply_current") .in_range(minimum=min_a, maximum=max_a) .with_units(units.AMPERE), ) def current_check(test): test.measurements.supply_current = read_current() return current_checkFull Working Example
import openhtf as htffrom tofupilot.openhtf import TofuPilotfrom station.config.loader import load_sku_configfrom station.phases.factory import make_voltage_phase, make_current_phaseSKU_CONFIG = load_sku_config()def power_on(test): """Powers on the DUT and waits for it to stabilise.""" import time time.sleep(0.5)def run_station(): # 1. Detect SKU sku_id = input("Scan barcode: ").strip() if sku_id not in SKU_CONFIG: raise ValueError(f"Unknown SKU '{sku_id}'. Available: {list(SKU_CONFIG)}") sku = SKU_CONFIG[sku_id] limits = sku["limits"] part_number = sku["part_number"] # 2. Compose phases from config phases = [power_on] if "supply_voltage" in limits: lim = limits["supply_voltage"] phases.append(make_voltage_phase(lim["min"], lim["max"])) if "supply_current" in limits: lim = limits["supply_current"] phases.append(make_current_phase(lim["min"], lim["max"])) # 3. Build and run test test = htf.Test(*phases, test_name=f"Station ({sku_id})") with TofuPilot(test): test.execute(test_start=lambda: input("Scan serial number: "))if __name__ == "__main__": run_station()Tracking Per-SKU Yield in TofuPilot
TofuPilot groups yield statistics by part number. Each SKU gets its own yield chart without extra configuration.
| Part Number | SKU | Runs | Pass Rate |
|---|---|---|---|
| PCB-001-A | SKU-A | 240 | 97.5% |
| PCB-001-B | SKU-B | 180 | 94.4% |
Filter by part number in the TofuPilot dashboard to compare SKU trends side by side.