UP | HOME

mrSCPI
Instrument Control Software

Author: Mitch Richling
Updated: 2026-03-24 10:15:31
Generated: 2026-03-24 10:15:38

Copyright 2026 Mitch Richling. All rights reserved.

Table of Contents

1. Introduction

From the README:


mrSCPI is a software tool for controlling programmable test equipment; however, I think of mrSCPI as more of a test bench productivity tool.

From a software/hardware architectural perspective, programmable test equipment is designed in such a way so as to make very complex test automation possible. Systems designed from the ground up to support very complex use cases frequently overly complicate things for people with simple use cases. Larry Wall captured the phenomenon quite clearly when he said:

Perl makes easy things easy and hard things possible. 
Professional programming languages tend to make all things equally difficult.

mrSCPI aims to make it possible to easily integrate test equipment automation into my day to day bench workflow. The goal is to make it so easy that I use it even for the hundreds of tiny, repetitive tasks I preform at the bench.

If you are wondering if mrSCPI is for you, that emphasized "my" is a warning! mrSCPI is very much designed around my personal workflow and tool preferences. I very much like command line tools with UNIX'ish interfaces. If this sounds like you, then mrSCPI might be for you. OTOH, if you were looking for a nice GUI to control your equipment, then you will most likely be quite disappointed with mrSCPI.

Lastly, mrSCPI is simple to set-up so I can run it anyplace. It has no dependencies beyond a standard Ruby install. No drivers. No modules. No packages. No PIPs. No GEMs. Nothing to compile. Just a single script – yes, the library and the executable are the same file!

Links:


2. Interfaces

The mrSCPI ecosystem provides three distinct ways to control SCPI instruments:

Ruby API (See the Ruby API section for more detail)
For complex test automation requiring a real programming language. This stateful API is unlike most test automation APIs and naturally compliments the way many SCPI tasks are preformed … Or at least the way I tend to use SCPI. ;)
require_relative 'mrSCPIlib'
SCPIsession.new(:url => '@tek2k',
                :cmd => '*IDN?')
mrSCPI.rb command
A CLI command providing sophisticated SCPI functionality from the command line – much like lxi-tools.
mrSCPI.rb --url @tek2k --cmd '*IDN?'
mrSCPI Scripts (See the Scripts section for more detail)
An efficient scripting language for simple SCPI tasks. Full Emacs org-mode Babel support is included enabling instrument control right from your laboratory notebook!
:url @tek2k
:cmd *IDN?

All of these methods use the same underlying infrastructure, and mirror each other in the way they work. So if you learn how to use one of them, you have learned how to use them all.

3. Emacs as a mrSCPI Interface

If you don't have any interest in Emacs, then feel free to skip this section.

mrSCPI really has nothing to do with Emacs; however, as an enthusiastic Emacs user, I wanted to be able to use mrSCPI from inside Emacs! To that end I have developed a bit of Emacs software to seamlessly integrate mrSCPI into Emacs. For me, because Emacs plays a central role in my workflow, this is one of the most important aspects of mrSCPI – 90% of my mrSCPI use is from within Emacs. This section provides some insight into my workflow. If you are not interested in Emacs, then you can safely skip this section.

3.1. The Laboratory Notebook

I always have my laboratory notebook open at the bench – just an org-mode document loaded up in Emacs running on my bench computer. I have implemented a major mode for mrSCPI scripts as well as org-mode Babel support. This allows me to embed SCPI commands right inside my notes making those notes EXECUTABLE. Here is an example excerpt from one of my notebooks:

To make this sort of thing effortless, I have a few scripts that capture instrument configuration details and insert them into my org-mode notebook. Then, when I come back to my experiment some time later, I can have org-mode reconfigure all the equipment on my bench to where I left off.

3.2. Test Equipment Notes & Macro Buttons

I keep general test equipment notes in a couple org-mode files. Of course I include in my notes little bits of mrSCPI script code for common things I do with my test equipment. I could simply put my cursor on those code bits in org-mode and execute them, but I wanted a quicker way to access these code snippets. So I added an Emacs function that finds all the mrSCPI code bits in an org-mode document, and produces a GUI window full of buttons to run them. This has proven to be super handy, and saves me a ton of time at the bench. Here is an example of one of my button buffers:

3.3. Integrated Data Capture and Analysis

