# Build a GPIBEE bench Paste this whole document to an AI, add one line saying which instrument and at which GPIB address, and it has everything it needs. No other context required. --- ## What you are being asked to produce **A GPIBEE bench**: one JSON document that describes a software front panel for a GPIB instrument. A device called a GPIBEE stores it and a browser renders it — buttons, readouts, switches — and turns every interaction into GPIB traffic. You are **not** writing code. You are writing a data file. The renderer already exists and never changes; your job is to express one instrument in the format below, using the right commands from that instrument's manual. Deliver **one JSON object per bench, each in its own code block** and nothing else in it. One bench is the usual answer; see *One instrument, several benches* below for when to deliver more than one. The user saves each as `.gpibee` and imports it on the GPIBEE Benches page (*import / restore*). `.gpibee` is just the extension the Benches page uses; the contents may be plain JSON, exactly as you write it — it does not have to be compressed. **Ask before you build.** Two things are worth one round of questions before you produce anything: which features the instrument actually has (see *Do not invent features* below) and, if you were not told, the GPIB address. Everything else you can decide yourself. --- ## How a bench runs A bench has a **tick** (default 500 ms). On each tick, every indicator whose divider is due sends its command sequence and updates. Everything else — buttons, selectors, entry fields — sends its sequence when the user touches it. Only **one GPIB operation happens at a time**, in order. A slow instrument slows the bench down rather than overlapping. A **command sequence** is a list of steps, executed top to bottom: | step | meaning | |---|---| | `["W", "text"]` | write `text` to the instrument | | `["Q", "text"]` | write `text`, then read the reply | | `["R", ""]` | read a reply without writing anything first | | `["T", "300"]` | wait 300 ms | | `["A", "trigger"]` | bus action: `trigger` `clear` `local` `remote` `readstb` | `A readstb` is a serial poll and **yields the status byte as its value**, so an indicator can show it without sending `*STB?` — no command parsing on the instrument's side, which works even on pre-488.2 boxes. The other actions yield nothing. An optional third entry is a timeout in ms for **that step only**, overriding the bench's `to`: `["Q", "*TST?", 20000]`. Use it for the few commands that are slow (self-test, calibration, a long sweep, a file download) rather than raising the bench timeout for everything — a short bench timeout is what makes a dead instrument fail quickly. In the editor the same thing is written as a suffix, `Q *TST? @20000`. **The reply of the last `Q`, `R` or `A readstb` in the sequence is the value shown.** Steps before it can configure, trigger or wait. `${v}` in any step's text is replaced by the value the user typed or the option they picked. This works for instruments with no query language at all. An HP 3478A has no SCPI: write its function/range codes, then read. ```json "s": [["W", "F1RAN5T1"], ["R", ""]] ``` --- ## The JSON format ### The bench ```json { "v": 1, "n": "HP34401A", "a": 4, "sa": null, "p": 500, "tm": "", "rt": null, "to": 1000, "e": [ ...elements... ] } ``` | key | meaning | |---|---| | `v` | format version. Always `1`. A higher number is refused, not guessed at. | | `n` | bench name, **max 24 characters**. Usually the model number. | | `a` | primary GPIB address, 0–30. **Ask the user if not given.** | | `sa` | secondary address 0–30, or `null` for none. Almost always `null`. | | `p` | tick in ms, 50–600000. 500 is a good default; 1000 for slow instruments. | | `tm` | write termination appended to every command, max 8 characters. | | `rt` | read termination: the byte a read stops at. `null` = EOI only. | | `to` | default timeout per step in ms, 10–120000. **See *Timeouts* below — keep this small.** | | `os` | *optional* command sequence sent once each time run is pressed. | | `e` | the elements, in display order. Keep it under about 35. | **`os` — the run commands.** Same step language as an element (see below), sent on the bench's address before the first reading of every run. Use it to put the instrument into the state the bench assumes, so a run does not depend on what the last user left behind: ```jsonc "os": [["W", "*CLS"], ["W", "CONF:VOLT:DC"], ["T", 200]] ``` Keep it short and side-effect free. Do not put `*RST` in it unless the user asked for that — pressing run should not wipe the front-panel setup by surprise. **Terminations.** Use exactly one of `""` (append nothing / stop only on EOI — the default and right for almost every instrument), `"\\n"`, `"\\r"`, `"\\r\\n"`. Those are *escape sequences written as text*: in JSON, `"\\n"` is a backslash followed by `n`, which the bench expands to a real LF. Set `tm` to `"\\n"` only if the instrument is known to need it. ### Elements Every element has: | key | meaning | |---|---| | `t` | type: `read` `act` `pick` `set` `text` | | `l` | label, **max 128 characters** (max 160 for `text`) | | `r` | style — the allowed values depend on `t`, see below | | `w` | width in grid columns, 1–4. Default 1. Give the main reading 2 or 3. | | `s` | command sequence (all types except `pick` and `text`) | | `ad` | primary address override for this element only (omit to use the bench's) | | `sad` | secondary address override | | `wt` | write termination override | | `rt` | read termination override | | `to` | timeout override in ms for this element's steps | | `c` | card colour, one letter — see below. Omit for the default blue. | | `g` | `1` = draw this element inside the card above it instead of its own card | `ad`/`sad`/`wt`/`rt`/`to` let one bench drive two instruments, or give one slow element more time. Omit them unless you need that. Precedence for a timeout: a step's own ` @ms` beats the element's `to`, which beats the bench's `to`. --- ## Limits Anything longer or larger than this is **silently truncated or clamped** when the bench is loaded — it does not fail, so a label you write at 200 characters simply comes back cut off, and the JSON on the device no longer matches what you wrote. Stay inside these: | field | limit | |---|---| | `n` bench name | 24 characters | | `l` label, all types except `text` | **128 characters** | | `l` label on `text` (heading / note) | 160 characters | | `tm`, `wt` write termination | 8 characters | | step argument, the text in `["W", …]` / `["Q", …]` | 512 characters | | `op` option label | 24 characters | | `op` option commands (all lines together) | 512 characters | | `op` number of options | 32 | | `un` `fm` `fs` `rx` on a `read` | 120 characters each | | `un` on a `set` | 24 characters | | `lv` lamp operand, when it is text | 40 characters | | `fn` download filename template | 80 characters | | `ml` download size cap | 1 … 16777216 bytes | | `a`, `sa`, `ad`, `sad` addresses | 0 … 30 | | `w` width | 1 … 4 | | `p` tick | 50 … 600000 ms | | `to` timeout, bench, element or step | 10 … 120000 ms | | `pd` poll divider | 0 … 10000 | | elements in one bench | 200 hard limit, **about 35 is a good bench** | | the whole file | 64 kB | 128 characters is a lot of label. It is there so a label can carry a real sentence when it has to (*"Sense: 4-wire, front terminals — check the rear/front switch"*), not so every label should. A card head shows roughly 30–45 characters before it ellipsises, and the full text appears on hover, so **put the short name first**: `"Range (auto is disabled while a fixed range is selected)"` reads well truncated, `"Note that the range is auto unless…"` does not. --- ## Timeouts The bench's `to` is how long a *routine* step may take before the bench gives up. It is not a safety margin — it is the delay the user waits, on every element, when something is wrong. A dead instrument, a wrong address or a bad command means the bench stalls for `to` on **every polled element, every tick**. At `to: 10000` a bench of ten indicators takes a minute and a half to tell the user the address is wrong. At `to: 1000` it tells them in ten seconds. **Pick the global `to` like this:** | instrument | `to` | |---|---| | designed from roughly the 1990s on, SCPI, IEEE 488.2 | **500 ms** | | designed before about 1988 — slow processors, byte-at-a-time handshakes | **2000 ms** | | you do not know the vintage | **2000 ms** | Then raise it only if a *routine, polled* step genuinely needs more — and raise that element's `to`, not the bench's. The global covers the slowest step you poll every tick, nothing else. **Everything slow gets its own override**, at the element or at the step: | what | typical | where | |---|---|---| | a measurement that integrates (10 NPLC, long gate time, averaging) | 1000–5000 ms | element `to` | | `*RST` and other settling commands | 2000–5000 ms | step ` @ms`, plus a `["T", …]` | | self-test, autocalibration | 20000–60000 ms | step ` @ms` | | a screenshot or waveform download | 20000–60000 ms | element `to` | | a swept measurement the button starts and then reads | the sweep time plus half | step ` @ms` | A download is where a long timeout is genuinely right: the instrument may think for seconds before the first byte, and the transfer itself runs at GPIB speed. A 120 kB screenshot is not unusual and can take the better part of a minute on an older instrument. Give the download button `"to": 30000` or more and set `ml`. Do not solve a slow instrument by raising the global timeout. Raise the **tick** (`p`) instead, or give the slow indicator a `pd` of 4 or 0. A bench that reads one value per second and answers quickly when something breaks is better than one that tries to read four times a second and hangs. --- ## Reads that change the instrument On a modern SCPI instrument a `…?` query is a question, and asking it does not change anything. **On older instruments that is often not true**, and a bench polls — so a readback with a side effect fires it several times a second. The HP 3325A is the standard example: reading back the amplitude, the frequency or the DC offset switches the instrument's display and its entry mode to the function you asked about. Poll all three at `pd: 1` and the front panel flips between them forever and the user cannot type anything at the instrument. **Rules:** - **Before about 1988, assume a settings readback may have a side effect** until the manual says it does not. Modern instruments are usually safe. - A readback that disturbs the instrument gets **`pd: 0`** — read only when the user presses the refresh icon — and a `text`/`p` note next to it saying why. - If reading one member of a group disturbs the others, do not make one element per member. Make **one** element that reads what matters, or leave the group off the bench. - **Registers that clear on read** (event and condition registers on most instruments) are the same problem in a different shape: one element, `pd: 0`. Several elements each decoding one bit will each destroy what the others were about to see. - **A serial poll (`["A", "readstb"]`) is non-destructive on essentially every instrument.** When you want a status lamp, prefer it over any `?` query — one lamp per bit, each doing its own poll, is fine. - A read that only changes what the instrument *displays* is annoying. A read that changes its *output* — a signal generator, a power supply, a source — is not acceptable at any poll rate. Put it behind a button or leave it out. - Say which readbacks you were unsure about in the notes after the JSON. The user can watch the instrument's own display for one run and tell you. --- ## Do not invent features The most common way a bench is wrong is not a typo — it is an element for something the instrument does not have. It looks right, it loads, and it answers with an error or a timeout for ever. Things that are **not** universal, however standard they look: - `*IDN?`, `*RST`, `*CLS`, `*TST?`, `*OPC?` — IEEE 488.2, so anything designed before roughly 1988 may have none of them. - An error queue at all. `SYST:ERR?` is SCPI; older instruments have an error *byte* you serial-poll, or nothing. - Screenshot or waveform download (`HCOP…`, `CURV?`, `:WAV:DATA?`) — spelled differently on every family, and absent on most non-scopes. - Statistics, min/max, limit testing, a maths block. - Autorange *readback*, as opposed to autorange itself. - Reading back what a control is set to. Plenty of instruments accept a setting and offer no way to ask what it is. - A secondary address, or anything on one. **So: consider them, then ask.** Work out what you would like to put on the bench, then put the uncertain parts to the user in **one batched question** before you deliver — a short list of "does your unit have these", not one question at a time. Then build the bench from what they confirm. If the user has said they want the file immediately and will test it themselves, deliver it, but keep every unverified element **`pd: 0`**, group them under a heading that says so, and list them in the notes. An unverified element that only runs when the user presses refresh costs them one click; an unverified element polled every tick costs them a bench that never settles. --- ### Cards, colour and grouping Every element normally gets its own card. Two optional keys change that. **`c` — colour.** One letter, applied to the card's title bar and border: | `c` | colour | use it for | |---|---|---| | *(omitted)* | blue | everything, unless there is a reason not to | | `r` | red | destructive or dangerous: output on, high voltage, reset | | `o` | orange | needs attention: limits, protection, calibration | | `n` | green | confirmation, "good" state | | `g` | grey | secondary or rarely used | | `b` | black | identity, service, low-level | Colour is decoration, not information — never make it the only way to tell two things apart. Use it sparingly; a bench where every card is a different colour reads worse than one that is all blue. Three or four coloured cards in a bench of twenty is about right. **`g` — grouping.** `"g": 1` means "do not open a new card, draw me inside the one above". The element list stays flat; there is no nesting. Use it to put a row of related buttons, or a reading and the control that changes it, in one card instead of five. The card is titled by a **heading placed directly in front of it** — a `text` element with `r: "h"` whose next element has `g: 1` becomes that card's title bar instead of a section line of its own, and its `w` and `c` size and colour the card. That is the form to use for a group of buttons: ```jsonc { "t": "text", "l": "Trigger controls", "r": "h", "w": 2, "c": "o" }, { "t": "act", "l": "Trigger now", "r": "btn", "g": 1, "s": [["W", "*TRG"]] }, { "t": "act", "l": "Single", "r": "btn", "g": 1, "s": [["W", "TRIG:COUN 1"]] }, { "t": "act", "l": "Abort", "r": "btn", "g": 1, "s": [["W", "ABOR"]] } ``` Without a heading in front, the **first member's** label becomes the title and is not repeated inside the card — which is what you want for a reading plus the controls that change it: ```jsonc { "t": "read", "l": "Output voltage", "r": "num", "w": 2, "un": "V", "s": [["Q", "MEAS:VOLT?"]] }, // titles the card { "t": "pick", "l": "Output", "r": "toggle", "g": 1, "op": [["OFF", "OUTP OFF"], ["ON", "OUTP ON"]] }, { "t": "set", "l": "Level", "r": "num", "g": 1, "un": "V", "s": [["W", "VOLT ${v}"]] } ``` Consequences, all of them deliberate: - A **button** never doubles as the title — its label stays on the button. A button group without a heading in front of it simply has no title bar. - The card's `w` and `c` come from whichever element starts it, the heading or the first member. Later members' `w` and `c` are ignored — they are stacked full width inside the card. - `g` on the *first* element of a bench is ignored (there is no card above to join). A `text` element can *start* a card but never joins one. - A heading only becomes a title bar when the element after it is grouped; followed by anything else it stays an ordinary full-width section line. - Groups are formed by position, so moving an element in the editor changes which card it belongs to. --- ### `read` — an indicator Styles `r`: **`num`** (a formatted number), **`txt`** (the reply verbatim, no processing at all), **`gauge`** (dial), **`bar`**, **`led`** (a lamp). | key | meaning | |---|---| | `pd` | poll divider. `1` = every tick, `4` = every 4th tick, `0` = only when the user presses the refresh icon. Default 1. | | `fs` | field separator, e.g. `","` or `"ws"` for whitespace — for replies with several fields | | `fi` | which field to take, 0-based | | `rx` | regex; capture group 1 if there is one, else the whole match | | `tv` | `"first"` / `"last"`: take the first or last number found anywhere in the reply and ignore the letters around it. Absent = the whole reply must be a number. | | `up` | `1`: read an SI prefix in front of the unit and scale by it (`1.234 MHz` → 1.234e6). Needs `un` set. | | `sc` | multiply by | | `of` | add | | `fm` | display format: `eng` (engineering prefixes, the default) `fix` `sci` `int` `text` | | `dg` | digits: significant digits for `eng`, decimals for `fix`/`sci` | | `un` | unit string, e.g. `"V"` | | `mn` `mx` | scale ends, for `gauge` and `bar` | | `wl` `wh` | alarm below / above — the value turns red | | `sk` | `1` to draw a trend sparkline with min/max/average | | `lm` `lv` `li` | lamp condition, see below | | `lo` | `1`: lamp only — do not print the value next to the lamp | The chain runs in that order: split → regex → number → unit prefix → scale/offset → format. **Replies with letters in them.** Many instruments prefix or suffix their answer: a Racal 1999 counter says `FA+001.01901942E+06`, a scope says `CH1:FREQ 1.234E6`, others append the unit as `1.234 MHz`. Reach for `tv` before `rx`: - `"tv": "last"` for `FA+001.019E+06` and for `CH1:FREQ 1.234E6` — note that `"first"` would grab the `1` out of `CH1`. - `"tv": "last", "up": 1, "un": "Hz"` for `1.234 MHz`, giving 1.234e6. - `up` only treats a letter as an SI prefix when the element's own unit follows it, so `20 dBm` with `"un": "dBm"` stays 20 and is not read as deci-Bm. Use `rx` only for shapes those cannot express. **Lamp (`r: "led"`)** — `lm` says when it is lit, `lv` is the operand, `li: 1` inverts it: | `lm` | lit when | |---|---| | `nz` (default) | the value is not zero | | `gt` / `lt` / `eq` | value >, <, = `lv` | | `bit` | bit number `lv` of the value is set — for status bytes | | `txt` | the text equals `lv` | Add `"lo": 1` when the number beside the lamp says nothing — decoding one register into several lamps would otherwise repeat the same value next to each of them. **A register you decode must be safe to read repeatedly** — see *Reads that change the instrument* above. ### `act` — a button Styles `r`: `btn`, or `danger` for a red one. | key | meaning | |---|---| | `am` | `run` (default) sends the sequence; `dl` downloads the reply to a file | | `cf` | `1` asks for confirmation first — use it for `*RST` and self-test | | `fn` | filename template for `dl`, e.g. `"screen_{date}_{time}.png"` (max 80 chars) | | `ml` | maximum bytes to accept for `dl`, up to 16 MB | | `dm` | `"blk"` for the counted transfer `#` — for instruments that never assert EOI at the end of a file. Omit for the normal "read until the instrument stops, then strip a `#` header if present". | A download button is the one place a long timeout belongs: put `"to": 30000` (or more, for a big screenshot on a slow instrument) on the element itself, not on the bench. ### `pick` — a selector Styles `r`: **`gang`** (a row of buttons, good for 2–5 choices), **`combo`** (a dropdown, good for many), **`toggle`** (a two-position switch). ```json { "t": "pick", "l": "Function", "r": "gang", "w": 2, "ap": 1, "op": [["DC V", "CONF:VOLT:DC"], ["AC V", "CONF:VOLT:AC"]] } ``` `op` is a list of `[label, commands]`, at most 32 of them. **Several commands go on separate lines inside one string**: `"CALC:FUNC AVER\nCALC:STAT ON"`. `ini` is the index shown before anything is pressed. **A `toggle` must have exactly two options**, off first, on second. More than two is rejected and falls back to `gang`. A selector cannot know what the instrument is really set to, so it shows what was last pressed. Every selector gets a re-send icon in its title bar automatically. ### `set` — a value entry field ```json { "t": "set", "l": "Set voltage", "r": "num", "w": 1, "ap": 1, "s": [["W", "VOLT ${v}"]], "un": "V", "mn": 0, "mx": 30, "st": 0.1, "dv": 5 } ``` `mn`/`mx` limit what is accepted, `st` is the arrow-key step, `dv` the value shown initially. ### `ap` — apply what is on screen when run is pressed `"ap": 1` on a `pick` or a `set` writes whatever it is showing once at the start of every run: the option currently selected, or the value currently in the box. For a `set` that is `dv` until the user types something, so **`dv` doubles as the value the instrument is put into at the start of a run.** This is what makes a bench trustworthy. Without `ap`, a selector shows *DC V* while the instrument sits in AC because someone pressed a key on the front panel; the screen and the instrument disagree and the readings are quietly wrong. With `ap`, pressing run puts the instrument into the state the screen is showing. **Set `ap` on everything that defines what is being measured:** - function, mode, coupling, input or channel selection - range, and whether autorange is on - integration time, gate time, aperture, averaging, bandwidth, filter - trigger source, slope and level - reference or offset values a reading is relative to - for a source: the waveform shape and any level the bench claims to be showing **Leave `ap` off when writing it again would do something:** - anything that switches an output, an amplifier or a relay on — the bench should not energise anything because someone pressed run - one-shot actions: trigger, zero, autozero-now, calibrate, clear memory, reset - anything whose command has a side effect beyond the setting itself - a control the user is deliberately leaving where the instrument's front panel put it Rule of thumb: **`ap` belongs on anything you would have written into `os` if it had a fixed value.** If a setting matters enough to be forced at the start of a run, put the selector or the entry field on screen and mark it `ap` instead of hard-coding it in `os` — the user then sees what was applied and can change it. **Order at the start of a run**, all of it strictly one after the other on the bus: the bench's `os` commands, then every `pick` and `set` marked `ap` in element order, then the first reading. Elements are applied in the order they appear in `e`, so **put function before range**, and range before anything that depends on it — a range command usually means nothing until the function is set. ### `text` — a heading or a note `r: "h"` is a section heading, `r: "p"` an italic note. `l` holds the text, up to 160 characters. Neither talks to the instrument. --- ## Rules the renderer enforces Break these and the bench still loads, but with warnings, and parts of it are shown as red "unsupported" cards: - unknown `t` or unknown step verb → that element is inert - an element whose sequence has no usable steps → inert - a `pick` with no options → inert - a `toggle` without exactly two options → shown as `gang` - `fm` outside the list → `eng`; an invalid `rx` → dropped - labels and other text longer than the limits in *Limits* are **truncated without a warning**; numbers outside their range are clamped - more than 200 elements, or a file over 64 kB → refused --- ## One instrument, several benches **A bench is one job, not one instrument.** Most benchtop instruments have operating modes that exclude one another: entering one makes some controls meaningless, changes what a reading means, and makes other queries answer with an error instead of a value. When that is the case, write **one bench per mode** rather than one bench that tries to hold all of them. Signs you are looking at two benches and not one: - A control has no meaning unless the instrument is in a particular mode, and pressing it while in another mode either does nothing or throws the instrument out of what the user was doing. - A reading's unit, scale, formatting or alarm limits depend on the mode, so a single indicator would have to be labelled vaguely and left without a unit. - Turning one feature on makes another element's query return an error. Polling it then fills the error queue several times a second. - A control is only reachable through a sequence that first puts the instrument somewhere else, so touching it silently invalidates everything else on screen. - The element count is heading past about 35, mostly because two unrelated sets of controls are sitting next to each other. **How to split.** Name them ` ` — the name is what the user picks from the menu, and only the selected bench runs. Give each one an `os` that puts the instrument into that mode, and mark the mode-defining selectors and entry fields `"ap": 1`, so choosing the bench and pressing run is enough to get the instrument into the right state. Then each bench can commit: units on every reading, ranges that match the mode, sensible alarm limits, no defensive notes about which combinations are legal. Repeat the cheap common parts in every one of them — identity, the error queue, a *Local* button, and whatever reset the instrument needs. They cost a few elements each and they are exactly what the user reaches for when something is wrong. Storage is not a reason to merge: benches are stored together and share almost all their structure, so a second bench for the same instrument costs far less room than the first one did. **When not to split.** If the modes differ only in which command a selector sends, and everything else on screen stays valid, one bench with a function selector is better — the user does not want to change bench to switch between two readings of the same thing. Split when the *screen* has to change, not when only a command does. --- ## How to design a good bench **Group it.** Use `text`/`h` headings: *Measurement*, *Setup*, *Instrument*. Put the thing the user stares at first, wide (`w: 2` or `3`) and with `sk: 1`. **Poll what changes, at a sensible rate.** The main reading `pd: 1`. Settings read back from the instrument (range, function, integration time) `pd: 4` — they change rarely. Identity, error queue, self-test results `pd: 0`. Anything whose readback disturbs the instrument, `pd: 0`. **Never poll something that errors in the current mode.** If min/max readouts only work while a statistics mode is on, give them `pd: 0` and add a `text`/`p` note telling the user to press refresh. Polling them fills the instrument's error queue every few hundred ms. **Prefer a generic read over a function-forcing read.** If the bench has a function selector, poll `READ?` (or the equivalent), not `MEAS:VOLT:DC?` — the latter forces DC volts on every tick and fights the selector. **Keep the bench timeout small and give slow commands their own** — see *Timeouts*. **Include these three, always** (if the instrument has them — see *Do not invent features*): - identity — `*IDN?` (or the equivalent), `r: "txt"`, `pd: 0` - the error queue — e.g. `SYST:ERR?` with `fm: "text"` and `rx: "\"([^\"]*)\""` to pull the message out of `+0,"No error"`, `pd: 0`. This is how the user debugs a command you got wrong. - a way back to the front panel — `{"t":"act","l":"Local","s":[["A","local"]]}` On an instrument with none of the 488.2 commands, the equivalents are a status lamp fed by `["A", "readstb"]` and the *Local* button, which always works because it is a bus line and not a command. **Reset deserves `cf: 1`.** So does a self-test that takes the instrument offline for 20 seconds. **Match units to the function.** If a selector switches between volts and ohms, either drop `un` or split into two benches — see the section above. --- ## Worked example (abbreviated) A DC voltage bench for an HP 34401A. Note the shapes: a short bench timeout with one long step timeout on the self-test, a heading titling a card of readbacks, a group whose first member titles it, one coloured card, an `os` that puts the instrument where the bench assumes it is, and `ap` on the controls that define the measurement — but not on autozero, whose command triggers a zero measurement. ```json { "v": 1, "n": "HP34401A DCV", "a": 4, "sa": null, "p": 500, "tm": "", "rt": null, "to": 1000, "os": [["W", "*CLS"], ["W", "CONF:VOLT:DC"], ["T", "200"]], "e": [ { "t": "text", "l": "Measurement", "r": "h", "w": 1 }, { "t": "read", "l": "Reading", "r": "num", "w": 2, "s": [["Q", "READ?", 3000]], "pd": 1, "un": "V", "fm": "eng", "dg": 7, "sk": 1 }, { "t": "text", "l": "Instrument state", "r": "h", "w": 2, "c": "g" }, { "t": "read", "l": "Range", "r": "num", "w": 1, "g": 1, "s": [["Q", "VOLT:DC:RANG?"]], "pd": 4, "un": "V", "fm": "eng", "dg": 3 }, { "t": "read", "l": "Autorange", "r": "led", "w": 1, "g": 1, "s": [["Q", "VOLT:DC:RANG:AUTO?"]], "pd": 4, "fm": "int", "lm": "nz" }, { "t": "text", "l": "Setup", "r": "h", "w": 1 }, { "t": "text", "l": "Range and speed", "r": "h", "w": 2 }, { "t": "pick", "l": "Range", "r": "combo", "g": 1, "ap": 1, "op": [["Auto", "VOLT:DC:RANG:AUTO ON"], ["1 V", "VOLT:DC:RANG 1"], ["10 V", "VOLT:DC:RANG 10"], ["100 V", "VOLT:DC:RANG 100"]] }, { "t": "pick", "l": "Integration (NPLC)", "r": "gang", "g": 1, "ap": 1, "ini": 2, "op": [["0.02", "VOLT:DC:NPLC 0.02"], ["1", "VOLT:DC:NPLC 1"], ["10", "VOLT:DC:NPLC 10"]] }, { "t": "pick", "l": "Autozero", "r": "toggle", "g": 1, "ini": 1, "op": [["OFF", "ZERO:AUTO OFF"], ["ON", "ZERO:AUTO ON"]] }, { "t": "text", "l": "Instrument", "r": "h", "w": 1 }, { "t": "read", "l": "Identity", "r": "txt", "w": 2, "c": "b", "s": [["Q", "*IDN?"]], "pd": 0 }, { "t": "read", "l": "Last error", "r": "num", "w": 2, "c": "o", "s": [["Q", "SYST:ERR?"]], "pd": 0, "fm": "text", "rx": "\"([^\"]*)\"" }, { "t": "text", "l": "Service", "r": "h", "w": 2, "c": "r" }, { "t": "act", "l": "Self test", "r": "btn", "g": 1, "s": [["Q", "*TST?", 20000]], "am": "run", "cf": 1 }, { "t": "act", "l": "Reset", "r": "btn", "g": 1, "s": [["W", "*RST", 5000], ["T", "1500"]], "am": "run", "cf": 1 }, { "t": "act", "l": "Local", "r": "btn", "g": 1, "s": [["A", "local"]], "am": "run" } ] } ``` `to` is 1000 rather than 500 because `READ?` on this meter waits for the measurement, and the reading itself gets 3000 so that 10 NPLC with autorange has room without slowing down how fast the rest of the bench reports a fault. An AC voltage or a resistance bench for the same instrument is this file with the `os`, the ranges, the units and the name changed — which is exactly why splitting by mode is cheap. --- ## Before you answer 1. **Ask for the GPIB address** if the user did not give one. Ask which functions matter if the instrument does many things — a bench covering everything an oscilloscope can do is worse than one covering what this user measures. If the answer spans modes that exclude one another, say that you will deliver **one bench per mode** and deliver them as separate JSON blocks. 2. **Ask, in one batch, about the features you are unsure the instrument has** — error queue, self-test, identity, downloads, statistics, settings readback. See *Do not invent features*. 3. **Use commands you actually know.** Every instrument family spells things differently, and a plausible-looking command that the instrument rejects is worse than an omitted feature. If you are unsure of a command, either leave it out or **say so explicitly next to the JSON** — do not bury it. 4. **Prefer the instrument's own manual** over another model's command set. `VOLT:DC:ZERO:AUTO` and `ZERO:AUTO` are different instruments' spellings of the same idea, and only one of them will answer. 5. **Check the timeouts.** Global `to` 500 ms for a modern instrument, 2000 ms for anything from before about 1988 or of unknown vintage; long timeouts only on the individual slow steps and on downloads. 6. **Check every polled readback is safe to read repeatedly**, and drop the ones that are not to `pd: 0`. 7. **Check `ap`** is on the controls that define the measurement and off everything that switches something on or does something once. 8. **Check the lengths** against *Limits* — especially labels (128) and option labels (24), which truncate silently. 9. **Check the JSON is valid**: no comments, no trailing commas, no `NaN`, and every regex escaped for JSON (`"\"([^\"]*)\""`). 10. **After the JSON, list in plain words**: which commands you are confident about, which you guessed, which readbacks you were not sure were side-effect free, and what the user should verify first. The editor has a *Test* button on every element that shows the raw reply, and the error queue readout will name a bad command immediately.