-
Notifications
You must be signed in to change notification settings - Fork 101
SCPI API design
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.
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.
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.
You SHOULD use the most efficient binary 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 and this may even be harmful to overall performance accounting for the CPU required to compress/decompress on each end) to reduce communication/processing overhead and improve throughput on lower bandwidth links such as VPNs. You SHOULD zero-pad multi-byte samples (e.g. 12 bits) to an integer number of bytes per sample. You SHOULD NOT pack analog samples across bytes (for example, packing two 12-bit samples into three bytes).
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 document) 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 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.
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.
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
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.
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.)
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, as well as the reason this command was not accepted (bad syntax, not a recognized command, incorrect type of probe, option not installed, value out of range, etc). 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.
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.
Your API SHOULD be designed for discoverability: the same command set is likely used across a range of products in a line with different capabilities, and the user may wish to use the same tooling across multiple products in the line.
Your API SHOULD provide commands to query the available values for sample rate, memory depth, bandwidth limiters, and other values whose valid range varies across products. Your API SHOULD also allow querying limits for channel v/div and offset based on the currently attached probe and channel termination configuration. Your API SHOULD provide a command to list installed hardware/software options so client software knows, for example, which protocol triggers are available for use.
Triggering commands SHOULD provide a means to determine the actual instrument trigger state, including any processing latency or pre-trigger delay within the acquisition board. In other words, if the "get trigger status" command indicates the trigger is armed, the instrument SHOULD be able to act upon a trigger edge immediately. You SHOULD NOT return a status of "trigger armed" if the arm command has been partially executed, but the instrument is still acquiring pre-trigger samples or otherwise not ready to trigger.
This is necessary to ensure that multi-instrument setups or software-driven DUT setups can arm all instruments and then activate the desired stimulus immediately.
SCPI generally lends itself to in-band transmission of sample data and a polling-based workflow in which the client must poll the instrument for trigger status, then request the data from the instrument, then re-arm the trigger.
This forces many round trips on high latency connections such as VPNs, dramatically limiting achievable WFM/s even at shallow memory depths.
Our preferred alternative, which has been implemented successfully by two instrument vendors as of this writing, is to separate the control plane (with its strict request/response model) from the (largely unidirectional) data plane on separate sockets. This is generally done via a publish/subscribe model in which clients subscribe to data from specific instrument channels via SCPI, then the instrument publishes that data to them via the data-plane socket.
This allows the instrument to gather data from all channels of interest when a trigger event occurs, send to the client in a "push" flow without needing to wait for a round trip, then immediately re-arm the trigger - perhaps even before the first waveform data packets have reached the client's network interface.
We have successfully used this flow to stream 60 WFM/s from an oscilloscope in Seattle to a client in New York over an LTE VPN connection with around 200ms latency (so approximately twelve consecutive waveforms in flight across the Internet between scope and client).