org-mode allows us to integrate live code and results from different tools together into a single document. In this example we integrate code & results from mrSCPI, Octave/Matlab, Bourne shell, and Paraview in one document:

3.4. Setting Up Emacs

I put the mrSCPI stuff in my Emacs dot file directory. Of course it doesn't need to be in the dot file directory. That's just where I put it. If you have some other organizational structure for your auxiliary Emacs files, then feel free to use it!

3.4.1. Editing mrSCPI Code: mrscpi-mode

A simple mode for mrSCPI scripts is provided in emacs/mrscpi-mode.el. This should be before your org-mode config.

(if (file-exists-p "~/.emacs.d/mrscpi-mode.el")
    (progn (autoload 'mrscpi-mode "~/.emacs.d/mrscpi-mode.el" "Mode for mrSCPI files")
           (add-to-list 'auto-mode-alist '("\\.mrscpi$" . mrscpi-mode))))

3.4.2. org-mode Babel Support: ob-mrscpi.el & mrscpi-buttons.el

A language module for org-mode Babel may be found in emacs/ob-mrscpi.el. The magical button maker may be found in emacs/mrscpi-buttons.el – you don't need this one for just basic org-mode support. I load these things up at the very end of the org-mode setup block in my init.el file:

(if (and (file-exists-p "~/.emacs.d/ob-mrscpi.el") (file-exists-p "~/bin/mrSCPI.rb"))
    (load "~/.emacs.d/ob-mrscpi.el"))
(if (and (file-exists-p "~/.emacs.d/mrscpi-buttons.el") (file-exists-p "~/bin/mrSCPI.rb"))
    (load "~/.emacs.d/mrscpi-buttons.el"))

4. Ruby API

4.1. API Structure & Philosophy

In essence any SCPI program is about managing a communication session state between computer and instrument. The heart of mrSCPI is the SCPIsession class which encapsulates and manages session state. Session state is represented by named parameters (key-value pairs). For example the parameter :ip_address stores the IP address for the connection. As another example, the :print_results parameter stores a boolean value that determines if the results from SCPI commands will be printed. A few parameters, like :ip_address, may only be set when the SCPIsession is created. Other parameters, like :print_results may be freely changed at any time. Parameters are sticky in that they stay in effect until changed. Using the API always follows the same basic outline:

  1. Create an SCPIsession object (The :url, :ip_address, :net_protocol, and :net_port parameters are used by new).
  2. Set parameters regarding how we wish to run SCPI commands and what to do with the results.
  3. Set the :cmd parameter to execute an SCPI command.
  4. Go back to 2) till everything is done

See the documentation for the SCPIsession class for information about all the parameters.

4.2. Example

In the following example, we run the *IDN? command on my DMM. This is about the simplest thing we can do with mrSCPI – a single instrument connection through which we send a single command.

# Search for mrSCPI.rb on my shell PATH variable and load it up.
require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}

theSPCIsession = SCPIsession.new(:url => '@33210a',   # Connect using "nickname" to my DMM
                                 :cmd => "*IDN?")     # Send the *ION command to the instrument

puts("The Result: #{theSPCIsession.result.inspect}")  # Print out what we received

All the magic is in the SCPIsession.new call:

  • First we tell it where to connect with :url.
  • Lastly we tell it the command to use.

Source code: idn.rb.

5. mrSCPI Scripts & One Liners

Even if you only want to know about scripts, I encourage you to read the section on Ruby API Structure & Philosophy.

5.1. mrSCPI Script Example

To begin, here is a mrSCPI script that mirrors the example Ruby script given above.

:url @34401a  # @34401a is an instrument connection "nickname" for my DMM
:cmd *IDN?    # Run the *IDN? command

Notice how the code parallels the Ruby script above; however, there is one important difference – we don't need to explicitly print out the result from the command. When run in "script mode", mrSCPI echos the command and result to STDOUT. From an API standpoint, this means the following three parameters have different default values:

Table 1: Parameters With Different "script mode" vs "Library Mode" Defaults
parameter script mode Library mode
:print_result true false
:print_result_puts true false
:print_cmd true false

We can adjust the Ruby example above to work in the same way:

require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}

theSPCIsession = SCPIsession.new(:url  => '@33210a',   # Connect using "nickname" to my DMM
                                 :echo => true,        # Automatically print command and result
                                 :cmd  => "*IDN?")     # Send the *ION command to the instrument

Source code: idn_echo.rb.

