openc3 5.13.0 → 5.14.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of openc3 might be problematic. Click here for more details.

Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -3
  3. data/bin/openc3cli +18 -15
  4. data/data/config/command_modifiers.yaml +53 -1
  5. data/lib/openc3/accessors/accessor.rb +42 -29
  6. data/lib/openc3/accessors/binary_accessor.rb +11 -1
  7. data/lib/openc3/accessors/form_accessor.rb +11 -1
  8. data/lib/openc3/accessors/http_accessor.rb +38 -0
  9. data/lib/openc3/accessors/json_accessor.rb +15 -3
  10. data/lib/openc3/accessors/template_accessor.rb +150 -0
  11. data/lib/openc3/accessors/xml_accessor.rb +11 -1
  12. data/lib/openc3/accessors.rb +1 -0
  13. data/lib/openc3/api/limits_api.rb +3 -3
  14. data/lib/openc3/api/tlm_api.rb +8 -8
  15. data/lib/openc3/interfaces/interface.rb +9 -7
  16. data/lib/openc3/interfaces/protocols/cmd_response_protocol.rb +116 -0
  17. data/lib/openc3/interfaces/tcpip_client_interface.rb +4 -0
  18. data/lib/openc3/interfaces/tcpip_server_interface.rb +5 -0
  19. data/lib/openc3/interfaces.rb +1 -1
  20. data/lib/openc3/microservices/decom_microservice.rb +5 -2
  21. data/lib/openc3/microservices/interface_microservice.rb +10 -1
  22. data/lib/openc3/microservices/log_microservice.rb +6 -1
  23. data/lib/openc3/microservices/microservice.rb +28 -0
  24. data/lib/openc3/models/cvt_model.rb +16 -12
  25. data/lib/openc3/models/gem_model.rb +20 -3
  26. data/lib/openc3/models/microservice_model.rb +6 -2
  27. data/lib/openc3/models/plugin_model.rb +6 -2
  28. data/lib/openc3/models/target_model.rb +105 -12
  29. data/lib/openc3/operators/microservice_operator.rb +23 -21
  30. data/lib/openc3/packets/commands.rb +4 -0
  31. data/lib/openc3/packets/packet.rb +92 -4
  32. data/lib/openc3/packets/packet_config.rb +62 -8
  33. data/lib/openc3/packets/telemetry.rb +4 -0
  34. data/lib/openc3/script/api_shared.rb +11 -0
  35. data/lib/openc3/script/script.rb +6 -12
  36. data/lib/openc3/streams/tcpip_socket_stream.rb +19 -0
  37. data/lib/openc3/system/system.rb +43 -1
  38. data/lib/openc3/utilities/cli_generator.rb +15 -1
  39. data/lib/openc3/utilities/local_mode.rb +1 -1
  40. data/lib/openc3/utilities/store_queued.rb +126 -0
  41. data/lib/openc3/version.rb +6 -6
  42. data/templates/plugin/plugin.gemspec +2 -2
  43. data/templates/target/targets/TARGET/public/README.txt +1 -0
  44. data/templates/tool_angular/package.json +15 -15
  45. data/templates/tool_angular/yarn.lock +184 -78
  46. data/templates/tool_react/package.json +10 -10
  47. data/templates/tool_react/yarn.lock +236 -374
  48. data/templates/tool_svelte/package.json +13 -13
  49. data/templates/tool_svelte/yarn.lock +246 -235
  50. data/templates/tool_vue/package.json +12 -12
  51. data/templates/tool_vue/yarn.lock +63 -55
  52. data/templates/widget/package.json +11 -11
  53. data/templates/widget/yarn.lock +54 -46
  54. metadata +144 -154
  55. data/lib/openc3/io/openc3_snmp.rb +0 -61
@@ -742,7 +742,7 @@ module OpenC3
742
742
  end
743
743
  end
744
744
 
745
- def update_store(system)
745
+ def update_target_model(system)
746
746
  target = system.targets[@name]
747
747
 
748
748
  # Add in the information from the target and update
@@ -754,10 +754,11 @@ module OpenC3
754
754
  @tlm_unique_id_mode = target.tlm_unique_id_mode
755
755
  @limits_groups = system.limits.groups.keys
756
756
  update()
757
+ end
757
758
 
