cosmos 4.0.3 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -5
  3. data/Manifest.txt +11 -1
  4. data/README.md +3 -2
  5. data/Rakefile +18 -4
  6. data/appveyor.yml +19 -0
  7. data/cosmos.gemspec +14 -3
  8. data/data/config/cmd_tlm_server.yaml +3 -0
  9. data/data/crc.txt +63 -60
  10. data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
  11. data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
  12. data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
  13. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
  14. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
  15. data/demo/lib/cmd_sequence_exporter.rb +52 -0
  16. data/demo/lib/example_background_task.rb +1 -0
  17. data/demo/procedures/replay_test.rb +32 -0
  18. data/ext/cosmos/ext/structure/structure.c +39 -3
  19. data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
  20. data/install/config/tools/launcher/launcher.txt +2 -0
  21. data/lib/cosmos/config/config_parser.rb +2 -0
  22. data/lib/cosmos/core_ext/io.rb +89 -60
  23. data/lib/cosmos/gui/qt.rb +5 -8
  24. data/lib/cosmos/gui/qt_tool.rb +8 -8
  25. data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
  26. data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
  27. data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
  28. data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
  29. data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
  30. data/lib/cosmos/interfaces/udp_interface.rb +27 -14
  31. data/lib/cosmos/io/buffered_file.rb +0 -1
  32. data/lib/cosmos/io/json_drb.rb +134 -214
  33. data/lib/cosmos/io/json_drb_object.rb +22 -61
  34. data/lib/cosmos/io/json_drb_rack.rb +79 -0
  35. data/lib/cosmos/io/json_rpc.rb +27 -0
  36. data/lib/cosmos/io/udp_sockets.rb +102 -58
  37. data/lib/cosmos/packets/commands.rb +1 -1
  38. data/lib/cosmos/packets/structure.rb +1 -1
  39. data/lib/cosmos/packets/structure_item.rb +37 -5
  40. data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
  41. data/lib/cosmos/script/replay.rb +60 -0
  42. data/lib/cosmos/script/script.rb +20 -2
  43. data/lib/cosmos/script/scripting.rb +9 -9
  44. data/lib/cosmos/script/tools.rb +14 -0
  45. data/lib/cosmos/system/system.rb +185 -92
  46. data/lib/cosmos/system/target.rb +1 -1
  47. data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
  48. data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
  49. data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
  50. data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
  51. data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
  52. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
  53. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
  54. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
  55. data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
  56. data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
  57. data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
  58. data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
  59. data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
  60. data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
  61. data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
  62. data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
  63. data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
  64. data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
  65. data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
  66. data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
  67. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
  68. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
  69. data/lib/cosmos/tools/replay/replay.rb +5 -505
  70. data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
  71. data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
  72. data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
  73. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
  74. data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
  75. data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
  76. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
  77. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
  78. data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
  79. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
  80. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
  81. data/lib/cosmos/top_level.rb +34 -24
  82. data/lib/cosmos/utilities/csv.rb +60 -8
  83. data/lib/cosmos/version.rb +5 -5
  84. data/spec/config/config_parser_spec.rb +10 -1
  85. data/spec/core_ext/socket_spec.rb +4 -2
  86. data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
  87. data/spec/install/config/data/data.txt +1 -0
  88. data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
  89. data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
  90. data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
  91. data/spec/interfaces/serial_interface_spec.rb +1 -1
  92. data/spec/interfaces/udp_interface_spec.rb +14 -0
  93. data/spec/io/buffered_file_spec.rb +37 -0
  94. data/spec/io/json_drb_object_spec.rb +2 -15
  95. data/spec/io/json_drb_spec.rb +61 -121
  96. data/spec/io/udp_sockets_spec.rb +42 -2
  97. data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
  98. data/spec/packets/binary_accessor_spec.rb +1 -1
  99. data/spec/packets/packet_item_spec.rb +1 -1
  100. data/spec/packets/structure_item_spec.rb +5 -6
  101. data/spec/script/cmd_tlm_server_spec.rb +39 -4
  102. data/spec/script/commands_disconnect_spec.rb +1 -1
  103. data/spec/script/commands_spec.rb +2 -1
  104. data/spec/script/scripting_spec.rb +18 -3
  105. data/spec/script/telemetry_spec.rb +5 -0
  106. data/spec/spec_helper.rb +43 -26
  107. data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
  108. data/spec/system/system_spec.rb +11 -9
  109. data/spec/system/target_spec.rb +3 -0
  110. data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
  111. data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
  112. data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
  113. data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
  114. data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
  115. data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
  116. data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
  117. data/spec/tools/launcher/launcher_config_spec.rb +1 -1
  118. data/spec/tools/table_manager/table_item_spec.rb +1 -1
  119. data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
  120. data/spec/top_level/top_level_spec.rb +151 -3
  121. data/spec/utilities/csv_spec.rb +24 -5
  122. metadata +61 -9
  123. data/lib/cosmos/tools/replay/replay_server.rb +0 -91
