cosmos 3.7.1 → 3.8.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/Manifest.txt +1 -0
  3. data/bin/xtce_converter +83 -0
  4. data/cosmos.gemspec +2 -0
  5. data/data/crc.txt +279 -279
  6. data/demo/config/data/crc.txt +171 -171
  7. data/demo/config/targets/INST/cmd_tlm/inst_tlm.txt +1 -1
  8. data/demo/config/targets/INST/lib/sim_inst.rb +17 -1
  9. data/demo/config/targets/INST/screens/hs.txt +4 -1
  10. data/demo/config/targets/INST/screens/limits.txt +13 -13
  11. data/install/config/data/crc.txt +113 -113
  12. data/lib/cosmos/conversions/conversion.rb +6 -0
  13. data/lib/cosmos/conversions/generic_conversion.rb +12 -0
  14. data/lib/cosmos/conversions/new_packet_log_conversion.rb +6 -0
  15. data/lib/cosmos/conversions/polynomial_conversion.rb +6 -0
  16. data/lib/cosmos/conversions/processor_conversion.rb +10 -0
  17. data/lib/cosmos/conversions/segmented_polynomial_conversion.rb +10 -0
  18. data/lib/cosmos/conversions/unix_time_conversion.rb +6 -0
  19. data/lib/cosmos/core_ext/string.rb +18 -5
  20. data/lib/cosmos/gui/line_graph/lines.rb +13 -12
  21. data/lib/cosmos/packets/limits_response.rb +4 -0
  22. data/lib/cosmos/packets/packet.rb +59 -8
  23. data/lib/cosmos/packets/packet_config.rb +679 -6
  24. data/lib/cosmos/packets/packet_item.rb +230 -0
  25. data/lib/cosmos/packets/parsers/packet_parser.rb +30 -22
  26. data/lib/cosmos/processors/new_packet_log_processor.rb +5 -0
  27. data/lib/cosmos/processors/processor.rb +5 -0
  28. data/lib/cosmos/processors/statistics_processor.rb +5 -0
  29. data/lib/cosmos/processors/watermark_processor.rb +5 -0
  30. data/lib/cosmos/script/extract.rb +1 -1
  31. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +7 -2
  32. data/lib/cosmos/tools/tlm_grapher/data_objects/housekeeping_data_object.rb +3 -1
  33. data/lib/cosmos/tools/tlm_grapher/data_objects/xy_data_object.rb +3 -1
  34. data/lib/cosmos/tools/tlm_viewer/widgets/limits_widget.rb +10 -0
  35. data/lib/cosmos/tools/tlm_viewer/widgets/limitsbar_widget.rb +14 -6
  36. data/lib/cosmos/tools/tlm_viewer/widgets/limitscolumn_widget.rb +14 -6
  37. data/lib/cosmos/tools/tlm_viewer/widgets/rangebar_widget.rb +14 -6
  38. data/lib/cosmos/tools/tlm_viewer/widgets/rangecolumn_widget.rb +14 -6
  39. data/lib/cosmos/utilities/simulated_target.rb +1 -0
  40. data/lib/cosmos/version.rb +5 -5
  41. metadata +18 -2
@@ -296,8 +296,238 @@ module Cosmos
296
296
  hash
297
297
  end
298
298
 