758
- # Store Packet Definitions
759
- system.telemetry.all.each do |target_name, packets|
760
- Store.del("#{@scope}__openc3tlm__#{target_name}")
759
+ def update_store_telemetry(packet_hash, clear_old: true)
760
+ packet_hash.each do |target_name, packets|
761
+ Store.del("#{@scope}__openc3tlm__#{target_name}") if clear_old
761
762
  packets.each do |packet_name, packet|
762
763
  Logger.info "Configuring tlm packet: #{target_name} #{packet_name}"
763
764
  begin
@@ -773,8 +774,11 @@ module OpenC3
773
774
  CvtModel.set(json_hash, target_name: packet.target_name, packet_name: packet.packet_name, scope: @scope)
774
775
  end
775
776
  end
776
- system.commands.all.each do |target_name, packets|
777
- Store.del("#{@scope}__openc3cmd__#{target_name}")
777
+ end
778
+
779
+ def update_store_commands(packet_hash, clear_old: true)
780
+ packet_hash.each do |target_name, packets|
781
+ Store.del("#{@scope}__openc3cmd__#{target_name}") if clear_old
778
782
  packets.each do |packet_name, packet|
779
783
  Logger.info "Configuring cmd packet: #{target_name} #{packet_name}"
780
784
  begin
@@ -785,7 +789,9 @@ module OpenC3
785
789
  end
786
790
  end
787
791
  end
788
- # Store Limits Groups
792
+ end
793
+
794
+ def update_store_limits_groups(system)
789
795
  system.limits.groups.each do |group, items|
790
796
  begin
791
797
  Store.hset("#{@scope}__limits_groups", group, JSON.generate(items))
@@ -794,23 +800,113 @@ module OpenC3
794
800
  raise err
795
801
  end
796
802
  end
797
- # Merge in Limits Sets
803
+ end
804
+
805
+ def update_store_limits_sets(system)
798
806
  sets = Store.hgetall("#{@scope}__limits_sets")
799
807
  sets ||= {}
800
808
  system.limits.sets.each do |set|
801
809
  sets[set.to_s] = "false" unless sets.key?(set.to_s)
802
810
  end
803
811
  Store.hmset("#{@scope}__limits_sets", *sets)
812
+ end
804
813
 
814
+ def update_store_item_map
805
815
  # Create item_map
806
816
  item_map_key = "#{@scope}__#{@name}__item_to_packet_map"
807
817
  item_map = self.class.build_item_to_packet_map(@name, scope: @scope)
808
818
  Store.set(item_map_key, JSON.generate(item_map, :allow_nan => true))
809
819
  @@item_map_cache[@name] = [Time.now, item_map]
820
+ end
810
821
 
822
+ def update_store(system, clear_old: true)
823
+ update_target_model(system)
824
+ update_store_telemetry(system.telemetry.all, clear_old: clear_old)
825
+ update_store_commands(system.commands.all, clear_old: clear_old)
826
+ update_store_limits_groups(system)
827
+ update_store_limits_sets(system)
828
+ update_store_item_map()
811
829
  return system
812
830
  end
813
831
 
