Les tests HIL génèrent beaucoup de données : mesures, captures de formes d'onde, versions de firmware, conditions environnementales. Sans journalisation structurée, repérer les régressions entre les versions de firmware se transforme en projet d'archéologie de tableurs. Ce guide montre comment étiqueter, joindre et interroger les résultats HIL pour que les régressions remontent automatiquement.
Prérequis
- Python 3.9+
- OpenHTF installé (
pip install openhtf) - Client Python TofuPilot (
pip install "tofupilot[openhtf]") - Un test HIL fonctionnel (voir Comment configurer les tests HIL pour systèmes embarqués avec TofuPilot)
Étape 1 : Étiqueter les exécutions avec la version du firmware et la révision matérielle
Chaque exécution de test HIL doit porter la version du firmware et la révision matérielle du DUT. Enregistrez-les comme mesures pour pouvoir les filtrer et les interroger ultérieurement.
Interrogez la version du firmware depuis le DUT au début du test.
hil_regression/main.py27 lines
import openhtf as htffrom tofupilot.openhtf import uploadfrom hil_regression.phases import ( read_firmware_info, test_adc_accuracy, test_pwm_output, test_sleep_current,)def main(): test = htf.Test( read_firmware_info, test_adc_accuracy, test_pwm_output, test_sleep_current, procedure_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # UUID de procédure du dashboard part_number="DUT-100", ) test.add_output_callbacks(upload()) test.execute(lambda: input("Scanner le numéro de série du DUT : "))if __name__ == "__main__": main()La version du firmware et la révision matérielle sont enregistrées comme mesures, ce qui les rend recherchables sur toutes les exécutions.
import openhtf as htffrom hil_regression.serial_plug import SerialCommandPlug@htf.plug(serial=SerialCommandPlug)@htf.measures( htf.Measurement("firmware_version"), htf.Measurement("hardware_revision"), htf.Measurement("bootloader_version"),)def read_firmware_info(test, serial): """Enregistrer les identifiants firmware et matériel pour la traçabilité.""" test.measurements.firmware_version = serial.send_command("VERSION") test.measurements.hardware_revision = serial.send_command("HWREV") test.measurements.bootloader_version = serial.send_command("BLVER")Cela vous donne trois champs filtrables sur chaque exécution. Quand le rendement au premier passage baisse après une mise à jour du firmware, filtrez par firmware_version pour isoler le changement.
Placez cette phase en premier. Si elle échoue, vous obtenez une exécution dont les résultats ne peuvent être rattachés à une version de firmware, ce qui est pire qu'une absence d'exécution pour du travail de régression.
Étape 2 : Joindre les fichiers de formes d'onde et de logs
Les tests HIL capturent souvent des formes d'onde d'oscilloscope, des traces d'analyseur logique ou des logs de console du DUT. Joignez-les à l'exécution de test pour qu'ils soient disponibles pour l'analyse post-mortem sans fouiller dans des partages de fichiers.
hil_regression/capture.py32 lines
import csvimport timeimport openhtf as htffrom openhtf.plugs import BasePlugclass WaveformCapturePlug(BasePlug): """Capture des échantillons analogiques et les enregistre en CSV pour pièce jointe.""" def setUp(self): pass def capture_waveform(self, adc, channel: int, duration_s: float, sample_rate_hz: int) -> str: """Échantillonne un canal ADC et écrit les résultats dans un fichier CSV.""" filepath = f"/tmp/waveform_ch{channel}_{int(time.time())}.csv" samples = [] interval = 1.0 / sample_rate_hz for i in range(int(duration_s * sample_rate_hz)): voltage = adc.read_voltage(channel) samples.append((i * interval, voltage)) time.sleep(interval) with open(filepath, "w", newline="") as f: writer = csv.writer(f) writer.writerow(["time_s", "voltage_v"]) writer.writerows(samples) return filepath def tearDown(self): passPuis joignez le fichier dans votre phase de test :
hil_regression/phases.py27 lines
import openhtf as htffrom openhtf.util import unitsfrom hil_regression.capture import WaveformCapturePlugfrom hil_regression.analog_plug import AnalogInputPlugfrom hil_regression.serial_plug import SerialCommandPlug@htf.plug(serial=SerialCommandPlug, adc=AnalogInputPlug, capture=WaveformCapturePlug)@htf.measures( htf.Measurement("pwm_voltage_mean").in_range(minimum=1.6, maximum=1.7).with_units(units.VOLT),)def test_pwm_output(test, serial, adc, capture): """Commander un PWM à 50% et capturer la forme d'onde de sortie.""" serial.send_command("PWM SET 50") # Capturer 1 seconde de données à 1 kHz waveform_path = capture.capture_waveform(adc, channel=3, duration_s=1.0, sample_rate_hz=1000) # Joindre le CSV de forme d'onde à l'exécution de test test.attach("pwm_waveform", waveform_path, "text/csv") # Enregistrer également la tension moyenne comme mesure import csv with open(waveform_path) as f: reader = csv.DictReader(f) voltages = [float(row["voltage_v"]) for row in reader] test.measurements.pwm_voltage_mean = sum(voltages) / len(voltages)Les pièces jointes apparaissent dans la vue détaillée de l'exécution. Vous pouvez les télécharger ultérieurement pour une analyse hors ligne ou une comparaison entre versions de firmware.
Notez la répartition : la moyenne est la mesure, la forme d'onde est la pièce jointe. C'est important, car le nombre est ce qui alimente les tendances et les alertes, tandis que la capture brute est ce qu'on ouvre une fois qu'une tendance a déjà indiqué où regarder. Joindre une capture complète sur chaque unité en volume dépasse vite son utilité : envisagez de ne joindre qu'en cas d'échec ou par échantillonnage.
Étape 3 : Utiliser les sous-unités pour les configurations HIL multi-cartes
De nombreux produits contiennent plusieurs cartes (carte CPU principale, carte d'alimentation, carte capteur) testées ensemble dans un montage HIL. Les sous-unités permettent de suivre chaque carte indépendamment tout en les maintenant liées à l'assemblage parent.
sub_units est un champ de htf.Test(...), aux côtés de procedure_id et part_number :
hil_regression/multi_board.py30 lines
import openhtf as htffrom tofupilot.openhtf import uploadfrom hil_regression.phases import ( test_cpu_board, test_power_board, test_sensor_board,)def main(): test = htf.Test( test_cpu_board, test_power_board, test_sensor_board, procedure_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", part_number="ASSY-100", sub_units=[ {"serial_number": "CPU-BRD-0042", "part_number": "PCB-CPU-R3"}, {"serial_number": "PWR-BRD-0019", "part_number": "PCB-PWR-R2"}, {"serial_number": "SNS-BRD-0088", "part_number": "PCB-SNS-R1"}, ], ) test.add_output_callbacks(upload()) test.execute(lambda: input("Scanner le numéro de série de l'assemblage : "))if __name__ == "__main__": main()Chaque sous-unité obtient son propre enregistrement de traçabilité. Si la carte capteur commence à échouer après un changement de révision matérielle, vous pouvez filtrer par PCB-SNS-R1 et voir exactement quand les défaillances ont commencé.
Les numéros de série sont codés en dur ici pour garder l'exemple court. Sur un vrai montage, ils proviennent de scans de codes-barres collectés avant la construction du test, puisque sub_units est un argument du constructeur.
hil_regression/board_phases.py40 lines
import openhtf as htffrom openhtf.util import unitsfrom hil_regression.analog_plug import AnalogInputPlugfrom hil_regression.serial_plug import SerialCommandPlug@htf.plug(serial=SerialCommandPlug)@htf.measures( htf.Measurement("cpu_clock_mhz").in_range(minimum=79, maximum=81).with_units(units.HERTZ), htf.Measurement("cpu_temp").in_range(maximum=85).with_units(units.DEGREE_CELSIUS),)def test_cpu_board(test, serial): """Vérifier l'horloge et la température de la carte CPU.""" test.measurements.cpu_clock_mhz = float(serial.send_command("CLOCK?")) test.measurements.cpu_temp = float(serial.send_command("TEMP?"))@htf.plug(adc=AnalogInputPlug)@htf.measures( htf.Measurement("pwr_12v_rail").in_range(minimum=11.4, maximum=12.6).with_units(units.VOLT), htf.Measurement("pwr_efficiency_pct").in_range(minimum=85),)def test_power_board(test, adc): """Vérifier les rails de sortie et le rendement de la carte d'alimentation.""" test.measurements.pwr_12v_rail = adc.read_voltage(channel=0) vin = adc.read_voltage(channel=4) vout = adc.read_voltage(channel=5) test.measurements.pwr_efficiency_pct = (vout / vin) * 100 if vin > 0 else 0@htf.plug(serial=SerialCommandPlug, adc=AnalogInputPlug)@htf.measures( htf.Measurement("sensor_offset_mv").in_range(minimum=-5, maximum=5), htf.Measurement("sensor_gain_error_pct").in_range(minimum=-1, maximum=1),)def test_sensor_board(test, serial, adc): """Vérifier la calibration de la carte capteur.""" serial.send_command("SENSOR CAL_CHECK") test.measurements.sensor_offset_mv = float(serial.send_command("SENSOR OFFSET?")) test.measurements.sensor_gain_error_pct = float(serial.send_command("SENSOR GAIN_ERR?"))Détecter les régressions
Chaque valeur de mesure est suivie sur toutes les exécutions. Pour détecter les régressions firmware :
- Filtrer par version de firmware. Filtrez les exécutions par la mesure
firmware_versionet comparez les taux de réussite et les distributions entre versions. - Vérifier les tendances des mesures. Les graphiques de tendance montrent les valeurs dans le temps. Un changement soudain après une mise à jour firmware est un signal clair de régression.
- Comparer le Cpk par version. Si le Cpk d'une mesure baisse après un changement de firmware, la capabilité du processus s'est dégradée.
Une réserve sur la comparaison entre versions : les releases firmware tombent rarement sur une frontière nette. Une version coïncide souvent avec un changement de montage, un nouveau lot de composants ou un autre rythme d'équipes. Avant d'attribuer un décalage au firmware, vérifiez si les autres dimensions ont bougé en même temps.
Suivi manuel vs journalisation structurée
| Aspect | Tableur / Manuel | Journalisation structurée |
|---|---|---|
| Étiquetage version firmware | Copier-coller dans une colonne | Enregistrée comme mesure par exécution |
| Stockage des formes d'onde | Disque partagé avec conventions de nommage | Joint à l'exécution, toujours trouvable |
| Traçabilité multi-cartes | Feuilles ou onglets séparés | Sous-unités liées à l'assemblage parent |
| Détection de régression | Inspection manuelle des graphiques | Filtrer par version firmware, comparer les tendances |
| Comparaison inter-stations | Fusionner des fichiers de différents PC | Toutes les stations envoient vers un seul espace de travail |
| Recherche historique | « Dans quel dossier c'était déjà ? » | Recherche par numéro de série, référence ou date |
| Piste d'audit | Espérer que personne n'a supprimé une ligne | Enregistrements immuables avec horodatage |