299
+ def to_config(cmd_or_tlm, default_endianness)
300
+ config = ''
301
+ if cmd_or_tlm == :TELEMETRY
302
+ if self.array_size
303
+ config << " ARRAY_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.array_size} \"#{self.description.to_s.gsub("\"", "'")}\""
304
+ elsif self.id_value
305
+ config << " ID_ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.id_value} \"#{self.description.to_s.gsub("\"", "'")}\""
306
+ else
307
+ config << " ITEM #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.description.to_s.gsub("\"", "'")}\""
308
+ end
309
+ else # :COMMAND
310
+ if self.array_size
311
+ config << " ARRAY_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.array_size} \"#{self.description.to_s.gsub("\"", "'")}\""
312
+ elsif self.id_value
313
+ if self.data_type == :BLOCK or self.data_type == :STRING
314
+ config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.default}\" \"#{self.description.to_s.gsub("\"", "'")}\""
315
+ else
316
+ config << " ID_PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.range.first} #{self.range.last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
317
+ end
318
+ else
319
+ if self.data_type == :BLOCK or self.data_type == :STRING
320
+ config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} \"#{self.default}\" \"#{self.description.to_s.gsub("\"", "'")}\""
321
+ else
322
+ config << " PARAMETER #{self.name.to_s.quote_if_necessary} #{self.bit_offset} #{self.bit_size} #{self.data_type} #{self.range.first} #{self.range.last} #{self.default} \"#{self.description.to_s.gsub("\"", "'")}\""
323
+ end
324
+ end
325
+ end
326
+ config << " #{self.endianness}" if self.endianness != default_endianness
327
+ config << "\n"
328
+
329
+ config << " REQUIRED\n" if self.required
330
+ config << " FORMAT_STRING #{self.format_string.to_s.quote_if_necessary}\n" if self.format_string
331
+ config << " UNITS #{self.units_full.to_s.quote_if_necessary} #{self.units.to_s.quote_if_necessary}\n" if self.units
332
+ config << " OVERFLOW #{self.overflow}\n" if self.overflow != :ERROR
333
+
334
+ if @states
335
+ @states.each do |state_name, state_value|
336
+ config << " STATE #{state_name.to_s.quote_if_necessary} #{state_value.to_s.quote_if_necessary}"
337
+ if @hazardous and @hazardous[state_name]
338
+ config << " HAZARDOUS #{@hazardous[state_name].to_s.quote_if_necessary}"
339
+ end
340
+ if @state_colors and @state_colors[state_name]
341
+ config << " #{@state_colors[state_name]}"
342
+ end
343
+ config << "\n"
344
+ end
345
+ end
346
+
347
+ config << self.read_conversion.to_config(:READ) if self.read_conversion
348
+ config << self.write_conversion.to_config(:WRITE) if self.write_conversion
349
+
350
+ if self.limits
351
+ if self.limits.values
352
+ self.limits.values.each do |limits_set, limits_values|
353
+ config << " LIMITS #{limits_set} #{self.limits.persistence_setting} #{self.limits.enabled ? 'ENABLED' : 'DISABLED'} #{limits_values[0]} #{limits_values[1]} #{limits_values[2]} #{limits_values[3]} #{limits_values[4]} #{limits_values[5]}\n"
354
+ end
355
+ end
356
+ config << self.limits.response.to_config if self.limits.response
357
+ end
358
+
359
+ if @meta
360
+ @meta.each do |key, values|
361
+ config << " META #{key.to_s.quote_if_necessary} #{values.map {|a| a.to_s.quote_if_necessary}.join(" ")}\n"
362
+ end
363
+ end
364
+
365
+ config
366
+ end
367
+
368
+ def to_xtce_type(param_or_arg, xml)
369
+ # TODO: Arrays, Spline Conversions
370
+ case self.data_type
371
+ when :INT, :UINT
372
+ attrs = { :name => (self.name + '_Type') }
373
+ attrs[:initialValue] = self.default if self.default and !self.array_size
374
+ attrs[:shortDescription] = self.description if self.description
375
+ if @states and self.default and @states.key(self.default)
376
+ attrs[:initialValue] = @states.key(self.default) and !self.array_size
377
+ end
378
+ if self.data_type == :INT
379
+ signed = 'true'
380
+ encoding = 'twosCompliment'
381
+ else
382
+ signed = 'false'
383
+ encoding = 'unsigned'
384
+ end
385
+ if @states
386
+ xml['xtce'].send('Enumerated' + param_or_arg + 'Type', attrs) do
387
+ to_xtce_units(xml)
388
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding)
389
+ xml['xtce'].EnumerationList do
390
+ @states.each do |state_name, state_value|
391
+ xml['xtce'].Enumeration(:value => state_value, :label => state_name)
392
+ end
393
+ end
394
+ end
395
+ else
396
+ if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
397
+ type_string = 'Float' + param_or_arg + 'Type'
398
+ else
399
+ type_string = 'Integer' + param_or_arg + 'Type'
400
+ attrs[:signed] = signed
401
+ end
402
+ xml['xtce'].send(type_string, attrs) do
403
+ to_xtce_units(xml)
404
+ if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
405
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding) do
406
+ to_xtce_conversion(xml)
407
+ end
408
+ else
409
+ xml['xtce'].IntegerDataEncoding(:sizeInBits => self.bit_size, :encoding => encoding)
410
+ end
411
+ if self.limits
412
+ if self.limits.values
413
+ self.limits.values.each do |limits_set, limits_values|
414
+ if limits_set == :DEFAULT
415
+ xml['xtce'].DefaultAlarm do
416
+ xml['xtce'].StaticAlarmRanges do
417
+ xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
418
+ xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end
425
+ if self.range
426
+ xml['xtce'].ValidRange(:minInclusive => self.range.first, :maxInclusive => self.range.last)
427
+ end
428
+ end # Type
429
+ end # if @states
430
+ when :FLOAT
431
+ attrs = { :name => (self.name + '_Type'), :sizeInBits => self.bit_size }
432
+ attrs[:initialValue] = self.default if self.default and !self.array_size
433
+ attrs[:shortDescription] = self.description if self.description
434
+ xml['xtce'].send('Float' + param_or_arg + 'Type', attrs) do
435
+ to_xtce_units(xml)
436
+ if (self.read_conversion and self.read_conversion.class == PolynomialConversion) or (self.write_conversion and self.write_conversion.class == PolynomialConversion)
437
+ xml['xtce'].FloatDataEncoding(:sizeInBits => self.bit_size, :encoding => 'IEEE754_1985') do
438
+ to_xtce_conversion(xml)
439
+ end
440
+ else
441
+ xml['xtce'].FloatDataEncoding(:sizeInBits => self.bit_size, :encoding => 'IEEE754_1985')
442
+ end
443
+
444
+ if self.limits
445
+ if self.limits.values
446
+ self.limits.values.each do |limits_set, limits_values|
447
+ if limits_set == :DEFAULT
448
+ xml['xtce'].DefaultAlarm do
449
+ xml['xtce'].StaticAlarmRanges do
450
+ xml['xtce'].WarningRange(:minInclusive => limits_values[1], :maxInclusive => limits_values[2])
451
+ xml['xtce'].CriticalRange(:minInclusive => limits_values[0], :maxInclusive => limits_values[3])
452
+ end
453
+ end
454
+ end
455
+ end
456
+ end
457
+ end
458
+
459
+ if self.range
460
+ xml['xtce'].ValidRange(:minInclusive => self.range.first, :maxInclusive => self.range.last)
461
+ end
462
+
463
+ end # Type
464
+ when :STRING
465
+ # TODO: COSMOS Variably sized strings are not supported in XTCE
466
+ attrs = { :name => (self.name + '_Type'), :characterWidth => 8 }
467
+ attrs[:initialValue] = self.default if self.default and !self.array_size
468
+ attrs[:shortDescription] = self.description if self.description
469
+ xml['xtce'].send('String' + param_or_arg + 'Type', attrs) do
470
+ to_xtce_units(xml)
471
+ xml['xtce'].StringDataEncoding(:encoding => 'UTF-8') do
472
+ xml['xtce'].SizeInBits do
473
+ xml['xtce'].Fixed do
474
+ xml['xtce'].FixedValue(self.bit_size.to_s)
475
+ end
476
+ end
477
+ end
478
+ end
479
+ when :BLOCK
480
+ # TODO: COSMOS Variably sized blocks are not supported in XTCE
481
+ # TODO: Write string to hex method to support initial value
482
+ attrs = { :name => (self.name + '_Type') }
483
+ attrs[:shortDescription] = self.description if self.description
484
+ #attrs[:initialValue] = self.default if self.default and !self.array_size
485
+ xml['xtce'].send('Binary' + param_or_arg + 'Type', attrs) do
486
+ to_xtce_units(xml)
487
+ xml['xtce'].BinaryDataEncoding do
488
+ xml['xtce'].SizeInBits do
489
+ xml['xtce'].FixedValue(self.bit_size.to_s)
490
+ end
491
+ end
492
+ end
493
+ when :DERIVED
494
+ raise "DERIVED data type not supported in XTCE"
495
+ end
496
+ end
497
+
498
+ def to_xtce_item(param_or_arg, xml)
499
+ xml['xtce'].send(param_or_arg, :name => self.name, "#{param_or_arg.downcase}TypeRef" => self.name + '_Type')
500
+ end
501
+
299
502
  protected