832
+ def dynamic_update(packets, cmd_or_tlm = :TELEMETRY, filename = "dynamic_tlm.txt")
833
+ # Build hash of targets/packets
834
+ packet_hash = {}
835
+ packets.each do |packet|
836
+ target_name = packet.target_name.upcase
837
+ packet_hash[target_name] ||= {}
838
+ packet_name = packet.packet_name.upcase
839
+ packet_hash[target_name][packet_name] = packet
840
+ end
841
+
842
+ # Update Redis
843
+ if cmd_or_tlm == :TELEMETRY
844
+ update_store_telemetry(packet_hash, clear_old: false)
845
+ update_store_item_map()
846
+ else
847
+ update_store_commands(packet_hash, clear_old: false)
848
+ end
849
+
850
+ # Build dynamic file for cmd_tlm
851
+ configs = {}
852
+ packets.each do |packet|
853
+ target_name = packet.target_name.upcase
854
+ configs[target_name] ||= ""
855
+ config = configs[target_name]
856
+ config << packet.to_config(cmd_or_tlm)
857
+ config << "\n"
858
+ end
859
+ configs.each do |target_name, config|
860
+ begin
861
+ bucket_key = "#{@scope}/targets_modified/#{target_name}/cmd_tlm/#{filename}"
862
+ client = Bucket.getClient()
863
+ client.put_object(
864
+ # Use targets_modified to save modifications
865
+ # This keeps the original target clean (read-only)
866
+ bucket: ENV['OPENC3_CONFIG_BUCKET'],
867
+ key: bucket_key,
868
+ body: config
869
+ )
870
+ end
871
+ end
872
+
873
+ # Inform microservices of new topics
874
+ # Need to tell loggers to log, and decom to decom
875
+ # We do this for no downtime
876
+ raw_topics = []
877
+ decom_topics = []
878
+ packets.each do |packet|
879
+ if cmd_or_tlm == :TELEMETRY
880
+ raw_topics << "#{@scope}__TELEMETRY__{#{@name}}__#{packet.packet_name.upcase}"
881
+ decom_topics << "#{@scope}__DECOM__{#{@name}}__#{packet.packet_name.upcase}"
882
+ else
883
+ raw_topics << "#{@scope}__COMMAND__{#{@name}}__#{packet.packet_name.upcase}"
884
+ decom_topics << "#{@scope}__DECOMCMD__{#{@name}}__#{packet.packet_name.upcase}"
885
+ end
886
+ end
887
+ if cmd_or_tlm == :TELEMETRY
888
+ Topic.write_topic("MICROSERVICE__#{@scope}__PACKETLOG__#{@name}", {'command' => 'ADD_TOPICS', 'topics' => raw_topics.as_json.to_json})
889
+ add_topics_to_microservice("#{@scope}__PACKETLOG__#{@name}", raw_topics)
890
+ Topic.write_topic("MICROSERVICE__#{@scope}__DECOMLOG__#{@name}", {'command' => 'ADD_TOPICS', 'topics' => decom_topics.as_json.to_json})
891
+ add_topics_to_microservice("#{@scope}__DECOMLOG__#{@name}", decom_topics)
892
+ Topic.write_topic("MICROSERVICE__#{@scope}__DECOM__#{@name}", {'command' => 'ADD_TOPICS', 'topics' => raw_topics.as_json.to_json})
893
+ add_topics_to_microservice("#{@scope}__DECOM__#{@name}", raw_topics)
894
+ else
895
+ Topic.write_topic("MICROSERVICE__#{@scope}__COMMANDLOG__#{@name}", {'command' => 'ADD_TOPICS', 'topics' => raw_topics.as_json.to_json})
896
+ add_topics_to_microservice("#{@scope}__COMMANDLOG__#{@name}", raw_topics)
897
+ Topic.write_topic("MICROSERVICE__#{@scope}__DECOMCMDLOG__#{@name}", {'command' => 'ADD_TOPICS', 'topics' => decom_topics.as_json.to_json})
898
+ add_topics_to_microservice("#{@scope}__DECOMCMDLOG__#{@name}", decom_topics)
899
+ end
900
+ end
901
+
902
+ def add_topics_to_microservice(microservice_name, topics)
903
+ model = MicroserviceModel.get_model(name: microservice_name, scope: @scope)
904
+ model.topics.concat(topics)
905
+ model.topics.uniq!
906
+ model.ignore_changes = true # Don't restart the microservice right now
907
+ model.update
908
+ end
909
+
814
910
  def deploy_commmandlog_microservice(gem_path, variables, topics, instance = nil, parent = nil)
815
911
  microservice_name = "#{@scope}__COMMANDLOG#{instance}__#{@name}"
816
912
  microservice = MicroserviceModel.new(
@@ -990,6 +1086,7 @@ module OpenC3
990
1086
  cmd: ["ruby", "multi_microservice.rb", *@children],
991
1087
  work_dir: '/openc3/lib/openc3/microservices',
992
1088
  plugin: @plugin,
1089
+ needs_dependencies: @needs_dependencies,
993
1090
  scope: @scope
994
1091
  )
995
1092
  microservice.create
@@ -1048,7 +1145,6 @@ module OpenC3
1048
1145
  decom_command_topic_list = []
1049
1146
  packet_topic_list = []
1050
1147
  decom_topic_list = []
1051
- reduced_topic_list = []
1052
1148
  begin
