class SCPIsession
Introduction¶ ↑
SCPIsession
manages network connections and connection state for SPCI sessions.
SCPIsession
State Parameters¶ ↑
The object is controlled via changing the object’s state parameters. I know, that’s not much to go on. It’s really pretty simple. A few examples are probably the best way to understand it. See the mrSCPI readme.
SCPIsession
State Parameters: Global behavior¶ ↑
:verbose
(P/Integer)-
How much to print to
:log_file
(DEFAULT:1
)
Values:
-
0
: Silent -
1
: Errors -
2
: Warnings -
3
: Information -
4
: Debug-4 -
5
: Debug-5 Same as Debug4 (TODO: Future Feature) -
6
: Debug-6 Same as Debug4 (TODO: Future Feature) -
7
: Debug-7 Same as Debug4 (TODO: Future Feature) -
8
: Debug-8 Print out data structures
:exit_on_error
(L/Boolean)-
Exit if an error is encountered (DEFAULT: true)
See:
PrintyPrintyBangBang
:exit_0
(L/Boolean)-
Abnormal exits are always zero – required for org-mode (DEFAULT:
false
)
See:
PrintyPrintyBangBang
:execute_on_cmd
(Boolean)-
Immediately execute when :cmd is updated (DEFAULT:
true
)
If false, then the
execute
method must be used to force command execution:log_file
(L/String)-
Name of file for log messages (DEFAULT:
"STDERR"
)
Some file names are special:
"STDOUT"
,"STDERR"
, &"/dev/null"
See:PrintyPrintyBangBang
SCPIsession
State Parameters: Command string specification¶ ↑
:cmd
(String)-
The current command string
:scpi_prefix
(String)-
An SCPI prefix to be added when :cmd is sent to instrument (DEFAULT:
""
)
:eol
(String)-
End of line string to append to command strings sent to instrument (DEFAULT:
"\n"
)
SCPIsession
State Parameters: Instrument I/O tuning¶ ↑
:read_timeout_first_byte
(Integer/milliseconds)-
Timeout for first byte of data from instrument.
Default depends on :net_protocol:
:raw
-
1000
:soip
-
500
:plgx
-
500
:file
-
0
:read_timeout_next_byte
(Integer/milliseconds)-
Timeout for more data after receiving a response from instrument
Default depends on :net_protocol:
:raw
-
2000
:soip
-
100
:plgx
-
100
:file
-
0
:delay_before_first_read
(Integer/milliseconds)-
Time to wait before trying to read a result from instrument
Default depends on :net_protocol:
:raw
-
100
:soip
-
50
:plgx
-
50
:file
-
0
:delay_after_complete
(Integer/milliseconds)-
Time to wait after a command has completed before returning
Default depends on :net_protocol:
:raw
-
0
:soip
-
100
:plgx
-
50
:t3k
-
200
:file
-
0
:read_retry_delay
(Integer/milliseconds)-
Time to wait before retrying a read operation
Default depends on :net_protocol:
:raw
-
200
:soip
-
200
:plgx
-
100
:file
-
0
:read_eot_sentinel
(nil|String)-
Stop reading when the character appears on the input stream (DEFAULT:
nil
)
This can dramatically speed up query commands, but requires the instrument send an “end of transmission” character. Frequently we can exploit the fact that an instrument will terminate some responses with a newline character, and thus use the newline as an EOT character. For Prologix devices, an EOT character may be defined; however, this can interfere when binary data is to be transmitted from an instrument.
:good_std_eot
(P/Boolean)-
Set
:read_eot_sentinel
to"\n"
iftrue
, and tonil
iffalse
.
:socket_close_write
(Boolean)-
For raw & soip do a write_close on the TCP socket after sending command string (DEFAULT:
false
)
:socket_close
(Boolean)-
For raw & soip do a close on the TCP socket after reading is complete (DEFAULT:
false
)
:read_buffer_size
(Integer/bytes)-
Maximum number of bytes to attempt to read at once (DEFAULT:
1048576
)
:read_max_bytes
(Integer/bytes)-
Stop reading after we get this many bytes or more (DEFAULT:
nil
)
This is useful when transfusing data from the instrument and you know exactly how much data to expect
SCPIsession
State Parameters: Connection Parameters¶ ↑
Connection parameters may only be set when an SCPIsession
object is created (via new).
:url
(P/String)-
Used to set
:ip_address
and potentially:net_protocol
&:net_port
. If any of these options are explicitly set, then the explicitly set value wins.
URL formats:
-
[net_protocol://]ip_address[:net_port]
-
[network]@nickname
— Note nicknames always contain an@
:ip_address
(String)-
IP address
:net_protocol
(Symbol)-
Network protocol.
Values:
:raw
-
Raw socket connection (DEFAULT)
By far the most well tested mode as most of my equipment has an Ethernet port.
:soip
-
Serial Over IP
Designed for serial console servers or Ethernet to serial converters with a raw TCP/IP socket interface. The
:socket_close_write
parameter may break connections on console servers that use connection status to set serial hardware flow control. These devices may require longer timeouts. This feature has been tested with the following hardware:-
Lantronix EDS4100 Terminal Server
-
Keysight 34401A Bench Digital Multimeter
-
Tektronix TDS3052B Digital Phosphor Oscilloscope
-
Tektronix TDS2024 Digital Storage Oscilloscope
:t3k
-
SCPI over HTTP for TDS3000B series
Sends SCPI commands via an HTTP form, and scrapes the result out of the returned HTML document. Do not use commands that return binary data. Also note that TCP/IP read loop is bypassed for this mode, and so read timeouts and delays are ignored.
:lxi
-
VX11/LXI over IP (TODO: Future Feature)
:plgx
-
SCPI via a Prologix Ethernet adapter
The Prologix adapter configured with the following commands when the TCP/IP port is opened:
++savecfg 0 ..... Prevents permanent adapter config changes, and extends EPROM life ++mode 1 ........ Sets adapter to be a controller ++auto 0 ........ Turns off read after write avoiding errors on some equipment ++eoi 1 ......... Turns on end of input ++read_tmo_ms ... Set to :read_timeout_first_byte - 50 ++eos ........... Set based on :eol value
If
:result_type
is non-nil
, then an'++read eoi'
command is issued before the TCP/IP read loop. This is sent before the:delay_before_first_read
. This feature has been tested with the following hardware:-
Keysight 34401A Bench Digital Multimeter
-
Tektronix TDS3052B Digital Phosphor Oscilloscope
If
:read_timeout_first_byte
or:eol
is changed after the socket is opened, set:socket_close
to close the socket after the next command. This will be fixed someday.:file
-
Read response from a file with the same name as :cmd parameter
This feature is used for simulating SCPI responce strings without a physical device. It is used for testing.
:net_port
(Integer)-
TCP/IP Port number (DEFAULT: 0 for
:file
,:soip
, &:lxi
. 5025 for:raw
. 1234 for:plgx
. 80 for:t3k
)
SCPIsession
State Parameters: Output Control¶ ↑
:out_file
(L/String)-
Filename for print operations – not log operations (DEFAULT:
"STDOUT"
)
Some file names are special:
"STDOUT"
,"STDERR"
, &"/dev/null"
See:PrintyPrintyBangBang
:print_raw_result
(Boolean)-
Output raw results (the raw string returned from the instrument) to :out_file (DEFAULT:
false
)
:print_result
(Boolean)-
Output post processed results to :out_file (DEFAULT:
false
)
Nothing is printed when
:result_type
isnil
WARNING:SCPIsequence
sets this to true when it constructs anSCPIsession
object:print_result_puts
(Boolean)-
Use
puts
for prints – i.e. insure all prints have a newline at the end
:print_cmd
(Boolean)-
Output command to :out_file (DEFAULT:
false
)
Output is prefixed and postfixed by
'>>'
and followed by a newline WARNING:SCPIsequence
sets this to true when it constructs anSCPIsession
object:echo
(P/Boolean)-
Sets both
:print_cmd
&:print_result
at the same time (DEFAULT:false
)
:print_debug
(L/Boolean)-
All printed results are in Ruby
.inspect
format and followed by a newline
See:
PrintyPrintyBangBang
:print_max_len
(L/Integer or nil)-
Limit the number of characters printed (DEFAULT:
nil
)
If a print is truncated, then the next line in :out_file will be something like:
LAST LINE TRUNCATED (:print_max_len=N)
See:
PrintyPrintyBangBang
SCPIsession
State Parameters: Result processing¶ ↑
Strings returned from instruments can be processed via a standard set of transformations controlled by the options in this section. The actions occur in the order they are documented in this section. For example, operations after :result_split
are applied each array element resulting from the split. If :result_type
is nil
, none of these actions are taken.
:result_extract_block
(Boolean)-
If true and a block header is present, then replace the result sting with the data payload. Otherwise leave the string unmodified.
A block header starts with a literal pound character
#
and a single hex digit indicating the number of decimal digits that follow.If this digit is zero, then we have a variable sized block that ends with an EOL terminator.
If the digit is non-zero, then it tells us the number of ASCII decimal digits that follow it indicate the length of the data payload.
:result_split
(Symbol)-
If non-nil, then split the result sting into an array. (DEFAULT:
nil
)
Values:
nil
-
Don’t do anything – no split at all
:char
-
Split on the characters in
:result_split_arg
:string
-
Split on the string in
:result_split_arg
:unpack
-
Split via an unpack string in
:result_split_arg
WARNING: unpack may produce an array containing non-strings, and thus :result_type must usually be set to :mixed!
:block
-
Split into two element array containing block header and block payload
:regex
-
Split via the regex string in
:result_split_arg
:line
-
Split on horizontal whitespace
:space
-
Split on /s+/ (i.e. whitespace separated values)
:csv
-
Split on /s*,s*/ (i.e. comma separated values)
:ssv
-
Split on /s*;s*/ (i.e. semicolon separated values)
Handy for devices (R&S) that return semicolon separated values on compound SCPI statements Example:
:measure: SCALar:VOLTage:DC?; :MEASure:SCALar:CURRent:DC? "0.0; 0.0"
:result_split_arg
(String or nil)-
Argument required for the
:result_split
parameter. (DEFAULT:nil
)
:result_squeeze
(Boolean)-
If true, adjacent whitespace characters are squeezed into a single character – a single space or newline
:result_chomp
(Boolean)-
If true, the stored/returned result string is chomped (DEFAULT:
false
)
Note overlap with
:result_strip
!:result_strip
(Boolean)-
If true, the stored/returned result string is stripped of whitespace (DEFAULT:
false
)
Zaps
NL
(null),HT
(horizontal tab),LF
(line feed),VT
(vertical tab),FF
(form feed),CR
(carriage return), &SP
(space):result_last_word
(Boolean)-
If true, the stored/returned result string is right stripped and only the last word is returned (DEFAULT:
false
)
Some instruments return the query command string followed by the value, and this can be a handy way to get just the value. Example:
MEASUrement:IMMed:VALue? :measurement: IMMED:VALUE 9.9E37
:result_type
(Boolean)-
Expected return from SCPI command (DEFAULT:
nil
)
Used to coerce values into Ruby types Values:
nil
-
no data – A response is not even read from instrument
:mixed
-
When
:result_split
is non-NIL, leave returned data as is.
NOTE: Usually the correct value when
:result_split
is :unpack!:string
-
string. Usually a NOP except, when
:result_split
values of :unpack. In the :unpack case, this will convert any non-string elements in an array into strings.
:float
-
float (NR2, NR3, NRf) – NRf is only partly supported in that whitespace is not allowed around the “E”.
:int
-
integer (NR1)
:bool
-
Convert strings to
true
,false
, ornil
true
-
if result =~ /^s*(on|1|true|yes)s*$/i
false
-
if result =~ /^s*(off|0|false|no)s*$/i
nil
-
otherwise
Non-strings are left untouched
SCPIsession
State Parameters: Result storage¶ ↑
:name
(String)-
Name of current command.
:store_named_in_var
(Boolean)-
Store named results into a variable of the same name (DEFAULT:
true
)
:store_last_in_ans
(Boolean)-
Store result into a variable named ‘ANS’ (DEFAULT:
true
)
:store_results
(Boolean)-
Store the instrument response on the results stack (DEFAULT:
true
)
:store_raw_results
(Boolean)-
Store the raw instrument response string on the results stack (DEFAULT:
true
)
:store_cmd
(Boolean)-
Store the raw command string sent to the instrument on the results stack (DEFAULT:
true
)
SCPIsession
State Parameters: Macros¶ ↑
The following pseudo-options are provided for connivance, and are equivalent to setting several other options simultaneously. Like all option-like entities, these pseudo-options take an argument. This argument must be provided even if the macro ignores it.
- Result processing & Output Control Macros :result_extract_block :result_split :result_chomp :result_strip :result_last_word :result_type :print_max_len :print_debug - :result_macro_bin (P) ....... false nil false false false :string nil false - :result_macro_block (P) ..... true nil false false false :string nil false - :result_macro_ascii (P) ..... false nil true true false :string nil false - :result_macro_debug (P) ..... false nil true true false :string 256 true - :result_macro_csv (P) ....... false ',' true true false :string nil false
Public Class Methods
# File src/mrSCPI.rb, line 428 def initialize(options) @re = SCPIregexLibrary.instance @gblOpt = Hash.new @tcpSocket = nil @results = Array.new @varList = Hash.new @validProto = [:raw, :soip, :lxi, :plgx, :t3k, :file] @validType = [nil, :mixed, :string, :float, :int, :bool] @validSplits = [nil, :char, :string, :space, :unpack, :block, :regex, :line, :csv, :ssv] @gblOptReqPsu = Set.new(PrintyPrintyBangBang.instance.validOptions + [ :url ]) @gblOptReqNew = { :ip_address => lambda { |x| (x.is_a?(String)) && (x.length > 1) }, :net_port => lambda { |x| (x.is_a?(Integer)) && (x >= 0) && (x <= 65535) }, :net_protocol => lambda { |x| @validProto.member?(x) }, } @gblOptReqRun = { :cmd => lambda { |x| (x.is_a?(String)) && (x.length > 1) }, :delay_after_complete => lambda { |x| (x.is_a?(Integer)) && (x >= 0) }, :delay_before_first_read => lambda { |x| (x.is_a?(Integer)) && (x >= 0) }, :eol => lambda { |x| (x.is_a?(String)) }, :execute_on_cmd => lambda { |x| [true, false].member?(x) }, :name => lambda { |x| x.nil? || ((x.is_a?(String)) && (x.length > 0)) }, :print_cmd => lambda { |x| [true, false].member?(x) }, #print_debug Part of PrintyPrintyBangBang #print_max_len Part of PrintyPrintyBangBang :print_raw_result => lambda { |x| [true, false].member?(x) }, :print_result => lambda { |x| [true, false].member?(x) }, :read_buffer_size => lambda { |x| (x.is_a?(Integer)) && (x > 0) }, :read_eot_sentinel => lambda { |x| x.nil? || ((x.is_a?(String)) && (x.length > 0)) }, :read_max_bytes => lambda { |x| x.nil? || ((x.is_a?(Integer)) && (x > 0)) }, :read_retry_delay => lambda { |x| (x.is_a?(Integer)) && (x >= 0) }, :read_timeout_first_byte => lambda { |x| (x.is_a?(Integer)) && (x >= 0) }, :read_timeout_next_byte => lambda { |x| (x.is_a?(Integer)) && (x >= 0) }, :result_chomp => lambda { |x| [true, false].member?(x) }, :result_extract_block => lambda { |x| [true, false].member?(x) }, :result_last_word => lambda { |x| [true, false].member?(x) }, :result_split => lambda { |x| @validSplits.member?(x) }, :result_split_arg => lambda { |x| x.nil? || ((x.is_a?(String)) && (x.length > 0)) }, :result_squeeze => lambda { |x| [true, false].member?(x) }, :result_strip => lambda { |x| [true, false].member?(x) }, :result_type => lambda { |x| @validType.member?(x) }, :scpi_prefix => lambda { |x| x.is_a?(String) }, :socket_close => lambda { |x| [true, false].member?(x) }, :socket_close_write => lambda { |x| [true, false].member?(x) }, :store_cmd => lambda { |x| [true, false].member?(x) }, :store_last_in_ans => lambda { |x| [true, false].member?(x) }, :store_named_in_var => lambda { |x| [true, false].member?(x) }, :store_raw_results => lambda { |x| [true, false].member?(x) }, :store_results => lambda { |x| [true, false].member?(x) }, :var => lambda { |x| x.nil? || ((x.is_a?(String)) && (x.length > 0)) }, } # Set initial parameter values set({ #delay_after_complete Set from proto #delay_before_first_read Set from proto :eol => "\n", :execute_on_cmd => true, :name => nil, :exit_0 => false, :exit_on_error => true, :print_cmd => false, :print_debug => false, :print_max_len => nil, :print_raw_result => false, :print_result => false, :read_buffer_size => 1048576, :read_eot_sentinel => nil, :read_max_bytes => nil, #read_retry_delay Set from proto #read_timeout_first_byte Set from proto #read_timeout_next_byte Set from proto :result_chomp => false, :result_extract_block => false, :result_last_word => false, :result_split => nil, :result_split_arg => nil, :result_squeeze => false, :result_strip => false, :result_type => :string, :scpi_prefix => '', :socket_close => false, :socket_close_write => false, :store_cmd => true, :store_last_in_ans => true, :store_named_in_var => true, :store_raw_results => true, :store_results => true, :var => nil, }.merge(options)) end
Public Instance Methods
While .set
perfectly capable of setting the :cmd
parameter, this method provides provides a more readable alternative.
# File src/mrSCPI.rb, line 635 def command(cmd, options=nil) if options && options.member?(:cmd) then PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: :cmd must not appear in options list when calling 'command'!", self, 74) end # Set options first -- so we don't trigger automatic execution. if options set(options) end # Now set command by ourselves without the set method so we can execute the command ourselves and return a result if :execute_on_cmd is on @gblOpt[:cmd] = cmd if @gblOpt[:execute_on_cmd] then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Attempting automatic execution", self) return execute() end end
This method executes the command specified by the :cmd
parameter. This is method is completely unnecessary if the :execute_on_cmd
parameter is non-nil
.
# File src/mrSCPI.rb, line 691 def execute() time_exe_start = Time.now # Make sure all required options required by new are set and valid @gblOptReqRun.each do |k, validator| if !(validator.call(@gblOpt[k])) then PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Unable to execute. Execution option is invalid: #{k.inspect} = #{@gblOpt[k].inspect}!", self, 76) end end PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Execution options are good", self) # We are good to go! curCmd = @gblOpt[:cmd] strToSend = @gblOpt[:scpi_prefix] + @gblOpt[:cmd] if @gblOpt[:net_protocol] == :plgx then strToSend = strToSend + "\n" else strToSend = strToSend + @gblOpt[:eol] end if @gblOpt[:print_cmd] then PrintyPrintyBangBang.instance.outPrinter(">>#{strToSend.chomp.strip}>>", newline=true) end strWeGot = '' if @gblOpt[:net_protocol] == :file then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Opening file", self) begin strWeGot = open(curCmd, "rb").read(); rescue PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Unable to open file #{curCmd}!", self, 87) end if @gblOpt[:print_raw_result] then PrintyPrintyBangBang.instance.outPrinter(strWeGot, newline=false) end else if [:raw, :soip, :plgx].member?(@gblOpt[:net_protocol]) then if @tcpSocket.nil? then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Opening Socket", self) begin @tcpSocket = TCPSocket.open(@gblOpt[:ip_address], @gblOpt[:net_port]) rescue PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Unable to open TCP/IP socket!", self, 82) end if @gblOpt[:net_protocol] == :plgx then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Sending :plgx startup instructions", self) @tcpSocket.write("++mode 1\n") @tcpSocket.write("++savecfg 0\n") @tcpSocket.write("++auto 0\n") @tcpSocket.write("++eoi 1\n") # We only do these when we open the socket. Unfortunatly that means we miss :eol and :read_timeout_next_byte changes after socket open... BUG TODO @tcpSocket.write("++read_tmo_ms #{[50, @gblOpt[:read_timeout_first_byte]-50].max}\n") @tcpSocket.write("++eos " + ({"\0d\0a" => '0', "\0d" => '1', "\0a" => '2'}[@gblOpt[:eol]] || '3') + "\n") #@tcpSocket.write("++ifc\n") end end PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Sending Command: #{strToSend.inspect}", self) @tcpSocket.write(strToSend) if !(@gblOpt[:result_type].nil?) && (@gblOpt[:net_protocol] == :plgx) then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Sending :plgx ++read instruction.", self) @tcpSocket.write("++read eoi\n") end if @gblOpt[:socket_close_write] then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Closing TCP write connection.", self) @tcpSocket.close_write() end if !(@gblOpt[:result_type].nil?) then if @gblOpt[:delay_before_first_read] > 0 then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: DELAY: Pre-read delay_time = #{@gblOpt[:delay_before_first_read]}ms.", self) sleep(@gblOpt[:delay_before_first_read]/1000.0) end PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Read loop starting...", self) time_send = Time.now time_read = nil total_recvd = 0 loop do begin recvd_data = @tcpSocket.recv_nonblock(@gblOpt[:read_buffer_size]) strWeGot += recvd_data recvd_length = recvd_data.length() total_recvd += recvd_length if time_read.nil? then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Received #{recvd_length} bytes in first read.", self) else PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Received #{recvd_length} bytes in last read. Total of #{total_recvd} so far.", self) end time_read = Time.new if @gblOpt[:print_raw_result] then PrintyPrintyBangBang.instance.outPrinter(recvd_data, newline=false) end if recvd_length < 1 then if total_recvd == 0 then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Received no data. Transmission complete.", self) else PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Received nothing in last read. Total of #{total_recvd}. Transmission complete.", self) end break end if @gblOpt[:read_eot_sentinel] && recvd_data.include?(@gblOpt[:read_eot_sentinel]) then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Received #{total_recvd} bytes and EOT sentinel. Transmission complete.", self) break end if @gblOpt[:read_max_bytes] && (total_recvd >= @gblOpt[:read_max_bytes]) then if total_recvd == @gblOpt[:read_max_bytes] then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Received #{total_recvd}. As expected. Transmission complete.", self) else PrintyPrintyBangBang.instance.logPrinter(2, "WARNING: Received #{total_recvd}. Only expected #{@gblOpt[:read_max_bytes]}. Transmission complete.", self) end break end rescue IO::WaitReadable time_now = Time.now if time_read.nil? then delta = time_now - time_send if delta > 1 then PrintyPrintyBangBang.instance.logPrinter(2, "WARNING: Instrument has not responded in #{delta} seconds", self) else PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Instrument has not responded in #{delta} seconds", self) end if @gblOpt[:read_timeout_first_byte] < (delta*1000) then PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Giving up due to first byte timeout", self, nil); break else if @gblOpt[:read_retry_delay] > 0 then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: DELAY: io_retry_delay = #{@gblOpt[:delay_before_first_read]}ms.", self) sleep(@gblOpt[:read_retry_delay]/1000.0) end end else delta = time_now-time_read if delta > 1 then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Received #{total_recvd} bytes. No new data received for #{delta} seconds", self) else PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Received #{total_recvd} bytes. No new data received for #{delta} seconds", self) end if @gblOpt[:read_timeout_next_byte] < (1000*delta) then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Received #{total_recvd} bytes. Assuming no more data to receive.", self); break else if @gblOpt[:read_retry_delay] > 0 then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: DELAY: io_retry_delay = #{@gblOpt[:delay_before_first_read]}ms.", self) sleep(@gblOpt[:read_retry_delay]/1000.0) end end end retry end end else PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Not waiting for response from instrument (:result_type nil).", self) end if @gblOpt[:socket_close] || @gblOpt[:socket_close_write] then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Closing TCP connection.", self) @tcpSocket.close() @tcpSocket = nil end elsif @gblOpt[:net_protocol] == :t3k then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Sending Command: #{strToSend.inspect}", self) res = Net::HTTP.post_form(URI.parse("http://#{@gblOpt[:ip_address]}:#{@gblOpt[:net_port]}/Comm.html"), { 'COMMAND' => strToSend }) if !(res.nil?) then if !(@gblOpt[:result_type].nil?) then strWeGot = res.body.sub(/^.*NAME="name">/m, '').sub(/<\/TEXTAREA.*$/m, '') if strWeGot.length > 0 then if @gblOpt[:result_type] && @gblOpt[:print_raw_result] then PrintyPrintyBangBang.instance.outPrinter(strWeGot, newline=false) end end end else PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: unknown protocol: #{@gblOpt[:net_protocol].inspect}", self, 77) end end end time_exe_done = Time.now # Command execution is complete PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Raw result string: #{strWeGot[0..100].inspect}", self) PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Execution time: #{time_exe_done - time_exe_start}", self) thingyWeReturn = strWeGot if (@gblOpt[:result_extract_block]) && (thingyWeReturn.length >= 2) && (thingyWeReturn[0] == '#') then blockHeadLen = thingyWeReturn[1, 1].to_i(16) if (blockHeadLen.zero?) then thingyWeReturn = thingyWeReturn.slice(2) else blockBodyLen = thingyWeReturn[2, blockHeadLen].to_i() thingyWeReturn = thingyWeReturn[blockHeadLen+2, blockBodyLen] end end case @gblOpt[:result_split] when :char thingyWeReturn = thingyWeReturn.split(Regexp.new("[#{@gblOpt[:result_split_arg]}]", Regexp::EXTENDED | Regexp::MULTILINE)) when :regex thingyWeReturn = thingyWeReturn.split(Regexp.new(@gblOpt[:result_split_arg], Regexp::EXTENDED | Regexp::MULTILINE)) when :space thingyWeReturn = thingyWeReturn.split(@re.a(:split_space)) when :line thingyWeReturn = thingyWeReturn.split(@re.a(:split_line)) when :csv thingyWeReturn = thingyWeReturn.split(@re.a(:split_csv)) when :ssv thingyWeReturn = thingyWeReturn.split(@re.a(:split_ssv)) when :string thingyWeReturn = thingyWeReturn.split(@gblOpt[:result_split_arg]) when :unpack thingyWeReturn = thingyWeReturn.unpack(@gblOpt[:result_split_arg]) when :block if (thingyWeReturn.length >= 2) && (thingyWeReturn[0] == '#') then blockHeadLen = thingyWeReturn[1, 1].to_i(16) if blockHeadLen.zero? then thingyWeReturn = [ thingyWeReturn[0, 2], thingyWeReturn.slice(2) ] else blockBodyLen = thingyWeReturn[2, blockHeadLen].to_i() thingyWeReturn = [ thingyWeReturn[0, blockHeadLen+2], thingyWeReturn[blockHeadLen+2, blockBodyLen] ] end else thingyWeReturn = [ "", thingyWeReturn ] end else thingyWeReturn = [ thingyWeReturn ] end if @gblOpt[:result_squeeze] then thingyWeReturn = thingyWeReturn.map { |x| (x.is_a?(String) ? x.gsub(@re.a(:split_space)) { |m| (m.match?(@re.a(:split_line)) ? "\n" : " "); } : x) } end if @gblOpt[:result_chomp] then thingyWeReturn = thingyWeReturn.map { |x| (x.is_a?(String) ? x.chomp : x) } end if @gblOpt[:result_strip] then thingyWeReturn = thingyWeReturn.map { |x| (x.is_a?(String) ? x.strip : x) } end if @gblOpt[:result_last_word] then thingyWeReturn = thingyWeReturn.map { |x| (x.is_a?(String) ? x.rstrip.sub(/^.* /, '') : x) } end if @gblOpt[:result_type] != :mixed then # It can't be nil if we get to this line case @gblOpt[:result_type] when :string thingyWeReturn = thingyWeReturn.map(&:to_s) when :float thingyWeReturn = thingyWeReturn.map { |x| ((x.is_a?(String) || x.is_a?(Numeric)) ? x.to_f : x) } when :int # TODO: Add support for hex, oct, & binary integers thingyWeReturn = thingyWeReturn.map { |x| ((x.is_a?(String) || x.is_a?(Numeric)) ? x.to_f : x) } when :bool thingyWeReturn = thingyWeReturn.map { |x| (x.is_a?(String) ? (x.strip.match?(@re.a(:o488_TRUEx)) ? true : (x.strip.match?(@re.a(:o488_FALSEx)) ? false : nil)) : x) } end end if !(@gblOpt[:result_split]) then # If we didn't split, then we return a scaler thingyWeReturn = thingyWeReturn.first end PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Result we return: #{thingyWeReturn.to_s[0..100].inspect}", self) @results.push( { :name => @gblOpt[:name], :cmd_str => (@gblOpt[:store_cmd] ? strToSend : nil), :ret_val => (@gblOpt[:store_results] ? thingyWeReturn : nil), :raw_str => (@gblOpt[:store_raw_results] ? strWeGot : nil) }) if @gblOpt[:store_last_in_ans] then @varList['ANS'] = thingyWeReturn end if @gblOpt[:store_named_in_var] && @gblOpt[:name] then @varList[@gblOpt[:name]] = thingyWeReturn end if @gblOpt[:result_type] && @gblOpt[:print_result] then PrintyPrintyBangBang.instance.outPrinter(thingyWeReturn.to_s, newline=false) end if @gblOpt[:delay_after_complete] > 0 then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: DELAY: between_timeout = #{@gblOpt[:delay_after_complete]}ms.", self) sleep(@gblOpt[:delay_after_complete]/1000.0) end return thingyWeReturn end
Return a new string with constructs like ${variable_name} & ${variable_name:default_value} expanded into the value of the variable(s).
# File src/mrSCPI.rb, line 668 def expand (instr) expCnt = 0 instr.gsub!(@re.a(:mrs_varexp)) do |m| varName = $1 varDefault = $2 expCnt += 1 if @varList.member?(varName) then next @varList[varName] else if varDefault then next varDefault else PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Unknown variable (#{varName}) without default value!", self, 88) next s end end end return instr end
After execution SCPI commands have several data elements stored, and this method provides access to that data.
index
-
Specify SCPI command for which to return data.
-
An integer: Return the result at the given array index. Some notes:
-
This option is fast at O(1).
-
-1
is the last SCPI command -
0
is the first one.
-
-
A string: Return an array of all results with a matching
:name
ordered by index. Some notes:-
An empty array is returned if no matches are found.
-
This option is slow at O(n) – n # results.
-
The option
:store_named_in_var
and thevariable
method are much faster; however, this only gets tht last result with the given name.
-
type
-
A symbol specifing the data to return. The symbol must be one of:
:all
-
A hash with all data
:name
-
String with the command name/variable
:cmd_str
-
String sent to the instrument
:ret_val
-
Processed result from the command
:raw_str
-
String received from the instrument
# File src/mrSCPI.rb, line 974 def result(index: -1, type: :ret_val) if index.instance_of?(String) then ret = @results.find_all { |x| x[:name] == index } if type == :all then return ret.clone else if !(ret.empty?) then return ret.map { |x| x[type] }.clone else return ret.clone end end else if type == :all then return @results[index].clone else return @results[index][type] end end end
Set state parameters – see the parameter list in the main documentation for the SCPIsession
class.
# File src/mrSCPI.rb, line 519 def set(options=Hash.new) objectUnderConstruction = @gblOpt.empty? #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # Take care of pseudo-option :good_std_eot if options.member?(:good_std_eot) then if options[:good_std_eot] options[:read_eot_sentinel] = "\n" else options[:read_eot_sentinel] = nil end options.delete(:good_std_eot) end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # Take care of pseudo-options :result_macro_* { :result_macro_bin => { :result_extract_block => false, :result_split => nil, :result_chomp => false, :result_strip => false, :result_last_word => false, :result_type => :string, :print_max_len => nil, :print_debug => false }, :result_macro_block => { :result_extract_block => true, :result_split => nil, :result_chomp => false, :result_strip => false, :result_last_word => false, :result_type => :string, :print_max_len => nil, :print_debug => false }, :result_macro_ascii => { :result_extract_block => false, :result_split => nil, :result_chomp => true, :result_strip => true, :result_last_word => false, :result_type => :string, :print_max_len => nil, :print_debug => false }, :result_macro_debug => { :result_extract_block => false, :result_split => nil, :result_chomp => true, :result_strip => true, :result_last_word => false, :result_type => :string, :print_max_len => 1024, :print_debug => true }, :result_macro_csv => { :result_extract_block => false, :result_split => :csv, :result_chomp => true, :result_strip => true, :result_last_word => false, :result_type => :string, :print_max_len => nil, :print_debug => false }, }.each do |macro_sym, macro_options| if options.member?(macro_sym) then macro_options.each do |macro_opt_sym, macro_opt_val| options[macro_opt_sym] = macro_opt_val end options.delete(macro_sym) end end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # Take care of pseudo-options for PrintyPrintyBangBang PrintyPrintyBangBang.instance.set(options) #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # Take care of pseudo-option :echo if options.member?(:echo) then options[:print_cmd] = options[:echo] options[:print_result] = options[:echo] options.delete(:echo) end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # Take care of pseudo-option :url -> zap it and expand it into :net_protocol, :ip_address, :net_port if options.member?(:url) then urlBits = SCPIrcFile.instance.urlParser(options[:url]) if !(urlBits.nil?) then urlBits.each do |k, v| if !(v.nil?) then if (options.member?(k)) then PrintyPrintyBangBang.instance.logPrinter(3, "INFO: Explicit #{k.inspect} option (#{options[k].inspect}) overrode value in URL: #{options[:url].inspect}", self) else options[k] = v; end end end end options.delete(:url) end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # Set values provided in arguments options.each do |k, v| if @gblOptReqNew.member?(k) || @gblOptReqRun.member?(k) || @gblOptReqPsu.member?(k) then if !(objectUnderConstruction) && @gblOptReqNew.member?(k) then PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Option can't be set post new: #{k.inspect}", self, 71) end @gblOpt[k] = v else PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Unknown Session Option: #{k.inspect} => #{v.inspect}", self, 72) end end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # If we are constructing the object, then work harder if objectUnderConstruction then if !(@gblOpt.member?(:net_protocol)) then @gblOpt[:net_protocol] = :raw end # Apply rules to set unset options { :net_port => {:file=> nil, :raw=> 5025, :soip=> nil, :plgx=> 1234, :lxi => nil, :t3k=> 80}, :delay_after_complete => {:file=> 0, :raw=> 0, :soip=> 100, :plgx=> 50, :lxi => 0, :t3k=> 200}, :delay_before_first_read => {:file=> 0, :raw=> 100, :soip=> 50, :plgx=> 10, :lxi => 0, :t3k=> 0}, :read_retry_delay => {:file=> 0, :raw=> 200, :soip=> 200, :plgx=> 100, :lxi => 0, :t3k=> 0}, :read_timeout_first_byte => {:file=> 0, :raw=> 1000, :soip=> 500, :plgx=> 600, :lxi => 0, :t3k=> 0}, :read_timeout_next_byte => {:file=> 0, :raw=> 2000, :soip=> 100, :plgx=> 50, :lxi => 0, :t3k=> 0} }.each do |k, defs| if !(@gblOpt.member?(k)) || @gblOpt[k].nil? then @gblOpt[k] = defs[@gblOpt[:net_protocol]] if @gblOpt[k].nil? then PrintyPrintyBangBang.instance.logPrinter(2, "WARNING: Could not set #{k} to a default value!", self) else PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Successfully set #{k} to a default value!", self) end end end # Make sure all options required by new are set and valid @gblOptReqNew.each do |k, validator| if !(validator.call(@gblOpt[k])) then PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Option required by new is invalid: #{k} = #{@gblOpt[k].inspect}!", self, 73) end end if [:lxi].member?(@gblOpt[:net_protocol]) then # (TODO: Future Feature) PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: :lxi is not currently supported -- it will be someday!!", self, 86) end PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Object fully constructed.", self) else end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- @gblOpt.keys.sort.each do |k| PrintyPrintyBangBang.instance.logPrinter(8, "DEBUG-8: @gblOpt: #{(k.to_s+' ').ljust(25, '.')} #{@gblOpt[k].inspect}", self) end #--------------------------------------------------------------------------------------------------------------------------------------------------------------- # If we got a :cmd & :execute_on_cmd is on, then we attempt to execute if options.member?(:cmd) && @gblOpt[:execute_on_cmd] then PrintyPrintyBangBang.instance.logPrinter(4, "DEBUG-4: Attempting automatic execution", self) return execute() end end
Access, or set when value
is non-nil
, a SCPIsession
variable.
# File src/mrSCPI.rb, line 653 def variable (name, value=nil) if value.nil? then if @varList.member?(name) then return @varList[name] else PrintyPrintyBangBang.instance.logPrinter(1, "ERROR: Result not found with given name: #{name.inspect}!", self, 75) end else @varList[name] = value return value end end