openc3 5.0.8 → 5.0.10

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.

Potentially problematic release.


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

Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/openc3cli +128 -95
  3. data/data/config/_id_items.yaml +2 -1
  4. data/data/config/_id_params.yaml +2 -1
  5. data/data/config/_items.yaml +2 -1
  6. data/data/config/_params.yaml +2 -1
  7. data/data/config/command_modifiers.yaml +30 -0
  8. data/data/config/item_modifiers.yaml +10 -0
  9. data/data/config/microservice.yaml +12 -0
  10. data/data/config/param_item_modifiers.yaml +10 -0
  11. data/data/config/plugins.yaml +0 -9
  12. data/data/config/telemetry_modifiers.yaml +10 -0
  13. data/data/config/widgets.yaml +49 -73
  14. data/ext/openc3/ext/packet/packet.c +20 -2
  15. data/ext/openc3/ext/structure/structure.c +12 -17
  16. data/lib/openc3/accessors/accessor.rb +71 -0
  17. data/lib/openc3/accessors/binary_accessor.rb +1226 -0
  18. data/lib/openc3/accessors/cbor_accessor.rb +83 -0
  19. data/lib/openc3/accessors/html_accessor.rb +28 -0
  20. data/lib/openc3/accessors/json_accessor.rb +131 -0
  21. data/lib/openc3/accessors/xml_accessor.rb +67 -0
  22. data/lib/openc3/accessors.rb +23 -0
  23. data/lib/openc3/api/cmd_api.rb +5 -2
  24. data/lib/openc3/api/interface_api.rb +15 -0
  25. data/lib/openc3/api/limits_api.rb +3 -3
  26. data/lib/openc3/config/config_parser.rb +10 -4
  27. data/lib/openc3/core_ext/file.rb +5 -0
  28. data/lib/openc3/core_ext/tempfile.rb +20 -0
  29. data/lib/openc3/io/json_rpc.rb +3 -3
  30. data/lib/openc3/microservices/cleanup_microservice.rb +7 -5
  31. data/lib/openc3/models/cvt_model.rb +1 -10
  32. data/lib/openc3/models/interface_model.rb +41 -0
  33. data/lib/openc3/models/microservice_model.rb +33 -1
  34. data/lib/openc3/models/plugin_model.rb +1 -1
  35. data/lib/openc3/models/target_model.rb +85 -68
  36. data/lib/openc3/packets/binary_accessor.rb +2 -1207
  37. data/lib/openc3/packets/packet.rb +106 -6
  38. data/lib/openc3/packets/packet_config.rb +30 -7
  39. data/lib/openc3/packets/parsers/limits_response_parser.rb +1 -3
  40. data/lib/openc3/packets/parsers/processor_parser.rb +1 -2
  41. data/lib/openc3/packets/parsers/xtce_parser.rb +68 -3
  42. data/lib/openc3/packets/structure.rb +39 -14
  43. data/lib/openc3/packets/structure_item.rb +15 -1
  44. data/lib/openc3/script/storage.rb +19 -1
  45. data/lib/openc3/utilities/local_mode.rb +521 -0
  46. data/lib/openc3/utilities/simulated_target.rb +3 -2
  47. data/lib/openc3/utilities/target_file.rb +146 -0
  48. data/lib/openc3/version.rb +5 -5
  49. data/lib/openc3.rb +1 -0
  50. metadata +45 -7