1053
1149
  system.commands.packets(@name).each do |packet_name, packet|
1054
1150
  command_topic_list << "#{@scope}__COMMAND__{#{@name}}__#{packet_name}"
@@ -1061,9 +1157,6 @@ module OpenC3
1061
1157
  system.telemetry.packets(@name).each do |packet_name, packet|
1062
1158
  packet_topic_list << "#{@scope}__TELEMETRY__{#{@name}}__#{packet_name}"
1063
1159
  decom_topic_list << "#{@scope}__DECOM__{#{@name}}__#{packet_name}"
1064
- reduced_topic_list << "#{@scope}__REDUCED_MINUTE__{#{@name}}__#{packet_name}"
1065
- reduced_topic_list << "#{@scope}__REDUCED_HOUR__{#{@name}}__#{packet_name}"
1066
- reduced_topic_list << "#{@scope}__REDUCED_DAY__{#{@name}}__#{packet_name}"
1067
1160
  end
1068
1161
  rescue
1069
1162
  # No telemetry packets for this target
@@ -98,28 +98,30 @@ module OpenC3
98
98
  previous_parent = @previous_microservices[microservice_name]['parent']
99
99
  if @previous_microservices[microservice_name] != microservice_config
100
100
  # CHANGED
101
- scope = microservice_name.split("__")[0]
102
- Logger.info("Changed microservice detected: #{microservice_name}\nWas: #{@previous_microservices[microservice_name]}\nIs: #{microservice_config}", scope: scope)
103
- if parent or previous_parent
104
- if parent == previous_parent
105
- # Same Parent - Respawn parent
106
- @changed_microservices[parent] = @microservices[parent] if @microservices[parent] and @previous_microservices[parent]
107
- elsif parent and previous_parent
108
- # Parent changed - Respawn both parents
109
- @changed_microservices[parent] = @microservices[parent] if @microservices[parent] and @previous_microservices[parent]
110
- @changed_microservices[previous_parent] = @microservices[previous_parent] if @microservices[previous_parent] and @previous_microservices[previous_parent]
111
- elsif parent
112
- # Moved under a parent - Respawn parent and kill standalone
113
- @changed_microservices[parent] = @microservices[parent] if @microservices[parent] and @previous_microservices[parent]
114
- @removed_microservices[microservice_name] = microservice_config
115
- else # previous_parent
116
- # Moved to standalone - Respawn previous parent and make new
117
- @changed_microservices[previous_parent] = @microservices[previous_parent] if @microservices[previous_parent] and @previous_microservices[previous_parent]
118
- @new_microservices[microservice_name] = microservice_config
101
+ if not microservice_config['ignore_changes']
102
+ scope = microservice_name.split("__")[0]
103
+ Logger.info("Changed microservice detected: #{microservice_name}\nWas: #{@previous_microservices[microservice_name]}\nIs: #{microservice_config}", scope: scope)
104
+ if parent or previous_parent
105
+ if parent == previous_parent
106
+ # Same Parent - Respawn parent
107
+ @changed_microservices[parent] = @microservices[parent] if @microservices[parent] and @previous_microservices[parent]
108
+ elsif parent and previous_parent
109
+ # Parent changed - Respawn both parents
110
+ @changed_microservices[parent] = @microservices[parent] if @microservices[parent] and @previous_microservices[parent]
111
+ @changed_microservices[previous_parent] = @microservices[previous_parent] if @microservices[previous_parent] and @previous_microservices[previous_parent]
112
+ elsif parent
113
+ # Moved under a parent - Respawn parent and kill standalone
114
+ @changed_microservices[parent] = @microservices[parent] if @microservices[parent] and @previous_microservices[parent]
115
+ @removed_microservices[microservice_name] = microservice_config
116
+ else # previous_parent
117
+ # Moved to standalone - Respawn previous parent and make new
118
+ @changed_microservices[previous_parent] = @microservices[previous_parent] if @microservices[previous_parent] and @previous_microservices[previous_parent]
119
+ @new_microservices[microservice_name] = microservice_config
120
+ end
121
+ else
122
+ # Respawn regular microservice
123
+ @changed_microservices[microservice_name] = microservice_config
119
124
  end