300
503
 
504
+ def to_xtce_units(xml)
505
+ if self.units
506
+ xml['xtce'].UnitSet do
507
+ xml['xtce'].Unit(self.units, :description => self.units_full)
508
+ end
509
+ else
510
+ xml['xtce'].UnitSet
511
+ end
512
+ end
513
+
514
+ def to_xtce_conversion(xml)
515
+ if self.read_conversion
516
+ conversion = self.read_conversion
517
+ else
518
+ conversion = self.write_conversion
519
+ end
520
+ if conversion and conversion.class == PolynomialConversion
521
+ xml['xtce'].DefaultCalibrator do
522
+ xml['xtce'].PolynomialCalibrator do
523
+ conversion.coeffs.each_with_index do |coeff, index|
524
+ xml['xtce'].Term(:coefficient => coeff, :exponent => index)
525
+ end
526
+ end
527
+ end
528
+ end
529
+ end
530
+
301
531
  # Convert a value into the given data type
302
532
  def convert(value, data_type)
303
533
  case data_type
@@ -68,33 +68,15 @@ module Cosmos
68
68
 
69
69
  def create_command(target_name, commands, warnings)
70
70
  packet = create_packet(target_name)
71
- warning = check_for_duplicate('Command', commands, packet)
72
- warnings << warning if warning
73
- commands[packet.target_name] ||= {}
74
- packet
71
+ PacketParser.finish_create_command(packet, commands, warnings)
75
72
  end
