Skip to content

SCPI API design

Andrew Zonenberg edited this page Jun 9, 2024 · 17 revisions

What makes a good SCPI API?

If you're building a new generation of instrument, or responsible for maintenance of the SCPI protocol stack on an existing instrument family, here's some guidance! Similar to RFC documents we use the capitalized terms SHOULD, and SHOULD NOT for positive / negative guidance, and MAY for design choices you may wish to consider but which are not strongly positive or negative guidance. We avoid the terms MUST and MUST NOT as this is a set of guidelines rather than a standard, but if you are considering violating this guidance please think carefully and be sure this is actually the best course of action for your customers.

There are a wide range of SCPI implementations across the industry. All of them can be improved in some way or other, however in the interests of neutrality and politeness this document will not mention specific implementations by name. If you are an instrument vendor and are uncertain of which of these items apply to your products, please get in touch and we'll gladly provide personalized guidance.

This guidance is primarily written in an oscilloscope-centric context for command examples, but applies to any T&M product with a SCPI API regardless of its capabilities.

SCPI is an API

In general, you SHOULD think of SCPI not as an interactive shell or console but as an API, one which happens to be encoded in mostly human readable text. Design for machine consumption.

If you wish to provide an interactive shell for SCPI (including features such as a prompt, command history, echoing of commands typed by the user, in-band error messages, tab complete, etc) you MAY do so, but this SHOULD be considered a separate interface from your API and run on a separate port (i.e. not the IANA standard TCP 5025 for unformatted SCPI, 1861 for VICP, or LXI RPC for VXI-11). Interactive shell functionality on USB, RS232, or other interfaces which do not support multiple ports SHOULD be disabled by default and require a setting to be changed on the scope to enable.

Performance

All other things being equal, your API SHOULD be fast. Make commands execute as quickly as possible; you SHOULD avoid unnecessary round trips in the API design.

Efficient encoding

You SHOULD use the most efficient encoding possible for data being transmitted on the wire (unless this comes at a high performance cost, for example there is no need to apply compression) to reduce communication/processing overhead and improve throughput on lower bandwidth links such as VPNs.

You SHOULD use SCPI binary data blocks for analog or digital waveform data (if this data is being sent in-band rather than via a separate socket as discussed later in this documment) and SHOULD NOT encode or frame the data in any way within these blocks other than providing a header with sample rate, gain/offset, or other related metadata.

For example, you SHOULD NOT have waveform data encoded as ASCII digits, base64 encoded binary data. XML CDATA blocks, etc. Digital channel data should be packed 8 binary (8 bits per byte), however both interleaved (one byte encodes one sample from each of eight channels) and sequential (one byte encodes eight consecutive samples from one channel) representations are equally acceptable.

You SHOULD have all instrument features directly exposed to SCPI via documented commands. You SHOULD NOT have a SCPI command which executes tunneled Python, VBScript, shell commands, or similar as the only way to access some features. You SHOULD NOT have undocumented commands for infrequently used functionality.

Pipelining

You SHOULD support pipelined transactions (sending multiple commands which execute in sequence as the instrument is ready for them, then sending replies in sequence if necessary). This provides a huge performance boost especially over higher latency connections such as Wi-Fi or VPNs. Do not

You do not need gigabytes of receive buffer, but sending several dozen commands in quick succession (such as when replaying a saved instrument configuration to resume a previous experiment) SHOULD work.

For example:

(Client) C1:EN
(Client) C2:EN
(Client) C3:EN
(Client) C4:EN
(Client) C1:VDIV?
(Client) C2:VDIV?
(Client) C3:VDIV?
(Client) C4:VDIV?
(Scope)  0.25
(Scope)  0.50
(Scope)  0.125
(Scope)  0.05

Multiple sessions

You SHOULD support multiple concurrent connections (e.g. multiple network clients, or one network client plus a USBTMC session) and use appropriate mutexing and synchronization internally to protect any relevant global state and serialize commands to the hardware.

This is particularly important for instruments that contain multiple logically distinct sub-instruments, such as an oscilloscope and function generator. It is entirely possible that the user may wish to perform automated testing of a DUT using the scope while testing a completely unrelated device, under control of a different piece of software, using the AWG.

Independence of sessions

Parallel or consecutive sessions SHOULD be fully independent at the SCPI protocol layer, with all state tied to that session and destroyed at the end of that session.

All configurable options at the SCPI layer (for example enabling or disabling command echoing, setting 8 or 16 bit waveform transfer format, any sort of "current channel" state used during readback) SHOULD be tied to that session and not global to the instrument. It should be possible to have two simultaneous socket connections with these options set differently, and new sessions should always start up with these options in a consistent state regardless of any commands sent by previous sessions.

If the user sends a query and disconnects before reading the reply, the reply SHOULD be discarded. It SHOULD NOT be held in an internal buffer and sent to a future client, regardless of whether they are connecting via the same interface or a different one. (Sending an API call and getting a reply meant for a prior call leads to the client becoming desynchronized and often renders the instrument unusable from automated tools until it is rebooted or the SCPI stack otherwise resets.)

Error handling

Things go wrong, this is a fact of life. Users send commands that are not well formed, or are not valid for the current instrument state (e.g. attempting to auto-zero a differential probe when none is connected).

Commands which fail SHOULD fail quickly, without any sort of multi-second timeout, whenever possible.

You SHOULD have an error log with a documented mechanism for seeing a list of all commands sent by the user which the instrument was unable to execute for some reason. This error log MAY be a file on the instrument filesystem, a list displayed in the front panel GUI, a buffer which is queried over SCPI, or any other means you consider appropriate. Entries in the log SHOULD be timestamped so the user can determine when the error occurred. The log SHOULD be possible to clear so the user can easily pay attention to errors from a specific test or piece of software and ignore prior errors.

Commands which do not normally return a value SHOULD NOT return any data to the user on failure, for example avoid the scenario shown below:

(Client) C1:AUTOZERO
(Scope)  Error: No active probe on channel 1

Queries which normally return a value SHOULD return a value even if they fail, for example avoid the scenario shown below:

(Client) C1:VDIV?
(scope does not reply because a MSO probe is connected to channel 1)

Commands which may fail with an error response SHOULD be documented as such. The error response SHOULD be documented and readily distinguishable from a successful response (non-numeric, preceded by the text "ERROR", etc).

Any command sending a reply when not expected, or query not sending a reply when expected, will lead to the client desynchronizing with the instrument.

Consistency / orthogonality

Commands which do similar things SHOULD use similar syntax. For example, enabling a MSO channel or an analog channel SHOULD use similar command structure. Commands SHOULD use the same names to refer to channels or objects: channel 1 MAY be referred to as "C1" or "CH1" or "CHAN1" or anything else along these lines, but you SHOULD NOT require the user to mix these for different commands. You MAY accept multiple names for the same channel as long as they are all legal to use in any command to refer to that channel.

For example, avoid requiring the scenario shown below:

C1:VDIV 1.25
CHAN1:OFFS 3.14

SCPI channel names SHOULD be consistent with instrument front panel port numbering, in both naming and zero vs one-based indexing. For example, you SHOULD NOT have the first channel be called C1 in the API but RX0 on the front panel, or vice versa.

When is SCPI not enough?

Clone this wiki locally