Acceptance recipes
Use a recipe to pass or fail a unit. It runs on a setup, steps it through a profile, takes measurements and records a verdict per bound. Each time you run it is a run.
Setup, recipe, run
| What it is | Holds | |
|---|---|---|
setup(...) | your test setup: the document you Deploy | channels, stop conditions, interlocks, safe values, the page |
recipe(...) | a procedure that runs on a setup | steps, measurements, bounds |
| run | one execution of a recipe | measurements, verdicts, the event log. Recorded, never edited |
A file with neither setup(...) nor recipe(...) first is a
notebook.
Safety lives in the setup. A recipe can't declare an interlock or a stop condition, so it can't weaken its setup.
Write the setup first and Deploy it. The recipe names it in setup= and
reads only channels the setup declares. If that setup isn't deployed, Start
run offers to deploy it. One setup runs at a time; a recipe for a different
setup offers to replace the deployed one and start.
Replacing a setup stops the running one first:
- A confirmation shows what will move, before you agree.
- Every output it controls goes to its safe value, as with Stop.
- The new setup deploys once Computer confirms those safe values.
If Computer never confirms, nothing is replaced. The old setup stays stopped, sending safe values, and the message names the outputs to check before you press Reset. A new revision of the same setup that keeps all its outputs stops nothing.
A recipe
recipe("unit_accept", setup="load_test", unit="serial")
ch = channels()
output = ch["/dut/output_v"]
temp = ch["/dut/temp_c"]
# Bounds — each declares WHAT is judged, then HOW.
output_v = value("output_at_load", unit="V")
require(output_v.within(11.8, 12.2))
warm = value("temp_after_load", unit="degC")
require(warm.below(60))
# Steps — actions first, measurements after.
step("load",
set(ch["/dut/enable"], True),
set(ch["/dut/load_a"], 2.0),
hold("30s"))
step("verify",
measure(output_v, output),
measure(warm, temp))
step("stow", set(ch["/dut/load_a"], 0.0), set(ch["/dut/enable"], False))
plan(steps=["load", "verify", "stow"])
page("Unit acceptance",
layout=col(row(readout(output), readout(temp)),
row(verifications("Bounds"), col(alarms(), log()))))
recipe(name, setup=, unit=) | the header. setup= names the setup it runs on; unit= names what identifies the unit under test ("serial") |
ch["/path"] | a channel the setup declares, by its path on Live |
value(name, unit=) | a named measurement the run records |
require(v.below(x)) | the bound that judges it: above, below, within(lo, hi), outside(lo, hi) |
step(name, ...actions) | one ordered step: set, ramp, wait, hold, wait_until, confirm, exec, then measure |
measure(v, channel) | capture the channel's reading into the value, during that step |
plan(steps=[...]) | the order the steps run in |
page(title, layout=) | the run's live panel: a readout per measured channel, verifications() for the bounds as they are judged, alarms(), log(). Without one, the panel is empty |
Actions
| Action | Does |
|---|---|
set(ch["/out/enable"], True) | command an output the setup declared with ch.write |
ramp(ch["/x"], from_=0, to=100, over="20s") | move an output smoothly. from_ is required: a ramp never guesses where it starts |
wait("30s") | let time pass. Nothing is checked meanwhile |
hold("2m") | stay at the current commands. Recorded as part of the test, not dead time |
wait_until(lambda: ch["/p"] > 900, timeout="30s") | wait for a condition. timeout= is required; if it expires, the step faults |
confirm("Complete the manual action", timeout="10m") | a sign-off gate; the run keeps the signature. hold=True waits with no limit |
exec("python3 read.py --source running") | run a script and wait (see Run your own scripts) |
Durations are strings: "250ms", "30s", "2m".
Verdicts
A bound judges a measurement, never the setup. A failed bound fails that measurement, and the run reports it. It does not stop the test; that is the setup's interlock.
| The run says | Meaning | Is the unit scrap? |
|---|---|---|
| pass | every bound held | no |
| fail | a measurement was outside its bound | yes |
| unavailable | a measurement couldn't be taken: its channel published nothing | no, but not a pass either |
| passed_config_pending | only a persists=True bound failed. The unit is fine; a change didn't survive its save or power cycle | no. Redo the save |
value(..., persists=True) marks a bound that checks whether a change survived,
not whether the unit is good. Usually you put two bounds on one quantity: one
read live, one read after a power cycle.
Change, then measure, in separate steps
A step that acts (exec, set, ramp) measures nothing; it either completes
or is interrupted. The verdict comes from a later step that reads the unit
independently.
recipe("unit_configure_and_verify", setup="load_test", unit="serial")
ch = channels()
setting = ch["/dut/setting"]
drift = ch["/dut/config_diff_lines"]
applied = value("setting_applied")
require(applied.within(41.5, 42.5))
clean = value("config_drift", unit="lines")
require(clean.below(0.5))
kept = value("config_drift_after_restart", unit="lines", persists=True)
require(kept.below(0.5))
# The change. Nothing is measured here.
step("configure", exec("python3 configure.py --setting 42"))
# An independent read of what is live now, and the verdict.
step("verify",
exec("python3 read.py --source running"),
measure(applied, setting),
measure(clean, drift))
# Did it survive: someone restarts the unit, then the read repeats.
step("restart",
confirm("Restart the unit, then continue", hold=True),
wait("30s"),
exec("python3 read.py --source startup"),
measure(kept, drift))
plan(steps=["configure", "verify", "restart"])
A bound on a channel nothing publishes still compiles, but every run reports unavailable. Name only channels the setup declares and that something is sending. Check the list on Live.
Updated