120
- else
121
- # Respawn regular microservice
122
- @changed_microservices[microservice_name] = microservice_config
123
125
  end
124
126
  end
125
127
  else
@@ -303,6 +303,10 @@ module OpenC3
303
303
  @config.commands
304
304
  end
305
305
 
306
+ def dynamic_add_packet(packet, affect_ids: false)
307
+ @config.dynamic_add_packet(packet, :COMMAND, affect_ids: affect_ids)
308
+ end
309
+
306
310
  protected
307
311
 
308
312
  def set_parameters(command, params, range_checking)
@@ -89,6 +89,18 @@ module OpenC3
89
89
  # @return [String] Base data for building packet
90
90
  attr_reader :template
91
91
 
92
+ # @return [Array<Target Name, Packet Name>] Related response packet
93
+ attr_accessor :response
94
+
95
+ # @return [Array<Target Name, Packet Name>] Related error response packet
96
+ attr_accessor :error_response
97
+
98
+ # @return [Array<Target Name, Screen Name>] Related telemetry screen
99
+ attr_accessor :screen
100
+
101
+ # @return [Array<Array<Target Name, Packet Name, Item Name>>] Related items
102
+ attr_accessor :related_items
103
+
92
104
  # Valid format types
93
105
  VALUE_TYPES = [:RAW, :CONVERTED, :FORMATTED, :WITH_UNITS]
94
106
 
@@ -297,6 +309,10 @@ module OpenC3
297
309
  @config_name
298
310
  end
299
311
 
312
+ def clear_config_name
313
+ @config_name = nil
314
+ end
315
+
300
316
  # (see Structure#buffer=)
301
317
  def buffer=(buffer)
302
318
  synchronize() do
@@ -306,7 +322,6 @@ module OpenC3
306
322
  Logger.instance.error "#{@target_name} #{@packet_name} received with actual packet length of #{buffer.length} but defined length of #{@defined_length}"
307
323
  end
308
324
  @read_conversion_cache.clear if @read_conversion_cache
309
- process()
310
325
  end
311
326
  end
312
327
 
@@ -861,6 +876,33 @@ module OpenC3
861
876
  item.description = 'OpenC3 packet received count'
862
877
  end
863
878
 
879
+ # Reset the packet to just derived items
880
+ def clear_all_non_derived_items
881
+ @defined_length = 0
882
+ @defined_length_bits = 0
883
+ @pos_bit_size = 0
884
+ @neg_bit_size = 0
885
+ @fixed_size = true
886
+ @short_buffer_allowed = false
887
+ @id_items = nil
888
+ @limits_items = nil
889
+ new_items = {}
890
+ new_sorted_items = []
891
+ @items.each do |name, item|
892
+ if item.data_type == :DERIVED
893
+ new_items[name] = item
894
+ end
895
+ end
896
+ @sorted_items.each do |item|
897
+ if item.data_type == :DERIVED
898
+ new_sorted_items << item
899
+ end
900
+ end
901
+ @items = new_items
902
+ @sorted_items = new_sorted_items
903
+ clear_config_name()
904
+ end
905
+
864
906
  # Enable limits on an item by name
865
907
  #
866
908
  # @param name [String] Name of the item to enable limits
@@ -1008,6 +1050,13 @@ module OpenC3
1008
1050
  else
1009
1051
  config << "COMMAND #{@target_name.to_s.quote_if_necessary} #{@packet_name.to_s.quote_if_necessary} #{@default_endianness} \"#{@description}\"\n"
1010
1052
  end
1053
+ if @accessor.class.to_s != 'OpenC3::BinaryAccessor'
1054
+ config << " ACCESSOR #{@accessor.class.to_s} #{@accessor.args.map { |a| a.to_s.quote_if_necessary }.join(" ")}\n"
1055
+ end
1056
+ # TODO: Add TEMPLATE_ENCODED so this can always be done inline regardless of content
1057
+ if @template
1058
+ config << " TEMPLATE '#{@template}'"
1059
+ end
1011
1060
  config << " ALLOW_SHORT\n" if @short_buffer_allowed
1012
1061
  config << " HAZARDOUS #{@hazardous_description.to_s.quote_if_necessary}\n" if @hazardous
1013
1062
  config << " DISABLE_MESSAGES\n" if @messages_disabled