@@ -0,0 +1,83 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
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 Affero 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
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ require 'cbor'
17
+ require 'openc3/accessors/json_accessor'
18
+
19
+ module OpenC3
20
+ class CborAccessor < JsonAccessor
21
+ def self.read_item(item, buffer)
22
+ return nil if item.data_type == :DERIVED
23
+ if String === buffer
24
+ parsed = CBOR.decode(buffer)
25
+ else
26
+ parsed = buffer
27
+ end
28
+ return super(item, parsed)
29
+ end
30
+
31
+ def self.write_item(item, value, buffer)
32
+ return nil if item.data_type == :DERIVED
33
+
34
+ # Convert to ruby objects
35
+ if String === buffer
36
+ decoded = CBOR.decode(buffer)
37
+ else
38
+ decoded = buffer
39
+ end
40
+
41
+ # Write the value
42
+ write_item_internal(item, value, decoded)
43
+
44
+ # Update buffer
45
+ if String === buffer
46
+ buffer.replace(decoded.to_cbor)
47
+ end
48
+
49
+ return buffer
50
+ end
51
+
52
+ def self.read_items(items, buffer)
53
+ # Prevent JsonPath from decoding every call
54
+ if String === buffer
55
+ decoded = CBOR.decode(buffer)
56
+ else
57
+ decoded = buffer
58
+ end
59
+ super(items, decoded)
60
+ end
61
+
62
+ def self.write_items(items, values, buffer)
63
+ # Convert to ruby objects
64
+ if String === buffer
65
+ decoded = CBOR.decode(buffer)
66
+ else
67
+ decoded = buffer
68
+ end
69
+
70
+ items.each_with_index do |item, index|
71
+ write_item_internal(item, values[index], decoded)
72
+ end
73
+
74
+ # Update buffer
75
+ if String === buffer
76
+ buffer.replace(decoded.to_cbor)
77
+ end
78
+
79
+ return buffer
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
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 Affero 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
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ require 'openc3/accessors/xml_accessor'
17
+
18
+ module OpenC3
19
+ class HtmlAccessor < XmlAccessor
20
+ def self.buffer_to_doc(buffer)
21
+ Nokogiri.HTML(buffer)
22
+ end
23
+
24
+ def self.doc_to_buffer(doc)
25
+ doc.to_html
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,131 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
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 Affero 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
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ require 'json'
17
+ require 'jsonpath'
18
+ require 'openc3/accessors/accessor'
19
+
20
+ module OpenC3
21
+ class JsonAccessor < Accessor
22
+ def self.read_item(item, buffer)
23
+ return nil if item.data_type == :DERIVED
24
+ return JsonPath.on(buffer, item.key).first
25
+ end
26
+
27
+ def self.write_item(item, value, buffer)
28
+ return nil if item.data_type == :DERIVED
29
+
30
+ # Convert to ruby objects
31
+ if String === buffer
32
+ decoded = JSON.parse(buffer, :allow_nan => true)
33
+ else
34
+ decoded = buffer
35
+ end
36
+
37
+ # Write the value
38
+ write_item_internal(item, value, decoded)
39
+
40
+ # Update buffer
41
+ if String === buffer
42
+ buffer.replace(JSON.generate(decoded, :allow_nan => true))
43
+ end
44
+
45
+ return buffer
46
+ end
47
+
48
+ def self.read_items(items, buffer)
49
+ # Prevent JsonPath from decoding every call
50
+ if String === buffer
51
+ decoded = JSON.parse(buffer)
52
+ else
53
+ decoded = buffer
54
+ end
55
+ super(items, decoded)
56
+ end
57
+
58
+ def self.write_items(items, values, buffer)
59
+ # Start with an empty object if no buffer
60
+ buffer.replace("{}") if buffer.length == 0 or buffer[0] == "\x00"
61
+
62
+ # Convert to ruby objects
63
+ if String === buffer
64
+ decoded = JSON.parse(buffer, :allow_nan => true)
65
+ else
66
+ decoded = buffer
67
+ end
68
+
69
+ items.each_with_index do |item, index|
70
+ write_item_internal(item, values[index], decoded)
71
+ end
72
+
73
+ # Update buffer
74
+ if String === buffer
75
+ buffer.replace(JSON.generate(decoded, :allow_nan => true))
76
+ end
77
+
78
+ return buffer
79
+ end
80
+
81
+ def self.write_item_internal(item, value, decoded)
82
+ return nil if item.data_type == :DERIVED
83
+
84
+ # Save traversal state
85
+ parent_node = nil
86
+ parent_key = nil
87
+ node = decoded
88
+
89
+ # Parse the JsonPath
90
+ json_path = JsonPath.new(item.key)
91
+
92
+ # Handle each token
93
+ json_path.path.each do |token|
94
+ case token
95
+ when '$'
96
+ # Ignore start - it is implied
97
+ next
98
+ when /\[.*\]/
99
+ # Array or Hash Index
100
+ if token.index("'") # Hash index
101
+ key = token[2..-3]
102
+ if not (Hash === node)
103
+ node = {}
104
+ parent_node[parent_key] = node
105
+ end
106
+ parent_node = node
107
+ parent_key = key
108
+ node = node[key]
109
+ else # Array index
110
+ key = token[1..-2].to_i
111
+ if not (Array === node)
112
+ node = []
113
+ parent_node[parent_key] = node
114
+ end
115
+ parent_node = node
116
+ parent_key = key
117
+ node = node[key]
118
+ end
119
+ else
120
+ raise "Unsupported key/token: #{item.key} - #{token}"
121
+ end
122
+ end
123
+ if parent_node
124
+ parent_node[parent_key] = value
125
+ else
126
+ decoded.replace(value)
127
+ end
128
+ return decoded
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
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 Affero 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
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ require 'nokogiri'
17
+ require 'openc3/accessors/accessor'
18
+
19
+ module OpenC3
20
+ class XmlAccessor < Accessor
21
+ def self.read_item(item, buffer)
22
+ return nil if item.data_type == :DERIVED
23
+ doc = buffer_to_doc(buffer)
24
+ return convert_to_type(doc.xpath(item.key).first.to_s, item)
25
+ end
26
+
27
+ def self.write_item(item, value, buffer)
28
+ return nil if item.data_type == :DERIVED
29
+ doc = buffer_to_doc(buffer)
30
+ node = doc.xpath(item.key).first
31
+ node.content = value.to_s
32
+ buffer.replace(doc_to_buffer(doc))
33
+ end
34
+
35
+ def self.read_items(items, buffer)
36
+ doc = buffer_to_doc(buffer)
37
+ result = {}
38
+ items.each do |item|
39
+ if item.data_type == :DERIVED
40
+ result[item.name] = nil
41
+ else
42
+ result[item.name] = convert_to_type(doc.xpath(item.key).first.to_s, item)
43
+ end
44
+ end
45
+ return result
46
+ end
47
+
48
+ def self.write_items(items, values, buffer)
49
+ doc = buffer_to_doc(buffer)
50
+ items.each_with_index do |item, index|
51
+ next if item.data_type == :DERIVED
52
+ node = doc.xpath(item.key).first
53
+ node.content = values[index].to_s
54
+ end
55
+ buffer.replace(doc_to_buffer(doc))
56
+ end
57
+
58
+ def self.buffer_to_doc(buffer)
59
+ Nokogiri.XML(buffer)
60
+ end
61
+
62
+ def self.doc_to_buffer(doc)
63
+ doc.to_xml
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
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 Affero 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
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ module OpenC3
17
+ autoload(:Accessor, 'openc3/accessors/accessor.rb')
18
+ autoload(:BinaryAccessor, 'openc3/accessors/binary_accessor.rb')
19
+ autoload(:CborAccessor, 'openc3/accessors/cbor_accessor.rb')
20
+ autoload(:HtmlAccessor, 'openc3/accessors/html_accessor.rb')
21
+ autoload(:JsonAccessor, 'openc3/accessors/json_accessor.rb')
22
+ autoload(:XmlAccessor, 'openc3/accessors/xml_accessor.rb')
23
+ end
@@ -281,9 +281,12 @@ module OpenC3
281
281
 