@@ -169,9 +169,8 @@ module Cosmos
169
169
  return filename if File.exist? filename
170
170
 
171
171
  # Check relative to executing file
172
- filename = Cosmos.path($0, 'config/data/' + name)
172
+ filename = Cosmos.path($0, ['config', 'data', name])
173
173
  return filename if File.exist? filename
174
-
175
174
  nil
176
175
  end
177
176
 
@@ -192,7 +191,7 @@ module Cosmos
192
191
  else
193
192
  current_dir = File.join(BASE_PWD, calling_file)
194
193
  end
195
- while true
194
+ loop do
196
195
  test_path = File.join(current_dir, partial_path)
197
196
  if File.exist?(test_path)
198
197
  return test_path
@@ -298,13 +297,14 @@ module Cosmos
298
297
  thread = Thread.new do
299
298
  output, _ = Open3.capture2e(command)
300
299
  if !output.empty?
301
- # Work around modalSession messages on Mac Mavericks
302
- real_lines = 0
300
+ # Ignore modalSession messages on Mac Mavericks and Qt Untested Windows 10
301
+ new_output = ''
303
302
  output.each_line do |line|
304
- real_lines += 1 if line !~ /modalSession/
303
+ new_output << line if line !~ /modalSession/ && line !~ /Untested Windows version 10.0/
305
304
  end
305
+ output = new_output
306
306
 
307
- if real_lines > 0
307
+ if !output.empty?
308
308
  Logger.error output
309
309
  self.write_unexpected_file(output)
310
310
  if defined? ::Qt and $qApp
@@ -395,7 +395,7 @@ module Cosmos
395
395
  # @yieldparam file [File] The log file
396
396
  # @return [String|nil] The fully pathed log file name or nil if there was
397
397
  # an error creating the log file.
398
- def self.create_log_file(filename, log_dir)
398
+ def self.create_log_file(filename, log_dir = nil)
399
399
  log_file = nil
400
400
  Cosmos.set_working_dir do
401
401
  begin
@@ -403,31 +403,34 @@ module Cosmos
403
403
  # system.txt configuration file. If this has an error we won't be able
404
404
  # to determine the log path but we still want to write the log.
405
405
  log_dir = System.instance.paths['LOGS'] unless log_dir
406
- log_file = File.join(log_dir,
407
- File.build_timestamped_filename([filename]))
408
406
  # Make sure the log directory exists
409
407
  raise unless File.exist?(log_dir)
410
- log_file
411
408
  rescue Exception