Notice the addition of the :echo parameter (which sets :print_result, :print_result_puts, & :print_cmd at one time). Also note that we have removed the puts call in the previous example.

5.2. Running mrSCPI Scripts

Note the shebang line (#!/usr/bin/env -S mrSCPI.rb -f) in the first script example. This is how we make a mrSCPI script executable on the command line – just like a shell script. From the For example directory, we can run this script in a shell like this:

./idn_echo.mrscpi

We can also run scripts by invoking mrSCPI directly and using the -f option like so:

mrSCPI.rb -f idn.mrscpi

Note that scripts can also be provided on STDIN. If we continue with the previous example, then here is another way to run the script:

mrSCPI.rb -f STDIN <idn.mrscpi

5.3. Running mrSCPI One Liners

Scripts can be described on the command line with arguments instead of being put in a script file – something of an extension of the traditional UNIX "one liner script" concept. For the most part we replace the mrSCPI script commands with arguments of the same name. Notice that it parallels first script example above:

mrSCPI.rb --url @34401a --cmd '*IDN?'

Note the quotes on the command are required because the asterisk (*) and question mark (?) have special meaning for the shell.

5.4. mrSCPI Combining Scripts & One-Liners (Command Line Arguments)

What if you wanted to make the URL part of mrSCPI script be passed from the command line. You could resort to using a one-liner, but mrSCPI provides another way. mrSCPI has the ability to combine one-liner commands with commands found in a script file. When we do this it is as if we prepend the script file with the commands given on the command line. One thing to note is that the first :url option will override any subsequent instances – this is also true for :ip_address, :net_port, & :net_protocol as well. For example we could do something like this to connect to an alternate DMM:

./idn_echo.mrscpi --url @dmm2

A common technique is to parameterize things with variables, provide default values for the variables in the script, and then override one or more variables on the command line. For example, suppose we wish to set our power supply up with the first two channels providing analog voltage rails and the third channel providing a voltage for a microcontroller. We might use a script like the following:

:url @hmc8043
:result_type nil
:delay_after_complete 100
:cmd :OUTPut:MASTer:STATe OFF'
:cmd :INSTrument:NSELect 1; :SOURce:VOLTage:LEVel:IMMediate:AMPLitude ${analog_voltage:10}; :OUTPut:CHANnel:STATe ON
:cmd :INSTrument:NSELect 2; :SOURce:VOLTage:LEVel:IMMediate:AMPLitude ${analog_voltage:10}; :OUTPut:CHANnel:STATe ON
:cmd :INSTrument:NSELect 3; :SOURce:VOLTage:LEVel:IMMediate:AMPLitude ${digital_voltage:5}; :OUTPut:CHANnel:STATe ON

Source code: power_setup.mrscpi.

mrSCPI.rb -D analog_voltage=15 -D digitial_voltage=3.3 power_setup.mrscpi

If we wanted the default values, then we could have just used this:

mrSCPI.rb -f power_setup.mrscpi

Inside of Emacs in an org-mode code block, we could set these variables via the src header arguments like so:

#+begin_src mrscpi :output verbatum :var analog_voltage="15" digitial_voltage="3.3"

Note that the "new" commands (:url, :ip_address, :net_port, & :net_protocol) can not be parameterized with variables, but they can be overridden.

5.5. mrSCPI Script Syntax

mrSCPI scripts and one-liners construct an SCPIsequence object in the background. SCPIsequence objects provide a container mrSCPI for a script/one-liner object. As such mrSCPI scripts may contain constructions and syntax that is not part of the SCPIsession object:

  • Infrastructure to parse mrSCPI scripts into Ruby objects.
  • Simple conditionals and loops.
  • Ruby expression parsing and evaluation.
  • Variable interpolation.

This example runs *IDN?, and performs different actions based on the result (illustrating conditionals & variable interpolation):

:url @34401a                      # @34401a is an instrument connection "nickname"
:name ids                         # We name the next :cmd that will run
:cmd *IDN?                        # Creates ids variable and stores command result in it
:skip_if ${ids}~34401[aA]         # Checks the result against a regular expression, and skips the next command if it matches
:stop Unknown Device: ${ids}      # This line is skipped if the previous one was true.  Note :stop ends the script.
:print Known Device: 34401A       # The normal script end -- when the regex above matches indicating a recognized device

Source code: conditional.mrscpi.

This example we construct a filename based on today's date and then store an oscilloscope screenshot in the file (illustrating Ruby code evaluation & variable interpolation):

:url @dho4k                                                                # Connect to my DHO4k
:eval imgFileName=Time.now.localtime.strftime('%Y%m%d%H%M%S') + "_DHO.png" # Filename for PNG
:result_macro_block true                                                   # Expect/Extract a block
:echo false                                                                # Do not echo the command
:out_file ${imgFileName}                                                   #   into a filename
:cmd :DISPlay:DATA? PNG                                                    # Send the command

Source code: dho4ksc.mrscpi.

The entirety of mrSCPI script syntax may be compactly stated:

Each line of a mrSCPI script with an mrSCPI command, and each command is a parameter for one of the following classes:

Syntax for the command arguments is completely defined by the @optValToStr member of SCPIsequence.

6. mrSCPI Library Use Vs. mrSCPI Scripts

If all we need to do is execute a simple list of linear SCPI commands on a instrument, then a mrSCPI script or one-liner is probably the most direct option. If the application requires complex logic, sophisticated data structures, or involved calculations; then using a Ruby script and SCPI as a library is usually the best choice. Between these two extremes, the choice between using mrSCPI as a library required by a Ruby script vs a mrSCPI script is largely up to personal preference.

Passing IEEE-488.2 binary block format data as SCPI command arguments is an example of something that is much easier to do when using Ruby directly. This is because in library mode we can pass parameters directly to an SCPIsession object as native Ruby types. This makes library mode a bit more flexible from a data processing perspective.

In the code below we provide 8192, 14-bit sample points as an IEEE-488.2 binary data block containing 16-bit integers to an Agilent 33210A arbitrary waveform generator. This function generator can accept ASCII formatted floats, ASCII formatted integers, and binary integers. With only 8k points there isn't much practical benefit to using binary mode, but it gives us a nice way to demonstrate how to do so with mrSCPI!

require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}

