class DmmUtil::Fluke28xDriver

Constants

MPQ_PROPS
MP_PROPS

Public Class Methods

new(port) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 24
def initialize(port)
  @port = port
  @map_cache = {}
end

Public Instance Methods

data_ok?(data, count) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 313
def data_ok?(data, count)      
  # No status code yet
  return false if data.size < 2
  
  # Non-OK status
  return true if data.size == 2 && data[0,1] != "0" && data[1,1] == "\r"
  
  # Non-OK status with extra data on end
  raise MeterError.new("Error parsing status from meter (Non-OK status with extra data on end)") if data.size > 2 && data[0,1] != "0"
  
  # We should now be in OK state
  raise MeterError.new("Error parsing status from meter (status:#{data[0,1]} size:#{data.size})") unless data[0,1] == "0" && data[1,1] == "\r"
  
  if count
    data.size == count
  else
    data.size >= 4 && data[-1,1] == "\r"
  end
  
end
do_min_max_cmd(cmd, idx) click to toggle source

Low level stuff ##########

# File lib/dmm_util/fluke28x_driver.rb, line 244
def do_min_max_cmd(cmd, idx)
  res = meter_command("#{cmd} #{idx}")
  # un8 = 0, un2 = 0, always bolt
  reading_count = get_u16(res, 52)
  raise MeterError.new("qsmmsi parse error, expected at least #{reading_count * 30 + 54} bytes, got #{res.size}") unless res.size >= reading_count * 30 + 54
  
  # All bytes parsed
  { :seq_no => get_u16(res, 0),
    :un2 => get_u16(res, 2),      # High byte of seq no?
    :ts1 => parse_time(get_double(res, 4)),
    :ts2 => parse_time(get_double(res, 12)),
    :prim_function => get_map_value(:primfunction, res, 20),
    :sec_function => get_map_value(:secfunction, res, 22),
    :autorange => get_map_value(:autorange, res, 24),
    :unit => get_map_value(:unit, res, 26),
    :range_max  => get_double(res, 28),
    :unit_multiplier => get_s16(res, 36),
    :bolt => get_map_value(:bolt, res, 38),
    :ts3 => parse_time(get_double(res, 40)),
    :mode => get_multimap_value(:mode, res, 48), 
    :un8 => get_u16(res, 50),
    # 52 is reading_count
    :readings => parse_readings(res[54, reading_count * 30]),
    :name => res[(54 + reading_count * 30)..-1]
    }
end
get_map_value(map_name, str, offset) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 291
def get_map_value(map_name, str, offset)
  map = @map_cache[map_name.to_sym] ||= qemap(map_name)
  value = get_u16(str, offset)
  raise MeterError.new("Can not find key #{value} in map #{map_name}") unless map.has_key?(value)
  map[value]
end
get_multimap_value(map_name, str, offset) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 298
def get_multimap_value(map_name, str, offset)
  map = @map_cache[map_name.to_sym] ||= qemap(map_name)
  value = get_u16(str, offset)
  check = 0
  ret = []
  map.keys.sort.each do |key|
    if (value & key) != 0
      ret << map[key]
      check |= key
    end
  end
  raise MeterError.new("Can not find key #{value} in map #{map_name}") unless check == value
  ret
end
id() click to toggle source

Fluke28xDrivercommands ##################

# File lib/dmm_util/fluke28x_driver.rb, line 44
def id
  res = meter_command("ID") 
  {:model_number => res[0], :software_version => res[1], :serial_number => res[2]}      
end
meter_command(cmd, expected_result = nil) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 347
def meter_command(cmd, expected_result = nil)
  @port.write "#{cmd}\r"
  data = read_retry(expected_result ? (expected_result + 2) : nil )
  status = data[0,1]
  raise MeterError.new("Command returned error code #{status}", Integer(status)) unless status == "0"
  raise MeterError.new("Did not receive complete reply from meter") unless data[-1,1] == "\r"
  
  binary = (data[2,2] == "#0")
  
  if binary
    data[4..-2]
  else
    tokens = []
    state = :init
    current_token = []
    data[2..-2].each_byte do |b|
      c = b.chr
      case state
      when :init
        case c
        when ","
          tokens << current_token.join
          current_token = []
        when "'"
          raise MeterError.new("Unexpected quote") unless current_token.empty?
          state = :sq
        when '"'
          raise MeterError.new("Unexpected double-quote") unless current_token.empty?
          state = :dq
        else
          current_token << c
        end
      when :sq
        case c
        when "'"
          state = :sqe
        else
          current_token << c
        end
      when :sqe
        case c
        when "'"
          current_token << c
          state = :sq
        when ","
          tokens << current_token.join
          current_token = []
          state = :init
        else
          raise MeterError.new("Expected comma after single-quoted string")
        end
      when :dq
        case c
        when '"'
          state = :dqe
        else
          current_token << c
        end
      when :dqe
        case c
        when ","
          tokens << current_token.join
          current_token = []
          state = :init
        else
          raise MeterError.new("Expected comma after double-quoted string")
        end
      else
        raise MeterError.new("Invalid parser state")
      end
    end

    raise MeterError.new("Did not find end of string") unless [:init, :sqe, :dqe].include?(state)
    tokens << current_token.join 
    
  end