412
- # If not then we just build a file locally
413
- if File.exist?('./outputs/logs')
414
- log_file = File.join('./outputs/logs', File.build_timestamped_filename([filename]))
415
- elsif File.exist?('./logs')
416
- log_file = File.join('./logs', File.build_timestamped_filename([filename]))
417
- else
418
- log_file = File.build_timestamped_filename([filename])
419
- end
409
+ log_dir = nil # Reset log dir since it failed above
410
+ # First check for ./logs
411
+ log_dir = './logs' if File.exist?('./logs')
412
+ # Prefer ./outputs/logs if it exists
413
+ log_dir = './outputs/logs' if File.exist?('./outputs/logs')
414
+ # If all else fails just use the local directory
415
+ log_dir = '.' unless log_dir
416
+ end
417
+ log_file = File.join(log_dir,
418
+ File.build_timestamped_filename([filename]))
419
+ # Check for the log file existing. This could happen if this method gets
420
+ # called more than once in the same second.
421
+ if File.exist?(log_file)
422
+ sleep 1.01 # Sleep before rebuilding the timestamp to get something unique
423
+ log_file = File.join(log_dir,
424
+ File.build_timestamped_filename([filename]))
420
425
  end
421
426
  begin
422
- # Log exception to file, open the file in append mode in case we get
423
- # multiple exceptions in the same second. That way we don't lose
424
- # exceptions by overwritting the last exception file.
425
427
  COSMOS_MUTEX.synchronize do
426
428
  begin
427
- file = File.open(log_file, 'a')
429
+ file = File.open(log_file, 'w')
428
430
  yield file
429
431
  ensure
430
432
  file.close unless file.closed?
433
+ File.chmod(0444, log_file) # Make file read only
431
434
  end
432
435
  end
433
436
  rescue Exception
@@ -738,8 +741,15 @@ module Cosmos
738
741
  Logger.info "Thread owner #{owner.class} does not support graceful_kill"
739
742
  end
740
743
  if thread.alive?
744
+ # If the thread dies after alive? but before backtrace, bt will be nil.
745
+ bt = thread.backtrace
746
+
741
747
  # Graceful failed
742
- Logger.warn "Failed to gracefully kill thread:\n Caller Backtrace:\n #{caller().join("\n ")}\n \n Thread Backtrace:\n #{thread.backtrace.join("\n ")}\n\n"
748
+ msg = "Failed to gracefully kill thread:\n"
749
+ msg << " Caller Backtrace:\n #{caller().join("\n ")}\n"
750
+ msg << " \n Thread Backtrace:\n #{bt.join("\n ")}\n" if bt
751
+ msg << "\n"
752
+ Logger.warn msg
743
753
  thread.kill
744
754
  end_time = Time.now.sys + hard_timeout
745
755
  while thread.alive? && ((end_time - Time.now.sys) > 0)
@@ -51,13 +51,26 @@ module Cosmos
51
51
  # @return [Boolean] Single value converted to a boolean (true or false)
52
52
  def bool(item, index = 0)
53
53
  raise "#{item} not found" unless keys.include?(item)
54
- case @hash[item][index].upcase
55
- when 'TRUE'
56
- true
57
- when 'FALSE'
58
- false
54
+ if Range === index
55
+ @hash[item][index].map do |x|
56
+ case x.upcase
57
+ when 'TRUE'
58
+ true
59
+ when 'FALSE'
60
+ false
61
+ else
62
+ raise "#{item} value of #{x} not boolean. Must be 'TRUE' 'or 'FALSE'."
63
+ end
64
+ end
59
65
  else
60
- raise "#{item} value of #{@hash[item][index]} not boolean. Must be 'TRUE' 'or 'FALSE'."
66
+ case @hash[item][index].upcase
67
+ when 'TRUE'
68
+ true
69
+ when 'FALSE'
70
+ false
71
+ else
72
+ raise "#{item} value of #{@hash[item][index]} not boolean. Must be 'TRUE' 'or 'FALSE'."
73
+ end
61
74
  end
62
75
  end
63
76
  alias boolean bool
@@ -69,7 +82,11 @@ module Cosmos
69
82
  # @return [Integer] Single value converted to an integer
70
83
  def int(item, index = 0)
71
84
  raise "#{item} not found" unless keys.include?(item)
72
- @hash[item][index].to_i
85
+ if Range === index
86
+ @hash[item][index].map {|x| x.to_i }
87
+ else
88
+ @hash[item][index].to_i
89
+ end
73
90
  end
74
91
  alias integer int
75
92
 
