Manual de Crucible
Crucible simula un banco de instrumentos de medida. Describes cada instrumento en un fichero YAML y Crucible lo sirve por red hablando SCPI de verdad, igual que el aparato físico. Tu software de medida —PyVISA, LabVIEW, un secuenciador como Anvil— se conecta a él como si el banco estuviera enchufado, sin saber que no lo está.
Este manual también está en inglés: el botón EN de la barra superior lleva a la misma página en el otro idioma.
Este manual explica cómo usarlo: instalarlo, arrancar un banco, conectar tu software y describir tus propios instrumentos.
Versión 0.1.0. Escrito y comprobado contra Crucible 0.1.0 en Linux x86-64. Crucible es joven: el capítulo Qué no hace (todavía) dice sin rodeos dónde se queda corto.
Por dónde empezar
- Quiero verlo funcionar: capítulos 2 a 4, en orden.
- Quiero simular mi banco: después, 5 a 9. El 9 es la receta paso a paso.
- Busco un campo o una opción: Referencia.
- Algo no responde: Problemas frecuentes.
Convenciones
Las órdenes empiezan por $ y se ejecutan desde la carpeta de tu banco, una
copia del banco de ejemplo (el capítulo 5 explica cómo hacerla), con el banco
arrancado en otra terminal, salvo que el texto diga otra cosa. Las líneas que
siguen a una orden son lo que imprimió.
Los ejemplos de terminal usan nc (netcat) porque viene en cualquier Linux. En
Windows, el capítulo 4 da el equivalente en PowerShell.
Cómo se mantiene cierto este manual
Nada de lo que imprime una orden está tecleado a mano. Cada sesión de terminal
es un fichero en
listings/sesiones/
que el capítulo incluye tal cual, y
check.sh
las vuelve a ejecutar contra el binario y avisa si alguna ya no da lo mismo.
Los perfiles y el banco de ejemplo que ves en las páginas son los ficheros del
repositorio, no copias.
$ docs/manual/check.sh target/debug/crucible
Lo que no se ha podido ejecutar aquí se marca como no verificado, con esas palabras.
Si buscas cómo está hecho Crucible por dentro —el formato como estándar, las decisiones de diseño— está en la documentación del repositorio.
Qué es Crucible
Crucible sirve para probar software de test sin el hardware. En vez de necesitar la fuente, el multímetro y la SMU encima de la mesa para desarrollar tu secuencia, describes cómo se comportan en unos ficheros de texto y Crucible los pone a escuchar en la red.
Tu software no nota la diferencia porque no la hay en lo que ve: abre un
recurso VISA, manda *IDN?, configura VOLT 5, pregunta MEAS:CURR? y recibe
respuestas con el formato del aparato real. Con los mismos errores, además: un
comando mal escrito deja su entrada en SYST:ERR?, como en un instrumento de
verdad.
Qué simula y qué no
Un instrumento de test es, casi siempre, una máquina de estados con respuestas: recibe un comando, cambia algo por dentro y contesta. Crucible modela eso:
- Estado: la tensión programada, si la salida está encendida, el rango…
- Comandos: qué entiende el instrumento y qué cambia cada uno.
- Modelos: cómo calcula lo que “mide”, con una fórmula sobre el estado. La corriente de una fuente puede ser la ley de Ohm con un poco de ruido.
No simula física continua ni circuitos: la fuente no alimenta de verdad al multímetro. Si tu prueba necesita que un valor pase de un instrumento a otro, tu script lo fija (el capítulo 8 enseña cómo).
Las palabras
| Palabra | Qué significa aquí |
|---|---|
| banco | Los instrumentos que se simulan a la vez. Un fichero banco.yaml. |
| perfil | La descripción de un modelo de instrumento: su *IDN?, su estado, sus comandos y sus modelos. Un fichero YAML por modelo. |
| dispositivo | Una unidad del banco: un perfil servido en un puerto. Dos multímetros iguales son dos dispositivos con el mismo perfil. |
| estado | Las variables que recuerda el instrumento. Es uno por dispositivo y lo comparten todas las conexiones. |
| modelo | Una fórmula que calcula la respuesta de una consulta a partir del estado. |
| recurso VISA | La dirección con la que tu software abre el instrumento: TCPIP0::127.0.0.1::5025::SOCKET. |
Qué necesitas
- Linux x86-64 o Windows. Todo lo de este manual se ha ejecutado en Linux. En Windows las órdenes son las mismas; la instalación y la prueba de PowerShell del capítulo 4 las ejecuta la integración continua en cada versión, el resto está no verificado en Windows.
- Para probarlo sin tu software:
nc(netcat) en Linux, o PowerShell en Windows. - Para el ejemplo de Python: Python 3 con
pyvisaypyvisa-py.
Ningún instrumento, ninguna licencia y ningún driver VISA especial.
Instalación
Los instaladores están en la página de versiones de GitHub.
Linux (Debian, Ubuntu y derivadas)
$ sudo apt install ./crucible_0.1.0_amd64.deb
Deja:
- el comando
crucibleen/usr/bin; - el banco de ejemplo en
/usr/share/crucible/banco, de solo lectura (el capítulo 5 explica cómo copiarlo para editarlo);
Comprueba la instalación:
$ crucible --version
crucible 0.1.0
Windows
- Ejecuta
crucible-0.1.0-setup.exe. No pide permisos de administrador. - El instalador deja:
- En el menú Inicio → Crucible: Arrancar banco, Abrir carpeta del banco, Terminal de Crucible (una consola con la ayuda del comando) y Manual (abre esta web).
- El comando
crucibleen elPATH, para cualquier terminal nueva (cmd o PowerShell). - El banco de ejemplo en
%LOCALAPPDATA%\Programs\Crucible\banco, que puedes editar ahí mismo.
Al actualizar a una versión nueva no se sobrescriben los ficheros del banco que ya existan: tus cambios se conservan.
Desde el código fuente
Con Rust 1.90 o posterior:
$ git clone https://github.com/anlaco/Crucible
$ cd Crucible
$ cargo build --release -p crucible
El binario queda en target/release/crucible y el banco de ejemplo en
banco/. Sustituye crucible por esa ruta en el resto del manual, o añádela
al PATH.
Primer arranque
Crucible trae un banco de ejemplo con tres instrumentos: una fuente de alimentación DC, un multímetro y un Keithley 2400. Sirve para verlo funcionar antes de describir nada.
Arrancar el banco
- Linux: ejecuta
cruciblesin argumentos. - Windows: menú Inicio → Crucible → Arrancar banco.
$ crucible
Crucible 0.1.0 — banco.yaml
ID MODELO RECURSO VISA
fuente FUENTE-DC TCPIP0::127.0.0.1::5025::SOCKET
multimetro MULTIMETRO TCPIP0::127.0.0.1::5026::SOCKET
smu KEITHLEY-2400 TCPIP0::127.0.0.1::5027::SOCKET
Listo. Ctrl+C para parar.
Cada fila es un instrumento escuchando en su puerto. Mientras la ventana siga
abierta, están “encendidos”; cada conexión que llega queda anotada debajo.
Ctrl+C los apaga.
Sin argumentos, crucible usa el primer banco que encuentra de esta lista:
banco.yamlen la carpeta desde la que lo ejecutas.banco/banco.yamljunto al ejecutable (instalación de Windows)./usr/share/crucible/banco/banco.yaml(instalación de Linux).
Para arrancar uno concreto: crucible ruta/a/banco.yaml.
Preguntar a cada instrumento quién es
En otra terminal, con el banco arrancado:
$ printf '*IDN?\n' | nc -q1 127.0.0.1 5025
Crucible,FUENTE-DC,SIM0001,1.0
$ printf '*IDN?\n' | nc -q1 127.0.0.1 5026
Crucible,MULTIMETRO,SIM0002,1.0
$ printf '*IDN?\n' | nc -q1 127.0.0.1 5027
Keithley,2400,1234567,A1.2
Cada puerto es un instrumento distinto, y cada uno contesta con su *IDN?.
-q1importa. Sin él, elncde Debian y Ubuntu no cierra la conexión al terminar de enviar y la orden se queda esperando para siempre.
Y eso es todo lo que hay que saber para empezar: son sockets TCP que hablan SCPI terminado en salto de línea. El capítulo siguiente conecta un programa de verdad.
Conectar tu software
Cada instrumento del banco es un socket TCP en 127.0.0.1, en su puerto.
Para VISA, el recurso es:
TCPIP0::127.0.0.1::<puerto>::SOCKET
Terminación: los mensajes acaban en salto de línea (\n, 0x0A) en los dos
sentidos. Con recursos ::SOCKET, VISA no la pone por defecto: configúrala en
tu sesión, o tu software se quedará esperando una respuesta que Crucible no
procesa hasta recibir el salto de línea.
PyVISA
Con pip install pyvisa pyvisa-py basta; no hace falta NI-VISA. Este script
habla con la fuente y el multímetro del banco de ejemplo:
import pyvisa
rm = pyvisa.ResourceManager("@py") # o ResourceManager() si usas NI-VISA
opciones = dict(read_termination="\n", write_termination="\n")
fuente = rm.open_resource("TCPIP0::127.0.0.1::5025::SOCKET", **opciones)
dmm = rm.open_resource("TCPIP0::127.0.0.1::5026::SOCKET", **opciones)
print(fuente.query("*IDN?"))
fuente.write("VOLT 5")
fuente.write("OUTP ON")
print(fuente.query("MEAS:CURR?")) # 5 V sobre la carga de 10 ohmios
print(dmm.query("MEAS:VOLT?"))
$ python3 conectar.py
Crucible,FUENTE-DC,SIM0001,1.0
0.50001
+3.300139E+00
La fuente tiene conectada una carga simulada de 10 Ω, así que a 5 V da medio amperio. El multímetro mide 3,3 V porque el banco de ejemplo se lo fija al arrancar (capítulo 5). El último decimal es ruido gaussiano, que se repite igual cada vez que arrancas el banco (capítulo 7).
NI MAX y LabVIEW
No verificado en este manual.
Añade el instrumento en NI MAX como VISA TCP/IP Resource → Manual Entry of
Raw Socket, con host 127.0.0.1 y el puerto del banco. En LabVIEW, al abrir
la sesión VISA, activa el carácter de terminación \n (0x0A) para la lectura
(Termination Character Enabled) y acaba cada escritura con \n.
Windows, sin instalar nada
Desde PowerShell. Es la misma prueba con la que la integración continua comprueba el instalador de Windows en cada versión:
$c = New-Object Net.Sockets.TcpClient('127.0.0.1', 5025); $s = $c.GetStream()
$w = New-Object IO.StreamWriter($s); $w.AutoFlush = $true; $w.WriteLine('*IDN?')
(New-Object IO.StreamReader($s)).ReadLine(); $c.Close()
Si tu software apunta a las IP del banco real
El software que hoy abre TCPIP0::192.168.1.10::5025::SOCKET,
TCPIP0::192.168.1.11::5025::SOCKET… tiene que abrir
TCPIP0::127.0.0.1::5025::SOCKET, TCPIP0::127.0.0.1::5026::SOCKET… Cambia
las direcciones en su configuración; los puertos los decides tú en el
banco.yaml.
Si usa recursos ::INSTR (VXI-11), puedes dejarlos: declara el transporte
vxi11 en el banco (ver más abajo) o, si prefieres no complicarte, cámbialos
por ::SOCKET.
VXI-11 (::INSTR)
Si tu software no puede cambiar de recurso, pon tipo: vxi11 en el transporte
y dale a cada instrumento su IP, como en el banco real:
- id: generador
perfil: perfiles/n5171b.yaml
transporte: { tipo: vxi11, host: 127.0.0.2, puerto: 5025, device: inst0 }
- id: fuente
perfil: perfiles/n5767a.yaml
transporte: { tipo: vxi11, host: 127.0.0.3, puerto: 5025, device: inst0 }
TCPIP0::127.0.0.2::inst0::INSTR
TCPIP0::127.0.0.3::inst0::INSTR
Que es exactamente la forma del banco físico: cada equipo es una caja con su
IP, su portmapper en el UDP 111 y su device inst0. Del banco real al
simulado solo cambian las direcciones, ni el puerto ni el device.
En Linux todo 127.x.x.x es local y no hay que configurar nada.
En Windows solo responde 127.0.0.1. Allí pon los instrumentos en esa IP,
con el mismo puerto, y distínguelos por el device:
transporte: { tipo: vxi11, host: 127.0.0.1, puerto: 5025, device: inst0 }
transporte: { tipo: vxi11, host: 127.0.0.1, puerto: 5025, device: inst1 }
Esa segunda forma es la de un chasis VXI o una pasarela LAN/GPIB: una sola dirección para varios instrumentos. Funciona igual de bien, pero obliga a tocar los recursos de tu software.
Hace falta el puerto UDP 111, que es privilegiado. En Linux, una vez:
sudo setcap 'cap_net_bind_service=+ep' /ruta/a/crucible
En Windows, arranca como administrador. Si no, el banco no arranca y te dice exactamente esto: prefiere no arrancar a anunciar instrumentos que luego no encuentra nadie.
Anvil
El secuenciador Anvil habla SCPI por socket, así que se conecta a Crucible sin nada especial. La guía Anvil mide contra un instrumento de Crucible lo recorre de punta a punta.
Crear tu banco
Un banco es una carpeta con esta forma:
mi_banco/
├── banco.yaml ← qué instrumentos hay y en qué puerto
└── perfiles/
├── fuente_xyz.yaml ← un perfil por MODELO de instrumento
└── dmm_abc.yaml
Empezar copiando el de ejemplo
Lo más rápido es partir del banco de ejemplo y cambiarlo:
-
Linux:
$ cp -r /usr/share/crucible/banco ~/mi_banco $ cd ~/mi_banco -
Windows: menú Inicio → Crucible → Abrir carpeta del banco, y edita ahí o copia la carpeta a otro sitio.
Desde esa carpeta, crucible sin argumentos arranca tu banco: es el primer
sitio donde busca.
banco.yaml
Este es el del ejemplo:
# Banco de ejemplo de Crucible.
#
# Un banco es la lista de instrumentos que se simulan a la vez. Cada uno apunta
# a su perfil (qué instrumento es y cómo responde) y dice en qué puerto escucha.
#
# Para tu banco: copia esta carpeta, añade un perfil por cada modelo de
# instrumento y una entrada por cada unidad física. Dos unidades del mismo
# modelo comparten perfil, pero cada una necesita su propio puerto.
#
# Comprueba el banco sin arrancarlo: crucible --validar banco.yaml
# Arráncalo: crucible banco.yaml
banco:
nombre: banco-de-ejemplo
dispositivos:
- id: fuente
perfil: perfiles/fuente_dc.yaml
transporte: { tipo: tcp, puerto: 5025 }
- id: multimetro
perfil: perfiles/multimetro.yaml
transporte: { tipo: tcp, puerto: 5026 }
# Se puede cambiar el estado de arranque de una unidad sin tocar su perfil.
estado_inicial:
tension_entrada: 3.3
- id: smu
perfil: perfiles/keithley_2400_demo.yaml
transporte: { tipo: tcp, puerto: 5027 }
Cada entrada de dispositivos es una unidad:
| Campo | Qué es |
|---|---|
id | Un nombre para ti. Sale en la tabla del arranque y en los errores. |
perfil | El perfil del instrumento, con la ruta relativa a banco.yaml. |
transporte | Por dónde escucha: { tipo: tcp, puerto: 5025 }. Hoy tipo solo puede ser tcp. |
estado_inicial | Opcional. Valores que sustituyen a los de estado del perfil solo para esta unidad. |
Dos unidades del mismo modelo comparten perfil y cambian de puerto, y si
hace falta, de estado_inicial:
- id: dmm_entrada
perfil: perfiles/dmm_abc.yaml
transporte: { tipo: tcp, puerto: 5026 }
- id: dmm_salida
perfil: perfiles/dmm_abc.yaml
transporte: { tipo: tcp, puerto: 5027 }
estado_inicial:
tension_entrada: 12.0
Validar antes de arrancar
$ crucible --validar banco.yaml
banco.yaml es válido:
ID MODELO RECURSO VISA
fuente FUENTE-DC TCPIP0::127.0.0.1::5025::SOCKET
multimetro MULTIMETRO TCPIP0::127.0.0.1::5026::SOCKET
smu KEITHLEY-2400 TCPIP0::127.0.0.1::5027::SOCKET
--validar carga el banco y todos sus perfiles, comprueba patrones, fórmulas y
puertos, y enseña la tabla de recursos sin abrir ningún puerto, así que
puedes usarlo con el banco arrancado. Hazlo cada vez que edites un YAML: los
errores dicen qué fichero, qué comando y qué está mal.
Cada instrumento necesita su puerto. Con este banco, que repite el 5025:
banco:
nombre: dos-fuentes
dispositivos:
- id: fuente_a
perfil: perfiles/fuente_dc.yaml
transporte: { tipo: tcp, puerto: 5025 }
- id: fuente_b
perfil: perfiles/fuente_dc.yaml
transporte: { tipo: tcp, puerto: 5025 }
$ crucible --validar puerto_repetido.yaml
error: 'fuente_a' y 'fuente_b' usan los dos 127.0.0.1:5025; cada instrumento necesita su puerto
Tres reglas que conviene conocer
Si un perfil tiene un error, el banco entero no arranca. Es a propósito: un banco al que le falta un instrumento daría resultados falsos sin que nadie se enterase.
*RST devuelve la unidad a los valores del perfil, no a los de
estado_inicial. El multímetro del ejemplo arranca con 3,3 V en los bornes,
pero tras un *RST mide 0 V (lo verás en el capítulo 8). Si tu software manda
*RST al empezar, fija el valor en el perfil o vuelve a fijarlo por SCPI
después.
Una variable mal escrita en estado_inicial no da error. Aquí
tension_entarda no existe en el perfil del multímetro, y aun así:
banco:
nombre: errata
dispositivos:
- id: dmm
perfil: perfiles/multimetro.yaml
transporte: { tipo: tcp, puerto: 5030 }
estado_inicial:
tension_entarda: 12.0
$ crucible --validar errata.yaml
errata.yaml es válido:
ID MODELO RECURSO VISA
dmm MULTIMETRO TCPIP0::127.0.0.1::5030::SOCKET
El multímetro arrancará midiendo 0 V. Si una unidad no arranca con el valor que esperabas, revisa primero cómo has escrito el nombre.
Describir un instrumento
Un perfil describe un modelo de instrumento: quién es, qué recuerda, qué comandos entiende y qué hace con cada uno. Tiene cinco secciones:
dispositivo: # quién es
protocolo: # cómo se habla; hoy, siempre scpi
estado: # sus variables internas y su valor al encender
comandos: # qué entiende y qué hace con cada cosa
modelos: # cómo calcula lo que "mide" (capítulo 7)
Este es el perfil de la fuente del banco de ejemplo. Merece la pena leerlo entero: el resto del capítulo lo va desmontando.
# 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 # obligatorio; sale en la tabla del arranque
idn: "ACME,DMM-ABC,SN12345,FW2.01" # lo que contesta *IDN?
Si tu software comprueba el *IDN? para reconocer el instrumento, copia aquí
exactamente la respuesta del aparato real.
protocolo
scpi. El formato reserva otros valores (modbus_tcp, serial_ascii…) para lo
que vendrá, pero hoy un perfil que no sea scpi no arranca.
estado
Las variables que el instrumento recuerda, con su valor al encender y al que
vuelven con *RST:
estado:
tension: 0.0
salida: false
funcion: VOLT
Admite números, true/false y texto. El estado es uno por dispositivo y lo
comparten todas las conexiones: el capítulo 8 explica qué implica.
comandos
Cada entrada es una orden o una consulta. Si un comando tiene las dos
formas, como VOLT 5 y VOLT?, se declara dos veces:
comandos:
# Orden: VOLT 5 → guarda 5 en la variable 'tension'
- patron: "[SOURce:]VOLTage[:LEVel]"
args: [v]
muta: { tension: "<v>" }
# Consulta: VOLT? → contesta con el valor de 'tension'
- patron: "[SOURce:]VOLTage[:LEVel]"
query: true
respuesta: "{tension}"
# Consulta calculada: MEAS:VOLT? → la contesta el modelo 'lectura'
- patron: "MEASure:VOLTage[:DC]"
query: true
modelo: lectura
| Campo | Qué es |
|---|---|
patron | La cabecera en notación de manual (abajo). Sin ? y sin argumentos. |
query | true si es la consulta. Si no se pone, es una orden. |
args | Nombres para los argumentos que llegan, en orden: con args: [v], en VOLT 5 <v> vale 5. |
muta | Variables del estado que cambia: { tension: "<v>" }, o un valor fijo, { funcion: VOLT }. |
respuesta | (consultas) El texto que se contesta. {variable} se sustituye por el estado y <arg> por el argumento. |
modelo | (consultas) El modelo de modelos que calcula la respuesta. |
Toda consulta necesita respuesta o modelo. Una orden puede llevar solo
muta, o nada si basta con que se acepte sin error.
La notación del patrón
Es la de los manuales de los fabricantes:
- Las mayúsculas son la forma corta obligatoria y las minúsculas, lo que
completa la forma larga.
VOLTageaceptaVOLTyVOLTAGE, en mayúsculas o minúsculas. - Los corchetes marcan nodos que se pueden omitir.
[SOURce:]VOLTage[:LEVel]aceptaVOLT,SOUR:VOLT,volt:lev,SOURCE:VOLTAGE:LEVEL…
Un patrón cubre así todas las formas que admite el instrumento real, sin enumerarlas. Contra la fuente del ejemplo:
$ 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
Las órdenes no contestan nada, como en un instrumento real: por eso tras
VOLT 5 no aparece ninguna línea.
Copia los patrones del manual del fabricante (
MEASure:VOLTage[:DC]) y no la forma corta que usa tu software (MEAS:VOLT). Así el perfil aceptará lo que envíe cualquier otro programa.
Argumentos
ON,OFF,1y0se guardan como verdadero o falso, y los modelos los tratan igual.- Si un argumento declarado no llega (
CONF:VOLTsin rango), la variable se queda como estaba. - Sin nombres en
args, los argumentos se referencian por posición:<0>,<1>. - Una referencia que no existe, como
{variable_que_no_hay}, se contesta literal, con llaves incluidas. Si ves llaves en una respuesta, hay una errata en el perfil.
Instrumentos de varios canales
Un patrón por canal, con el número dentro:
- patron: "SOURce1:VOLTage"
args: [v]
muta: { tension_1: "<v>" }
- patron: "SOURce2:VOLTage"
args: [v]
muta: { tension_2: "<v>" }
SOUR1:VOLT 3 va al canal 1 y SOUR2:VOLT 7 al 2. SOUR:VOLT, sin número,
no se toma como canal 1: si tu software lo manda así, declara también el
patrón sin número. Una lista de canales como MEAS:VOLT? (@2) llega como un
argumento con el texto (@2).
Cuando un comando no está en el perfil
No se corta la conexión ni se contesta nada: se anota en la cola de errores,
como haría el instrumento. VOLT:PROT no está en el perfil de la fuente:
$ 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? es la primera herramienta para completar un perfil: dice qué
cabecera no reconoció. Cada lectura saca un error de la cola; cuando se vacía,
contesta 0,"No error".
Lo que no hay que declarar
*IDN?, *RST, SYST:ERR? y el resto de comandos comunes los resuelve
Crucible para todos los instrumentos (lista completa en el capítulo 8).
Declararlos es un 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'
Modelos: lo que el instrumento mide
Una consulta con respuesta: "{tension}" devuelve lo que se configuró. Una
medida es otra cosa: la corriente que da una fuente depende de la tensión, de la
carga y de si la salida está encendida. Eso es un modelo.
modelos:
corriente_medida:
tipo: formula
cuando: { salida: true }
expr: "min(tension / carga_ohm, limite_corriente) + gauss(0, 1e-5)"
fallback: 0.0
formato: fijo:5
Y la consulta que lo usa:
- patron: "MEASure[:SCALar]:CURRent[:DC]"
query: true
modelo: corriente_medida
Campos
| Campo | Qué es |
|---|---|
tipo | Siempre formula. |
expr | La fórmula. |
cuando | Condiciones sobre el estado. Si alguna no se cumple, se contesta fallback. Si hay varias, deben cumplirse todas. |
fallback | La respuesta cuando no se cumple cuando. Por defecto 0.0. Número o texto. |
formato | Cómo se escribe el número. Opcional. |
Qué cabe en expr
- Variables del estado, por su nombre:
tension,carga_ohm. - Números:
10,0.5,1e-3,2.5E+2. + - * /con la precedencia de siempre, paréntesis y signo negativo.- Funciones:
| Función | Resultado |
|---|---|
gauss(media, sigma) | Ruido gaussiano. Cambia en cada lectura, pero la serie se repite igual cada vez que arrancas Crucible, así que las pruebas automáticas son reproducibles. |
abs(x) | Valor absoluto. |
sqrt(x) | Raíz cuadrada. |
min(a, b), max(a, b) | Mínimo y máximo. |
No hay condicionales dentro de la fórmula: cuando solo elige entre la fórmula
y un fallback fijo.
El modelo de la fuente, en marcha
Contra el banco recién arrancado:
$ printf 'MEAS:CURR?\n' | nc -q1 127.0.0.1 5025
0.00000
$ printf 'OUTP?\n' | nc -q1 127.0.0.1 5025
0
$ printf 'VOLT 5;:OUTP ON;:MEAS:CURR?\n' | nc -q1 127.0.0.1 5025
0.50001
$ printf 'VOLT 20;:MEAS:CURR?;:OUTP?\n' | nc -q1 127.0.0.1 5025
0.99999;1
$ printf 'MEAS:VOLT?\n' | nc -q1 127.0.0.1 5026
+3.300139E+00
Paso a paso:
- Con la salida apagada no se cumple
cuando, y la medida es elfallback, escrito con el formato del modelo:0.00000. OUTP?contesta0, nofalse: lo contesta el modelosalida_como_numero, conformato: entero, como un instrumento real.- A 5 V sobre 10 Ω, medio amperio más ruido.
- A 20 V serían 2 A, pero
minrecorta al límite de 1 A: la fuente entra en corriente constante. - El multímetro escribe en notación científica, como la mayoría.
formato
Tu software espera los números como los escribe el instrumento real:
formato | Respuesta de ejemplo | Uso típico |
|---|---|---|
| (sin poner) | 1.0, 4.501385029307777 | — |
entero | 1, 0, -3 | Estados (OUTP?) y contadores. |
fijo:N | fijo:4 → 4.5014 | Fuentes que contestan con decimales fijos. |
cientifico:N | cientifico:6 → +4.501385E+00 | Multímetros y la mayoría de medidas SCPI. |
El formato también se aplica al fallback si es numérico.
Las consultas con respuesta: "{variable}" devuelven el valor tal cual
(5.0). Si tu software espera otra forma, usa un modelo cuya expr sea la
variable, con el formato que toque.
Errores en las fórmulas
Los de sintaxis —un paréntesis sin cerrar, una función que no existe— salen al validar o al arrancar, con la posición:
dispositivo:
modelo: DMM-ABC
idn: "ACME,DMM-ABC,SN12345,FW2.01"
protocolo: scpi
estado:
tension_entrada: 0.0
comandos:
- patron: "MEASure:VOLTage[:DC]"
query: true
modelo: lectura
modelos:
lectura:
tipo: formula
expr: "tension_entrada + gaus(0, 0.0001)"
$ crucible --validar perfiles/formula_rota.yaml
error: perfiles/formula_rota.yaml: perfil inválido: modelo 'lectura': error de evaluación: fórmula 'tension_entrada + gaus(0, 0.0001)': función 'gaus' desconocida; existen: gauss, abs, sqrt, min, max (posición 23)
Una variable que no existe en el estado, en cambio, solo se detecta al
hacer la consulta: no llega respuesta y SYST:ERR? devuelve
-200,"Execution error;…la variable '…' no existe en el estado…". Declara en
estado todas las variables que usen tus fórmulas.
Lo que todo instrumento sabe hacer
Hay cosas que todo instrumento SCPI hace igual, y Crucible las da hechas. No se declaran en el perfil.
Comandos comunes
| Comando | Qué hace |
|---|---|
*IDN? | Devuelve dispositivo.idn. |
*RST | Devuelve el estado a los valores del perfil. |
*CLS | Vacía la cola de errores y el registro de estado. |
*OPC, *OPC?, *WAI | Sincronización. Siempre listo: nada tarda. |
*ESE, *ESE?, *ESR?, *SRE, *SRE?, *STB?, *TST? | Registros de IEEE 488.2. |
SYSTem:ERRor[:NEXT]? | El siguiente error de la cola, p. ej. -113,"Undefined header;FOO". |
SYSTem:VERSion? | La versión de SCPI. |
Mensajes compuestos
Varios comandos separados por ; se ejecutan en orden, y las respuestas de las
consultas salen en una línea, separadas también por ; (lo viste en el
capítulo 7: 0.99999;1).
Hay una regla de SCPI que conviene tener clara. Tras un ;, la cabecera
siguiente es relativa al nodo de la anterior, salvo que empiece por ::
$ printf 'VOLT 5;OUTP ON\n' | nc -q1 127.0.0.1 5025
$ printf 'OUTP?\n' | nc -q1 127.0.0.1 5025
1
$ printf 'SOUR:VOLT 3;OUTP OFF\n' | nc -q1 127.0.0.1 5025
$ printf 'SYST:ERR?\n' | nc -q1 127.0.0.1 5025
-113,"Undefined header;SOUR:OUTP"
$ printf 'SOUR:VOLT 3;:OUTP OFF;:OUTP?\n' | nc -q1 127.0.0.1 5025
0
VOLT 5;OUTP ONfunciona:VOLTcuelga de la raíz, así queOUTPtambién.SOUR:VOLT 3;OUTP OFFno: el nodo actual esSOUR, yOUTPse lee comoSOUR:OUTP, que no existe. La salida sigue encendida.- Con
:delante (;:OUTP OFF) se vuelve a la raíz y funciona siempre.
Un instrumento real hace exactamente lo mismo. Si dudas, pon ;:.
El estado es compartido
Cada dispositivo tiene un solo estado, y lo comparten todas las conexiones. No se clona por cliente ni se reinicia al desconectar: igual que un aparato real no se resetea cuando cambia quien lo maneja.
El multímetro del ejemplo tiene un comando que el real no tiene,
SIMulate:INPut, para fijar la tensión en sus bornes desde un script. Cada
orden de esta sesión es una conexión distinta:
$ printf 'SIM:INP 12\n' | nc -q1 127.0.0.1 5026
$ printf 'MEAS:VOLT?\n' | nc -q1 127.0.0.1 5026
+1.200014E+01
$ printf '*RST\n' | nc -q1 127.0.0.1 5026
$ printf 'MEAS:VOLT?\n' | nc -q1 127.0.0.1 5026
-6.313870E-05
Los 12 V fijados por una conexión los mide la siguiente. Y tras *RST vuelve a
0 V —el valor del perfil—, no a los 3,3 V del estado_inicial con el que el
banco lo arrancó.
Consecuencias prácticas:
- Si tu software configura algo y termina, la siguiente ejecución lo encuentra
configurado. Manda
*RSTal empezar, o reinicia Crucible, si quieres partir de cero. SIMulate:INPutes la manera de encadenar instrumentos: los dispositivos no se ven entre sí, así que tu script lee la fuente y se lo pasa al multímetro. Puedes añadir comandos así a cualquier perfil.
Los errores no cortan la conexión
Un comando desconocido, un argumento que falta o una fórmula que no se puede
evaluar dejan su error en la cola y la conexión sigue abierta. Tu software lo ve
como en el instrumento real: preguntando SYST:ERR?.
Receta: de un instrumento real a su perfil
No hace falta describir el manual entero del instrumento. Hace falta describir lo que usa tu software, y hacerlo bien.
- Averigua qué comandos manda tu software.
- Si tienes el instrumento real, captura el tráfico: NI I/O Trace, el log de tu aplicación o Wireshark sobre el puerto del instrumento.
- Si no, busca en el código
write(,query(,VISA Write…
- Copia el
*IDN?del aparato real endispositivo.idn. - Por cada comando de configuración (
VOLT 5,RANG 10):- una variable en
estado, con su valor al encender; - una orden con
muta; - y, si tu software también lo consulta, una consulta con
respuesta: "{variable}".
- una variable en
- Por cada medida (
MEAS:VOLT?,READ?): un modelo con la física más sencilla que le sirva a tu prueba. Una constante con algo de ruido, o la ley de Ohm con la tensión de la fuente. Ajusta elformatoal del aparato. - Añade el instrumento al banco con su puerto (capítulo 5).
- Valida:
crucible --validar banco.yaml. - Arranca y ejecuta tu software. Si algo no responde, pregunta
SYST:ERR?a ese instrumento: dice qué cabecera no reconoció. Añádela al perfil y repite.
Consejos
- Patrones del manual, no de tu código.
MEASure:VOLTage[:DC]aceptaMEAS:VOLT,measure:voltage:dcy el resto de formas;MEAS:VOLTsolo acepta esa. - Un perfil por modelo, un escenario por banco. Si una prueba necesita que
el instrumento arranque ya configurado, no toques el perfil: usa
estado_inicialen el banco. El perfil describe el aparato; el banco, la situación. - Comandos de simulación. Para provocar escenarios desde el script (una
tensión fuera de rango, un sensor que falla) añade comandos que el aparato
real no tiene, como el
SIMulate:INPutdel multímetro de ejemplo. - Empieza por los perfiles de ejemplo.
fuente_dc.yamlymultimetro.yamlestán pensados como plantilla y comentados línea a línea.
Referencia
La orden crucible
$ crucible --help
Crucible — simulador de bancos de instrumentos SCPI por red
USO
crucible arranca el banco por defecto (ver abajo)
crucible <banco.yaml> arranca todos los instrumentos del banco
crucible <perfil.yaml> [puerto] arranca un solo instrumento (puerto 5025)
crucible --validar <fichero> comprueba un banco o perfil sin arrancarlo
crucible --version
BANCO POR DEFECTO (sin argumentos), el primero que exista de:
1. banco.yaml en el directorio actual
2. banco/banco.yaml junto al ejecutable (instalación Windows)
3. ../share/crucible/banco/banco.yaml (instalación Linux)
Cada instrumento tiene su dirección. Desde VISA:
TCPIP0::127.0.0.1::<puerto>::SOCKET (transporte tcp)
TCPIP0::127.0.0.2::inst0::INSTR (transporte vxi11)
Como en un banco real, cada instrumento puede tener su propia IP: en Linux
todo 127.x.x.x es local y no hay que configurar nada; en Windows solo
responde 127.0.0.1, y ahí se distinguen por el puerto.
Los instrumentos vxi11 necesitan además el portmapper en UDP 111 de su IP,
que es un puerto privilegiado: en Linux hace falta sudo o setcap.
Manual: https://anlaco.github.io/Crucible/
| Forma | Qué hace |
|---|---|
crucible | Arranca el primer banco que encuentra: banco.yaml en la carpeta actual, luego junto al ejecutable, luego el de la instalación. |
crucible <banco.yaml> | Arranca todos los instrumentos del banco. |
crucible <perfil.yaml> [puerto] | Arranca un solo instrumento, sin banco. Puerto 5025 si no se indica. |
crucible --validar <fichero> | Comprueba un banco o un perfil sin abrir puertos. Sale con código 1 si hay errores. |
crucible --version | La versión. |
crucible --help | La ayuda. También -h y --ayuda. |
Si el fichero es un banco o un perfil lo decide su contenido, no una opción:
tiene banco: o tiene dispositivo:.
Cualquier error hace que crucible salga con código 1. Sin argumentos
—normalmente un doble clic en Windows— espera además a que pulses Enter, para
que la ventana no se cierre antes de leer el motivo.
banco.yaml
banco:
nombre: <texto> # opcional
dispositivos:
- id: <texto> # obligatorio
perfil: <ruta> # obligatorio; relativa a banco.yaml
transporte:
tipo: tcp # obligatorio; hoy solo tcp
puerto: <número> # obligatorio; distinto en cada dispositivo
host: <dirección> # opcional; 127.0.0.1 por defecto
estado_inicial: # opcional
<variable>: <valor>
host decide en qué interfaz escucha el dispositivo. Por defecto 127.0.0.1,
solo el propio equipo. Con host: 0.0.0.0 escucha en todas, y otro equipo de la
red puede conectarse; en ese caso la tabla del arranque muestra
TCPIP0::0.0.0.0::…, que no es una dirección válida para conectar: usa la IP del
equipo que ejecuta Crucible. El acceso desde otro equipo está no verificado
en este manual.
Perfil
dispositivo:
modelo: <texto> # obligatorio
idn: <texto> # respuesta de *IDN?
protocolo: scpi # obligatorio
estado:
<variable>: <número | true | false | texto>
comandos:
- patron: <cabecera> # obligatorio; sin '?' ni argumentos, sin '*'
query: <true | false> # false por defecto
args: [<nombre>, ...]
muta: { <variable>: <valor | "<arg>"> }
respuesta: <texto con {variable} y <arg>> # consultas
modelo: <nombre de modelo> # consultas
modelos:
<nombre>:
tipo: formula # obligatorio
expr: <fórmula> # obligatorio
cuando: { <variable>: <valor> }
fallback: <número | texto> # 0.0 por defecto
formato: <entero | fijo:N | cientifico:N>
Fórmulas
| Elemento | Ejemplos |
|---|---|
| Variables | tension, carga_ohm |
| Números | 10, 0.5, 1e-3, 2.5E+2 |
| Operadores | + - * /, paréntesis, signo negativo |
| Funciones | gauss(media, sigma), abs(x), sqrt(x), min(a, b), max(a, b) |
Errores SCPI que verás
| Error | Cuándo |
|---|---|
0,"No error" | La cola está vacía. |
-113,"Undefined header;…" | La cabecera no está en el perfil, o no como orden o consulta. |
-200,"Execution error;…" | Una fórmula no se pudo evaluar, p. ej. por una variable que no está en estado. |
Qué no hace (todavía)
Conviene saberlo antes de describir tu banco.
| Limitación | Consecuencia |
|---|---|
Solo SCPI sobre socket TCP (::SOCKET) y VXI-11 (::INSTR). No hay HiSLIP, USB, GPIB ni serie. | Un banco por GPIB o USB no se puede simular aún. |
| VXI-11 necesita el puerto UDP 111, que es privilegiado. | Hay que arrancar con permisos (sudo setcap 'cap_net_bind_service=+ep' crucible en Linux, administrador en Windows) o el banco no arranca. |
Una IP por instrumento solo en Linux. Windows únicamente atiende 127.0.0.1. | En Windows, los instrumentos comparten IP y se distinguen por el puerto (tcp) o por el device (vxi11). |
Sin Modbus ni protocolos serie propios. El formato los tiene previstos, pero un perfil que no sea scpi no arranca. | Una cámara térmica o un fixture con protocolo propio no se pueden simular aún. |
| Los instrumentos no se ven entre sí. La fuente no alimenta al multímetro. | Para encadenar valores, tu script los fija (capítulo 8). |
Sin condicionales en las fórmulas. cuando solo elige entre la fórmula y un fallback fijo. | Un READ? que cambie según la función configurada (tensión o corriente) no se puede modelar; simula la que use tu prueba. |
Sin datos binarios: bloques #, formas de onda, capturas de pantalla. | Solo respuestas de texto. |
Sin tiempos: todo responde al instante. No hay *OPC que tarde ni timeouts simulados. | No sirve para probar cómo maneja tu software los timeouts. |
| El ruido es reproducible: la misma serie en cada arranque. | Bueno para pruebas automáticas; no esperes valores distintos entre ejecuciones. |
| Los YAML se leen al arrancar. | Tras editar un fichero, para Crucible y vuelve a arrancarlo. |
Si alguna bloquea tu banco, abre una incidencia con el ejemplo concreto: qué instrumento y qué comandos.
Problemas frecuentes
«no puede escuchar en 127.0.0.1:5025»
$ crucible banco.yaml
error: 'fuente' no puede escuchar en 127.0.0.1:5025 (Address already in use (os error 98)). ¿Hay otro programa, u otro Crucible, usando ese puerto?
Ya hay otro Crucible abierto, u otro programa, en ese puerto. Cierra la otra
ventana o cambia el puerto en banco.yaml. Para ver quién lo tiene:
- Linux:
ss -ltnp | grep :5025 - Windows:
netstat -ano | findstr :5025
«no encuentro ningún banco»
$ cd /tmp
$ crucible
error: no encuentro ningún banco. He buscado en:
banco.yaml
/usr/bin/banco/banco.yaml
/usr/share/crucible/banco/banco.yaml
Indica uno: crucible <banco.yaml>
Pulsa Enter para cerrar.
Has ejecutado crucible sin argumentos en una carpeta sin banco.yaml, y no
hay banco de ejemplo instalado donde lo busca. Indica la ruta:
crucible ~/mi_banco/banco.yaml. (Sin argumentos, Crucible espera a que pulses
Enter también en una terminal.)
La ventana de Windows se cierra al hacer doble clic
Si hay un error, la ventana espera a que pulses Enter. Si aun así se cierra,
ábrela desde una terminal (cmd) para leer el mensaje.
nc se queda esperando
Falta -q1: el nc de Debian y Ubuntu no cierra la conexión al acabar de
enviar. printf '*IDN?\n' | nc -q1 127.0.0.1 5025.
Mi software da timeout
- Falta la terminación
\nal escribir: Crucible no procesa la línea hasta el salto de línea. Con recursos::SOCKET, configúrala en VISA (capítulo 4). - Es una orden, sin
?: no contesta nada, como un instrumento real. No leas después de una orden. - La consulta no está en el perfil, o una fórmula falló: no llega respuesta
y el error queda en la cola. Pregunta
SYST:ERR?.
SYST:ERR? devuelve -113,"Undefined header;…"
Esa cabecera no está en el perfil. Revisa el patrón, y si es orden o consulta
(query: true).
Un comando encadenado no hace nada
SOUR:VOLT 3;OUTP OFF deja un -113 con SOUR:OUTP. Es la regla de SCPI:
tras ;, la cabecera es relativa al nodo anterior. Escribe ;:OUTP OFF
(capítulo 8).
MEAS:…? devuelve siempre 0.0
No se cumple el cuando del modelo, por ejemplo porque la salida está apagada.
Comprueba el estado con su consulta (OUTP?).
Una respuesta sale con llaves: {tensoin}
La variable no existe en el estado: hay una errata en respuesta.
Una unidad no arranca con el valor de estado_inicial
El nombre de la variable está mal escrito: estado_inicial no avisa de las
variables que no existen (capítulo 5). O algo mandó *RST, que vuelve a los
valores del perfil.
He cambiado un YAML y no noto el cambio
Crucible lee los ficheros al arrancar. Para con Ctrl+C y vuelve a arrancar.
El valor configurado «se ha quedado» de una ejecución anterior
El estado es compartido y dura mientras Crucible esté abierto (capítulo 8).
Manda *RST al empezar tus pruebas o reinicia Crucible.