# Number of data points for our curve.
size = 8192

if (size < 1) then
  STDERR.puts("ERROR: Data size too small!")
  exit
elsif (size > 8192) then
  STDERR.puts("ERROR: Data size too big!!")
  exit
end

# Populate floating point data: fdata
# We evaluate |sin(t)| over [0, 2pi].
fdata = Array.new(size)
fdata.each_index do |i|
  fdata[i] = (Math.sin(2 * Math::PI * i.to_f / size.to_f)).abs
end

# Populate short integer data: sdata
# We convert and scale the data in fdata, and store it in sdata.
fmin = fdata.min
fmax = fdata.max

bmin = -8191
bmax =  8191

if ((fmax - fmin) < 1.0e-8) then
  STDERR.puts("WARNING: Curve is constant.")
  m = 0
  b = 0
else
  m = (bmax - bmin).to_f / (fmax - fmin)
  b = bmax.to_f - m * fmax
end

sdata = Array.new(size)
fdata.each_with_index do |v, i|
  sdata[i] = [[(m * v + b).to_i, bmax].min, bmin].max
end

# Construct a string with the contents of the IEEE-488.2 binary block with our waveform data.
blen    = (size * 2).to_s
blenlen = blen.length.to_s
bdata   = "#" + blenlen + blen + sdata.pack('n*')

# Send everything to the device
theSPCIsession = SCPIsession.new(:url         => '@33210a',   # Change for your instrument's IP address
                                 :result_type => nil)         # None of the following commands have any output to capture
theSPCIsession.set({:cmd => '*RST'})                          # Reset the unit to defaults.
theSPCIsession.set({:cmd => ':DATA:DAC VOLATILE, ' + bdata }) # Send our data
theSPCIsession.set({:cmd => ':FUNCtion:USER VOLATILE'})       # Set the ARB waveform to 'VOLATILE'.
theSPCIsession.set({:cmd => ':APPLy:USER 2000,4,0'})          # 2 kHz, 4 Vpp, 0 V offset

Source code: bin_scpi_arg.rb.

7. SCPIrcFile Class

mrSCPI can use a configuration file to keep track of things like IP addresses, port numbers, protocols, and firewall details. The SCPIrcFile class is the primary interface to the information in the RC file. For the details, see the class documentation.

7.1. Simple RC file