76
73
 
77
74
  def create_telemetry(target_name, telemetry, latest_data, warnings)
78
75
  packet = create_packet(target_name)
79
- warning = check_for_duplicate('Telemetry', telemetry, packet)
80
- warnings << warning if warning
81
-
82
- # Add received time packet items
83
- item = packet.define_item('RECEIVED_TIMESECONDS', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, '%0.6f', ReceivedTimeSecondsConversion.new)
84
- item.description = 'COSMOS Received Time (UTC, Floating point, Unix epoch)'
85
- item = packet.define_item('RECEIVED_TIMEFORMATTED', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedTimeFormattedConversion.new)
86
- item.description = 'COSMOS Received Time (Local time zone, Formatted string)'
87
- item = packet.define_item('RECEIVED_COUNT', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedCountConversion.new)
88
- item.description = 'COSMOS packet received count'
89
-
90
- unless telemetry[packet.target_name]
91
- telemetry[packet.target_name] = {}
92
- latest_data[packet.target_name] = {}
93
- end
94
- packet
76
+ PacketParser.finish_create_telemetry(packet, telemetry, latest_data, warnings)
95
77
  end
96
78
 
97
- private
79
+ #private
98
80
 
99
81
  def create_packet(target_name)
100
82
  params = @parser.parameters
@@ -108,7 +90,7 @@ module Cosmos
108
90
  Packet.new(target_name, packet_name, endianness, description)
109
91
  end
110
92
 
111
- def check_for_duplicate(type, list, packet)
93
+ def self.check_for_duplicate(type, list, packet)
112
94
  msg = nil
113
95
  if list[packet.target_name]
114
96
  if list[packet.target_name][packet.packet_name]
@@ -119,5 +101,31 @@ module Cosmos
119
101
  msg
120
102
  end
121
103
 
