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 htf
class 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.120
SKU-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 Path
import yaml
def 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 htf
from openhtf.util import units
def 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_check
def 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 htf
from tofupilot.openhtf import TofuPilot
from station.config.loader import load_sku_config
from station.phases.factory import make_voltage_phase, make_current_phase
SKU_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.