Calibration de capteurs à grande échelle avec TofuPilot
Calibrer 10 capteurs est un travail manuel. En calibrer 10 000 nécessite un système. Vous avez besoin de mesures de référence multi-points, de critères de réussite/échec par rapport à des bandes de tolérance, de certificats de calibration et d'une traçabilité jusqu'aux étalons de référence.
TofuPilot gère la partie données pour que vous puissiez vous concentrer sur la procédure de calibration elle-même.
Ce qu'implique la calibration de capteurs
La calibration de capteurs en production suit généralement ce flux :
- Appliquer des stimuli de référence connus (température, pression, force, etc.)
- Lire la sortie du capteur à chaque point de référence
- Calculer l'erreur, la linéarité et l'hystérésis
- Appliquer des facteurs de correction si nécessaire
- Vérifier que la sortie corrigée respecte les spécifications
- Générer un certificat de calibration
Prérequis
- Python 3.8+ avec
openhtfettofupilotinstallés - Un étalon de référence (source calibrée ou capteur de référence)
- Un système d'acquisition de données ou un instrument pour lire la sortie du capteur
Étape 1 : Définir les mesures de calibration multi-points
Une calibration typique utilise 5 à 11 points de référence sur toute la plage du capteur. Définissez les mesures pour chaque point :
import openhtf as htf
from openhtf.util import units
CAL_POINTS = [0, 25, 50, 75, 100] # Pourcentage de la pleine échelle
measures = []
for pct in CAL_POINTS:
measures.append(
htf.Measurement(f"error_at_{pct}pct")
.with_units(units.PERCENT)
.in_range(-0.5, 0.5)
.doc(f"Erreur de mesure à {pct}% de la pleine échelle")
)
measures.append(
htf.Measurement("max_linearity_error")
.with_units(units.PERCENT)
.at_most(0.25)
.doc("Déviation maximale de linéarité par rapport à la droite de meilleur ajustement")
)
measures.append(
htf.Measurement("hysteresis")
.with_units(units.PERCENT)
.at_most(0.1)
.doc("Hystérésis maximale entre les balayages montant et descendant")
)
@htf.measures(*measures)
def calibration_test(test, reference_source, sensor_reader):
"""Exécuter une calibration multi-points avec balayage montant et descendant."""
full_scale = 100.0 # Ajuster selon la plage de votre capteur
up_readings = {}
down_readings = {}
# Balayage montant
for pct in CAL_POINTS:
ref_value = full_scale * pct / 100.0
reference_source.set_output(ref_value)
time.sleep(2.0) # Temps de stabilisation
reading = sensor_reader.read()
up_readings[pct] = reading
error = (reading - ref_value) / full_scale * 100
setattr(test.measurements, f"error_at_{pct}pct", error)
# Balayage descendant pour l'hystérésis
for pct in reversed(CAL_POINTS):
ref_value = full_scale * pct / 100.0
reference_source.set_output(ref_value)
time.sleep(2.0)
reading = sensor_reader.read()
down_readings[pct] = reading
# Hystérésis : différence maximale entre les lectures montantes et descendantes
max_hyst = max(
abs(up_readings[p] - down_readings[p]) / full_scale * 100
for p in CAL_POINTS
)
test.measurements.hysteresis = max_hyst
# Linéarité : déviation par rapport à la droite de meilleur ajustement
import numpy as np
ref_vals = [full_scale * p / 100.0 for p in CAL_POINTS]
read_vals = [up_readings[p] for p in CAL_POINTS]
coeffs = np.polyfit(ref_vals, read_vals, 1)
fit_vals = np.polyval(coeffs, ref_vals)
linearity_errors = [(r - f) / full_scale * 100 for r, f in zip(read_vals, fit_vals)]
test.measurements.max_linearity_error = max(abs(e) for e in linearity_errors)Étape 2 : Stocker les courbes de calibration comme données multidimensionnelles
Capturez la courbe de calibration complète, pas seulement le résultat réussite/échec :
import openhtf as htf
from openhtf.util import units
@htf.measures(
htf.Measurement("calibration_curve")
.with_dimensions(units.PERCENT)
.doc("Courbe de calibration complète : entrée de référence vs sortie capteur"),
htf.Measurement("correction_curve")
.with_dimensions(units.PERCENT)
.doc("Facteurs de correction à chaque point de calibration"),
)
def capture_calibration_curve(test, reference_source, sensor_reader):
"""Capturer une courbe de calibration dense pour post-traitement."""
full_scale = 100.0
for pct in range(0, 101, 5): # Incréments de 5%
ref_value = full_scale * pct / 100.0
reference_source.set_output(ref_value)
time.sleep(1.0)
reading = sensor_reader.read()
test.measurements.calibration_curve[pct] = reading
# Facteur de correction : ce qu'il faut ajouter pour obtenir la vraie valeur
correction = ref_value - reading
test.measurements.correction_curve[pct] = correctionTofuPilot stocke la courbe complète. Vous pouvez comparer les courbes entre capteurs, détecter les variations de lot et suivre la dérive de calibration dans le temps.
Étape 3 : Suivre la traçabilité des étalons de référence
Chaque calibration ne vaut que ce que vaut sa référence. Enregistrez quel étalon de référence a été utilisé :
from tofupilot import TofuPilotClient
client = TofuPilotClient()
result = client.create_run(
procedure_id="pressure-sensor-cal",
unit_under_test={
"serial_number": sensor_sn,
"part_number": "PS-500-A",
},
run_passed=True,
properties={
"reference_standard": "FLUKE-8845A-SN12345",
"reference_cal_date": "2026-01-15",
"reference_cal_due": "2027-01-15",
"reference_cert_number": "CAL-2026-0042",
"ambient_temp_c": 23.1,
"ambient_humidity_pct": 45,
"operator": "OP-003",
},
)Quand un auditeur interroge votre chaîne de traçabilité, chaque test de calibration est lié à l'étalon de référence, son propre certificat de calibration et les conditions environnementales pendant la procédure.
Étape 4 : Gérer les plannings de recalibration
Les capteurs dérivent. Suivez quand chaque unité a été calibrée pour la dernière fois et quand la prochaine est due :
from tofupilot import TofuPilotClient
from datetime import date, timedelta
client = TofuPilotClient()
# Récupérer tous les tests de calibration pour un type de capteur
cal_runs = client.get_runs(
procedure_id="pressure-sensor-cal",
limit=5000,
)
# Trouver la dernière calibration par numéro de série
latest_cal = {}
for run in cal_runs:
sn = run.unit.serial_number
if sn not in latest_cal or run.started_at > latest_cal[sn].started_at:
latest_cal[sn] = run
# Vérifier les recalibrations en retard (intervalle de 12 mois)
cal_interval = timedelta(days=365)
today = date.today()
overdue = []
upcoming = []
for sn, run in latest_cal.items():
cal_date = run.started_at.date()
due_date = cal_date + cal_interval
if due_date < today:
overdue.append((sn, due_date))
elif due_date < today + timedelta(days=30):
upcoming.append((sn, due_date))
print(f"En retard : {len(overdue)} capteurs")
print(f"À échéance dans 30 jours : {len(upcoming)} capteurs")Étape 5 : Détecter la dérive de calibration
Comparez les résultats de calibration dans le temps pour repérer les capteurs qui dérivent vers leurs limites de tolérance :
from tofupilot import TofuPilotClient
client = TofuPilotClient()
# Récupérer l'historique de calibration pour un capteur spécifique
cal_history = client.get_runs(
procedure_id="pressure-sensor-cal",
unit_serial_number="PS-500-A-0042",
limit=10,
)
print(f"Historique de calibration pour PS-500-A-0042 :")
print(f"{'Date':<12} {'Erreur@50%':<12} {'Linéarité':<12} {'Statut'}")
print("-" * 48)
for run in reversed(cal_history):
error_50 = run.measurements.get("error_at_50pct", {}).get("value", "N/A")
linearity = run.measurements.get("max_linearity_error", {}).get("value", "N/A")
status = "PASS" if run.passed else "FAIL"
print(f"{run.started_at.date()!s:<12} {error_50:<12} {linearity:<12} {status}")Si l'erreur à 50 % est de 0,1 % cette année et était de 0,05 % l'année dernière, vous savez dans quelle direction elle évolue. Remplacez ou ajustez avant qu'elle n'échoue.