@@ -1025,7 +1074,7 @@ module OpenC3
1025
1074
 
1026
1075
  if @meta
1027
1076
  @meta.each do |key, values|
1028
- config << " META #{key.to_s.quote_if_necessary} #{values.map { |a| a..to_s.quote_if_necessary }.join(" ")}\n"
1077
+ config << " META #{key.to_s.quote_if_necessary} #{values.map { |a| a.to_s.quote_if_necessary }.join(" ")}\n"
1029
1078
  end
1030
1079
  end
1031
1080
 
@@ -1043,6 +1092,20 @@ module OpenC3
1043
1092
  end
1044
1093
  end
1045
1094
 
1095
+ if @response
1096
+ config << " RESPONSE #{@response[0].to_s.quote_if_necessary} #{@response[1].to_s.quote_if_necessary}"
1097
+ end
1098
+ if @error_response
1099
+ config << " ERROR_RESPONSE #{@error_response[0].to_s.quote_if_necessary} #{@error_response[1].to_s.quote_if_necessary}"
1100
+ end
1101
+ if @screen
1102
+ config << " SCREEN #{@screen[0].to_s.quote_if_necessary} #{@screen[1].to_s.quote_if_necessary}"
1103
+ end
1104
+ if @related_items
1105
+ @related_items.each do |target_name, packet_name, item_name|
1106
+ config << " RELATED_ITEM #{target_name.to_s.quote_if_necessary} #{packet_name.to_s.quote_if_necessary} #{item_name.to_s.quote_if_necessary}"
1107
+ end
1108
+ end
1046
1109
  config
1047
1110
  end
1048
1111
 
@@ -1086,6 +1149,18 @@ module OpenC3
1086
1149
  items << item.as_json(*a)
1087
1150
  end
1088
1151
  end
1152
+ if @response
1153
+ config['response'] = @response
1154
+ end
1155
+ if @error_response
1156
+ config['error_response'] = @error_response
1157
+ end
1158
+ if @screen
1159
+ config['screen'] = @screen
1160
+ end
1161
+ if @related_items
1162
+ config['related_items'] = @related_items
1163
+ end
1089
1164
 
1090
1165
  config
1091
1166
  end
@@ -1117,6 +1192,19 @@ module OpenC3
1117
1192
  hash['items'].each do |item|
1118
1193
  packet.define(PacketItem.from_json(item))
1119
1194
  end
1195
+ if hash['response']
1196
+ packet.response = hash['response']
1197
+ end
1198
+ if hash['error_response']
1199
+ packet.error_response = hash['error_response']
1200
+ end
1201
+ if hash['screen']
1202
+ packet.screen = hash['screen']
1203
+ end
1204
+ if hash['related_items']
1205
+ packet.related_items = hash['related_items']
1206
+ end
1207
+
1120
1208
  packet
1121
1209
  end
1122
1210
 
@@ -1144,8 +1232,6 @@ module OpenC3
1144
1232
  json_hash
1145
1233
  end
1146
1234
 
1147
- protected
1148
-
1149
1235
  # Performs packet specific processing on the packet.
1150
1236
  # Intended to only be run once for each packet received
1151
1237
  def process(buffer = @buffer)
@@ -1156,6 +1242,8 @@ module OpenC3
1156
1242
  end
1157
1243
  end
1158
1244
 
1245
+ protected
1246
+
1159
1247
  def handle_limits_states(item, value)
1160
1248
  # Retrieve limits state for the given value
1161
1249
  limits_state = item.state_colors[value]
@@ -214,7 +214,8 @@ module OpenC3
214
214
  'PARAMETER', 'ID_ITEM', 'ID_PARAMETER', 'ARRAY_ITEM', 'ARRAY_PARAMETER', 'APPEND_ITEM',\
215
215
  'APPEND_PARAMETER', 'APPEND_ID_ITEM', 'APPEND_ID_PARAMETER', 'APPEND_ARRAY_ITEM',\
216
216
  'APPEND_ARRAY_PARAMETER', 'ALLOW_SHORT', 'HAZARDOUS', 'PROCESSOR', 'META',\
