Working with DUTs
A DUT (“device under test”) wraps a serial connection and exposes a small,
consistent API for writing commands and waiting for expected output. The entry
point is dut_wrapper, which adapts several input types into a DutBase
object.
Creating a DUT
dut_wrapper accepts different kinds of input:
from serial import Serial
from esptest.all import DutConfig, dut_wrapper
# 1. From an already-open pyserial connection
ser = Serial('/dev/ttyUSB0', 115200, timeout=0.01)
dut = dut_wrapper(ser, name='DUT', log_file='./dut_logs/dut.log')
# 2. From a device path string
dut = dut_wrapper('/dev/ttyUSB0', name='DUT')
# 3. From a DutConfig (most flexible)
config = DutConfig(name='DUT', device='/dev/ttyUSB0', baudrate=115200)
dut = dut_wrapper(config)
Use it as a context manager so the port and the background reader thread are released when the block exits:
with dut_wrapper(ser, 'DUT', './dut_logs/dut.log') as dut:
...
Sending data and matching output
import re
with dut_wrapper(ser, 'DUT') as dut:
dut.flush_data() # drop buffered output
dut.write('restart\r\n') # raw write
dut.write_line('reboot', end='\r\n') # write + line ending
# str/bytes pattern: waits for the text, returns None
dut.expect('main_task: Returned from app_main', timeout=5)
# compiled pattern: returns the re.Match object
match = dut.expect(re.compile(r'offset (0x\w+)'), timeout=5)
print(match.group(1))
Reading buffered data directly:
text = dut.read_all_data() # decoded str, flushes by default
raw = dut.read_all_bytes() # bytes, keeps the cache by default
cache = dut.data_cache # current cache without flushing
If expect does not find the pattern within timeout seconds it raises
TimeoutError.
Configuring a DUT with DutConfig
DutConfig is a dataclass describing how the DUT should be created. Commonly
used fields:
Field |
Purpose |
|---|---|
|
DUT name (defaults to the device/port base name) |
|
Serial device, e.g. |
|
Console baudrate (0 = derive from bin path or default 115200) |
|
Extra pyserial kwargs, e.g. |
|
Firmware path; used to derive chip/stub/baudrate |
|
Log file path (auto-generated under |
|
List of |
|
Default timeout for |
config = DutConfig(
name='JAP1',
device='/dev/ttyUSB0',
baudrate=115200,
serial_configs={'timeout': 0.1},
)
with dut_wrapper(config) as dut:
dut.write_line('help', end='\r\n')
dut.expect('ready', timeout=5)
Custom DUT classes
You can attach mixins by passing a custom class to wrap_cls. It must derive
from the DUT interface:
from esptest.adapter.dut.esp_dut import EspDut
class MyDut(EspDut):
def hello(self) -> None:
self.write_line('hello')
with dut_wrapper(config, wrap_cls=MyDut) as dut:
dut.hello()
Attaching monitors
Pass DataMonitor instances through DutConfig.monitors to react to output as
it streams in (for example capturing a reset reason). See
Streaming data monitor for the full pattern.