Un test matériel n'est pas une vérification unique. C'est une séquence : mise sous tension, attente du démarrage, mesure des tensions, calibration, vérification des communications, test de stress, mise hors tension. Capturer chaque étape avec ses mesures et son timing donne un enregistrement structuré du flux complet.
Pourquoi le séquencement des tests est important
Exécuter des tests manuellement depuis un instrument de banc fonctionne pour les prototypes. Cela ne fonctionne pas en production. Les tests de production nécessitent :
- Répétabilité : Chaque unité passe par les mêmes étapes exactement dans le même ordre
- Rapidité : Pas d'attente pour qu'un opérateur clique sur « suivant »
- Capture des données : Chaque mesure est enregistrée automatiquement avec ses limites et son verdict
- Traçabilité : Un enregistrement exact de ce qui a été testé, dans quel ordre, avec quels résultats
L'orchestration de test consiste à définir cette séquence une fois et à l'exécuter de la même manière à chaque fois.
Architecture d'une séquence de test
Une séquence de test bien structurée suit un schéma :
┌─────────────┐
│ Setup │ Mise sous tension, initialisation des instruments, identification du DUT
├─────────────┤
│ Étape 1 │ Mesure ou action avec critères de conformité
├─────────────┤
│ Étape 2 │ Mesure ou action suivante
├─────────────┤
│ ... │ Étapes supplémentaires selon les besoins
├─────────────┤
│ Teardown │ Mise hors tension, libération des instruments, envoi des résultats
└─────────────┘
Chaque étape produit des mesures. Chaque mesure a des limites. La séquence s'arrête en cas de défaillance critique ou continue selon votre stratégie.
Construire une séquence de test avec OpenHTF
OpenHTF est un framework Python conçu pour le séquencement de tests matériels. TofuPilot s'intègre en tant que callback de sortie.
Les plugs héritent de BasePlug avec setUp et tearDown (attention à la casse, OpenHTF n'appellera pas setup/teardown), et sont injectés dans une phase avec @htf.plug(nom=ClassePlug).
production_test_sequence.py97 lines
import timeimport openhtf as htffrom openhtf.plugs import BasePlugfrom tofupilot.openhtf import uploadclass PowerSupplyPlug(BasePlug): """Contrôle l'alimentation de banc.""" def setUp(self): self.psu = connect_power_supply() def set_voltage(self, voltage): self.psu.write(f"VOLT {voltage}") def enable_output(self): self.psu.write("OUTP ON") def disable_output(self): self.psu.write("OUTP OFF") def tearDown(self): self.disable_output()class DMMPlug(BasePlug): """Lit les valeurs du multimètre numérique.""" def setUp(self): self.dmm = connect_dmm() def measure_voltage(self): return float(self.dmm.query("MEAS:VOLT:DC?")) def measure_current(self): return float(self.dmm.query("MEAS:CURR:DC?"))# Étape 1 : Vérification des rails d'alimentation@htf.PhaseOptions(name="Power Rail Verification")@htf.plug(psu=PowerSupplyPlug, dmm=DMMPlug)@htf.measures( htf.Measurement("vcc_3v3").in_range(3.25, 3.35).with_units("V"), htf.Measurement("vcc_1v8").in_range(1.75, 1.85).with_units("V"), htf.Measurement("vcc_5v0").in_range(4.90, 5.10).with_units("V"),)def power_rail_check(test, psu, dmm): psu.set_voltage(12.0) psu.enable_output() time.sleep(0.5) # Attendre la stabilisation des rails test.measurements.vcc_3v3 = dmm.measure_voltage() # Changer le canal du DMM et mesurer les autres rails test.measurements.vcc_1v8 = dmm.measure_voltage() test.measurements.vcc_5v0 = dmm.measure_voltage()# Étape 2 : Consommation de courant@htf.PhaseOptions(name="Current Consumption")@htf.plug(psu=PowerSupplyPlug, dmm=DMMPlug)@htf.measures( htf.Measurement("idle_current_ma").in_range(30, 60).with_units("mA"), htf.Measurement("active_current_ma").in_range(80, 150).with_units("mA"),)def current_check(test, psu, dmm): test.measurements.idle_current_ma = dmm.measure_current() * 1000 trigger_active_mode() time.sleep(0.2) test.measurements.active_current_ma = dmm.measure_current() * 1000# Étape 3 : Vérification des communications@htf.PhaseOptions(name="Communication Interfaces")@htf.measures( htf.Measurement("uart_loopback").equals(True), htf.Measurement("spi_whoami").equals(0x68),)def comm_check(test): test.measurements.uart_loopback = verify_uart_loopback() test.measurements.spi_whoami = read_spi_register(0x75)def main(): test = htf.Test( power_rail_check, current_check, comm_check, procedure_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # UUID de procédure du dashboard part_number="PCBA-100", ) test.add_output_callbacks(upload()) test.execute(lambda: input("Scanner le numéro de série du DUT : "))if __name__ == "__main__": main()Installez-le avec pip install "tofupilot[openhtf]". Chaque phase s'exécute dans l'ordre, chaque mesure est capturée, et la séquence complète est envoyée à la fin du test.
Un point que l'exemple passe sous silence : les trois rails sont lus depuis le même appel DMM sans changer de canal. Sur un vrai montage, il faudrait commuter le multiplexeur entre les lectures ou utiliser des canaux distincts, sinon les trois mesures enregistrent le même rail.
Construire une séquence de test avec le SDK Python
Si vous n'utilisez pas OpenHTF, créez l'exécution directement. Chaque étape devient une phase avec ses mesures et son verdict.
sequence_with_client.py68 lines
import osimport timefrom datetime import datetime, timezonefrom tofupilot.v2 import TofuPilotserial = input("Scanner le numéro de série du DUT : ")phases = []# Étape 1 : Rails d'alimentationstarted = datetime.now(timezone.utc)psu.enable(12.0)time.sleep(0.5)vcc_3v3 = dmm.measure_voltage(channel=1)vcc_1v8 = dmm.measure_voltage(channel=2)rails_ok = 3.25 <= vcc_3v3 <= 3.35 and 1.75 <= vcc_1v8 <= 1.85phases.append({ "name": "Power Rail Verification", "outcome": "PASS" if rails_ok else "FAIL", "started_at": started, "ended_at": datetime.now(timezone.utc), "measurements": [ { "name": "VCC 3V3", "measured_value": vcc_3v3, "units": "V", "validators": [ {"operator": ">=", "expected_value": 3.25}, {"operator": "<=", "expected_value": 3.35}, ], }, { "name": "VCC 1V8", "measured_value": vcc_1v8, "units": "V", "validators": [ {"operator": ">=", "expected_value": 1.75}, {"operator": "<=", "expected_value": 1.85}, ], }, ],})# Étape 2 : Vérification fonctionnellestarted = datetime.now(timezone.utc)boot_ok = wait_for_boot(timeout=5)phases.append({ "name": "Boot Sequence", "outcome": "PASS" if boot_ok else "FAIL", "started_at": started, "ended_at": datetime.now(timezone.utc), "measurements": [ {"name": "Boot Success", "measured_value": boot_ok}, ],})# Envoyer la séquence complèterun_outcome = "PASS" if all(p["outcome"] == "PASS" for p in phases) else "FAIL"with TofuPilot(api_key=os.getenv("TOFUPILOT_API_KEY")) as client: client.runs.create( procedure_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", serial_number=serial, part_number="PCBA-100", outcome=run_outcome, phases=phases, )Notez que les limites accompagnent la mesure sous forme de validators plutôt que de vivre uniquement dans le if au-dessus. C'est ce qui garde le résultat interprétable plus tard, quand les limites auront changé et que vous regarderez d'anciennes données.
Bonnes pratiques de conception de séquence
| Pratique | Pourquoi |
|---|---|
| Tester les éléments simples en premier | Si un rail est en court-circuit, inutile de perdre du temps sur les communications |
| Regrouper les mesures liées | Toutes les tensions dans une étape, tous les courants dans une autre |
| Utiliser des noms d'étapes cohérents | « Power Rail Verification » partout, pas « Voltage Check » ici et « Rail Test » là |
| Inclure setup/teardown | Toujours couper l'alimentation du DUT à la fin, même si le test échoue |
| Définir des limites sur chaque mesure | Une mesure sans limites ne peut être ni suivie ni analysée |
Le point sur le teardown mérite d'être souligné. Une phase qui lève une exception laisse l'alimentation active à moins que le tearDown du plug ne la coupe : c'est précisément pourquoi la mise hors tension appartient au plug et non à la fin du corps d'une phase.
Gestion des échecs de séquence
Deux stratégies quand une étape échoue :
Échec rapide (fail-fast) : Arrêter la séquence au premier échec. Pour les tests critiques pour la sécurité ou quand un échec à l'étape 1 rend les suivantes inutiles (un rail en court-circuit rend inutile le test des communications).
Exécuter tout (run-all) : Continuer malgré un échec. Quand vous voulez des données de diagnostic complètes, savoir lesquelles parmi 20 mesures ont échoué aide à l'analyse des causes.
Dans OpenHTF, une phase retourne htf.PhaseResult.STOP pour arrêter la séquence, ou CONTINUE pour poursuivre. La plupart des séquences de production sont en run-all avec un garde-fou fail-fast sur les premières phases liées à la sécurité.
Ce qui est enregistré pour chaque séquence
Chaque exécution de test capture :
| Donnée | Objectif |
|---|---|
| Identifiant de procédure | Quelle séquence a été exécutée |
| Numéro de série | Quelle unité a été testée |
| Verdict global | La séquence a-t-elle réussi ? |
| Phases avec mesures | Chaque étape, chaque mesure, chaque limite |
| Horodatages | Quand le test a commencé et terminé |
| Identifiant de station | Quelle station a exécuté le test |
| Durée | Combien de temps la séquence a pris |
Ces données structurées sont ce qui permet le suivi des tendances, la comparaison et l'analyse sur tout votre historique de production.