217
- 'DISABLE_MESSAGES', 'HIDDEN', 'DISABLED', 'ACCESSOR', 'TEMPLATE', 'TEMPLATE_FILE'
217
+ 'DISABLE_MESSAGES', 'HIDDEN', 'DISABLED', 'ACCESSOR', 'TEMPLATE', 'TEMPLATE_FILE',\
218
+ 'RESPONSE', 'ERROR_RESPONSE', 'SCREEN', 'RELATED_ITEM'
218
219
  raise parser.error("No current packet for #{keyword}") unless @current_packet
219
220
 
220
221
  process_current_packet(parser, keyword, params)
@@ -314,30 +315,60 @@ module OpenC3
314
315
  hash = @cmd_id_value_hash[@current_packet.target_name]
315
316
  hash = {} unless hash
316
317
  @cmd_id_value_hash[@current_packet.target_name] = hash
317
- update_id_value_hash(hash)
318
+ update_id_value_hash(@current_packet, hash)
318
319
  else
319
320
  @telemetry[@current_packet.target_name][@current_packet.packet_name] = @current_packet
320
321
  hash = @tlm_id_value_hash[@current_packet.target_name]
321
322
  hash = {} unless hash
322
323
  @tlm_id_value_hash[@current_packet.target_name] = hash
323
- update_id_value_hash(hash)
324
+ update_id_value_hash(@current_packet, hash)
324
325
  end
325
326
  @current_packet = nil
326
327
  @current_item = nil
327
328
  end
328
329
  end
329
330
 
331
+ def dynamic_add_packet(packet, cmd_or_tlm = :TELEMETRY, affect_ids: false)
332
+ if cmd_or_tlm == :COMMAND
333
+ @commands[packet.target_name][packet.packet_name] = packet
334
+
335
+ if affect_ids
336
+ hash = @cmd_id_value_hash[packet.target_name]
337
+ hash = {} unless hash
338
+ @cmd_id_value_hash[packet.target_name] = hash
339
+ update_id_value_hash(packet, hash)
340
+ end
341
+ else
342
+ @telemetry[packet.target_name][packet.packet_name] = packet
343
+
344
+ # Update latest_data lookup for telemetry
345
+ packet.sorted_items.each do |item|
346
+ target_latest_data = @latest_data[packet.target_name]
347
+ target_latest_data[item.name] ||= []
348
+ latest_data_packets = target_latest_data[item.name]
349
+ latest_data_packets << packet unless latest_data_packets.include?(packet)
350
+ end
351
+
352
+ if affect_ids
353
+ hash = @tlm_id_value_hash[packet.target_name]
354
+ hash = {} unless hash
355
+ @tlm_id_value_hash[packet.target_name] = hash
356
+ update_id_value_hash(packet, hash)
357
+ end
358
+ end
359
+ end
360
+
330
361
  protected
331
362
 
332
- def update_id_value_hash(hash)
333
- if @current_packet.id_items.length > 0
363
+ def update_id_value_hash(packet, hash)
364
+ if packet.id_items.length > 0
334
365
  key = []
335
- @current_packet.id_items.each do |item|
366
+ packet.id_items.each do |item|
336
367
  key << item.id_value
337
368
  end
338
- hash[key] = @current_packet
369
+ hash[key] = packet
339
370
  else
340
- hash['CATCHALL'.freeze] = @current_packet
371
+ hash['CATCHALL'.freeze] = packet
341
372
  end
342
373
  end
343
374
 
@@ -460,7 +491,30 @@ module OpenC3
460
491
  rescue Exception => err
461
492
  raise parser.error(err)
462
493
  end
494
+
495
+ when 'RESPONSE'
496
+ usage = "#{keyword} <Target Name> <Packet Name>"
497
+ parser.verify_num_parameters(2, 2, usage)
498
+ @current_packet.response = [params[0].upcase, params[1].upcase]
499
+
500
+ when 'ERROR_RESPONSE'
501
+ usage = "#{keyword} <Target Name> <Packet Name>"
502
+ parser.verify_num_parameters(2, 2, usage)
503
+ @current_packet.error_response = [params[0].upcase, params[1].upcase]
504
+
505
+ when 'SCREEN'
506
+ usage = "#{keyword} <Target Name> <Screen Name>"
507
+ parser.verify_num_parameters(2, 2, usage)
508
+ @current_packet.screen = [params[0].upcase, params[1].upcase]
509
+
510
+ when 'RELATED_ITEM'
511
+ usage = "#{keyword} <Target Name> <Packet Name> <Item Name>"
512
+ parser.verify_num_parameters(3, 3, usage)
513
+ @current_packet.related_items ||= []
514
+ @current_packet.related_items << [params[0].upcase, params[1].upcase, params[2].upcase]
515
+
463
516
  end
