5. Measuring and judging
A step reports what it saw. The sequence says what is acceptable. Anvil compares the two. Keeping the criterion out of the step is what lets you tighten a tolerance by editing a text file that anyone can review, without rebuilding the executor.
Three more steps
Create Bench/Board.Judging.cs, which adds three steps to the board module:
using Anvil.Step;
public static partial class Board
{
/// <summary>Measures the leakage current with the board idle, in amps.</summary>
[Step]
public static double MeasureLeakage() => 0.0004;
/// <summary>Checks that the power LED is lit.</summary>
[Step]
public static bool CheckLed() => true;
/// <summary>Reads the on-board temperature sensor.</summary>
[Step]
public static Outcome ReadTemperature() =>
Outcome.Errored("the temperature sensor did not answer");
}
Stop the executor with Ctrl+C in its terminal and start it again with
dotnet run --project Bench -- --port 9201, so it serves the new steps.
Limits
sequences/judging.yseq uses each kind of judgement:
name: judging
executors:
- { name: bench, type: grpc, host: 127.0.0.1, port: 9201 }
main:
- name: board/measure_rail
executor: bench
limit: { type: range, min: 4.75, max: 5.25 }
- name: board/measure_leakage
executor: bench
limit: { type: comparison, op: le, expected: 0.001 }
- name: board/check_led
executor: bench
$ anvil sequences/judging.yseq 2>/dev/null
=== judging: pass ===
[pass] board/measure_rail:
[pass] board/measure_leakage:
[pass] board/check_led:
board/measure_railreturns a number and has arangelimit.board/measure_leakagehas acomparison: the measurement is compared withexpectedusingop, one ofeq,ne,lt,le,gtorge. Here, “less than or equal to 1 mA”.board/check_ledreturns abool, so the step itself decides pass or fail and needs no limit.
Now the same rail against a limit it cannot meet, in
sequences/rail-tight.yseq:
name: rail_tight
executors:
- { name: bench, type: grpc, host: 127.0.0.1, port: 9201 }
main:
- name: board/measure_rail
executor: bench
limit: { type: range, min: 5.0, max: 5.25 }
$ anvil sequences/rail-tight.yseq 2>/dev/null
=== rail_tight: fail ===
[fail] board/measure_rail: 4.98 fuera de rango [5, 5.25]
$ echo $?
1
The step still returned 4.98 and did not change; the verdict did. The message
reads “4.98 out of range [5, 5.25]”, and the exit code is 1.
Fail is about the unit, error is about the bench
board/read_temperature does not measure anything: it answers
Outcome.Errored, as a real step would when a sensor does not reply.
name: error
executors:
- { name: bench, type: grpc, host: 127.0.0.1, port: 9201 }
main:
- name: board/read_temperature
executor: bench
- name: board/check_led
executor: bench
$ anvil sequences/error.yseq 2>/dev/null
=== error: error ===
[error] board/read_temperature: the temperature sensor did not answer
$ echo $?
1
The verdict is error, not fail, and the difference is deliberate. A fail
is a statement about the unit — it is out of spec, reject it. An error says
Anvil could not judge the unit at all: the bench is broken, the unit may be
perfectly good, and treating it as rejected would scrap a good board.
So when you write a step:
- If you measured, return the measurement and let the limit judge.
- If the step itself can decide, return
boolorOutcome.Failed. - If you could not measure or decide, return
Outcome.Erroredwith a message saying why. Never return a fail to mean “something went wrong”.
An exception that escapes your method also becomes error, and the executor
keeps running.
Two more things the output shows:
mainstops at the first step that does not pass.board/check_lednever ran.- A sequence’s verdict is the worst of its steps: any
errormakes iterror; otherwise anyfailmakes itfail. Both exit with1; the report tells them apart.