Here is an example of a very simple RC file with four devices (an HP universal counter, Agilent arbitrary waveform generator, a Keysight DMM, and a Tektronix oscilloscope) each using a different connection protocol (Prologix Ethernet to GPIB, raw TCP/IP sockets, Serial over TCP/IP, and T4K – a specialized HTTP mode for TDS3000B scopes). This configuration has no network subnet configuration at all. Two nicknames are provided for each address – the first being the equipment model number (like 53131a or tds3000b) and the second being more descriptive (counter, awg, dmm, oscope).

nickname 53131a   counter => plgx://gpib-rover.home.mitchr.me:1234
nickname 33210a   awg     =>  raw://awg-agilent.home.mitchr.me:5025
nickname 34401a   dmm     => soip://serial.home.mitchr.me:10001
nickname tds3052b oscope  =>  t3k://oscope-tek.home.mitchr.me:80

7.2. My RC file

My home network has the following configuration:

  • Everything on my bench is in the 172.16.3.X subnet.
  • Everything on my computer desk is in the 172.16.1.X subnet.
  • Equipment on the bench is allowed to communicate with each other.
  • Systems on the computer desk are allowed to communicate to bench equipment.
  • The only bench access provided outside these two subnets is via an SSH bastion server using SSH tunnels.

So when I'm at my bench or computer desk I can access the test equipment directly – for example I can access my AWG directly via raw://awg-agilent.home.mitchr.me:5025. When I'm anyplace else on my home network I need to use an SSH tunnel – for example I can access my AWG through an SSH tunnel via raw://127.0.0.1:9001. The mrSCPI RC file allows us to define alternate addresses for instruments based upon which network from which we are connecting. So with mrSCPI I can always use the alias "33210a" to connect to my AWG regardless of where I'm running the script.

I actually generate this configuration file via a bit of code inside the org-mode document for my Test Equipment Notes.

#####################################################################################################################
# SCPI Nicknames ####################################################################################################
#........CNAME......nicknames.................Directly Connected Network....................ssh to bench-pi...........
nickname 33210ae    33210a    aawge  aawg  => direct@raw://awg-agilent.home.mitchr.me:5025  ssh@raw://127.0.0.1:9001
nickname 33210aw              aawgw        => direct@https://awg-agilent.home.mitchr.me:443 ssh@https://127.0.0.1:9002
nickname 34401ap              dmmp         => direct@plgx://gpib-rover.home.mitchr.me:1234  ssh@plgx://127.0.0.1:9003
nickname 34401as    34401a    dmms   dmm   => direct@soip://serial.home.mitchr.me:10001     ssh@soip://127.0.0.1:9004
nickname 53131ap    53131a    countp count => direct@plgx://gpib-rover.home.mitchr.me:1234  ssh@plgx://127.0.0.1:9005
nickname dg2052e    dg2052    dg2ke  dg2k  => direct@raw://awg-rigol.home.mitchr.me:5555    ssh@raw://127.0.0.1:9006
nickname dg2052w              dg2kw        => direct@http://awg-rigol.home.mitchr.me:80     ssh@http://127.0.0.1:9007
nickname dho4204e   dho4204   dho4ke dho4k => direct@raw://oscope-rigol.home.mitchr.me:5555 ssh@raw://127.0.0.1:9008
nickname dho4204w             dho4kw       => direct@http://oscope-rigol.home.mitchr.me:80  ssh@http://127.0.0.1:9009
nickname dmm6500e   dmm6500   6500e  6500  => direct@raw://dmm-keithley.home.mitchr.me:5025 ssh@raw://127.0.0.1:9010
nickname dmm6500w             6500w        => direct@http://dmm-keithley.home.mitchr.me:80  ssh@http://127.0.0.1:9011
nickname hmc8043e   hmc8043   pse    ps    => direct@raw://ps-rns.home.mitchr.me:5025       ssh@raw://127.0.0.1:9012
nickname hmc8043w             psw          => direct@http://ps-rns.home.mitchr.me:80        ssh@http://127.0.0.1:9013
nickname sds2504xpe sds2504xp sig2ke sig2k => direct@raw://oscope-sig.home.mitchr.me:5025   ssh@raw://127.0.0.1:9014
nickname sds2504xpw           sig2kw       => direct@http://oscope-sig.home.mitchr.me:80    ssh@http://127.0.0.1:9015
nickname serialw              serial       => direct@http://serial.home.mitchr.me:80        ssh@http://127.0.0.1:9016
nickname tds2024s             tek2ks tek2k => direct@soip://serial.home.mitchr.me:10002     ssh@soip://127.0.0.1:9017
nickname tds3052bh  tds3052b  tek3kh tek3k => direct@t3k://oscope-tek.home.mitchr.me:80     ssh@t3k://127.0.0.1:9018
nickname tds3052bp            tek3kp       => direct@plgx://gpib-rover.home.mitchr.me:1234  ssh@plgx://127.0.0.1:9019
nickname tds3052bs            tek3ks       => direct@soip://serial.home.mitchr.me:10003     ssh@soip://127.0.0.1:9020
nickname tds3052bw            tek3kw       => direct@http://oscope-tek.home.mitchr.me:80    ssh@http://127.0.0.1:9021
nickname test                              => direct@raw://127.0.0.1:1234                   ssh@raw://127.0.0.1:1234

