Environmental Testing with TofuPilot
Products fail in the field because of temperature, humidity, vibration, and altitude. Environmental testing simulates these conditions in the lab. TofuPilot tracks every environmental test result so you can build a reliability picture across your entire product lifecycle.
Types of Environmental Tests
| Test | What it simulates | Standards |
|---|---|---|
| Thermal cycling | Temperature extremes during shipping and operation | IEC 60068-2-14, MIL-STD-810 |
| Thermal shock | Rapid temperature transitions | IEC 60068-2-14 |
| Humidity testing | Moisture exposure, condensation | IEC 60068-2-78 |
| HALT (Highly Accelerated Life Test) | Find design weaknesses fast | No formal standard |
| HASS (Highly Accelerated Stress Screen) | Production screening | No formal standard |
| Vibration | Mechanical stress during transport and operation | IEC 60068-2-6, IEC 60068-2-64 |
| Altitude | Low pressure environments | IEC 60068-2-13 |
| Salt spray | Corrosion resistance | IEC 60068-2-11 |
Logging Thermal Cycling Results
A thermal cycling test runs a product through temperature extremes for hundreds of cycles. After each cycle (or batch of cycles), perform a functional check and log results.
from tofupilot import TofuPilotClient
client = TofuPilotClient()
def log_thermal_cycle_check(serial, cycle_number, temp_profile, functional_results):
"""Log post-cycle functional check results."""
measurements = [
{"name": "cycle_number", "value": cycle_number, "unit": "cycles"},
{"name": "temp_high_c", "value": temp_profile["high"], "unit": "°C"},
{"name": "temp_low_c", "value": temp_profile["low"], "unit": "°C"},
{"name": "dwell_time_min", "value": temp_profile["dwell"], "unit": "min"},
]
# Add functional measurements taken after thermal exposure
for name, result in functional_results.items():
measurements.append({
"name": name,
"value": result["value"],
"unit": result["unit"],
"limit_low": result.get("limit_low"),
"limit_high": result.get("limit_high"),
})
all_pass = all(
r.get("limit_low", float("-inf")) <= r["value"] <= r.get("limit_high", float("inf"))
for r in functional_results.values()
)
client.create_run(
procedure_id="THERMAL-CYCLING-500",
unit_under_test={"serial_number": serial},
run_passed=all_pass,
steps=[{
"name": f"Post-Cycle {cycle_number} Functional Check",
"step_type": "measurement",
"status": all_pass,
"measurements": measurements,
}],
)
# Example: check after every 50 cycles
for cycle in range(50, 501, 50):
functional = {
"vcc_3v3": {"value": measure_voltage(), "unit": "V", "limit_low": 3.25, "limit_high": 3.35},
"current_ma": {"value": measure_current() * 1000, "unit": "mA", "limit_low": 30, "limit_high": 60},
"comm_check": {"value": 1 if uart_ok() else 0, "unit": "bool", "limit_low": 1},
}
log_thermal_cycle_check(
serial="EVT-UNIT-003",
cycle_number=cycle,
temp_profile={"high": 85, "low": -40, "dwell": 15},
functional_results=functional,
)Tracking Degradation Over Cycles
The key question in environmental testing: "At what point does the product start degrading?"
TofuPilot's measurement trending shows how each parameter changes across thermal cycles:
- Cycle 50: vcc_3v3 = 3.31V (nominal)
- Cycle 200: vcc_3v3 = 3.30V (still nominal)
- Cycle 350: vcc_3v3 = 3.28V (drifting)
- Cycle 450: vcc_3v3 = 3.26V (approaching limit)
- Cycle 500: vcc_3v3 = 3.24V (below limit, FAIL)
This degradation curve tells you the product survives 400+ cycles before the 3.3V rail drifts out of spec. If your spec requires 500 cycles, you need a design change.
HALT Testing
HALT finds design weaknesses by pushing products beyond their specifications. There are no pass/fail limits. The goal is to find the breaking point.
# HALT step stress results
halt_steps = [
{"stress": "Cold Step", "temp_c": -40, "functional": True},
{"stress": "Cold Step", "temp_c": -50, "functional": True},
{"stress": "Cold Step", "temp_c": -60, "functional": False}, # Lower operating limit found
{"stress": "Hot Step", "temp_c": 85, "functional": True},
{"stress": "Hot Step", "temp_c": 95, "functional": True},
{"stress": "Hot Step", "temp_c": 105, "functional": False}, # Upper operating limit found
{"stress": "Vibration", "grms": 10, "functional": True},
{"stress": "Vibration", "grms": 20, "functional": True},
{"stress": "Vibration", "grms": 30, "functional": False}, # Vibration limit found
]
steps = []
for h in halt_steps:
steps.append({
"name": f"{h['stress']} - {'temp_c' in h and f\"{h['temp_c']}°C\" or f\"{h['grms']}Grms\"}",
"step_type": "measurement",
"status": h["functional"],
"measurements": [{
"name": "functional_after_stress",
"value": 1 if h["functional"] else 0,
"unit": "pass/fail",
}],
})
client.create_run(
procedure_id="HALT-STEP-STRESS",
unit_under_test={"serial_number": "EVT-UNIT-001"},
run_passed=True, # HALT always "passes" - it's about finding limits
steps=steps,
)Environmental Test Matrix
Track which environmental tests have been completed across your product variants.
| Product variant | Thermal cycle | Humidity | Vibration | HALT | ESD |
|---|---|---|---|---|---|
| Rev A | 500 cycles ✓ | 85/85 ✓ | 10G ✓ | Done ✓ | 8kV ✓ |
| Rev B | 250/500 | Pending | Pending | Done ✓ | 8kV ✓ |
| Rev C (new layout) | Not started | Not started | Not started | Pending | Not started |
TofuPilot tracks completion automatically. If THERMAL-CYCLING-500 has a passing run for "Rev A," it shows as complete. No spreadsheet maintenance needed.
Production Screening (HASS/ESS)
Environmental stress screening in production catches infant mortality failures. Run every unit through a short thermal cycle and vibration profile, then functional test.
# Quick production screen: 10 thermal cycles + vibration
client.create_run(
procedure_id="ESS-PRODUCTION-SCREEN",
unit_under_test={"serial_number": "PROD-UNIT-4521"},
run_passed=True,
steps=[
{
"name": "Post-Thermal Functional",
"step_type": "measurement",
"status": True,
"measurements": [
{"name": "vcc_3v3", "value": 3.31, "unit": "V", "limit_low": 3.25, "limit_high": 3.35},
{"name": "current_ma", "value": 44, "unit": "mA", "limit_low": 30, "limit_high": 60},
],
},
{
"name": "Post-Vibration Functional",
"step_type": "measurement",
"status": True,
"measurements": [
{"name": "vcc_3v3", "value": 3.30, "unit": "V", "limit_low": 3.25, "limit_high": 3.35},
{"name": "current_ma", "value": 45, "unit": "mA", "limit_low": 30, "limit_high": 60},
],
},
],
)Track ESS yield separately from functional test yield. If ESS catches 0.5% of units that passed functional test, it's paying for itself by preventing field failures.