104
+ def self.finish_create_command(packet, commands, warnings)
105
+ warning = PacketParser.check_for_duplicate('Command', commands, packet)
106
+ warnings << warning if warning
107
+ commands[packet.target_name] ||= {}
108
+ packet
109
+ end
110
+
111
+ def self.finish_create_telemetry(packet, telemetry, latest_data, warnings)
112
+ warning = PacketParser.check_for_duplicate('Telemetry', telemetry, packet)
113
+ warnings << warning if warning
114
+
115
+ # Add received time packet items
116
+ item = packet.define_item('RECEIVED_TIMESECONDS', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, '%0.6f', ReceivedTimeSecondsConversion.new)
117
+ item.description = 'COSMOS Received Time (UTC, Floating point, Unix epoch)'
118
+ item = packet.define_item('RECEIVED_TIMEFORMATTED', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedTimeFormattedConversion.new)
119
+ item.description = 'COSMOS Received Time (Local time zone, Formatted string)'
120
+ item = packet.define_item('RECEIVED_COUNT', 0, 0, :DERIVED, nil, packet.default_endianness, :ERROR, nil, ReceivedCountConversion.new)
121
+ item.description = 'COSMOS packet received count'
122
+
123
+ unless telemetry[packet.target_name]
124
+ telemetry[packet.target_name] = {}
125
+ latest_data[packet.target_name] = {}
126
+ end
127
+ packet
128
+ end
129
+
122
130
  end
123
131
  end # module Cosmos
@@ -29,6 +29,11 @@ module Cosmos
29
29
  end
30
30
  end
31
31
 
32
+ # Convert to configuration file string
33
+ def to_config
34
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@packet_log_writer_name}\n"
35
+ end
36
+
32
37
  end # class NewPacketLogProcessor
33
38
 
34
39
  end # module Cosmos
@@ -66,6 +66,11 @@ module Cosmos
66
66
  end
67
67
  alias dup clone
68
68
 
69
+ # Convert to configuration file string
70
+ def to_config
71
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@value_type}\n"
72
+ end
73
+
69
74
  end # class Processor
70
75
 
71
76
  end # module Cosmos
@@ -60,6 +60,11 @@ module Cosmos
60
60
  end
61
61
  alias dup clone
62
62
 
63
+ # Convert to configuration file string
64
+ def to_config
65
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@item_name} #{@samples_to_average} #{@value_type}\n"
66
+ end
67
+
63
68
  end # class StatisticsProcessor
64
69
 
65
70
  end # module Cosmos
@@ -39,6 +39,11 @@ module Cosmos
39
39
  @results[:LOW_WATER] = nil
40
40
  end
41
41
 
42
+ # Convert to configuration file string
43
+ def to_config
44
+ " PROCESSOR #{@name} #{self.class.name.to_s.class_name_to_filename} #{@item_name} #{@value_type}\n"
45
+ end
46
+
42
47
  end # class WatermarkProcessor
43
48
 
44
49
  end # module Cosmos
@@ -25,7 +25,7 @@ module Cosmos
25
25
  end
26
26
 
27
27
  def extract_fields_from_cmd_text(text)
28
- split_string = text.split(/\s*with\s*/i, 2)
28
+ split_string = text.split(/\s+with\s+/i, 2)
29
29
  raise "ERROR: 'with' must be followed by parameters : #{text}" if split_string.length == 1 and text =~ /\s*with\s*/i
30
30
 
31
31
  # Extract target_name and cmd_name
@@ -35,7 +35,11 @@ module Cosmos
35
35
  @tlm_thread = nil
36
36
  @shutdown_tlm_thread = false
37
37
  @mode = :WITH_UNITS
38
- @polling_rate = 1.0
38
+ if options.rate
39
+ @polling_rate = options.rate
40
+ else
41
+ @polling_rate = 1.0
42
+ end
39
43
  @colorblind = false
40
44
 
41
45
  initialize_actions()
@@ -249,7 +253,7 @@ module Cosmos
249
253
  end
250
254
 
251
255
  def file_options