517
+
464
518
  end
465
519
 
466
520
  def process_current_item(parser, keyword, params)
@@ -442,5 +442,9 @@ module OpenC3
442
442
  def all
443
443
  @config.telemetry
444
444
  end
445
+
446
+ def dynamic_add_packet(packet, affect_ids: false)
447
+ @config.dynamic_add_packet(packet, :TELEMETRY, affect_ids: affect_ids)
448
+ end
445
449
  end # class Telemetry
446
450
  end
@@ -525,6 +525,17 @@ module OpenC3
525
525
  # Private implementation details
526
526
  ###########################################################################
527
527
 
528
+ # This must be here for custom microservices that might block.
529
+ # Overriden by running_script.rb for script sleep
530
+ def openc3_script_sleep(sleep_time = nil)
531
+ if sleep_time
532
+ sleep(sleep_time)
533
+ else
534
+ prompt("Press any key to continue...")
535
+ end
536
+ return false
537
+ end
538
+
528
539
  # Creates a string with the parameters upcased
529
540
  def _upcase(target_name, packet_name, item_name)
530
541
  "#{target_name.upcase} #{packet_name.upcase} #{item_name.upcase}"
@@ -93,17 +93,8 @@ module OpenC3
93
93
  $script_runner_api_server = nil
94
94
  end
95
95
 
96
- # This isn't part of the public API because users should use wait()
97
- def openc3_script_sleep(sleep_time = nil)
98
- if sleep_time
99
- sleep(sleep_time)
100
- else
101
- prompt("Press any key to continue...")
102
- end
103
- return false
104
- end
105
-
106
96
  # Internal method used in scripts when encountering a hazardous command
97
+ # Not part of public APIs but must be implemented here
107
98
  def prompt_for_hazardous(target_name, cmd_name, hazardous_description)
108
99
  loop do
109
100
  message = "Warning: Command #{target_name} #{cmd_name} is Hazardous. "
@@ -140,6 +131,7 @@ module OpenC3
140
131
  default = ''
141
132
  if blank_or_default != true && blank_or_default != false
142
133
  question << " (default = #{blank_or_default})"
134
+ default = blank_or_default.to_s
143
135
  allow_blank = true
144
136
  else
145
137
  allow_blank = blank_or_default
@@ -150,7 +142,9 @@ module OpenC3
150
142
  answer.chomp!
151
143
  break if allow_blank
152
144
  end
153
- answer = default if answer.empty? and !default.empty?
145
+ if answer.empty? and !default.empty?
146
+ answer = default
147
+ end
154
148
  return answer
155
149
  end
156
150
 
@@ -162,7 +156,7 @@ module OpenC3
162
156
 
163
157
  def message_box(string, *buttons, **options)
164
158
  print "#{string} (#{buttons.join(", ")}): "
165
- print "Details: #{details}\n" if details
159
+ print "Details: #{details}\n" if options['details']
166
160
  gets.chomp
167
161
  end
168
162
 
@@ -160,5 +160,24 @@ module OpenC3
160
160
  @pipe_writer.write('.')
161
161
  @connected = false
162
162
  end
163
+
164
+ def set_option(option_name, option_values)
165
+ option_name_upcase = option_name.upcase
166
+
167
+ case option_name_upcase
168
+ when 'KEEPALIVE'
169
+ @write_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
170
+ @read_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
171
+ when 'KEEPCNT'
172
+ @write_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, Integer(option_values[0]))
173
+ @read_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, Integer(option_values[0]))
174
+ when 'KEEPIDLE'
175
+ @write_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, Integer(option_values[0]))
176
+ @read_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, Integer(option_values[0]))
177
+ when 'KEEPINTVL'
178
+ @write_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, Integer(option_values[0]))
179
+ @read_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, Integer(option_values[0]))
180
+ end
181
+ end
163
182
  end
164
183
  end