Benches¶
A bench is an XPressServer: it reports its name and, for each of its drivers — the UART to the unit, the TCP link to a power supply, a digital I/O card — what it offers: values to read, values to set, and commands with their parameters and answers. The binding connects the data points of your workspace to that offering. A data point that is not bound is served by the operator, exactly as without a bench.
Until the driver SDK exists, a bench is a simulated bench: it runs in the page, declares its offering in the same form a real server will report it, and answers from what it declares.
Open Test-Editor → Benches.
Adding a bench¶
Type the name — the one its server reports — and choose Add simulated
bench. Add bench, for a real bench, is greyed out and marked Not in
demo: a real bench needs the driver SDK. A simulated bench starts from a template with three
drivers: a UART to the unit (mode, diode_temp, SetSystemMode,
RunSelfTest), a power supply (ch1.volt, SetVoltage) and an I/O card
(relay.K1 … K4, in1). Change its offering and behaviour under
Simulation — see The simulation.
Each bench is a file benches/<name>.json in the workspace and travels with
it; every change is in the history. A bench is known by its name, not by
its file: two benches with the same name are an error. The address of a real
bench's server is kept per machine, in .xpress/benches.local.json — the same
bench can be reached differently from two computers.
The simulation¶
A simulated bench answers from what it declares. Under Simulation on its page stand its offering — in the form a real XPressServer will report it — and six things that say how it behaves:
| What it says | Named by | Example | |
|---|---|---|---|
values |
What a reading reports: one value, or a list that is read in turn, one per reading, and starts again at its end | the offering: driver.name |
"dut.mode": "RDY", "io.in1": [false, false, true] |
answers |
What a command answers, in the bench's own words. Without an entry it answers the first of its possible answers | the command | "SetSystemMode": "NOT_ALLOWED" |
delayMs |
How long every access takes, in milliseconds | — | "delayMs": 200 |
faults |
That a data point does not answer ok: failed — the bench answers with an error — or timeout — it does not answer at all |
the data point of the workspace | "System/State": "timeout" |
effects |
Which reading a command sets when it is answered ok — so that what was set is what is read afterwards. With overMs a number moves there over that time instead of jumping |
the command; inside, the offering | "SetVoltage": [{ "set": "psu.ch1.volt", "from": "volt", "overMs": 2000 }] |
safeState |
What safe_state of a driver leaves behind |
the driver; inside, the offering | "psu": [{ "set": "psu.ch1.volt", "value": 0 }] |
faults is the one thing named by the data point of your workspace
(System/State): it says what a test meets. Everything else is named by what
the bench offers (dut.mode), because it says what the bench does.
All of it together:
"simulation": {
"values": {
"dut.mode": "RDY",
"dut.diode_temp": 25,
"psu.ch1.volt": 0,
"io.in1": [false, false, true]
},
"answers": { "SetSystemMode": "DONE", "SetVoltage": "DONE", "RunSelfTest": "DONE" },
"delayMs": 0,
"faults": { "Diode/Temp": "failed" },
"effects": {
"SetSystemMode": [{ "set": "dut.mode", "from": "mode" }],
"SetVoltage": [{ "set": "psu.ch1.volt", "from": "volt", "overMs": 2000 }]
},
"safeState": {
"dut": [{ "set": "dut.mode", "value": "STBY" }],
"psu": [{ "set": "psu.ch1.volt", "value": 0 }]
}
}
Effects. set names the reading; from the parameter of the command whose
value it takes — as it went to the bench, after translation and scale —, or
value a fixed one. The effect happens only when the command was answered ok:
a refused or failed command changes nothing. The template sets the voltage read
back from SetVoltage and the mode from SetSystemMode.
Safe state. A driver either has safe_state or it has not — the offering
says only that ("safe_state": true per driver); what it does is the driver's
own, not a list of steps in the bench file. For a simulated bench, what it
leaves behind stands under safeState. Safe state on the Data points page
calls it on every driver. A driver that is disabled or does not offer it is not
called, and the answer says so — so that nobody takes safe state for the whole
bench when a part was not reached. Under Drivers and what they offer every
driver says whether it offers safe_state.
Where a simulated bench starts. A run always begins with what the bench
declares: the values as they stand in the file. What a run sets is gone when
it ends. Operating by hand on the Data points page is different: what you set
there stays, in that window, until you reload the page. So you can set a
voltage and read it back a minute later.
An entry that names a command, a reading, a driver or a parameter the bench does not offer is a finding of the check, with its place. The simulation is no part of the binding and does not change its version.
The box is read-only until you click Edit. While you edit, the page checks
the text as you type and says below the box what is wrong — text that is not
JSON, or an offering it cannot read (a driver without read, set or
commands, an entry without a name); until it is right, Save simulation
stays greyed out. What the check of the bench will report afterwards stands
there as a hint. After saving, the box is read-only again. The offering
belongs to the commissioning: change it here and the bench says the bench
offers something else now: commission it again. A change under simulation
alone does not ask for that.
The binding¶
One row per data point: the driver, the target in its offering, and how they translate.
| Target | a value to read for a read point; a value to set or a command for a set point; a command — or a sequence of writes and a wait — for a request |
| Values | workspace names → the bench's values (Ready → RDY); for on/off, on and off |
| Scale | bench = workspace × scale, where the units differ (s → ms: 1000); empty means 1 |
| Parameters | each parameter of a request → a parameter of the command; a set point's value goes to the command as value |
| Fixed values | parameters the bench needs and the test does not know, each marked technical (a channel, a baud rate) or affects the result (a ramp, a supply voltage) |
| Answers | every answer of the command → ok, refused, failed or timeout. An answer without one counts as failed |
A row is edited as a table: your workspace on the left, the bench on the right. When you choose a data point to bind, the page says what it is: its direction, its type with range and unit, its named values with the number behind each, or for a request its parameters. Values are chosen, not typed: a named value of the workspace is mapped to one of the bench from a list, and a value of the bench shows its number too. A scale shows the range it turns the point's range into, and says when that leaves what the bench can hold. A fixed value is entered in its own type — a number within the bench's range, a choice from its names, on/off — and the type stands beside it. A translation that no longer fits after the target changed is removed, and the ones that can be filled in are filled in for you.
A data point that holds a record or a list, and a request that takes one, cannot be bound yet — to a simulated bench neither: binding field by field comes with the driver SDK. The page shows such a point greyed out and does not offer it for a row; the check reports a row somebody wrote for one by hand; an assistant's row or proposal for one is refused. In a run on the bench the operator serves it, as a point that is not bound.
The check¶
The page checks the binding against the bench's offering and your data points, and says every deviation with its place and fix: a required parameter without a value, a unit without a scale or with a scale of 0, a value that does not fit, an answer mapped to nothing, a fixed value without its kind. A bench with such a finding needs attention and cannot be commissioned.
Which values have to fit depends on the direction:
- Reading — the bench reports, the workspace understands. Every value the bench can report stands for exactly one value of the data point. That the bench never reports ChangingToReady is fine; STBY standing for both Standby and EmergencyStop is not — the reading would be ambiguous.
- Setting and requests — the workspace sends, the bench has to take it. Every value of the data point arrives, and every translated value fits the target: true or false for a boolean, one of its values for an enum, its range for a number.
A fixed value that affects the result is a hint: it is a hidden test condition — add it to System/BringToState as a parameter so test cases state it. Two benches that fix the same such value differently get a hint as well: it explains why their results can differ.
A state bound to a number is a hint too. A data point with named states — LOW, HIGH — that reads a number of the bench can be handed values no name stands for. Such a value is not a reading: where the bench reports 25 and the binding translates 0 and 1, the access fails at the bench, with the value that came and the ones the binding knows. Read now and Watch show it as failed, commissioning refuses the bench, and in a run the step is aborted by the bench — it is not counted as a failure of the unit under test.
Runs here / operator takes over lists every data point and who serves it on this bench. It is folded; its line says how many run here and how many the operator takes over.
Disabling¶
A row or a whole driver can be disabled — an instrument in calibration, a relay that sticks. It stays in the binding, greyed out, with who disabled it, when and why; a reason is required. Its data points are served by the operator until it is enabled again.
A point set from a block or a Python step
The operator is asked by a form step. A block or Python step that sets a point the bench does not serve — disabled, or not bound — asks nobody: the step passes, and the value was never applied. The list before a run starts and the page of the bench mark such points, and the report says nobody applied it for each one a run touched. From the version that brings the driver SDK on, such a step asks the operator as well.
Disabling is an operating state, not a change of the binding: the version of the binding stays the same and a commissioning stays valid. A run records what was disabled, with its reason.
Commissioning¶
Commission runs a probe call for every bound read point and for every request that is safe to call by itself and, when they answer, marks the bench in operation, with the date, the person and the version of the binding. Every probe call is kept with its parameters — as the workspace gave them and as the bench got them.
A request is not probed by itself when it can change the safe state (its safe state effect is anything but none, or it says nothing about it), or when a required parameter has no default: XPressRT does not send a value nobody chose to a device. Such requests are listed under Not probed, below the commissioning. Probe … opens the request with its defaults filled in and its required parameters empty, shows its safe state effect and what goes to the bench, and calls it only when you press Probe it now. It is locked while a run is going on the same bench. A bench is in operation with requests not probed; the list belongs to the bench, not to a run.
Change the binding afterwards and the bench is not checked again until it is commissioned anew. The same holds when the bench offers something else than it did when it was commissioned — a driver more or less, another range, a command that is new or gone: the page says the offering changed since and names what changed, and asks to commission it again. With a real bench this is where a swap at the test place shows. No run starts on a real bench whose binding or offering is not the one commissioned. A simulated bench is there to try things out: a run on it starts, and the list before the start and the top of the report say binding v4 not commissioned. A real bench needs the driver SDK for commissioning.
An assistant cannot commission a bench or probe a request: both call the device.
The bench journal¶
At the bottom of a bench's page stands its journal, folded until you open it: what somebody did on this bench outside a test — Read now, Set, Run and Safe state on the Data points page. Newest first, with who, when, the point, the value or the parameters, what went to the bench and what it answered; filter it by a data point. The list is read-only.
The journal is not a test result. No run contains it, and neither does a run list, a statistic, a plan report, a coverage run or a download of results. A value that stands only here was never measured in a test.
It is kept in results/bench-journal/<bench>/<date>.jsonl: one line per
operation, appended and never changed, each with its version number. It is not
part of the workspace history.
What an assistant does on a bench¶
An assistant reads benches with list_benches and read_binding. On the
binding of a bench, simulated or real, it can do three things:
- Write a row with
write_binding_row— a new one, or in place of the row that is there. The check runs first: a row it finds an error in is refused with the place and the fix, and nothing is written. A hint, such as a fixed value that affects the result, comes back with the answer. - Remove a row with
remove_binding_row. From then on the operator serves that point in a run on this bench. - Propose a row with
propose_binding, saying why, when you should decide. The proposal waits under Proposed rows, with its reason and what the check would find in it; nothing of the binding changes until you Accept it — or Reject it.
Every row it writes or removes names its reason, and the history keeps it with the assistant as the one who made the change. It is a new version of the binding, so the bench says commission it again — and only a person commissions a bench. On a real bench no run starts before that: a person stands before every run on real equipment, also when the assistant wrote the binding. A simulated bench is for trying out — a run starts on it, and list and report say that its binding is not commissioned.
An assistant cannot disable or enable a row or a driver — a row that was disabled stays disabled when it writes it again —, write a simulation, create a bench, commission one or probe a request.
When a bound data point changes¶
Renaming a data point, moving its folder, renaming a value, changing its unit or deleting it reaches every bench that binds it. The Data points page lists those benches before it writes and offers to carry the binding along where it can — see Renaming, turning round and deleting. A row left behind makes the bench need attention; move it to the new point here, with Move the row to ….
When an assistant renames a data point (rename_data_point), the binding moves
with it in the same change — catalogue, blocks, steps and binding are one entry
in the history, all or nothing. The bench then has a new version of its binding
and is commissioned again by a person.
Every change of a bench — adding it, a row, disabling with its reason, a proposal, accepting it, commissioning — is a change of its file and stands in the history, with who made it and why.
Running on a bench¶
Choose the bench when you start a run — see
On a bench. Every value it served is recorded with
its clock (SIM-1/dut), with who served it, the bench and the driver — and for
a command with what was sent and the fixed values of the binding. run.json
names the bench and the version of its binding. The report, the Run Viewer and
the plan report show it: see
Who served a value and
The plan report.
A server that reports another name¶
When the server at a bench's address reports another name than the bench has, no run starts on it. If the bench was renamed, take the binding over to the new name — only after you confirm; the file is renamed with it, and the history follows.