282
282
  # States are an array of the name followed by a hash of 'value' and sometimes 'hazardous'
283
283
  item['states'].each do |name, hash|
284
+ param_name = params[item['name']]
285
+ # Remove quotes from string parameters
286
+ param_name = param_name.gsub('"', '').gsub("'", '') if param_name.is_a?(String)
284
287
  # To be hazardous the state must be marked hazardous
285
288
  # Check if either the state name or value matches the param passed
286
- if hash['hazardous'] && (name == params[item['name']] || hash['value'].to_f == params[item['name']].to_f)
289
+ if hash['hazardous'] && (name == param_name || hash['value'] == param_name)
287
290
  return true
288
291
  end
289
292
  end
@@ -396,7 +399,7 @@ module OpenC3
396
399
  'hazardous_check' => hazardous_check,
397
400
  'raw' => raw
398
401
  }
399
- Logger.info build_cmd_output_string(target_name, cmd_name, cmd_params, packet, raw) if !packet["messages_disabled"]
402
+ Logger.info(build_cmd_output_string(target_name, cmd_name, cmd_params, packet, raw), scope: scope) if !packet["messages_disabled"]
400
403
  CommandTopic.send_command(command, scope: scope)
401
404
  end
402
405
 
@@ -32,6 +32,7 @@ module OpenC3
32
32
  'start_raw_logging_interface',
33
33
  'stop_raw_logging_interface',
34
34
  'get_all_interface_info',
35
+ 'map_target_to_interface',
35
36
  ])
36
37
 
37
38
  # Get information about an interface
@@ -113,5 +114,19 @@ module OpenC3
113
114
  info.sort! { |a, b| a[0] <=> b[0] }
114
115
  info
