Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Describing an instrument

A profile describes an instrument model: who it is, what it remembers, which commands it understands and what it does with each one. It has five sections:

dispositivo:   # who it is
protocolo:     # how it is spoken to; today, always scpi
estado:        # its internal variables and their power-on value
comandos:      # what it understands and what it does with each thing
modelos:       # how it computes what it "measures" (chapter 7)

This is the profile of the supply on the example bench. It is worth reading in full (the comments are in Spanish): the rest of the chapter takes it apart.

# Fuente de alimentación DC de un canal, con una carga resistiva conectada.
#
# Es una plantilla: sustituye el `idn` y los comandos por los de tu fuente real.
# Declara solo lo que usa tu software de medida, no el manual entero.

dispositivo:
  modelo: FUENTE-DC
  tipo: instrumento
  # Lo que contesta *IDN?. Pon el de tu instrumento real si tu software lo comprueba.
  idn: "Crucible,FUENTE-DC,SIM0001,1.0"

protocolo: scpi

# Variables internas del instrumento y su valor al encender (y tras *RST).
estado:
  tension: 0.0          # V programados
  limite_corriente: 1.0 # A
  salida: false
  carga_ohm: 10.0       # la resistencia que "hay conectada" a la salida

comandos:
  # VOLT 5  /  SOUR:VOLT:LEV 5  /  volt 5  → todas equivalen.
  - patron: "[SOURce:]VOLTage[:LEVel][:IMMediate][:AMPLitude]"
    args: [v]
    muta: { tension: "<v>" }
  - patron: "[SOURce:]VOLTage[:LEVel][:IMMediate][:AMPLitude]"
    query: true
    respuesta: "{tension}"

  - patron: "[SOURce:]CURRent[:LEVel][:IMMediate][:AMPLitude]"
    args: [i]
    muta: { limite_corriente: "<i>" }
  - patron: "[SOURce:]CURRent[:LEVel][:IMMediate][:AMPLitude]"
    query: true
    respuesta: "{limite_corriente}"

  # OUTP ON / OUTP OFF / OUTP 1 / OUTP 0
  - patron: "OUTPut[:STATe]"
    args: [estado]
    muta: { salida: "<estado>" }
  - patron: "OUTPut[:STATe]"
    query: true
    modelo: salida_como_numero

  - patron: "MEASure[:SCALar]:VOLTage[:DC]"
    query: true
    modelo: tension_medida
  - patron: "MEASure[:SCALar]:CURRent[:DC]"
    query: true
    modelo: corriente_medida

modelos:
  # Los instrumentos SCPI contestan 1/0, no true/false ni 1.0.
  salida_como_numero:
    tipo: formula
    cuando: { salida: true }
    expr: "1"
    fallback: 0
    formato: entero

  tension_medida:
    tipo: formula
    cuando: { salida: true }
    expr: "tension + gauss(0, 0.0005)"
    fallback: 0.0
    formato: fijo:4

  # Ley de Ohm, recortada al límite de corriente (la fuente entra en modo CC).
  corriente_medida:
    tipo: formula
    cuando: { salida: true }
    expr: "min(tension / carga_ohm, limite_corriente) + gauss(0, 1e-5)"
    fallback: 0.0
    formato: fijo:5

dispositivo

dispositivo:
  modelo: DMM-ABC                        # required; shown in the start-up table
  idn: "ACME,DMM-ABC,SN12345,FW2.01"     # what *IDN? answers

If your software checks *IDN? to recognise the instrument, copy the real device’s answer here exactly.

protocolo

scpi. The format reserves other values (modbus_tcp, serial_ascii…) for what is coming, but today a profile that is not scpi does not start.

estado

The variables the instrument remembers, with their power-on value, the one they return to on *RST:

estado:
  tension: 0.0
  salida: false
  funcion: VOLT

Numbers, true/false and text are accepted. The state is one per device, shared by every connection: chapter 8 explains what that implies.

comandos

Each entry is a command or a query. If a command has both forms, like VOLT 5 and VOLT?, it is declared twice:

comandos:
  # Command: VOLT 5  →  stores 5 in the 'tension' variable
  - patron: "[SOURce:]VOLTage[:LEVel]"
    args: [v]
    muta: { tension: "<v>" }

  # Query: VOLT?  →  answers with the value of 'tension'
  - patron: "[SOURce:]VOLTage[:LEVel]"
    query: true
    respuesta: "{tension}"

  # Computed query: MEAS:VOLT?  →  answered by the 'lectura' model
  - patron: "MEASure:VOLTage[:DC]"
    query: true
    modelo: lectura
FieldWhat it is
patronThe header in manual notation (below). No ? and no arguments.
querytrue if it is the query. Otherwise, it is a command.
argsNames for incoming arguments, in order: with args: [v], in VOLT 5 <v> is 5.
mutaState variables it changes: { tension: "<v>" }, or a fixed value, { funcion: VOLT }.
respuesta(queries) The text to answer. {variable} is replaced by the state and <arg> by the argument.
modelo(queries) The model in modelos that computes the answer.

Every query needs respuesta or modelo. A command may carry only muta, or nothing if being accepted without error is enough.

Pattern notation

It is the notation of manufacturers’ manuals:

  • Upper case is the required short form, and lower case completes the long form. VOLTage accepts VOLT and VOLTAGE, in upper or lower case.
  • Brackets mark nodes that can be left out. [SOURce:]VOLTage[:LEVel] accepts VOLT, SOUR:VOLT, volt:lev, SOURCE:VOLTAGE:LEVEL…

One pattern thus covers every form the real instrument accepts, without listing them. Against the example supply:

$ printf 'VOLT 5\n' | nc -q1 127.0.0.1 5025
$ printf 'VOLT?\n' | nc -q1 127.0.0.1 5025
5.0
$ printf 'source:voltage:level 7.5\n' | nc -q1 127.0.0.1 5025
$ printf 'SOUR:VOLT?\n' | nc -q1 127.0.0.1 5025
7.5

Commands answer nothing, as on a real instrument: that is why no line appears after VOLT 5.

Copy patterns from the manufacturer’s manual (MEASure:VOLTage[:DC]), not the short form your software uses (MEAS:VOLT). That way the profile accepts whatever any other program sends.

Arguments

  • ON, OFF, 1 and 0 are stored as true or false, and models treat them the same.
  • If a declared argument does not arrive (CONF:VOLT with no range), the variable stays as it was.
  • Without names in args, arguments are referred to by position: <0>, <1>.
  • A reference that does not exist, such as {no_such_variable}, is answered literally, braces included. If you see braces in an answer, there is a typo in the profile.

Multi-channel instruments

One pattern per channel, with the number inside:

  - patron: "SOURce1:VOLTage"
    args: [v]
    muta: { tension_1: "<v>" }
  - patron: "SOURce2:VOLTage"
    args: [v]
    muta: { tension_2: "<v>" }

SOUR1:VOLT 3 goes to channel 1 and SOUR2:VOLT 7 to channel 2. SOUR:VOLT, with no number, is not taken as channel 1: if your software sends it that way, declare the unnumbered pattern too. A channel list such as MEAS:VOLT? (@2) arrives as an argument with the text (@2).

When a command is not in the profile

The connection is not dropped and nothing is answered: the error goes into the error queue, as the instrument would do. VOLT:PROT is not in the supply’s profile:

$ printf 'VOLT:PROT 30\n' | nc -q1 127.0.0.1 5025
$ printf 'SYST:ERR?\n' | nc -q1 127.0.0.1 5025
-113,"Undefined header;VOLT:PROT"
$ printf 'SYST:ERR?\n' | nc -q1 127.0.0.1 5025
0,"No error"

SYST:ERR? is the first tool for completing a profile: it tells you which header it did not recognise. Each read takes one error off the queue; when it is empty, it answers 0,"No error".

What you do not declare

*IDN?, *RST, SYST:ERR? and the other common commands are handled by Crucible for every instrument (full list in chapter 8). Declaring them is an error:

$ crucible --validar perfiles/rst_declarado.yaml

error: perfiles/rst_declarado.yaml: perfil inválido: el patrón '*RST' es un comando común de IEEE 488.2. Ya no se declaran en el perfil: los resuelve el motor. '*IDN?' sale de 'dispositivo.idn'

The message says *RST is an IEEE 488.2 common command, which the engine resolves, and that *IDN? comes from dispositivo.idn.