Test Types & Methods

Enregistrer les résultats HIL

Structurez les résultats de tests HIL avec les versions de firmware et les données environnementales pour le suivi de régression dans TofuPilot.

JJulien Buteau
intermediate10 min de lecture14 mars 2026

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

É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.py
27 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.

hil_regression/phases.py
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.py
32 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):        pass

Puis joignez le fichier dans votre phase de test :

hil_regression/phases.py
27 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.py
30 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.py
40 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 :

  1. Filtrer par version de firmware. Filtrez les exécutions par la mesure firmware_version et comparez les taux de réussite et les distributions entre versions.
  2. 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.
  3. 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

AspectTableur / ManuelJournalisation structurée
Étiquetage version firmwareCopier-coller dans une colonneEnregistrée comme mesure par exécution
Stockage des formes d'ondeDisque partagé avec conventions de nommageJoint à l'exécution, toujours trouvable
Traçabilité multi-cartesFeuilles ou onglets séparésSous-unités liées à l'assemblage parent
Détection de régressionInspection manuelle des graphiquesFiltrer par version firmware, comparer les tendances
Comparaison inter-stationsFusionner des fichiers de différents PCToutes 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'auditEspérer que personne n'a supprimé une ligneEnregistrements immuables avec horodatage

Plus de guides

Mettez ce guide en pratique