115
116
  end
117
+
118
+ # Associates a target and all its commands and telemetry with a particular
119
+ # interface. All the commands will go out over and telemetry be received
120
+ # from that interface.
121
+ #
122
+ # @param target_name [String] The name of the target
123
+ # @param interface_name (see #connect_interface)
124
+ def map_target_to_interface(target_name, interface_name, scope: $openc3_scope, token: $openc3_token)
125
+ authorize(permission: 'system_set', interface_name: interface_name, scope: scope, token: token)
126
+ new_interface = InterfaceModel.get_model(name: interface_name, scope: scope)
127
+ new_interface.map_target(target_name)
128
+ Logger.info("Target #{target_name} mapped to Interface #{interface_name}", scope: scope)
129
+ nil
130
+ end
116
131
  end
117
132
  end
@@ -260,7 +260,7 @@ module OpenC3
260
260
  message = "Setting '#{target_name} #{packet_name} #{item_name}' limits to #{red_low} #{yellow_low} #{yellow_high} #{red_high}"
261
261
  message << " #{green_low} #{green_high}" if green_low && green_high
262
262
  message << " in set #{limits_set} with persistence #{persistence} as enabled #{enabled}"
263
- Logger.info(message)
263
+ Logger.info(message, scope: scope)
264
264
 
265
265
  TargetModel.set_packet(target_name, packet_name, packet, scope: scope)
266
266
 
@@ -307,7 +307,7 @@ module OpenC3
307
307
  def set_limits_set(limits_set, scope: $openc3_scope, token: $openc3_token)
308
308
  authorize(permission: 'tlm_set', scope: scope, token: token)
309
309
  message = "Setting Limits Set: #{limits_set}"
310
- Logger.info(message)
310
+ Logger.info(message, scope: scope)
311
311
  LimitsEventTopic.write({ type: :LIMITS_SET, set: limits_set.to_s,
312
312
  time_nsec: Time.now.to_nsec_from_epoch, message: message }, scope: scope)
313
313
  end
@@ -343,7 +343,7 @@ module OpenC3
343
343
  group = get_limits_groups()[group_name]
344
344
  raise "LIMITS_GROUP #{group_name} undefined. Ensure your telemetry definition contains the line: LIMITS_GROUP #{group_name}" unless group
345
345
 
346
- Logger.info("Disabling Limits Group: #{group_name}")
346
+ Logger.info("Disabling Limits Group: #{group_name}", scope: scope)
347
347
  last_target_name = nil
348
348
  last_packet_name = nil
349
349
  packet = nil
@@ -161,14 +161,20 @@ module OpenC3
161
161
  if options[:locals]
162
162
  options[:locals].each { |key, value| b.local_variable_set(key, value) }
163
163
  end
164
+
165
+ return ERB.new(read_file(template_name), trim_mode: "-").result(b)
166
+ end
167
+
168
+ # Can be called during parsing to read a referenced file
169
+ def read_file(filename)
164
170
  # Assume the file is there. If not we raise a pretty obvious error
165
- if File.expand_path(template_name) == template_name # absolute path
166
- path = template_name
171
+ if File.expand_path(filename) == filename # absolute path
172
+ path = filename
167
173
  else # relative to the current @filename
168
- path = File.join(File.dirname(@filename), template_name)
174
+ path = File.join(File.dirname(@filename), filename)
169
175
  end
170
176
  OpenC3.set_working_dir(File.dirname(path)) do
171
- return ERB.new(File.read(path), trim_mode: "-").result(b)
177
+ return File.read(path)
172
178
  end
173
179
  end
174
180
 
@@ -80,4 +80,9 @@ class File
80
80
  end
81
81
  return nil
82
82
  end
83
+
84
+ def delete
85
+ self.close
86
+ File.delete(self.path)
87
+ end
83
88
  end
@@ -0,0 +1,20 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2022 OpenC3, Inc.
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 Affero 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
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+
16
+ require 'tempfile'
17
+
18
+ class Tempfile
19
+ attr_accessor :filename
20
+ end
@@ -80,9 +80,9 @@ class Float
80
80
  end
81
81
 
82
82
  def as_json(options = nil)
83
- return { "json_class" => Float, "raw" => "Infinity" } if self.infinite? == 1
84
- return { "json_class" => Float, "raw" => "-Infinity" } if self.infinite? == -1
85
- return { "json_class" => Float, "raw" => "NaN" } if self.nan?
83
+ return { "json_class" => "Float", "raw" => "Infinity" } if self.infinite? == 1
84
+ return { "json_class" => "Float", "raw" => "-Infinity" } if self.infinite? == -1
85
+ return { "json_class" => "Float", "raw" => "NaN" } if self.nan?
86
86
 