#####################################################################################################################
# Networks ##########################################################################################################
#.......Name...Subnet.Mask.....Comment...............................................................................
network direct 172.16.3.0/23   # Bench network with test equipment & bench-pi
network direct 172.16.1.0/23   # Computer desk beside bench
network ssh    172.16.2.0/24   # Isolated work network
network ssh    172.16.4.0/24   # Isolated office network

7.3. mrNetwork.rb

This script simply prints out the name of the current host's network as specified in the .mrSCPIrc file.

if ENV['MRSCPIPATH'] then                                # First look in MRSCPIPATH
  require File.join(ENV['MRSCPIPATH'], 'mrSCPI.rb')        # Note if MRSCPIPATH is set and require fails, then we want the script to fail!
else
  begin                                                  # Then look in path with the script is
    require_relative 'mrSCPI.rb'
  rescue LoadError
    begin                                                # Then look on LOADPATH
      require 'mrSCPI.rb'
    rescue LoadError                                     # Then look on PATH
      require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}
    end
  end
end

puts(SCPIrcFile.instance.lookupNetwork.inspect)

Other than finding and loading mrSCPI, the source above only has one line of code that actually performs any work – the last line instantiates an SCPIrcFile object and prints out the network name found in the RC file for the current host. For example, if we run this script on my laptop, it will print "ssh" because that's what I've named the network in my personal .mrSCPIrc file:

../src/mrNetwork.rb
"ssh"

7.4. teAlias.rb

This script looks up connection information in the .mrSCPIrc file.

if ENV['MRSCPIPATH'] then                                # First look in MRSCPIPATH
  require File.join(ENV['MRSCPIPATH'], 'mrSCPI.rb')        # Note if MRSCPIPATH is set and require fails, then we want the script to fail!
else
  begin                                                  # Then look in path with the script is
    require_relative 'mrSCPI.rb'
  rescue LoadError
    begin                                                # Then look on LOADPATH
      require 'mrSCPI.rb'
    rescue LoadError                                     # Then look on PATH
      require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}
    end
  end
end

if (ARGV.length < 1) || (ARGV.first.match(/^-+[hH]/)) then
  puts("USE: teAlias.rb [format] thingy")
  puts("       thingy is one of netqork@nickname, @nickname, or nickname")
  puts("         network is a network name in the mrSCPIrc file")
  puts("         nickname is a nickname name in the mrSCPIrc file")
  puts("       format is a format string for how to print the result. Default: '%u'")
  puts("         %s -- replaced by the scheme (http, https, soip, etc...)")
  puts("         %h -- replaced by the IP address or DNS name")
  puts("         %p -- replaced by the port number")
  puts("         %u -- replaced by the full URL")
  puts("       Example: telnet `teAlias '%h %p' @foobar`")
  puts("       Example: ssh `teAlias '-p %p %h' @foobar`")
  puts("       Example: chromium-browser `teAlias.rb @foobar`")
  puts("                Works because '%u' is the default format.")
  puts("")
  puts("See SCPIrcFile.lookupURLnickname for full details.")
  exit 0
elsif (ARGV.length == 1) then
  print(SCPIrcFile.instance.lookupURLnickname(ARGV[0]))
elsif (ARGV.length == 2) then
  urlBits = SCPIrcFile.instance.urlParser(ARGV[1])
  print(ARGV[0].gsub('%h', urlBits[:ip_address]).gsub('%s', urlBits[:net_protocol].to_s).gsub('%u', urlBits[:url]).gsub('%p', urlBits[:net_port].to_s))
end

