Soak run procedure
This is the working procedure for the PBI-112 device observation, which is the two-hour continuous
run of the firmware on the physical classic LILYGO T-Display. It covers the flash, the capture, what
counts as a pass, what counts as a failure, and the cases to check while the run is going.
The observation is maintainer evidence under section 10.6 of the active architecture. The transcript is half of it and the maintainer’s own words are the other half, so the run is watched rather than only recorded.
Before starting
The board is the classic variant on /dev/ttyUSB0, and the image is the one built from the current
tree. Two hours is the stated duration rather than a judgement, and a shorter run is not the same
evidence. The run is not reset, reflashed, or power cycled while it lasts, so the desk it sits on
should be one nobody needs for two hours.
Activate the ESP-IDF installation first, because flash and monitor both delegate to idf.py.
source ~/.local/opt/espressif/export.shFlash
cd platforms/esp_idf && idf.py build
cd ../.. && ./pet.py flashflash names an inactive SDK environment or an absent port before it writes anything, so a refusal
at this stage is a setup problem rather than a run failure. Add --port /dev/ttyUSB0 if another
serial device is attached.
Capture
monitor needs an interactive terminal, because ESP-IDF requires a TTY on standard input. Keep the
whole transcript, because the first heap line and the last one are the two readings the observation
is recorded from.
script -q -c "./pet.py monitor" _report/soak-transcript.txt_report is ignored by Git, so the transcript stays out of commits. The monitor exits with Ctrl-C
for runs started from platforms/esp_idf, and the script wrapper exits with it too. Note the wall
clock time the run started and the time it ended.
What a healthy run looks like
The transcript opens with the boot, the panel, and the readiness lines, then carries one heap line
about every minute and nothing else.
pet event=boot board=t-display-classic target=esp32 version=0.4.0
pet event=display-ready width=240 height=135 rotation=90 colour-order=rgb
pet event=runtime-ready tick-ms=40
pet event=heap free-bytes=142312 min-free-bytes=139880 uptime-s=0
pet event=heap free-bytes=142312 min-free-bytes=139880 uptime-s=72The heap reading is emitted every 1800 iterations rather than on a timer, so the difference between
two uptime-s values is the measured pace of the 1800 iterations between them. About 72 seconds is a
40 millisecond iteration. A longer gap is a slower loop and is worth recording as the pace it
actually is.
The panel shows the pet rather than a pattern. The presentation is a calm shape on a canvas that
fills the height of the panel, with a letterbox band at each side, and the body changes tone and size
when the activity or the expression changes. Without a button the world reaches curious about a
minute after boot and stays there, so exactly one change is expected on the glass for the whole run.
Pass and fail
The run passes when it reaches two hours on the board, the transcript shows no unexpected reset and no watchdog abort, the minimum free heap does not fall continuously across the run, and the loop is still reporting its readings at the end.
The run fails when the board resets or aborts on its own, when the readings stop arriving while the
board is still powered, when min-free-bytes falls step after step across the whole run, or when a
runtime-fault line appears and does not clear. A run that ends early is reported with the time it
reached and the reason rather than repeated quietly.
A single time-clamped line is not a failure by itself, and it is worth recording, because the
1000 millisecond bound was chosen against a 40 millisecond iteration and nothing ordinary should
reach it.
Any defect found here is filed in bugs rather than fixed inside PBI-112.
Cases to check
Each row is one step and what that step should produce. A row whose expected value does not appear is recorded with what appeared instead.
board-flashes-and-boots
| Step | Expected |
|---|---|
Run ./pet.py flash | The image is written and verified, and the tool reports no inactive SDK environment and no absent port |
| Start the monitor and watch the first seconds | One boot line naming t-display-classic, esp32, and the project version |
panel-comes-up
| Step | Expected |
|---|---|
Read the line after boot | pet event=display-ready width=240 height=135 rotation=90 colour-order=rgb |
| Look at the glass | The pet is visible, which is a lighter body centred on a canvas that fills the height of the panel, with a letterbox band at each side |
Search for display-failed | Nothing. A line here names the stage that refused and the run continues without the panel |
runtime-ready-arrives-once
| Step | Expected |
|---|---|
Search the whole transcript for runtime-ready | Exactly one line for the run |
| Read its fields | tick-ms=40 |
| Check where it sits | Before every heap line and after display-ready |
first-heap-reading-opens-the-run
| Step | Expected |
|---|---|
Read the first heap line | It carries uptime-s=0 and arrives immediately after runtime-ready |
Write down its free-bytes and min-free-bytes | Two values to compare the end of the run against |
heap-readings-keep-arriving
| Step | Expected |
|---|---|
| Watch the transcript for the first five minutes | A new heap line about every 72 seconds |
| Check again every twenty minutes or so | Readings still arriving, with uptime-s increasing each time |
| Check once more just before stopping the monitor | The last reading is no more than about 72 seconds old |
iteration-pace-holds
| Step | Expected |
|---|---|
Subtract the uptime-s of one early heap line from the next | About 72 seconds, which is a 40 millisecond iteration over 1800 of them |
| Repeat in the middle of the run | The same difference |
| Repeat for the last two readings | The same difference, and record whatever the value actually is |
minimum-free-heap-does-not-fall
| Step | Expected |
|---|---|
Put the first min-free-bytes and the last one side by side | The two values are the same or close, and the difference is recorded either way |
| Scan the readings between them | The value settles early and stays there |
| Look for a step down that repeats | Nothing. A drop every minute is a leak, and a single early drop as the bring-up runs is not |
free-heap-stays-steady
| Step | Expected |
|---|---|
Compare free-bytes across the readings | It stays about the same across the run |
| Compare the first reading with the last | No steady decline, which would be a leak min-free-bytes has not caught up with yet |
no-unexpected-reset
| Step | Expected |
|---|---|
Search the transcript for boot | One line for the whole run |
Search for rst: and Guru Meditation | Nothing. Both are SDK output that a reset or a panic produces |
no-watchdog-abort
| Step | Expected |
|---|---|
Search for watchdog, WDT, and task_wdt | Nothing. The loop yields every iteration, so the idle task feeds its watchdog |
app-main-does-not-return
| Step | Expected |
|---|---|
Search for Returned from app_main() | Nothing. The SDK prints it when the entry returns, which the run loop never does |
no-standing-fault
| Step | Expected |
|---|---|
Search for runtime-fault | Nothing in a healthy run |
| If a line appears, look for a second one after it | A fault is reported once when it appears and again once it clears, so a line with no partner means the condition never cleared |
time-is-not-clamped
| Step | Expected |
|---|---|
Search for time-clamped | Nothing in a healthy run |
If a line appears, record its elapsed-ms | The value says how much an iteration lost, and anything reaching the 1000 millisecond bound is worth reporting |
transcript-lines-follow-the-format
| Step | Expected |
|---|---|
| Scan the project’s own lines | Every one opens with pet event= and carries lower case hyphenated keys |
| Look for free-form status text among them | Nothing. A line that does not follow the format is not release evidence |
run-reaches-two-hours
| Step | Expected |
|---|---|
| Leave the board running without resetting, reflashing, or power cycling it | The run continues untouched for two hours |
Stop the monitor after two hours and read the last heap line | About uptime-s=7200, with the readings still arriving when the monitor was stopped |
| Note the wall clock start and end times | A duration that matches the uptime the transcript reports |
What to record
The observation needs the board and the variant, the firmware commit, the date, the duration reached,
the first and last min-free-bytes side by side, the pace the uptime-s differences imply, and
anything unexpected the run produced. The transcript is kept with it.
The run that was made
The run was made on 2026-08-07 and it passed. It started at 19:04 and the monitor was stopped just
after 21:08, and the last complete reading was uptime-s=7416, which is 2 hours 3 minutes 36
seconds. The board was the classic variant on /dev/ttyUSB0 and the image was the working tree over
commit 6c6e1e5. The transcript reports version=0.4.0 because that is the project version until
v0.5.0 is released, so the observation names the commit rather than the version.
All thirteen cases produced their expected value. The transcript carries 104 complete heap lines
and one boot, one display-ready, and one runtime-ready line, and no runtime-fault,
time-clamped, display-failed, Guru Meditation, watchdog, or Returned from app_main() output.
The one rst: line is the POWERON_RESET the run started from. free-bytes and min-free-bytes
were both 237104 at the first reading and at the last, unchanged through every reading between them,
and all 103 gaps were exactly 72 seconds, which is the announced 40 millisecond iteration over 1800
of them. The maintainer observed the pet on the glass across the run, changing once about a minute
after boot as the world reached curious and holding after that, which is the whole of the progress
that is available before PBI-125 maps a button to a greet.
The transcript is kept at _report/soak-transcript.txt and goes to the release evidence with the
observation when v0.5.0 is prepared. PBI-112 and PBI-116 are marked, E32 and E33 are
closed, the progress rows carry the readings, and LIM-012 is narrowed to what stays unverified
after a continuous run.
Repeating it
This file stays the procedure rather than becoming a record, because the release will want the run
repeated against the tagged image and PBI-128 asks for device evidence again.
When a run fails, the defect is filed in bugs with the transcript around it, the observation is not recorded as evidence, and the fix is its own task rather than part of it.
When a run ends early, the time it reached and the reason are recorded, and it is repeated from the start rather than continued, because two hours is the stated duration.