87
87
  return self
88
88
  end
@@ -35,9 +35,9 @@ module OpenC3
35
35
  @state = 'GETTING_OBJECTS'
36
36
  start_time = Time.now
37
37
  [
38
- ["#{@scope}/raw_logs/cmd/#{target_name}/", target.cmd_log_retain_time],
39
- ["#{@scope}/decom_logs/cmd/#{target_name}/", target.cmd_decom_log_retain_time],
40
- ["#{@scope}/raw_logs/tlm/#{target_name}/", target.tlm_log_retain_time],
38
+ ["#{@scope}/raw_logs/cmd/#{target_name}/", target.cmd_log_retain_time],
39
+ ["#{@scope}/decom_logs/cmd/#{target_name}/", target.cmd_decom_log_retain_time],
40
+ ["#{@scope}/raw_logs/tlm/#{target_name}/", target.tlm_log_retain_time],
41
41
  ["#{@scope}/decom_logs/tlm/#{target_name}/", target.tlm_decom_log_retain_time],
42
42
  ["#{@scope}/reduced_minute_logs/tlm/#{target_name}/", target.reduced_minute_log_retain_time],
43
43
  ["#{@scope}/reduced_hour_logs/tlm/#{target_name}/", target.reduced_hour_log_retain_time],
@@ -52,8 +52,10 @@ module OpenC3
52
52
  end
53
53
  if delete_items.length > 0
54
54
  @state = 'DELETING_OBJECTS'
55
- rubys3_client.delete_objects({ bucket: 'logs', delete: { objects: delete_items } })
56
- Logger.info("Deleted #{delete_items.length} #{target_name} log files")
55
+ delete_items.each_slice(1000) do |delete_slice|
56
+ rubys3_client.delete_objects({ bucket: 'logs', delete: { objects: delete_slice } })
57
+ Logger.info("Deleted #{delete_slice.length} #{target_name} log files")
58
+ end
57
59
  end
58
60
  end
59
61
 
@@ -26,16 +26,7 @@ module OpenC3
26
26
  @overrides = {}
27
27
 
28
28
  def self.build_json_from_packet(packet)
29
- json_hash = {}
30
- packet.sorted_items.each do |item|
31
- json_hash[item.name] = packet.read_item(item, :RAW)
32
- json_hash["#{item.name}__C"] = packet.read_item(item, :CONVERTED) if item.read_conversion or item.states
33
- json_hash["#{item.name}__F"] = packet.read_item(item, :FORMATTED) if item.format_string
34
- json_hash["#{item.name}__U"] = packet.read_item(item, :WITH_UNITS) if item.units
35
- limits_state = item.limits.state
36
- json_hash["#{item.name}__L"] = limits_state if limits_state
37
- end
38
- json_hash
29
+ packet.decom
39
30
  end
40
31
 
41
32
  # Delete the current value table for a target
@@ -277,5 +277,46 @@ module OpenC3
277
277
  end
278
278
  status_model.destroy if status_model
279
279
  end
280
+
281
+ def unmap_target(target_name)
282
+ target_name = target_name.to_s.upcase
283
+
284
+ # Remove from this interface
285
+ @target_names.delete(target_name)
286
+ update()
287
+
288
+ # Respawn the microservice
289
+ type = self.class._get_type
290
+ microservice_name = "#{@scope}__#{type}__#{@name}"
291
+ microservice = MicroserviceModel.get_model(name: microservice_name, scope: scope)
292
+ microservice.target_names.delete(target_name)
293
+ microservice.update
294
+ end
295
+
296
+ def map_target(target_name)
297
+ target_name = target_name.to_s.upcase
298
+
299
+ # Remove from old interface
300
+ all_interfaces = InterfaceModel.all(scope: scope)
301
+ old_interface = nil
302
+ all_interfaces.each do |old_interface_name, old_interface_details|
303
+ if old_interface_details['target_names'].include?(target_name)
304
+ old_interface = InterfaceModel.from_json(old_interface_details, scope: scope)
305
+ break
306
+ end
307
+ end
308
+ old_interface.unmap_target(target_name) if old_interface
309
+
310
+ # Add to this interface
311
+ @target_names << target_name unless @target_names.include?(target_name)
312
+ update()
313
+
314
+ # Respawn the microservice
315
+ type = self.class._get_type
316
+ microservice_name = "#{@scope}__#{type}__#{@name}"
317
+ microservice = MicroserviceModel.get_model(name: microservice_name, scope: scope)
318
+ microservice.target_names << target_name unless microservice.target_names.include?(target_name)
319
+ microservice.update
320
+ end
280
321
  end
281
322
  end
@@ -26,8 +26,15 @@ module OpenC3
26
26
  PRIMARY_KEY = 'openc3_microservices'
27
27
 
28
28
  attr_accessor :cmd
29
- attr_accessor :options
29
+ attr_accessor :container
30
+ attr_accessor :env
31
+ attr_accessor :folder_name
30
32
  attr_accessor :needs_dependencies
33
+ attr_accessor :options
34
+ attr_accessor :target_names
35
+ attr_accessor :topics
36
+ attr_accessor :work_dir
37
+ attr_accessor :ports
31
38
 
32
39
  # NOTE: The following three class methods are used by the ModelController
33
40
  # and are reimplemented to enable various Model class methods to work
@@ -75,6 +82,7 @@ module OpenC3
75
82
  folder_name: nil,
76
83
  cmd: [],
77
84
  work_dir: '.',
85
+ ports: [],
78
86
  env: {},
79
87
  topics: [],
80
88
  target_names: [],
@@ -97,6 +105,7 @@ module OpenC3
97
105
  @folder_name = folder_name
98
106
  @cmd = cmd
99
107
  @work_dir = work_dir
108
+ @ports = ports
100
109
  @env = env
101
110
  @topics = topics
102
111
  @target_names = target_names
@@ -111,6 +120,7 @@ module OpenC3
111
120
  'folder_name' => @folder_name,
112
121
  'cmd' => @cmd,
113
122
  'work_dir' => @work_dir,
123
+ 'ports' => @ports,
114
124
  'env' => @env,
115
125
  'topics' => @topics,
116
126
  'target_names' => @target_names,
@@ -126,6 +136,9 @@ module OpenC3
126
136
  result = "MICROSERVICE #{@folder_name ? @folder_name : 'nil'} #{@name.split("__")[-1]}\n"
127
137
  result << " CMD #{@cmd.join(' ')}\n"
128
138
  result << " WORK_DIR \"#{@work_dir}\"\n"
139
+ @ports.each do |port|
140
+ result << " PORT #{port}\n"
141
+ end
129
142
  @topics.each do |topic_name|
130
143
  result << " TOPIC #{topic_name}\n"
131
144
  end
@@ -150,6 +163,25 @@ module OpenC3
150
163
  when 'WORK_DIR'
151
164
  parser.verify_num_parameters(1, 1, "#{keyword} <Dir>")
152
165
  @work_dir = parameters[0]
166
+ when 'PORT'
167
+ usage = "PORT <Number> <Protocol (Optional)"
168
+ parser.verify_num_parameters(1, 2, usage)
169
+ begin
170
+ @ports << [Integer(parameters[0])]
171
+ rescue => err # In case Integer fails
172
+ raise ConfigParser::Error.new(parser, "Port must be an integer: #{parameters[0]}", usage)
173
+ end
174
+ protocol = ConfigParser.handle_nil(parameters[1])
175
+ if protocol
176
+ # Per https://kubernetes.io/docs/concepts/services-networking/service/#protocol-support
177
+ if %w(TCP UDP SCTP HTTP SCTP).include?(protocol.upcase)
178
+ @ports[-1] << protocol.upcase
179
+ else
180
+ raise ConfigParser::Error.new(parser, "Unknown port protocol: #{parameters[1]}", usage)
181
+ end
182
+ else
183
+ @ports[-1] << 'TCP'
184
+ end
153
185
  when 'TOPIC'
154
186
  parser.verify_num_parameters(1, 1, "#{keyword} <Topic Name>")
155
187
  @topics << parameters[0]
@@ -251,7 +251,7 @@ module OpenC3
251
251
  end
252
252
 
253
253
  def create(update: false, force: false)
254
- @name = @name + "__#{Time.now.utc.strftime("%Y%m%d%H%M%S")}" unless update
254
+ @name = @name + "__#{Time.now.utc.strftime("%Y%m%d%H%M%S")}" if not update and not @name.index("__")
255
255
  super(update: update, force: force)
256
256
  end
257
257