rescue MeterError => e
  if e.status == 8
    retry
  else
    raise
  end  
end
parse_readings(reading_bytes) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 271
def parse_readings(reading_bytes)
  readings = {}
  ByteStr.new(reading_bytes).each_slice(30) do |reading_arr|
    r = reading_arr.map{|b| b.chr}.join
    # All bytes parsed
    readings[get_map_value(:readingid, r, 0)] = {
                           :value => get_double(r, 2),
                           :unit => get_map_value(:unit, r, 10),
                           :unit_multiplier => get_s16(r, 12),
                           :decimals => get_s16(r, 14),
                           :display_digits => get_s16(r, 16), 
                           :state => get_map_value(:state, r, 18),
                           :attribute => get_map_value(:attribute, r, 20),
                           :ts => get_time(r, 22)
    }
  end
  readings
end
qdda() click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 49
def qdda
  res = meter_command("qdda")
  
  mode_count = Integer(res[8])
  reading_count = Integer(res[9 + mode_count])
  raise DmmUtil::MeterError("Error parsing qdda response") unless res.size == 10 + mode_count + reading_count * 9
  
  reading_map = {}
  res[(10 + mode_count)..-1].each_slice(9) do |reading|
    reading_map[reading[0]] = {:value => Float(reading[1]), 
                               :unit => reading[2], :unit_multiplier => Integer(reading[3]), 
                               :decimals => reading[4].to_i,
                               :state => reading[6], :ts => parse_time(reading[8].to_f),
                               :display_digits => Integer(reading[5]), :attribute => reading[7]}
   end
  
  {:prim_function => res[0], :sec_function => res[1], :mode => res[9,mode_count], 
   :auto_range => res[2], :range_max => Integer(res[4]),
   :unit => res[3], :unit_multiplier => Integer(res[5]),
   :bolt => res[6], :ts => (res[7].to_i == 0 ? nil : parse_time(Float(res[7]))),
   :readings => reading_map}
end
qddb() click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 72
def qddb
  bytes = meter_command("qddb")
  
  reading_count = get_u16(bytes, 32)
  raise MeterError.new("qddb parse error, expected #{reading_count * 30 + 34} bytes, got #{bytes.size}") unless bytes.size == reading_count * 30 + 34
  tsval = get_double(bytes, 20)
  # all bytes parsed
  {
    :prim_function => get_map_value(:primfunction, bytes, 0), 
    :sec_function => get_map_value(:secfunction, bytes, 2),
    :auto_range => get_map_value(:autorange, bytes, 4),
    :unit => get_map_value(:unit, bytes, 6),
    :range_max => get_double(bytes, 8),
    :unit_multiplier => get_s16(bytes, 16),
    :bolt => get_map_value(:bolt, bytes, 18),
    :ts => (tsval < 0.1) ? nil : parse_time(tsval), # 20
    :mode => get_multimap_value(:mode, bytes, 28),
    :un1 => get_u16(bytes, 30),
    # 32 is reading count
    :readings => parse_readings(bytes[34 .. -1])
  }
end
qemap(map_name) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 194
def qemap(map_name)
  res = meter_command("qemap #{map_name.to_s}")
  entry_count = Integer(res.shift)
  raise MeterError.new("Error parsing qemap") unless res.size == entry_count * 2
  
  map = {}
  res.each_slice(2) do |key, val|
    map[Integer(key)] = val
  end
  map
end
qmmsi(idx) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 129
def qmmsi(idx)
  # Get min/max
  do_min_max_cmd("qmmsi", idx)
end
qpsi(idx) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 134
def qpsi(idx)
  # Recorded peak
  do_min_max_cmd("qpsi", idx)