For example, if I wanted to fire up my web browser and connect to my SDS 2504X+ oscope, I might do this on the command line (zsh):

edge $(teAlias.rb sds2504xpw)

8. Features (current & future)

  • Broad platform support: If Ruby works, then mrSCPI should work.
  • Modern Ruby API for interacting with instruments via SCPI with a stateful programming model complimenting the structure of SCPI itself.
  • Simple mrSCPI scripting language
    • All the goodness of the Ruby API, but for simple applications
    • Usage that parallels how the Ruby API is used in practice
    • Shebang support for "executable" scripts on UNIX/Linux/Windows MSYS2
    • Support simple programming constructs (conditionals, looping, variables, etc…)
    • Emacs org-mode Babel support (both execution and header variables)
    • Emacs language mode (highlight mrSCPI script code & identify common syntax errors)
  • Protocol/Communication Features
    • SCPI over raw TCP/IP sockets
    • Serial (RS-232/RS-485 with DTE support) SCPI over Ethernet via a network attached serial console server.
    • GPIB SCPI over Ethernet with a Prologix lan controller
    • SCPI over HTTP for TDS3000B series oscilloscopes (working around the lack of raw sockets support)
  • Flexible timing control
    • Delay after sending a command but before doing a read on the line
    • Timeout to wait for a response to start
    • Timeout to for more data after a successful read
    • Delay before retrying an I/O operation
    • Delay after a command is run and all I/O processed, but before returning a value (or running the next command)
  • A set of standard post-processing steps for results
    • Standard string processing operations:
      • Chop (end of line characters)
      • Trim whitespace
      • Extract last word
      • Extract a 488.2 binary block data payload
      • Split into an array
        • Binary data can be unpack'ed into native types
        • Binary return blocks can be split into header and payload
        • Strings can be split on commas, semicolons, whitespace, vertical whitespace, regular expressions, fixed strings, or character sets
      • Compress sequences of one or more white-space characters into single spaces
    • Convert results into numeric Ruby types
      • Float or array of floats
      • Integer or array of integers
      • Boolean or array of booleans (actually tri-state: true, false, or nil)
  • Global Configuration File
    • Keeping track of instrument IP addresses or DNS names
    • Keeping track of port mappings so I can get through the firewall isolating my bench
    • Avoiding the need to type full DNS names or IP addresses
  • A library of regular expression strings for matching 488.2 results and program elements

9. FAQ

9.1. Why don't you use a simple require to load mrSCPI in Ruby scripts?

In my scripts, I normally load mrSCPI by searching my PATH environment variable like this:

require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}

Why? I normally put mrSCPI on my path, but I don't want to contaminate my Ruby LOAD_PATH by including a generic bin directory. This is just a personal preference in the way I set up my operating environment. I expect most people will use something like the following to load mrSCPI:

require_relative 'mrSCPI.rb'

For the scripts shipped in the src directory I do something quite elaborate allowing much more flexible control over which 'mrSCPI.rb file gets used. This way I can use various different versions of the library when I'm doing development work on the project.

if ENV['MRSCPIPATH'] then                                # First look in MRSCPIPATH
  require File.join(ENV['MRSCPIPATH'], 'mrSCPI.rb')        # Note if MRSCPIPATH is set and require fails, then we want the script to fail!
else
  begin                                                  # Then look in path with the script is
    require_relative 'mrSCPI.rb'
  rescue LoadError
    begin                                                # Then look on LOADPATH
      require 'mrSCPI.rb'
    rescue LoadError                                     # Then look on PATH
      require ENV['PATH'].split(File::PATH_SEPARATOR).map {|x| File.join(x, 'mrSCPI.rb')}.find {|x| FileTest.exist?(x)}
    end
  end
end

9.2. Where can I find more examples?

My TestEquipmentScripts github repository contains scripts related to test equipment. Many of those scripts use mrSCPI:

My Test Equipment Notes have a TON of mrSCPI script examples embedded inside the org-mode source.

9.3. Where is LXI support?

The SCPIsession class recognizes the protocol symbol :lxi in order to provide a meaningful error message.

I have no plans on implementing direct LXI support in mrSCPI. One of the most important goals of mrSCPI is to be simple and free of external software dependencies beyond a simple Ruby install. The complexity of the LXI protocol would necessitate external dependencies like liblxi and libtirpc.

If LXI is really what you need, then I suggest giving lxi-tools a try.