Files
py-dvt-ate/src/py_dvt_ate/tests/base.py

159 lines
5.1 KiB
Python

"""Base class and utilities for DVT test implementations.
This module provides common functionality shared across all DVT tests,
including thermal settling helpers, measurement utilities, and statistical
calculations.
"""
import time
from abc import ABC
from collections.abc import Callable
from py_dvt_ate.framework.context import ITest, TestContext
class BaseDVTTest(ITest, ABC):
"""Abstract base class for DVT tests with common utilities.
Provides helper methods for thermal settling, measurement averaging,
and other common test patterns. All DVT tests should inherit from
this class rather than directly from ITest.
"""
def wait_for_temperature(
self,
context: TestContext,
setpoint: float,
timeout: float = 300.0,
poll_interval: float = 1.0,
) -> bool:
"""Wait for thermal chamber to stabilise at setpoint.
Sets the chamber temperature and waits until stable. Logs progress
to the test logger.
Args:
context: Test context with instruments and logger.
setpoint: Target temperature in degrees Celsius.
timeout: Maximum wait time in seconds. Default 300s (5 minutes).
poll_interval: Time between stability checks. Default 1s.
Returns:
True if temperature stabilised within timeout, False if timed out.
Raises:
ConnectionError: If instrument communication fails.
IOError: If instrument reports error.
"""
chamber = context.instruments.chamber
# Set the temperature
chamber.set_temperature(setpoint)
context.logger.log_event(
f"Set thermal chamber to {setpoint:.1f}°C, waiting for stability...",
level="INFO",
)
# Wait for stability
start_time = time.time()
elapsed = 0.0
while elapsed < timeout:
if chamber.is_stable():
actual = chamber.get_temperature()
context.logger.log_event(
f"Chamber stable at {actual:.2f}°C "
f"(target {setpoint:.1f}°C) after {elapsed:.1f}s",
level="INFO",
)
return True
time.sleep(poll_interval)
elapsed = time.time() - start_time
# Timeout
actual = chamber.get_temperature()
context.logger.log_event(
f"Timeout waiting for stability. Chamber at {actual:.2f}°C, "
f"target {setpoint:.1f}°C after {timeout:.1f}s",
level="WARNING",
)
return False
def measure_averaged(
self,
measurement_func: Callable[[], float],
num_samples: int = 5,
settle_time: float = 0.1,
) -> tuple[float, float]:
"""Take multiple measurements and return mean and standard deviation.
Useful for reducing noise in measurements by averaging multiple samples.
Args:
measurement_func: Function that returns a single measurement.
num_samples: Number of samples to average. Default 5.
settle_time: Delay between samples in seconds. Default 0.1s.
Returns:
Tuple of (mean, standard_deviation).
Raises:
ValueError: If num_samples < 1.
Exception: If measurement_func raises an exception.
"""
if num_samples < 1:
raise ValueError("num_samples must be at least 1")
samples: list[float] = []
for _ in range(num_samples):
if settle_time > 0 and len(samples) > 0:
time.sleep(settle_time)
samples.append(measurement_func())
mean = sum(samples) / len(samples)
if len(samples) == 1:
std_dev = 0.0
else:
variance = sum((x - mean) ** 2 for x in samples) / (len(samples) - 1)
std_dev = variance ** 0.5
return mean, std_dev
def thermal_settle(
self,
context: TestContext,
additional_settle_time: float = 5.0,
) -> None:
"""Wait for additional thermal settling after chamber reports stable.
After the chamber reports stable temperature, this adds additional
settling time to ensure the DUT junction temperature has also stabilised.
This is important for measurements sensitive to self-heating effects.
Args:
context: Test context with logger.
additional_settle_time: Extra settling time in seconds. Default 5s.
"""
if additional_settle_time > 0:
context.logger.log_event(
f"Additional thermal settling for {additional_settle_time:.1f}s...",
level="INFO",
)
time.sleep(additional_settle_time)
def delay(self, seconds: float, message: str | None = None) -> None:
"""Sleep for specified duration.
Simple utility for adding delays in test sequences.
Args:
seconds: Delay duration in seconds.
message: Optional message describing reason for delay.
"""
if message:
# Could log this if needed
pass
time.sleep(seconds)