cosmos 4.0.3-java → 4.1.0-java

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 (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 +12 -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