dmm_util 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/dmm_util +127 -0
- data/dmm_util.gemspec +84 -0
- data/lib/dmm_util/cursor.rb +31 -0
- data/lib/dmm_util/fluke28x_driver.rb +440 -0
- data/lib/dmm_util/format_convertors.rb +46 -0
- data/lib/dmm_util/measurement.rb +64 -0
- data/lib/dmm_util/meter.rb +39 -0
- data/lib/dmm_util/reading.rb +64 -0
- data/lib/dmm_util/recording.rb +35 -0
- data/lib/dmm_util/recording_measurement.rb +62 -0
- data/lib/dmm_util/recording_measurement_cursor.rb +26 -0
- data/lib/dmm_util.rb +36 -0
- data/test/communication_test.rb +183 -0
- data/test/dmm_command_test.rb +489 -0
- data/test/format_convertors_test.rb +61 -0
- data/test/integration_itest.rb +290 -0
- data/test/measurement_test.rb +57 -0
- data/test/meter_test.rb +178 -0
- data/test/reading_test.rb +48 -0
- data/test/recording_measurement_test.rb +55 -0
- data/test/recording_test.rb +75 -0
- data/test/test_helper.rb +176 -0
- metadata +181 -0
@@ -0,0 +1,440 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'serialport'
|
3
|
+
require 'enumerator'
|
4
|
+
|
5
|
+
### It appears thwe are 32 bit vals:
|
6
|
+
## Mulit-map values
|
7
|
+
# Sequence numbers
|
8
|
+
# duration
|
9
|
+
|
10
|
+
module DmmUtil
|
11
|
+
class Fluke28xDriver
|
12
|
+
MPQ_PROPS = [:company, :contact, :operator, :site]
|
13
|
+
MP_PROPS = {:dcpol => [:pos, :neg], :rsm => [:off, :on], :si => [:off, :on],
|
14
|
+
:lang => [:japanese, :english, :chinese, :german, :french, :spanish, :italian],
|
15
|
+
:apoffto => [2700, 0, 3600, 900, 1500, 2100], :hzedge => [:rising, :falling], :lcdcont => :int, :acsmooth => [:off, :on],
|
16
|
+
:numfmt => [:point, :comma], :beeper => [:off, :on], :pwpol => [:pos, :neg], :aheventth => :int,
|
17
|
+
:timefmt => [12, 24], :cusdbm => :int, :dbmref => [16, 0, 600, 50, 8, 25, 75, 4, 1000, 32],
|
18
|
+
:receventth => [5, 0, 1, 25, 20, 15, 4, 10], :contbeepos => [:short, :open], :digits => [5, 4], :tempos => :int,
|
19
|
+
:clock => :timeval, :contbeep => [:off, :on], :ablto => [0, 600, 1200, 1800, 300, 900, 1500],
|
20
|
+
:tempunit => [:c, :f], :datefmt => [:mm_dd, :dd_mm]}
|
21
|
+
|
22
|
+
include FormatConvertors
|
23
|
+
|
24
|
+
def initialize(port)
|
25
|
+
@port = port
|
26
|
+
@map_cache = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
########## High level commands ################
|
30
|
+
|
31
|
+
def valid?
|
32
|
+
3.times do
|
33
|
+
begin
|
34
|
+
res = self.id
|
35
|
+
return true if res[:model_number] && res[:software_version] && res[:serial_number]
|
36
|
+
rescue MeterError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return false
|
40
|
+
end
|
41
|
+
|
42
|
+
########## Fluke28xDrivercommands ##################
|
43
|
+
|
44
|
+
def id
|
45
|
+
res = meter_command("ID")
|
46
|
+
{:model_number => res[0], :software_version => res[1], :serial_number => res[2]}
|
47
|
+
end
|
48
|
+
|
49
|
+
def qdda
|
50
|
+
res = meter_command("qdda")
|
51
|
+
|
52
|
+
mode_count = Integer(res[8])
|
53
|
+
reading_count = Integer(res[9 + mode_count])
|
54
|
+
raise DmmUtil::MeterError("Error parsing qdda response") unless res.size == 10 + mode_count + reading_count * 9
|
55
|
+
|
56
|
+
reading_map = {}
|
57
|
+
res[(10 + mode_count)..-1].each_slice(9) do |reading|
|
58
|
+
reading_map[reading[0]] = {:value => Float(reading[1]),
|
59
|
+
:unit => reading[2], :unit_multiplier => Integer(reading[3]),
|
60
|
+
:decimals => reading[4].to_i,
|
61
|
+
:state => reading[6], :ts => parse_time(reading[8].to_f),
|
62
|
+
:display_digits => Integer(reading[5]), :attribute => reading[7]}
|
63
|
+
end
|
64
|
+
|
65
|
+
{:prim_function => res[0], :sec_function => res[1], :mode => res[9,mode_count],
|
66
|
+
:auto_range => res[2], :range_max => Integer(res[4]),
|
67
|
+
:unit => res[3], :unit_multiplier => Integer(res[5]),
|
68
|
+
:bolt => res[6], :ts => (res[7].to_i == 0 ? nil : parse_time(Float(res[7]))),
|
69
|
+
:readings => reading_map}
|
70
|
+
end
|
71
|
+
|
72
|
+
def qddb
|
73
|
+
bytes = meter_command("qddb")
|
74
|
+
|
75
|
+
reading_count = get_u16(bytes, 32)
|
76
|
+
raise MeterError.new("qddb parse error, expected #{reading_count * 30 + 34} bytes, got #{bytes.size}") unless bytes.size == reading_count * 30 + 34
|
77
|
+
tsval = get_double(bytes, 20)
|
78
|
+
# all bytes parsed
|
79
|
+
{
|
80
|
+
:prim_function => get_map_value(:primfunction, bytes, 0),
|
81
|
+
:sec_function => get_map_value(:secfunction, bytes, 2),
|
82
|
+
:auto_range => get_map_value(:autorange, bytes, 4),
|
83
|
+
:unit => get_map_value(:unit, bytes, 6),
|
84
|
+
:range_max => get_double(bytes, 8),
|
85
|
+
:unit_multiplier => get_s16(bytes, 16),
|
86
|
+
:bolt => get_map_value(:bolt, bytes, 18),
|
87
|
+
:ts => (tsval < 0.1) ? nil : parse_time(tsval), # 20
|
88
|
+
:mode => get_multimap_value(:mode, bytes, 28),
|
89
|
+
:un1 => get_u16(bytes, 30),
|
90
|
+
# 32 is reading count
|
91
|
+
:readings => parse_readings(bytes[34 .. -1])
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def qsls
|
96
|
+
res = meter_command("qsls")
|
97
|
+
{:recording => Integer(res[0]), :min_max => Integer(res[1]),
|
98
|
+
:peak => Integer(res[2]), :measurement => Integer(res[3])}
|
99
|
+
end
|
100
|
+
|
101
|
+
def qsmr(idx)
|
102
|
+
# Get saved measurement
|
103
|
+
res = meter_command("qsmr #{idx}")
|
104
|
+
|
105
|
+
reading_count = get_u16(res, 36)
|
106
|
+
raise MeterError.new("qsmr parse error, expected at least #{reading_count * 30 + 38} bytes, got #{res.size}") unless res.size >= reading_count * 30 + 38
|
107
|
+
|
108
|
+
{ :seq_no => get_u16(res,0),
|
109
|
+
:un1 => get_u16(res,2), # 32 bit?
|
110
|
+
:prim_function => get_map_value(:primfunction, res,4), # prim?
|
111
|
+
:sec_function => get_map_value(:secfunction, res,6), # sec?
|
112
|
+
:auto_range => get_map_value(:autorange, res, 8),
|
113
|
+
:unit => get_map_value(:unit, res, 10),
|
114
|
+
:range_max => get_double(res, 12),
|
115
|
+
:unit_multiplier => get_s16(res, 20),
|
116
|
+
:bolt => get_map_value(:bolt, res, 22),
|
117
|
+
:un4 => get_u16(res,24), # ts?
|
118
|
+
:un5 => get_u16(res,26),
|
119
|
+
:un6 => get_u16(res,28),
|
120
|
+
:un7 => get_u16(res,30),
|
121
|
+
:mode => get_multimap_value(:mode, res,32),
|
122
|
+
:un9 => get_u16(res,34),
|
123
|
+
# 36 is reading count
|
124
|
+
:readings => parse_readings(res[38, reading_count * 30]),
|
125
|
+
:name => res[(38 + reading_count * 30)..-1],
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
def qmmsi(idx)
|
130
|
+
# Get min/max
|
131
|
+
do_min_max_cmd("qmmsi", idx)
|
132
|
+
end
|
133
|
+
|
134
|
+
def qpsi(idx)
|
135
|
+
# Recorded peak
|
136
|
+
do_min_max_cmd("qpsi", idx)
|
137
|
+
end
|
138
|
+
|
139
|
+
def qrsi(idx)
|
140
|
+
# Recorded session info
|
141
|
+
res = meter_command("qrsi #{idx}")
|
142
|
+
reading_count = get_u16(res, 76)
|
143
|
+
raise MeterError.new("qrsi parse error, expected at least #{reading_count * 30 + 78} bytes, got #{res.size}") unless res.size >= reading_count * 30 + 78
|
144
|
+
|
145
|
+
# All bytes parsed
|
146
|
+
{
|
147
|
+
:seq_no => get_u16(res, 0),
|
148
|
+
:un2 => get_u16(res, 2), # 32 bits?
|
149
|
+
:start_ts => parse_time(get_double(res, 4)),
|
150
|
+
:end_ts => parse_time(get_double(res, 12)),
|
151
|
+
:sample_interval => get_double(res, 20),
|
152
|
+
:event_threshold => get_double(res, 28),
|
153
|
+
:reading_index => get_u16(res, 36), # 32 bits?
|
154
|
+
:un3 => get_u16(res, 38),
|
155
|
+
:num_samples => get_u16(res, 40), # Is this 32 bits? Whats in 42
|
156
|
+
:un4 => get_u16(res, 42),
|
157
|
+
:prim_function => get_map_value(:primfunction, res, 44), # prim?
|
158
|
+
:sec_function => get_map_value(:secfunction, res, 46), # sec?
|
159
|
+
:auto_range => get_map_value(:autorange, res, 48),
|
160
|
+
:unit => get_map_value(:unit, res, 50),
|
161
|
+
:range_max => get_double(res, 52),
|
162
|
+
:unit_multiplier => get_s16(res, 60),
|
163
|
+
:bolt => get_map_value(:bolt, res, 62), #bolt?
|
164
|
+
:un8 => get_u16(res, 64), #ts3?
|
165
|
+
:un9 => get_u16(res, 66), #ts3?
|
166
|
+
:un10 => get_u16(res, 68), #ts3?
|
167
|
+
:un11 => get_u16(res, 70), #ts3?
|
168
|
+
:mode => get_multimap_value(:mode, res, 72),
|
169
|
+
:un12 => get_u16(res, 74),
|
170
|
+
# 76 is reading count
|
171
|
+
:readings => parse_readings(res[78, reading_count * 30]),
|
172
|
+
:name => res[(78 + reading_count * 30)..-1]
|
173
|
+
}
|
174
|
+
end
|
175
|
+
|
176
|
+
def qsrr(reading_idx, sample_idx)
|
177
|
+
res = meter_command("qsrr #{reading_idx},#{sample_idx}", 149)
|
178
|
+
|
179
|
+
raise MeterError.new("Invalid block size: #{res.size} should be 146") unless res.size == 146
|
180
|
+
# All bytes parsed - except there seems to be single byte at end?
|
181
|
+
{
|
182
|
+
:start_ts => parse_time(get_double(res, 0)),
|
183
|
+
:end_ts => parse_time(get_double(res, 8)),
|
184
|
+
:readings => parse_readings(res[16, 30*3]),
|
185
|
+
:duration => get_u16(res, 106) * 0.1,
|
186
|
+
:un2 => get_u16(res, 108),
|
187
|
+
:readings2 => parse_readings(res[110,30]),
|
188
|
+
:record_type => get_map_value(:recordtype, res, 140),
|
189
|
+
:stable => get_map_value(:isstableflag, res, 142),
|
190
|
+
:transient_state => get_map_value(:transientstate, res, 144)
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
def qemap(map_name)
|
195
|
+
res = meter_command("qemap #{map_name.to_s}")
|
196
|
+
entry_count = Integer(res.shift)
|
197
|
+
raise MeterError.new("Error parsing qemap") unless res.size == entry_count * 2
|
198
|
+
|
199
|
+
map = {}
|
200
|
+
res.each_slice(2) do |key, val|
|
201
|
+
map[Integer(key)] = val
|
202
|
+
end
|
203
|
+
map
|
204
|
+
end
|
205
|
+
|
206
|
+
MPQ_PROPS.each do |mpq_prop|
|
207
|
+
define_method(mpq_prop) do
|
208
|
+
self.meter_command("qmpq #{mpq_prop}")[0]
|
209
|
+
end
|
210
|
+
|
211
|
+
define_method("#{mpq_prop}=") do |val|
|
212
|
+
self.meter_command("mpq #{mpq_prop},#{quote_str(val)}", 0)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
MP_PROPS.each do |mp_prop, format|
|
217
|
+
define_method(mp_prop) do
|
218
|
+
val = self.meter_command("qmp #{mp_prop}")[0]
|
219
|
+
if format == :int
|
220
|
+
val = Integer(val)
|
221
|
+
elsif format == :timeval
|
222
|
+
tz_offset = Time.now.utc_offset
|
223
|
+
val = Time.at(Integer(val) - tz_offset)
|
224
|
+
end
|
225
|
+
val
|
226
|
+
end
|
227
|
+
|
228
|
+
define_method("#{mp_prop}=") do |val|
|
229
|
+
if format.is_a?(Array)
|
230
|
+
raise MeterError.new("Illegal value '#{val}' for #{mp_prop}. Legal values: #{format.join(", ")}") unless format.include?(val)
|
231
|
+
elsif format == :int
|
232
|
+
raise MeterError.new("Illegal value '#{val}' for #{mp_prop}. Must be integer.") unless val.is_a?(Integer)
|
233
|
+
elsif format == :timeval
|
234
|
+
raise MeterError.new("Clock command requires a Time object.") unless val.is_a?(Time)
|
235
|
+
tz_offset = Time.now.utc_offset
|
236
|
+
val = val.to_i + tz_offset
|
237
|
+
end
|
238
|
+
self.meter_command("mp #{mp_prop},#{val}",0)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
########## Low level stuff ##########
|
244
|
+
def do_min_max_cmd(cmd, idx)
|
245
|
+
res = meter_command("#{cmd} #{idx}")
|
246
|
+
# un8 = 0, un2 = 0, always bolt
|
247
|
+
reading_count = get_u16(res, 52)
|
248
|
+
raise MeterError.new("qsmmsi parse error, expected at least #{reading_count * 30 + 54} bytes, got #{res.size}") unless res.size >= reading_count * 30 + 54
|
249
|
+
|
250
|
+
# All bytes parsed
|
251
|
+
{ :seq_no => get_u16(res, 0),
|
252
|
+
:un2 => get_u16(res, 2), # High byte of seq no?
|
253
|
+
:ts1 => parse_time(get_double(res, 4)),
|
254
|
+
:ts2 => parse_time(get_double(res, 12)),
|
255
|
+
:prim_function => get_map_value(:primfunction, res, 20),
|
256
|
+
:sec_function => get_map_value(:secfunction, res, 22),
|
257
|
+
:autorange => get_map_value(:autorange, res, 24),
|
258
|
+
:unit => get_map_value(:unit, res, 26),
|
259
|
+
:range_max => get_double(res, 28),
|
260
|
+
:unit_multiplier => get_s16(res, 36),
|
261
|
+
:bolt => get_map_value(:bolt, res, 38),
|
262
|
+
:ts3 => parse_time(get_double(res, 40)),
|
263
|
+
:mode => get_multimap_value(:mode, res, 48),
|
264
|
+
:un8 => get_u16(res, 50),
|
265
|
+
# 52 is reading_count
|
266
|
+
:readings => parse_readings(res[54, reading_count * 30]),
|
267
|
+
:name => res[(54 + reading_count * 30)..-1]
|
268
|
+
}
|
269
|
+
end
|
270
|
+
|
271
|
+
def parse_readings(reading_bytes)
|
272
|
+
readings = {}
|
273
|
+
ByteStr.new(reading_bytes).each_slice(30) do |reading_arr|
|
274
|
+
r = reading_arr.map{|b| b.chr}.join
|
275
|
+
# All bytes parsed
|
276
|
+
readings[get_map_value(:readingid, r, 0)] = {
|
277
|
+
:value => get_double(r, 2),
|
278
|
+
:unit => get_map_value(:unit, r, 10),
|
279
|
+
:unit_multiplier => get_s16(r, 12),
|
280
|
+
:decimals => get_s16(r, 14),
|
281
|
+
:display_digits => get_s16(r, 16),
|
282
|
+
:state => get_map_value(:state, r, 18),
|
283
|
+
:attribute => get_map_value(:attribute, r, 20),
|
284
|
+
:ts => get_time(r, 22)
|
285
|
+
}
|
286
|
+
end
|
287
|
+
readings
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
def get_map_value(map_name, str, offset)
|
292
|
+
map = @map_cache[map_name.to_sym] ||= qemap(map_name)
|
293
|
+
value = get_u16(str, offset)
|
294
|
+
raise MeterError.new("Can not find key #{value} in map #{map_name}") unless map.has_key?(value)
|
295
|
+
map[value]
|
296
|
+
end
|
297
|
+
|
298
|
+
def get_multimap_value(map_name, str, offset)
|
299
|
+
map = @map_cache[map_name.to_sym] ||= qemap(map_name)
|
300
|
+
value = get_u16(str, offset)
|
301
|
+
check = 0
|
302
|
+
ret = []
|
303
|
+
map.keys.sort.each do |key|
|
304
|
+
if (value & key) != 0
|
305
|
+
ret << map[key]
|
306
|
+
check |= key
|
307
|
+
end
|
308
|
+
end
|
309
|
+
raise MeterError.new("Can not find key #{value} in map #{map_name}") unless check == value
|
310
|
+
ret
|
311
|
+
end
|
312
|
+
|
313
|
+
def data_ok?(data, count)
|
314
|
+
# No status code yet
|
315
|
+
return false if data.size < 2
|
316
|
+
|
317
|
+
# Non-OK status
|
318
|
+
return true if data.size == 2 && data[0,1] != "0" && data[1,1] == "\r"
|
319
|
+
|
320
|
+
# Non-OK status with extra data on end
|
321
|
+
raise MeterError.new("Error parsing status from meter (Non-OK status with extra data on end)") if data.size > 2 && data[0,1] != "0"
|
322
|
+
|
323
|
+
# We should now be in OK state
|
324
|
+
raise MeterError.new("Error parsing status from meter (status:#{data[0,1]} size:#{data.size})") unless data[0,1] == "0" && data[1,1] == "\r"
|
325
|
+
|
326
|
+
if count
|
327
|
+
data.size == count
|
328
|
+
else
|
329
|
+
data.size >= 4 && data[-1,1] == "\r"
|
330
|
+
end
|
331
|
+
|
332
|
+
end
|
333
|
+
|
334
|
+
def read_retry(count)
|
335
|
+
retry_count = 0
|
336
|
+
data = ""
|
337
|
+
|
338
|
+
while retry_count < 500 && !data_ok?(data, count)
|
339
|
+
data += @port.read
|
340
|
+
return data if data_ok?(data, count)
|
341
|
+
sleep 0.01
|
342
|
+
retry_count += 1
|
343
|
+
end
|
344
|
+
raise MeterError.new("Error parsing status from meter: #{data[0,1]} #{data.size} #{data[1,1] == "\r"} #{data[-1,1] == "\r"}")
|
345
|
+
end
|
346
|
+
|
347
|
+
def meter_command(cmd, expected_result = nil)
|
348
|
+
@port.write "#{cmd}\r"
|
349
|
+
data = read_retry(expected_result ? (expected_result + 2) : nil )
|
350
|
+
status = data[0,1]
|
351
|
+
raise MeterError.new("Command returned error code #{status}", Integer(status)) unless status == "0"
|
352
|
+
raise MeterError.new("Did not receive complete reply from meter") unless data[-1,1] == "\r"
|
353
|
+
|
354
|
+
binary = (data[2,2] == "#0")
|
355
|
+
|
356
|
+
if binary
|
357
|
+
data[4..-2]
|
358
|
+
else
|
359
|
+
tokens = []
|
360
|
+
state = :init
|
361
|
+
current_token = []
|
362
|
+
data[2..-2].each_byte do |b|
|
363
|
+
c = b.chr
|
364
|
+
case state
|
365
|
+
when :init
|
366
|
+
case c
|
367
|
+
when ","
|
368
|
+
tokens << current_token.join
|
369
|
+
current_token = []
|
370
|
+
when "'"
|
371
|
+
raise MeterError.new("Unexpected quote") unless current_token.empty?
|
372
|
+
state = :sq
|
373
|
+
when '"'
|
374
|
+
raise MeterError.new("Unexpected double-quote") unless current_token.empty?
|
375
|
+
state = :dq
|
376
|
+
else
|
377
|
+
current_token << c
|
378
|
+
end
|
379
|
+
when :sq
|
380
|
+
case c
|
381
|
+
when "'"
|
382
|
+
state = :sqe
|
383
|
+
else
|
384
|
+
current_token << c
|
385
|
+
end
|
386
|
+
when :sqe
|
387
|
+
case c
|
388
|
+
when "'"
|
389
|
+
current_token << c
|
390
|
+
state = :sq
|
391
|
+
when ","
|
392
|
+
tokens << current_token.join
|
393
|
+
current_token = []
|
394
|
+
state = :init
|
395
|
+
else
|
396
|
+
raise MeterError.new("Expected comma after single-quoted string")
|
397
|
+
end
|
398
|
+
when :dq
|
399
|
+
case c
|
400
|
+
when '"'
|
401
|
+
state = :dqe
|
402
|
+
else
|
403
|
+
current_token << c
|
404
|
+
end
|
405
|
+
when :dqe
|
406
|
+
case c
|
407
|
+
when ","
|
408
|
+
tokens << current_token.join
|
409
|
+
current_token = []
|
410
|
+
state = :init
|
411
|
+
else
|
412
|
+
raise MeterError.new("Expected comma after double-quoted string")
|
413
|
+
end
|
414
|
+
else
|
415
|
+
raise MeterError.new("Invalid parser state")
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
raise MeterError.new("Did not find end of string") unless [:init, :sqe, :dqe].include?(state)
|
420
|
+
tokens << current_token.join
|
421
|
+
|
422
|
+
end
|
423
|
+
rescue MeterError => e
|
424
|
+
if e.status == 8
|
425
|
+
retry
|
426
|
+
else
|
427
|
+
raise
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
end
|
432
|
+
|
433
|
+
class ByteStr < String
|
434
|
+
alias :each :each_byte
|
435
|
+
end
|
436
|
+
|
437
|
+
|
438
|
+
|
439
|
+
|
440
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module DmmUtil
|
2
|
+
|
3
|
+
module FormatConvertors
|
4
|
+
|
5
|
+
def get_u16(str, offset)
|
6
|
+
lo_byte = str[offset]
|
7
|
+
hi_byte = str[offset + 1]
|
8
|
+
hi_byte * 0x100 + lo_byte
|
9
|
+
end
|
10
|
+
|
11
|
+
def get_s16(str, offset)
|
12
|
+
val = get_u16(str, offset)
|
13
|
+
unless (val & 0x8000) == 0
|
14
|
+
val = -(0x10000 - val)
|
15
|
+
end
|
16
|
+
val
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_double(str, offset)
|
20
|
+
bytestr = str[offset, 8]
|
21
|
+
endianstr = bytestr[0,4].reverse + bytestr[4,4].reverse
|
22
|
+
endianstr.unpack("G")[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_time(str, offset)
|
26
|
+
parse_time(get_double(str, offset))
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_time(t)
|
30
|
+
tz_offset = Time.now.utc_offset
|
31
|
+
Time.at(t - tz_offset)
|
32
|
+
end
|
33
|
+
|
34
|
+
def quote_str(str)
|
35
|
+
has_single = str.include?("'")
|
36
|
+
has_double = str.include?('"')
|
37
|
+
|
38
|
+
if has_single && !has_double
|
39
|
+
"\"#{str}\""
|
40
|
+
else
|
41
|
+
"'#{str.gsub(/'/, "''")}'"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module DmmUtil
|
4
|
+
|
5
|
+
class Measurement
|
6
|
+
attr_reader :raw
|
7
|
+
|
8
|
+
def initialize(attrs)
|
9
|
+
@raw = attrs
|
10
|
+
end
|
11
|
+
|
12
|
+
def name
|
13
|
+
raw[:name] || raise("Not a named measurement")
|
14
|
+
end
|
15
|
+
|
16
|
+
def ts
|
17
|
+
raw[:ts] || self.primary.ts
|
18
|
+
end
|
19
|
+
|
20
|
+
def prim_function
|
21
|
+
raw[:prim_function]
|
22
|
+
end
|
23
|
+
|
24
|
+
def reading_names
|
25
|
+
raw[:readings].keys.map{|r| r.downcase.to_sym}
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
order = [:primary, :maximum, :average, :minimum, :rel_reference,
|
30
|
+
:secondary,
|
31
|
+
:db_ref, :temp_offset,
|
32
|
+
:live, :rel_live]
|
33
|
+
existing = reading_names
|
34
|
+
res = []
|
35
|
+
|
36
|
+
existing.delete(:live) if existing.include?(:live) && self.live == self.primary
|
37
|
+
|
38
|
+
(order - [:primary]).each do |name|
|
39
|
+
next unless existing.include?(name)
|
40
|
+
res << "#{name}: #{self.send(name).to_s}"
|
41
|
+
end
|
42
|
+
|
43
|
+
(existing - order).each do |name|
|
44
|
+
res << "#{name}: #{self.send(name).to_s}"
|
45
|
+
end
|
46
|
+
|
47
|
+
if res.empty?
|
48
|
+
primary.to_s
|
49
|
+
else
|
50
|
+
"#{primary.to_s} (#{res.join(", ")})"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_missing(meth, *args)
|
55
|
+
if raw[:readings].has_key?(meth.to_s.upcase)
|
56
|
+
Reading.new(raw[:readings][meth.to_s.upcase])
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DmmUtil
|
2
|
+
class Meter
|
3
|
+
attr_reader :driver
|
4
|
+
|
5
|
+
def initialize(driver)
|
6
|
+
@driver = driver
|
7
|
+
end
|
8
|
+
|
9
|
+
def recordings
|
10
|
+
Cursor.new(driver, :recording, :qrsi, Recording)
|
11
|
+
end
|
12
|
+
|
13
|
+
def saved_measurements
|
14
|
+
Cursor.new(driver, :measurement, :qsmr, Measurement)
|
15
|
+
end
|
16
|
+
|
17
|
+
def saved_min_max
|
18
|
+
Cursor.new(driver, :min_max, :qmmsi, Measurement)
|
19
|
+
end
|
20
|
+
|
21
|
+
def saved_peak
|
22
|
+
Cursor.new(driver, :peak, :qpsi, Measurement)
|
23
|
+
end
|
24
|
+
|
25
|
+
def measure_now
|
26
|
+
Measurement.new(driver.qddb)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
class MeterError < RuntimeError
|
32
|
+
attr_reader :status
|
33
|
+
def initialize(msg, status = nil)
|
34
|
+
super msg
|
35
|
+
@status = status
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module DmmUtil
|
2
|
+
class Reading
|
3
|
+
attr_reader :raw
|
4
|
+
|
5
|
+
MULTIPLIER_MAP = {
|
6
|
+
-12 => "p",
|
7
|
+
-9 => "n",
|
8
|
+
-6 => "u",
|
9
|
+
-3 => "m",
|
10
|
+
-2 => "c",
|
11
|
+
-1 => "d",
|
12
|
+
0 => "",
|
13
|
+
1 => "D",
|
14
|
+
2 => "h",
|
15
|
+
3 => "k",
|
16
|
+
6 => "M",
|
17
|
+
9 => "G",
|
18
|
+
12 => "T"
|
19
|
+
}
|
20
|
+
|
21
|
+
def initialize(attrs)
|
22
|
+
@raw = attrs
|
23
|
+
end
|
24
|
+
|
25
|
+
def ts
|
26
|
+
raw[:ts]
|
27
|
+
end
|
28
|
+
|
29
|
+
def value
|
30
|
+
raw[:value]
|
31
|
+
end
|
32
|
+
|
33
|
+
def unit
|
34
|
+
raw[:unit]
|
35
|
+
end
|
36
|
+
|
37
|
+
def scaled_value
|
38
|
+
decimals = @raw[:decimals]
|
39
|
+
multiplier = @raw[:unit_multiplier]
|
40
|
+
state = @raw[:state]
|
41
|
+
|
42
|
+
if state == "NORMAL"
|
43
|
+
val = "%.#{decimals}f" % (value / (10 ** multiplier))
|
44
|
+
elsif state == "OL_MINUS"
|
45
|
+
val = "-OL"
|
46
|
+
else
|
47
|
+
val = state
|
48
|
+
end
|
49
|
+
|
50
|
+
[val, "#{MULTIPLIER_MAP[multiplier]}#{unit}"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
sv = scaled_value
|
55
|
+
"#{sv.first} #{sv.last}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def ==(other)
|
59
|
+
value == other.value && unit == other.unit
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module DmmUtil
|
2
|
+
class Recording
|
3
|
+
attr_reader :raw
|
4
|
+
|
5
|
+
def initialize(driver, attrs)
|
6
|
+
@driver = driver
|
7
|
+
@raw = attrs
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
raw[:name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def start_ts
|
15
|
+
raw[:start_ts]
|
16
|
+
end
|
17
|
+
|
18
|
+
def end_ts
|
19
|
+
raw[:end_ts]
|
20
|
+
end
|
21
|
+
|
22
|
+
def seq_no
|
23
|
+
raw[:seq_no]
|
24
|
+
end
|
25
|
+
|
26
|
+
def num_samples
|
27
|
+
raw[:num_samples]
|
28
|
+
end
|
29
|
+
|
30
|
+
def measurements
|
31
|
+
RecordingMeasurementCursor.new(@driver, self)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|