252
- @polling_rate = Qt::InputDialog.getDouble(self, tr("Options"), tr("Polling Rate:"), @polling_rate, 0, 1000, 1, nil)
256
+ @polling_rate = Qt::InputDialog.getDouble(self, tr("Options"), tr("Polling Rate (sec):"), @polling_rate, 0, 1000, 1, nil)
253
257
  end
254
258
 
255
259
  def update_all
@@ -531,6 +535,7 @@ module Cosmos
531
535
  end
532
536
  options.packet = split
533
537
  end
538
+ option_parser.on("-r", "--rate PERIOD", "Set the polling rate to PERIOD (unit seconds)") { |arg| options.rate = Float(arg) }
534
539
  end
535
540
 
536
541
  super(option_parser, options)
@@ -163,7 +163,9 @@ module Cosmos
163
163
  # Bail on the values if they are NaN or nil as we can't graph them
164
164
  return if x_value.nil? || y_value.nil? ||
165
165
  (x_value.respond_to?(:nan?) && x_value.nan?) ||
166
- (y_value.respond_to?(:nan?) && y_value.nan?)
166
+ (y_value.respond_to?(:nan?) && y_value.nan?) ||
167
+ (x_value.respond_to?(:infinite?) && x_value.infinite?) ||
168
+ (y_value.respond_to?(:infinite?) && y_value.infinite?)
167
169
  @formatted_x_values << packet.read(@formatted_time_item_name) if @formatted_time_item_name
168
170
 
169
171
  upper_index = nil
@@ -195,7 +195,9 @@ module Cosmos
195
195
  # Bail on the values if they are NaN or nil as we can't graph them
196
196
  return if x_value.nil? || y_value.nil? ||
197
197
  (x_value.respond_to?(:nan?) && x_value.nan?) ||
198
- (y_value.respond_to?(:nan?) && y_value.nan?)
198
+ (y_value.respond_to?(:nan?) && y_value.nan?) ||
199
+ (x_value.respond_to?(:infinite?) && x_value.infinite?) ||
200
+ (y_value.respond_to?(:infinite?) && y_value.infinite?)
199
201
 
200
202
  time_value = packet.read(@time_item_name) if @time_item_name
201
203
 
@@ -34,6 +34,16 @@ module Cosmos
34
34
  end
35
35
 
36
36
  def value=(data)
37
+ if String === data
38
+ substring = data[0..2]
39
+ if substring == "Inf".freeze
40
+ data = Float::INFINITY
41
+ elsif substring == "-In".freeze
42
+ data = -Float::INFINITY
43
+ elsif substring == "NaN".freeze
44
+ data = Float::NAN
45
+ end
46
+ end
37
47
  @value = data.to_f
38
48
  update()
39
49
  end
@@ -81,12 +81,20 @@ module Cosmos
81
81
  @low_value = red_low - 0.1 * @bar_scale
82
82
  @high_value = red_high + 0.1 * @bar_scale
83
83
 
84
- @line_pos = (@x_pad + (@value - @low_value) / @bar_scale * @bar_width).to_i
85
- if @line_pos < @x_pad
86
- @line_pos = @x_pad
87
- end
88
- if @line_pos > @x_pad + @bar_width
89
- @line_pos = @bar_width + @x_pad
84
+ if @value.is_a?(Float) && (@value.infinite? || @value.nan?)
85
+ if @value.infinite? == 1
86
+ @line_pos = @bar_width + @x_pad
87
+ else
88
+ @line_pos = @x_pad
89
+ end
90
+ else
91
+ @line_pos = (@x_pad + (@value - @low_value) / @bar_scale * @bar_width).to_i
92
+ if @line_pos < @x_pad
93
+ @line_pos = @x_pad
94
+ end
95
+ if @line_pos > @x_pad + @bar_width
96
+ @line_pos = @bar_width + @x_pad
97
+ end
90
98
  end
91
99
 
92
100
  dc.addLineColor(@line_pos, @y_pad - 3, @line_pos, @y_pad + @bar_height + 3)