@@ -80,8 +97,42 @@ module Cosmos
80
97
  # @return [Float] Single value converted to a float
81
98
  def float(item, index = 0)
82
99
  raise "#{item} not found" unless keys.include?(item)
83
- @hash[item][index].to_f
100
+ if Range === index
101
+ @hash[item][index].map {|x| x.to_f }
102
+ else
103
+ @hash[item][index].to_f
104
+ end
105
+ end
106
+
107
+ # Convenience method to access a value by key and convert it to a string
108
+ #
109
+ # @param item [String] Key to access the value
110
+ # @param index [Integer] Which value to return
111
+ # @return [String] Single value converted to a string
112
+ def string(item, index = 0)
113
+ raise "#{item} not found" unless keys.include?(item)
114
+ if Range === index
115
+ @hash[item][index].map {|x| x.to_s }
116
+ else
117
+ @hash[item][index].to_s
118
+ end
119
+ end
120
+ alias str string
121
+
122
+ # Convenience method to access a value by key and convert it to a symbol
123
+ #
124
+ # @param item [String] Key to access the value
125
+ # @param index [Integer] Which value to return
126
+ # @return [Symbol] Single value converted to a symbol
127
+ def symbol(item, index = 0)
128
+ raise "#{item} not found" unless keys.include?(item)
129
+ if Range === index
130
+ @hash[item][index].map {|x| x.intern }
131
+ else
132
+ @hash[item][index].intern
133
+ end
84
134
  end
135
+ alias sym symbol
85
136
 
86
137
  # Creates a copy of the CSV file passed into the constructor. The file will
87
138
  # be prefixed by the current date and time to create a unique filename.
@@ -131,6 +182,7 @@ module Cosmos
131
182
  # Closes the archive file created by #{CSV#create_archive}.
132
183
  def close_archive
133
184
  @archive.close
185
+ File.chmod(0444, @archive_file)
134
186
  @archive = nil
135
187
  end
136
188
  end
@@ -1,12 +1,12 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- COSMOS_VERSION = '4.0.3'
3
+ COSMOS_VERSION = '4.1.0'
4
4
  module Cosmos
5
5
  module Version
6
6
  MAJOR = '4'
7
- MINOR = '0'
8
- PATCH = '3'
9
- BUILD = '04e4681c862983cb85d08e888a07efffb60b4fbb'
7
+ MINOR = '1'
8
+ PATCH = '0'
9
+ BUILD = '9731591f6e6dd90749a966e06a95d923b7f112ae'
10
10
  end
11
- VERSION = '4.0.3'
11
+ VERSION = '4.1.0'
12
12
  end
@@ -67,8 +67,17 @@ module Cosmos
67
67
  tf.unlink
68
68
  end
69
69
 
70
+ it "requires ERB partials begin with an underscore" do
71
+ tf = Tempfile.new('unittest')
72
+ tf.puts "<%= render 'partial.txt' %>"
73
+ tf.close
74
+
75
+ expect { @cp.parse_file(tf.path) }.to raise_error(ConfigParser::Error, /must begin with an underscore/)
76
+ tf.unlink
77
+ end
78
+
70
79
  it "supports ERB partials via render" do
71
- tf2 = Tempfile.new('partial.txt')
80
+ tf2 = Tempfile.new('_partial.txt')
72
81
  tf2.puts '<% if output %>'
73
82
  tf2.puts 'KEYWORD <%= id %> <%= desc %>'
74
83
  tf2.puts '<% end %>'
@@ -25,8 +25,10 @@ describe Socket do
25
25
 
26
26
  describe "lookup_hostname_from_ip" do
27
27
  it "returns the hostname for the ip address" do
28
- ipaddr = Resolv.getaddress "localhost"
29
- expect(Socket.lookup_hostname_from_ip(ipaddr)).to match "localhost"
28
+ if !ENV['APPVEYOR']
29
+ ipaddr = Resolv.getaddress "localhost"
30
+ expect(Socket.lookup_hostname_from_ip(ipaddr)).to match "localhost"
31
+ end
30
32
  end
31
33
  end
