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.
thermal_cycling_test.py53 lines
from tofupilot import TofuPilotClientclient = 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 cyclesfor 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_test.py32 lines
# HALT step stress resultshalt_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.
production_screen.py26 lines
# Quick production screen: 10 thermal cycles + vibrationclient.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.