end
qrsi(idx) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 139
def qrsi(idx)
  # Recorded session info
  res = meter_command("qrsi #{idx}")
  reading_count = get_u16(res, 76)
  raise MeterError.new("qrsi parse error, expected at least #{reading_count * 30 + 78} bytes, got #{res.size}") unless res.size >= reading_count * 30 + 78
  
  # All bytes parsed
  {
    :seq_no => get_u16(res, 0),
    :un2 => get_u16(res, 2),   # 32 bits?
    :start_ts => parse_time(get_double(res, 4)),
    :end_ts => parse_time(get_double(res, 12)),
    :sample_interval => get_double(res, 20),
    :event_threshold => get_double(res, 28),
    :reading_index => get_u16(res, 36), # 32 bits?
    :un3 => get_u16(res, 38),
    :num_samples => get_u16(res, 40),  # Is this 32 bits? Whats in 42
    :un4 => get_u16(res, 42),
    :prim_function => get_map_value(:primfunction, res, 44), # prim?
    :sec_function => get_map_value(:secfunction, res, 46), # sec?
    :auto_range => get_map_value(:autorange, res, 48),
    :unit => get_map_value(:unit, res, 50),
    :range_max  => get_double(res, 52),
    :unit_multiplier => get_s16(res, 60),
    :bolt => get_map_value(:bolt, res, 62),  #bolt?
    :un8 => get_u16(res, 64),  #ts3?
    :un9 => get_u16(res, 66),  #ts3?
    :un10 => get_u16(res, 68),  #ts3?
    :un11 => get_u16(res, 70),  #ts3?
    :mode => get_multimap_value(:mode, res, 72),
    :un12 => get_u16(res, 74),
    # 76 is reading count
    :readings => parse_readings(res[78, reading_count * 30]),
    :name => res[(78 + reading_count * 30)..-1]
  }
end
qsls() click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 95
def qsls
  res = meter_command("qsls")
  {:recording => Integer(res[0]), :min_max => Integer(res[1]), 
   :peak => Integer(res[2]), :measurement => Integer(res[3])}
end
qsmr(idx) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 101
def qsmr(idx)
  # Get saved measurement
  res = meter_command("qsmr #{idx}")

  reading_count = get_u16(res, 36)
  raise MeterError.new("qsmr parse error, expected at least #{reading_count * 30 + 38} bytes, got #{res.size}") unless res.size >= reading_count * 30 + 38
  
  { :seq_no => get_u16(res,0),
    :un1 => get_u16(res,2),   # 32 bit?
    :prim_function =>  get_map_value(:primfunction, res,4), # prim?
    :sec_function => get_map_value(:secfunction, res,6), # sec?
    :auto_range => get_map_value(:autorange, res, 8),
    :unit => get_map_value(:unit, res, 10),
    :range_max => get_double(res, 12),
    :unit_multiplier => get_s16(res, 20),
    :bolt => get_map_value(:bolt, res, 22),
    :un4 => get_u16(res,24),  # ts?
    :un5 => get_u16(res,26),
    :un6 => get_u16(res,28),
    :un7 => get_u16(res,30),
    :mode => get_multimap_value(:mode, res,32),
    :un9 => get_u16(res,34),
    # 36 is reading count
    :readings => parse_readings(res[38, reading_count * 30]),
    :name => res[(38 + reading_count * 30)..-1],
  }
end
qsrr(reading_idx, sample_idx) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 176
def qsrr(reading_idx, sample_idx)
  res = meter_command("qsrr #{reading_idx},#{sample_idx}", 149)

  raise  MeterError.new("Invalid block size: #{res.size} should be 146") unless res.size == 146
  # All bytes parsed - except there seems to be single byte at end?
  {
    :start_ts =>  parse_time(get_double(res, 0)),
    :end_ts =>  parse_time(get_double(res, 8)),
    :readings => parse_readings(res[16, 30*3]),
    :duration => get_u16(res, 106) * 0.1,
    :un2 => get_u16(res, 108),
    :readings2 => parse_readings(res[110,30]),
    :record_type =>  get_map_value(:recordtype, res, 140),
    :stable   => get_map_value(:isstableflag, res, 142), 
    :transient_state => get_map_value(:transientstate, res, 144)
  }      
end
read_retry(count) click to toggle source
# File lib/dmm_util/fluke28x_driver.rb, line 334
def read_retry(count)
  retry_count = 0
  data = ""
  
  while retry_count < 500 && !data_ok?(data, count)
    data += @port.read
    return data if data_ok?(data, count)
    sleep 0.01
    retry_count += 1
  end
  raise MeterError.new("Error parsing status from meter: #{data[0,1]} #{data.size} #{data[1,1] == "\r"} #{data[-1,1] == "\r"}")
end
valid?() click to toggle source

High level commands ################

# File lib/dmm_util/fluke28x_driver.rb, line 31
def valid?
  3.times do  
    begin
      res = self.id
      return true if res[:model_number] && res[:software_version] && res[:serial_number]
    rescue MeterError
    end
  end
  return false
end