32
34
  end
@@ -0,0 +1,102 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2014 Ball Aerospace & Technologies Corp.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is free software; you can modify and/or redistribute it
7
+ # under the terms of the GNU General Public License
8
+ # as published by the Free Software Foundation; version 3 with
9
+ # attribution addendums as found in the LICENSE.txt
10
+
11
+ if RUBY_ENGINE == 'ruby'
12
+ require 'spec_helper'
13
+ require 'cosmos/gui/qt'
14
+ require 'cosmos/script'
15
+ require 'cosmos/gui/utilities/script_module_gui'
16
+
17
+ module Cosmos
18
+ describe Script do
19
+ after(:all) do
20
+ # Load the original scripting file over the script_module_gui
21
+ load 'cosmos/script/scripting.rb'
22
+ end
23
+
24
+ def stub_null_object(constant)
25
+ class_double(constant).tap do |double|
26
+ stub_const(constant, double)
27
+ allow(double).to receive(:new).and_return(double(constant).as_null_object)
28
+ yield double if block_given?
29
+ end
30
+ end
31
+
32
+ let!(:qt_module) do
33
+ class_double('Qt').as_stubbed_const(:transfer_nested_constants => true).tap do |double|
34
+ stub_const('Qt', double)
35
+ stub_const('Qt::WindowTitleHint', double)
36
+ stub_const('Qt::WindowSystemMenuHint', double)
37
+ allow(double).to receive(:execute_in_main_thread).and_yield
38
+ allow(double).to receive(:debug_level)
39
+ end
40
+ stub_null_object('Qt::Widget')
41
+ stub_null_object('Qt::Dialog') do |double|
42
+ instance = double("Qt::Dialog").as_null_object
43
+ allow(instance).to receive(:exec).and_return 1 # break the loop by returning 1
44
+ allow(double).to receive(:new).and_return(instance)
45
+ end
46
+ # Stub Accepted to be 1 to match the instance exec returning 1
47
+ stub_const('Qt::Dialog::Accepted', 1)
48
+ stub_null_object('Qt::VBoxLayout')
49
+ stub_null_object('Qt::HBoxLayout')
50
+ stub_null_object('Qt::Label')
51
+ stub_null_object('Qt::PushButton')
52
+ end
53
+
54
+ describe "combo_box" do
55
+ it "should not modify the inputs" do
56
+ class_double('Cosmos::ComboboxChooser').as_stubbed_const.tap do |double|
57
+ chooser = double("ComboboxChooser").as_null_object
58
+ allow(chooser).to receive(:sel_command_callback=) do |args|
59
+ # Simulate the user clicking the TEST option in the combobox
60
+ args.call("TEST")
61
+ end
62
+ allow(double).to receive(:new).and_return(chooser)
63
+ end
64
+
65
+ buttons = %w(THIS IS A TEST)
66
+ answer = combo_box("HI", *buttons)
67
+ expect(buttons).to eq %w(THIS IS A TEST)
68
+ end
69
+ end
70
+
71
+ describe "message_box" do
72
+ it "should not modify the inputs" do
73
+ class_double('Qt::MessageBox').as_stubbed_const.tap do |double|
74
+ msg_box = double("MessageBox").as_null_object
75
+ allow(msg_box).to receive_message_chain(:clickedButton, :text).and_return("TEST")
76
+ allow(double).to receive(:new).and_return(msg_box)
77
+ stub_const('Qt::MessageBox::AcceptRole', 1)
78
+ stub_const('Qt::MessageBox::RejectRole', 0)
79
+ end
80
+
81
+ buttons = %w(THIS IS A TEST)
82
+ answer = message_box("HI", *buttons)
83
+ expect(buttons).to eq %w(THIS IS A TEST)
84
+ end
85
+ end
86
+
87
+ describe "vertical_message_box" do
88
+ it "should not modify the inputs" do
89
+ stub_null_object('Qt::PushButton') do |double|
90
+ instance = double("Qt::PushButton").as_null_object
91
+ allow(instance).to receive(:connect).and_yield
92
+ allow(double).to receive(:new).and_return(instance)
93
+ end
94
+
95
+ buttons = %w(THIS IS A TEST)
96
+ answer = vertical_message_box("HI", *buttons)
97
+ expect(buttons).to eq %w(THIS IS A TEST)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1 @@
1
+ # This is data
@@ -89,6 +89,8 @@ COMMAND INST ARYCMD BIG_ENDIAN "Command with array parameter"
89
89
  PARAMETER CCSDSLENGTH 32 16 UINT 0 65535 0 "CCSDS primary header packet length"
90
90
  ID_PARAMETER PKTID 48 16 UINT 0 65535 6 "Packet id"
91
91
  ARRAY_PARAMETER ARRAY 64 64 FLOAT 640 "Array parameter"
92
+ APPEND_ARRAY_PARAMETER ARRAY2 32 UINT 320 "Array parameter"
93
+ FORMAT_STRING "0x%0X"
92
94
 
93
95
  COMMAND INST SLRPNLDEPLOY BIG_ENDIAN "Deploy solar array panels"
94
96
  PARAMETER CCSDSVER 0 3 UINT 0 0 0 "CCSDS primary header version number"
@@ -32,8 +32,7 @@ module Cosmos
32
32
  allow_any_instance_of(Interface).to receive(:connected?).and_return(true)
33
33
  allow_any_instance_of(Interface).to receive(:disconnect)
34
34
  @cts = CmdTlmServer.new
35
- @cts.start
36
- sleep 0.1
35
+ sleep 1
37
36
  @ctsi = CmdTlmServerInterface.new
38
37
  end
39
38
 
@@ -173,7 +173,7 @@ module Cosmos
173
173
  @interface.write(packet)
174
174
  end
175
175
 
176
- it "processes responses" do
176
+ it "processes responses with no ID fields" do
177
177
  rsp_pkt = Packet.new('TGT', 'READ_VOLTAGE')
178
178
  rsp_pkt.append_item("VOLTAGE", 16, :UINT)
179
179
  allow(System).to receive_message_chain(:telemetry, :packet).and_return(rsp_pkt)
@@ -199,7 +199,77 @@ module Cosmos
199
199
  @interface.write(packet)
200
200
  sleep 0.55
201
201
  expect($write_buffer).to eql("SOUR:VOLT 11, (@1)\xAB\xCD")
202
- expect(read_result.read("VOLTAGE")).to eq 10
202
+ end
203
+
204
+ it "sets the response ID to the defined ID value" do
205
+ rsp_pkt = Packet.new('TGT', 'READ_VOLTAGE')
206
+ rsp_pkt.append_item("PKT_ID", 16, :UINT, nil, :BIG_ENDIAN, :ERROR, nil, nil, nil, 1) # ID == 1
207
+ rsp_pkt.append_item("VOLTAGE", 16, :UINT)
208
+ allow(System).to receive_message_chain(:telemetry, :packet).and_return(rsp_pkt)
209
+ @interface.instance_variable_set(:@stream, TemplateStream.new)
210
+ @interface.add_protocol(TemplateProtocol, %w(0xABCD 0xABCD 0 nil 1 true 0 nil false nil nil), :READ_WRITE)
211
+ @interface.target_names = ['TGT']
212
+ packet = Packet.new('TGT', 'CMD')
213
+ packet.append_item("CMD_ID", 16, :UINT, nil, :BIG_ENDIAN, :ERROR, nil, nil, nil, 1) # ID == 1
214
+ packet.get_item("CMD_ID").default = 1
215
+ packet.append_item("VOLTAGE", 16, :UINT)
216
+ packet.get_item("VOLTAGE").default = 11
217
+ packet.append_item("CHANNEL", 16, :UINT)
218
+ packet.get_item("CHANNEL").default = 1
219
+ packet.append_item("CMD_TEMPLATE", 1024, :STRING)
220
+ packet.get_item("CMD_TEMPLATE").default = "SOUR:VOLT <VOLTAGE>, (@<CHANNEL>)"
221
+ packet.append_item("RSP_TEMPLATE", 1024, :STRING)
222
+ packet.get_item("RSP_TEMPLATE").default = "<VOLTAGE>"
223
+ packet.append_item("RSP_PACKET", 1024, :STRING)
224
+ packet.get_item("RSP_PACKET").default = "READ_VOLTAGE"
225
+ packet.restore_defaults
226
+ @interface.connect
227
+ read_result = nil
228
+ $read_buffer = "\x31\x30\xAB\xCD" # ASCII 31, 30 is '10'
229
+ Thread.new { sleep(0.5); read_result = @interface.read }
230
+ @interface.write(packet)
231
+ sleep 0.55
232
+ expect($write_buffer).to eql("SOUR:VOLT 11, (@1)\xAB\xCD")
233
+ expect(read_result.read("PKT_ID")).to eql(1) # Result ID set to the defined value
234
+ end
235
+
236
+ it "handles multiple response IDs" do
237
+ rsp_pkt = Packet.new('TGT', 'READ_VOLTAGE')
238
+ rsp_pkt.append_item("APID", 16, :UINT, nil, :BIG_ENDIAN, :ERROR, nil, nil, nil, 10) # ID == 10
239
+ rsp_pkt.append_item("PKTID", 16, :UINT, nil, :BIG_ENDIAN, :ERROR, nil, nil, nil, 20) # ID == 20
240
+ rsp_pkt.append_item("VOLTAGE", 16, :UINT)
241
+ allow(System).to receive_message_chain(:telemetry, :packet).and_return(rsp_pkt)
242
+ @interface.instance_variable_set(:@stream, TemplateStream.new)
243
+ @interface.add_protocol(TemplateProtocol, %w(0xABCD 0xABCD 0 nil 1 true 0 nil false nil nil), :READ_WRITE)
244
+ @interface.target_names = ['TGT']
245
+ packet = Packet.new('TGT', 'CMD')
246
+ packet.append_item("APID", 16, :UINT, nil, :BIG_ENDIAN, :ERROR, nil, nil, nil, 1) # ID == 1
247
+ packet.get_item("APID").default = 1
248
+ packet.append_item("PKTID", 16, :UINT, nil, :BIG_ENDIAN, :ERROR, nil, nil, nil, 2) # ID == 2
249
+ packet.get_item("PKTID").default = 2
250
+ packet.append_item("VOLTAGE", 16, :UINT)
251
+ packet.get_item("VOLTAGE").default = 11
252
+ packet.append_item("CHANNEL", 16, :UINT)
253
+ packet.get_item("CHANNEL").default = 1
254
+ packet.append_item("CMD_TEMPLATE", 1024, :STRING)
255
+ packet.get_item("CMD_TEMPLATE").default = "SOUR:VOLT <VOLTAGE>, (@<CHANNEL>)"
256
+ packet.append_item("RSP_TEMPLATE", 1024, :STRING)
257
+ packet.get_item("RSP_TEMPLATE").default = "<VOLTAGE>"
258
+ packet.append_item("RSP_PACKET", 1024, :STRING)
259
+ packet.get_item("RSP_PACKET").default = "READ_VOLTAGE"
260
+ packet.restore_defaults
261
+ # Explicitly write in values to the ID items different than the defaults
262
+ packet.write("APID", 10)
263
+ packet.write("PKTID", 20)
264
+ @interface.connect
265
+ read_result = nil
266
+ $read_buffer = "\x31\x30\xAB\xCD" # ASCII 31, 30 is '10'
267
+ Thread.new { sleep(0.5); read_result = @interface.read }
268
+ @interface.write(packet)
269
+ sleep 0.55
270
+ expect($write_buffer).to eql("SOUR:VOLT 11, (@1)\xAB\xCD")
271
+ expect(read_result.read("APID")).to eql(10) # ID item set to the defined value
272
+ expect(read_result.read("PKTID")).to eql(20) # ID item set to the defined value
203
273
  end
204
274
 
205
275
  it "handles templates with more values than the response" do