openc3 5.19.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -7
  3. data/bin/openc3cli +12 -120
  4. data/bin/pipinstall +5 -3
  5. data/data/config/command_modifiers.yaml +17 -1
  6. data/data/config/interface_modifiers.yaml +21 -4
  7. data/data/config/item_modifiers.yaml +1 -1
  8. data/data/config/microservice.yaml +15 -2
  9. data/data/config/param_item_modifiers.yaml +1 -1
  10. data/data/config/parameter_modifiers.yaml +1 -1
  11. data/data/config/table_manager.yaml +2 -2
  12. data/data/config/target.yaml +11 -0
  13. data/data/config/telemetry_modifiers.yaml +17 -1
  14. data/data/config/tool.yaml +12 -0
  15. data/data/config/widgets.yaml +41 -17
  16. data/ext/openc3/ext/packet/packet.c +3 -0
  17. data/lib/openc3/accessors/form_accessor.rb +4 -3
  18. data/lib/openc3/accessors/html_accessor.rb +3 -3
  19. data/lib/openc3/accessors/http_accessor.rb +13 -13
  20. data/lib/openc3/accessors/xml_accessor.rb +16 -4
  21. data/lib/openc3/api/cmd_api.rb +4 -5
  22. data/lib/openc3/api/limits_api.rb +3 -3
  23. data/lib/openc3/api/target_api.rb +0 -30
  24. data/lib/openc3/api/tlm_api.rb +2 -1
  25. data/lib/openc3/bridge/bridge_config.rb +1 -2
  26. data/lib/openc3/ccsds/ccsds_parser.rb +12 -8
  27. data/lib/openc3/config/config_parser.rb +10 -3
  28. data/lib/openc3/conversions/bit_reverse_conversion.rb +1 -0
  29. data/lib/openc3/conversions/conversion.rb +5 -1
  30. data/lib/openc3/conversions/generic_conversion.rb +3 -8
  31. data/lib/openc3/conversions/object_read_conversion.rb +1 -8
  32. data/lib/openc3/conversions/polynomial_conversion.rb +3 -8
  33. data/lib/openc3/conversions/processor_conversion.rb +13 -11
  34. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +3 -11
  35. data/lib/openc3/conversions/unix_time_conversion.rb +4 -7
  36. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +4 -3
  37. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +4 -3
  38. data/lib/openc3/core_ext/array.rb +0 -16
  39. data/lib/openc3/core_ext.rb +0 -1
  40. data/lib/openc3/interfaces/file_interface.rb +198 -0
  41. data/lib/openc3/interfaces/http_client_interface.rb +71 -39
  42. data/lib/openc3/interfaces/http_server_interface.rb +1 -9
  43. data/lib/openc3/interfaces/interface.rb +3 -2
  44. data/lib/openc3/interfaces/mqtt_interface.rb +32 -15
  45. data/lib/openc3/interfaces/mqtt_stream_interface.rb +19 -4
  46. data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -0
  47. data/lib/openc3/interfaces/serial_interface.rb +1 -0
  48. data/lib/openc3/interfaces/tcpip_server_interface.rb +1 -2
  49. data/lib/openc3/interfaces/udp_interface.rb +5 -3
  50. data/lib/openc3/interfaces.rb +2 -4
  51. data/lib/openc3/io/json_drb.rb +5 -0
  52. data/lib/openc3/io/json_rpc.rb +10 -9
  53. data/lib/openc3/io/udp_sockets.rb +7 -5
  54. data/lib/openc3/microservices/decom_microservice.rb +24 -7
  55. data/lib/openc3/microservices/interface_microservice.rb +65 -7
  56. data/lib/openc3/microservices/microservice.rb +1 -2
  57. data/lib/openc3/microservices/multi_microservice.rb +3 -3
  58. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +31 -0
  59. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +46 -0
  60. data/lib/openc3/models/activity_model.rb +7 -3
  61. data/lib/openc3/models/cvt_model.rb +7 -1
  62. data/lib/openc3/models/interface_model.rb +9 -3
  63. data/lib/openc3/models/microservice_model.rb +8 -1
  64. data/lib/openc3/models/model.rb +1 -0
  65. data/lib/openc3/models/plugin_model.rb +11 -6
  66. data/lib/openc3/models/python_package_model.rb +10 -3
  67. data/lib/openc3/models/reaction_model.rb +14 -10
  68. data/lib/openc3/models/scope_model.rb +87 -25
  69. data/lib/openc3/models/target_model.rb +17 -1
  70. data/lib/openc3/models/timeline_model.rb +17 -5
  71. data/lib/openc3/models/tool_model.rb +15 -3
  72. data/lib/openc3/models/trigger_group_model.rb +6 -3
  73. data/lib/openc3/operators/microservice_operator.rb +10 -3
  74. data/lib/openc3/packets/commands.rb +17 -6
  75. data/lib/openc3/packets/limits.rb +0 -12
  76. data/lib/openc3/packets/packet.rb +10 -1
  77. data/lib/openc3/packets/packet_config.rb +34 -1
  78. data/lib/openc3/packets/packet_item.rb +30 -32
  79. data/lib/openc3/packets/structure_item.rb +2 -2
  80. data/lib/openc3/script/calendar.rb +1 -6
  81. data/lib/openc3/script/commands.rb +19 -13
  82. data/lib/openc3/script/critical_cmd.rb +91 -0
  83. data/lib/openc3/script/screen.rb +2 -2
  84. data/lib/openc3/script/script.rb +17 -10
  85. data/lib/openc3/script/web_socket_api.rb +5 -5
  86. data/lib/openc3/streams/mqtt_stream.rb +41 -33
  87. data/lib/openc3/streams/serial_stream.rb +27 -27
  88. data/lib/openc3/streams/stream.rb +17 -17
  89. data/lib/openc3/streams/tcpip_client_stream.rb +1 -1
  90. data/lib/openc3/streams/tcpip_socket_stream.rb +19 -19
  91. data/lib/openc3/system/system.rb +1 -1
  92. data/lib/openc3/system.rb +2 -3
  93. data/lib/openc3/tools/table_manager/table.rb +2 -2
  94. data/lib/openc3/tools/table_manager/table_parser.rb +1 -1
  95. data/lib/openc3/top_level.rb +9 -5
  96. data/lib/openc3/topics/command_decom_topic.rb +0 -7
  97. data/lib/openc3/topics/command_topic.rb +16 -0
  98. data/lib/openc3/topics/interface_topic.rb +2 -0
  99. data/lib/openc3/utilities/authentication.rb +7 -3
  100. data/lib/openc3/utilities/bucket_utilities.rb +1 -1
  101. data/lib/openc3/utilities/cli_generator.rb +0 -1
  102. data/lib/openc3/utilities/logger.rb +1 -0
  103. data/lib/openc3/utilities/store_queued.rb +1 -0
  104. data/lib/openc3/version.rb +6 -6
  105. data/templates/conversion/conversion.rb +2 -0
  106. data/templates/plugin/README.md +1 -1
  107. data/templates/target/targets/TARGET/lib/target.rb +1 -1
  108. data/templates/tool_angular/package.json +9 -9
  109. data/templates/tool_angular/src/app/app.component.html +4 -13
  110. data/templates/tool_angular/src/app/app.component.scss +5 -13
  111. data/templates/tool_angular/src/app/app.component.ts +5 -4
  112. data/templates/tool_angular/src/app/custom-overlay-container.ts +2 -2
  113. data/templates/tool_angular/src/app/openc3-api.d.ts +1 -1
  114. data/templates/tool_angular/src/main.single-spa.ts +1 -1
  115. data/templates/tool_react/package.json +1 -0
  116. data/templates/tool_react/src/root.component.js +1 -1
  117. data/templates/tool_svelte/build/smui.css +1 -1
  118. data/templates/tool_svelte/package.json +11 -9
  119. data/templates/tool_svelte/rollup.config.js +2 -0
  120. data/templates/tool_svelte/src/App.svelte +2 -2
  121. data/templates/tool_vue/eslint.config.mjs +68 -0
  122. data/templates/tool_vue/jsconfig.json +1 -1
  123. data/templates/tool_vue/package.json +26 -43
  124. data/templates/tool_vue/src/App.vue +3 -5
  125. data/templates/tool_vue/src/main.js +12 -23
  126. data/templates/tool_vue/src/router.js +19 -18
  127. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +2 -2
  128. data/templates/tool_vue/vite.config.js +52 -0
  129. data/templates/widget/package.json +19 -26
  130. data/templates/widget/src/Widget.vue +13 -15
  131. data/templates/widget/vite.config.js +26 -0
  132. metadata +25 -39
  133. data/lib/openc3/core_ext/hash.rb +0 -40
  134. data/lib/openc3/core_ext/httpclient.rb +0 -11
  135. data/lib/openc3/interfaces/linc_interface.rb +0 -480
  136. data/lib/openc3/interfaces/protocols/override_protocol.rb +0 -4
  137. data/lib/openc3/microservices/reaction_microservice.rb +0 -607
  138. data/lib/openc3/microservices/timeline_microservice.rb +0 -400
  139. data/lib/openc3/microservices/trigger_group_microservice.rb +0 -698
  140. data/lib/openc3/migrations/20230615000000_autonomic.rb +0 -86
  141. data/lib/openc3/migrations/20240915000000_activity_uuid.rb +0 -28
  142. data/lib/openc3/system/system_config.rb +0 -413
  143. data/templates/tool_svelte/src/services/api.js +0 -92
  144. data/templates/tool_svelte/src/services/axios.js +0 -85
  145. data/templates/tool_svelte/src/services/cable.js +0 -65
  146. data/templates/tool_svelte/src/services/config-parser.js +0 -198
  147. data/templates/tool_svelte/src/services/openc3-api.js +0 -606
  148. data/templates/tool_vue/.eslintrc.js +0 -43
  149. data/templates/tool_vue/babel.config.json +0 -11
  150. data/templates/tool_vue/vue.config.js +0 -38
  151. data/templates/widget/.eslintrc.js +0 -43
  152. data/templates/widget/babel.config.json +0 -11
  153. data/templates/widget/vue.config.js +0 -28
  154. /data/templates/tool_vue/{.prettierrc.js → .prettierrc.cjs} +0 -0
  155. /data/templates/widget/{.prettierrc.js → .prettierrc.cjs} +0 -0
@@ -27,6 +27,8 @@ require 'openc3/config/config_parser'
27
27
  module OpenC3
28
28
  # Base class for interfaces that send and receive messages over UDP
29
29
  class UdpInterface < Interface
30
+ HOST_127_0_0_1 = '127.0.0.1'
31
+
30
32
  # @param hostname [String] Machine to connect to
31
33
  # @param write_dest_port [Integer] Port to write commands to
32
34
  # @param read_port [Integer] Port to read telemetry from
@@ -56,7 +58,7 @@ module OpenC3
56
58
  @hostname = ConfigParser.handle_nil(hostname)
57
59
  if @hostname
58
60
  @hostname = @hostname.to_s
59
- @hostname = '127.0.0.1' if @hostname.casecmp('LOCALHOST').zero?
61
+ @hostname = HOST_127_0_0_1 if @hostname.casecmp('LOCALHOST').zero?
60
62
  end
61
63
  @write_dest_port = ConfigParser.handle_nil(write_dest_port)
62
64
  @write_dest_port = write_dest_port.to_i if @write_dest_port
@@ -66,7 +68,7 @@ module OpenC3
66
68
  @write_src_port = @write_src_port.to_i if @write_src_port
67
69
  @interface_address = ConfigParser.handle_nil(interface_address)
68
70
  if @interface_address && @interface_address.casecmp('LOCALHOST').zero?
69
- @interface_address = '127.0.0.1'
71
+ @interface_address = HOST_127_0_0_1
70
72
  end
71
73
  @ttl = ttl.to_i
72
74
  @ttl = 1 if @ttl < 1
@@ -81,7 +83,7 @@ module OpenC3
81
83
  @read_timeout = @read_timeout.to_f if @read_timeout
82
84
  @bind_address = ConfigParser.handle_nil(bind_address)
83
85
  if @bind_address && @bind_address.casecmp('LOCALHOST').zero?
84
- @bind_address = '127.0.0.1'
86
+ @bind_address = HOST_127_0_0_1
85
87
  end
86
88
  @write_socket = nil
87
89
  @read_socket = nil
@@ -14,7 +14,7 @@
14
14
  # GNU Affero General Public License for more details.
15
15
 
16
16
  # Modified by OpenC3, Inc.
17
- # All changes Copyright 2022, OpenC3, Inc.
17
+ # All changes Copyright 2024, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
20
  # This file may also be used under the terms of a commercial license
@@ -25,15 +25,13 @@ module OpenC3
25
25
  autoload(:HttpClientInterface, 'openc3/interfaces/http_client_interface.rb')
26
26
  autoload(:HttpServerInterface, 'openc3/interfaces/http_server_interface.rb')
27
27
  autoload(:MqttInterface, 'openc3/interfaces/mqtt_interface.rb')
28
+ autoload(:MqttStreamInterface, 'openc3/interfaces/mqtt_stream_interface.rb')
28
29
  autoload(:StreamInterface, 'openc3/interfaces/stream_interface.rb')
29
30
  autoload(:SerialInterface, 'openc3/interfaces/serial_interface.rb')
30
31
  autoload(:SimulatedTargetInterface, 'openc3/interfaces/simulated_target_interface.rb')
31
32
  autoload(:TcpipClientInterface, 'openc3/interfaces/tcpip_client_interface.rb')
32
33
  autoload(:TcpipServerInterface, 'openc3/interfaces/tcpip_server_interface.rb')
33
34
  autoload(:UdpInterface, 'openc3/interfaces/udp_interface.rb')
34
- autoload(:LincInterface, 'openc3/interfaces/linc_interface.rb')
35
- autoload(:LincHandshakeCommand, 'openc3/interfaces/linc_interface.rb')
36
- autoload(:LincHandshake, 'openc3/interfaces/linc_interface.rb')
37
35
 
38
36
  autoload(:Protocol, 'openc3/interfaces/protocols/protocol.rb')
39
37
  autoload(:BurstProtocol, 'openc3/interfaces/protocols/burst_protocol.rb')
@@ -302,6 +302,11 @@ module OpenC3
302
302
  response = JsonRpcErrorResponse.new(
303
303
  JsonRpcError.new(error_code, e.message, e), request.id
304
304
  )
305
+ elsif CriticalCmdError === e
306
+ error_code = JsonRpcError::ErrorCode::CRITICAL_CMD_ERROR
307
+ response = JsonRpcErrorResponse.new(
308
+ JsonRpcError.new(error_code, e.message, e), request.id
309
+ )
305
310
  else
306
311
  error_code = JsonRpcError::ErrorCode::OTHER_ERROR
307
312
  response = JsonRpcErrorResponse.new(
@@ -379,16 +379,17 @@ module OpenC3
379
379
  class JsonRpcError < JsonRpc
380
380
  # Enumeration of JSON RPC error codes
381
381
  class ErrorCode
382
- PARSE_ERROR = -32700
383
- INVALID_REQUEST = -32600
384
- METHOD_NOT_FOUND = -32601
385
- INVALID_PARAMS = -32602
386
- INTERNAL_ERROR = -32603
387
- AUTH_ERROR = -32500
388
- FORBIDDEN_ERROR = -32501
389
- HAZARDOUS_ERROR = -32502
382
+ PARSE_ERROR = -32700
383
+ INVALID_REQUEST = -32600
384
+ METHOD_NOT_FOUND = -32601
385
+ INVALID_PARAMS = -32602
386
+ INTERNAL_ERROR = -32603
387
+ AUTH_ERROR = -32500
388
+ FORBIDDEN_ERROR = -32501
389
+ HAZARDOUS_ERROR = -32502
390
+ CRITICAL_CMD_ERROR = -32503
390
391
  # Server error reserved: -32000 to -32099
391
- OTHER_ERROR = -1
392
+ OTHER_ERROR = -1
392
393
  end
393
394
 
394
395
  # @param code [Integer] The error type that occurred
@@ -17,7 +17,7 @@
17
17
  # All changes Copyright 2022, OpenC3, Inc.
18
18
  # All Rights Reserved
19
19
  #
20
- # This file may also be used under the terms of a commercial license
20
+ # This file may also be used under the terms of a commercial license
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
23
  require 'socket'
@@ -30,6 +30,8 @@ Socket::IP_MULTICAST_TTL = 10 unless Socket.const_defined?('IP_MULTICAST_TTL')
30
30
 
31
31
  module OpenC3
32
32
  class UdpReadWriteSocket
33
+ HOST_0_0_0_0 = '0.0.0.0'
34
+
33
35
  # @param bind_port [Integer[ Port to write data out from and receive data on (0 = randomly assigned)
34
36
  # @param bind_address [String] Local address to bind to (0.0.0.0 = All local addresses)
35
37
  # @param external_port [Integer] External port to write to
@@ -40,7 +42,7 @@ module OpenC3
40
42
  # @param write_multicast [Boolean] Whether or not to write to the external address as multicast
41
43
  def initialize(
42
44
  bind_port = 0,
43
- bind_address = "0.0.0.0",
45
+ bind_address = HOST_0_0_0_0,
44
46
  external_port = nil,
45
47
  external_address = nil,
46
48
  multicast_interface_address = nil,
@@ -76,7 +78,7 @@ module OpenC3
76
78
 
77
79
  # Receive messages sent to the multicast address
78
80
  if read_multicast
79
- multicast_interface_address = "0.0.0.0" unless multicast_interface_address
81
+ multicast_interface_address = HOST_0_0_0_0 unless multicast_interface_address
80
82
  membership = IPAddr.new(external_address).hton + IPAddr.new(multicast_interface_address).hton
81
83
  @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
82
84
  end
@@ -155,7 +157,7 @@ module OpenC3
155
157
  src_port = nil,
156
158
  multicast_interface_address = nil,
157
159
  ttl = 1,
158
- bind_address = "0.0.0.0"
160
+ bind_address = HOST_0_0_0_0
159
161
  )
160
162
 
161
163
  super(
@@ -180,7 +182,7 @@ module OpenC3
180
182
  recv_port = 0,
181
183
  multicast_address = nil,
182
184
  multicast_interface_address = nil,
183
- bind_address = "0.0.0.0"
185
+ bind_address = HOST_0_0_0_0
184
186
  )
185
187
 
186
188
  super(
@@ -20,6 +20,7 @@
20
20
  # This file may also be used under the terms of a commercial license
21
21
  # if purchased from OpenC3, Inc.
22
22
 
23
+ require 'time'
23
24
  require 'openc3/microservices/microservice'
24
25
  require 'openc3/microservices/interface_decom_common'
25
26
  require 'openc3/topics/telemetry_decom_topic'
@@ -28,6 +29,7 @@ require 'openc3/topics/limits_event_topic'
28
29
  module OpenC3
29
30
  class DecomMicroservice < Microservice
30
31
  include InterfaceDecomCommon
32
+ LIMITS_STATE_INDEX = { RED_LOW: 0, YELLOW_LOW: 1, YELLOW_HIGH: 2, RED_HIGH: 3, GREEN_LOW: 4, GREEN_HIGH: 5 }
31
33
 
32
34
  def initialize(*args)
33
35
  super(*args)
@@ -134,31 +136,46 @@ module OpenC3
134
136
  # @param log_change [Boolean] Whether to log this limits change event
135
137
  def limits_change_callback(packet, item, old_limits_state, value, log_change)
136
138
  return if @cancel_thread
137
- packet_time = packet.packet_time
139
+ # Make a copy because packet_time is frozen
140
+ packet_time = packet.packet_time.dup
138
141
  if value
139
142
  message = "#{packet.target_name} #{packet.packet_name} #{item.name} = #{value} is #{item.limits.state}"
143
+ if item.limits.values
144
+ values = item.limits.values[System.limits_set]
145
+ # Check if the state is RED_LOW, YELLOW_LOW, YELLOW_HIGH, RED_HIGH, GREEN_LOW, GREEN_HIGH
146
+ if LIMITS_STATE_INDEX[item.limits.state]
147
+ # Directly index into the values and return the value
148
+ message += " (#{values[LIMITS_STATE_INDEX[item.limits.state]]})"
149
+ elsif item.limits.state == :GREEN
150
+ # If we're green we display the green range (YELLOW_LOW - YELLOW_HIGH)
151
+ message += " (#{values[1]} to #{values[2]})"
152
+ elsif item.limits.state == :BLUE
153
+ # If we're blue we display the blue range (GREEN_LOW - GREEN_HIGH)
154
+ message += " (#{values[4]} to #{values[5]})"
155
+ end
156
+ end
140
157
  else
141
158
  message = "#{packet.target_name} #{packet.packet_name} #{item.name} is disabled"
142
159
  end
143
- message << " (#{packet.packet_time.sys.formatted})" if packet_time
144
160
 
145
- time_nsec = packet_time ? packet_time.to_nsec_from_epoch : Time.now.to_nsec_from_epoch
161
+ # Include the packet_time in the log json but not the log message
162
+ time = { packet_time: packet_time.utc.iso8601(6) }
146
163
  if log_change
147
164
  case item.limits.state
148
165
  when :BLUE, :GREEN, :GREEN_LOW, :GREEN_HIGH
149
166
  # Only print INFO messages if we're changing ... not on initialization
150
- @logger.info message if old_limits_state
167
+ @logger.info(message, other: time) if old_limits_state
151
168
  when :YELLOW, :YELLOW_LOW, :YELLOW_HIGH
152
- @logger.warn(message, type: Logger::NOTIFICATION)
169
+ @logger.warn(message, other: time, type: Logger::NOTIFICATION)
153
170
  when :RED, :RED_LOW, :RED_HIGH
154
- @logger.error(message, type: Logger::ALERT)
171
+ @logger.error(message, other: time, type: Logger::ALERT)
155
172
  end
156
173
  end
157
174
 
158
175
  # The openc3_limits_events topic can be listened to for all limits events, it is a continuous stream
159
176
  event = { type: :LIMITS_CHANGE, target_name: packet.target_name, packet_name: packet.packet_name,
160
177
  item_name: item.name, old_limits_state: old_limits_state.to_s, new_limits_state: item.limits.state.to_s,
161
- time_nsec: time_nsec, message: message.to_s }
178
+ time_nsec: packet_time.to_nsec_from_epoch, message: message.to_s }
162
179
  LimitsEventTopic.write(event, scope: @scope)
163
180
 
164
181
  if item.limits.response
@@ -26,6 +26,7 @@ require 'openc3/models/interface_model'
26
26
  require 'openc3/models/router_model'
27
27
  require 'openc3/models/interface_status_model'
28
28
  require 'openc3/models/router_status_model'
29
+ require 'openc3/models/scope_model'
29
30
  require 'openc3/topics/telemetry_topic'
30
31
  require 'openc3/topics/command_topic'
31
32
  require 'openc3/topics/command_decom_topic'
@@ -33,6 +34,12 @@ require 'openc3/topics/interface_topic'
33
34
  require 'openc3/topics/router_topic'
34
35
  require 'openc3/interfaces/interface'
35
36
 
37
+ begin
38
+ require 'openc3-enterprise/models/critical_cmd_model'
39
+ rescue LoadError
40
+ # Should never actual be used in Open Source
41
+ end
42
+
36
43
  module OpenC3
37
44
  class InterfaceCmdHandlerThread
38
45
  include InterfaceDecomCommon
@@ -41,6 +48,12 @@ module OpenC3
41
48
  @interface = interface
42
49
  @tlm = tlm
43
50
  @scope = scope
51
+ scope_model = ScopeModel.get_model(name: @scope)
52
+ if scope_model
53
+ @critical_commanding = scope_model.critical_commanding
54
+ else
55
+ @critical_commanding = 'OFF'
56
+ end
44
57
  @logger = logger
45
58
  @logger = Logger unless @logger
46
59
  @metric = metric
@@ -70,10 +83,21 @@ module OpenC3
70
83
  def run
71
84
  InterfaceTopic.receive_commands(@interface, scope: @scope) do |topic, msg_id, msg_hash, _redis|
72
85
  OpenC3.with_context(msg_hash) do
86
+ release_critical = false
73
87
  msgid_seconds_from_epoch = msg_id.split('-')[0].to_i / 1000.0
74
88
  delta = Time.now.to_f - msgid_seconds_from_epoch
75
89
  @metric.set(name: 'interface_topic_delta_seconds', value: delta, type: 'gauge', unit: 'seconds', help: 'Delta time between data written to stream and interface cmd start') if @metric
76
90
 
91
+ if topic == "OPENC3__SYSTEM__EVENTS"
92
+ msg = JSON.parse(msg_hash['event'])
93
+ if msg['type'] == 'scope'
94
+ if msg['name'] == @scope
95
+ @critical_commanding = msg['critical_commanding']
96
+ end
97
+ end
98
+ next 'SUCCESS'
99
+ end
100
+
77
101
  # Check for a raw write to the interface
78
102
  if topic =~ /CMD}INTERFACE/
79
103
  @directive_count += 1
@@ -150,10 +174,21 @@ module OpenC3
150
174
  handle_inject_tlm(msg_hash['inject_tlm'])
151
175
  next 'SUCCESS'
152
176
  end
177
+ if msg_hash.key?('release_critical')
178
+ # Note: intentional fall through below this point
179
+ model = CriticalCmdModel.get_model(name: msg_hash['release_critical'], scope: @scope)
180
+ if model
181
+ msg_hash = model.cmd_hash
182
+ release_critical = true
183
+ else
184
+ next "Critical command #{msg_hash['release_critical']} not found"
185
+ end
186
+ end
153
187
  end
154
188
 
155
189
  target_name = msg_hash['target_name']
156
190
  cmd_name = msg_hash['cmd_name']
191
+ manual = ConfigParser.handle_true_false(msg_hash['manual'])
157
192
  cmd_params = nil
158
193
  cmd_buffer = nil
159
194
  hazardous_check = nil
@@ -195,11 +230,29 @@ module OpenC3
195
230
  command.extra ||= {}
196
231
  command.extra['cmd_string'] = msg_hash['cmd_string']
197
232
  command.extra['username'] = msg_hash['username']
198
- if hazardous_check
199
- hazardous, hazardous_description = System.commands.cmd_pkt_hazardous?(command)
200
- # Return back the error, description, and the formatted command
201
- # This allows the error handler to simply re-send the command
202
- next "HazardousError\n#{hazardous_description}\n#{msg_hash['cmd_string']}" if hazardous
233
+ hazardous, hazardous_description = System.commands.cmd_pkt_hazardous?(command)
234
+
235
+ # Initial Are you sure? Hazardous check
236
+ # Return back the error, description, and the formatted command
237
+ # This allows the error handler to simply re-send the command
238
+ next "HazardousError\n#{hazardous_description}\n#{msg_hash['cmd_string']}" if hazardous_check and hazardous and not release_critical
239
+
240
+ # Check for Critical Command
241
+ if @critical_commanding and @critical_commanding != 'OFF' and not release_critical
242
+ restricted = command.restricted
243
+ if hazardous or restricted or (@critical_commanding == 'ALL' and manual)
244
+ if hazardous
245
+ type = 'HAZARDOUS'
246
+ elsif restricted
247
+ type = 'RESTRICTED'
248
+ else
249
+ type = 'NORMAL'
250
+ end
251
+ model = CriticalCmdModel.new(name: SecureRandom.uuid, type: type, interface_name: @interface.name, username: msg_hash['username'], cmd_hash: msg_hash, scope: @scope)
252
+ model.create
253
+ @logger.info("Critical Cmd Pending: #{msg_hash['cmd_string']}", user: msg_hash['username'], scope: @scope)
254
+ next "CriticalCmdError\n#{model.name}"
255
+ end
203
256
  end
204
257
 
205
258
  validate = ConfigParser.handle_true_false(msg_hash['validate'])
@@ -223,6 +276,12 @@ module OpenC3
223
276
 
224
277
  @count += 1
225
278
  @metric.set(name: 'interface_cmd_total', value: @count, type: 'counter') if @metric
279
+
280
+ log_message = ConfigParser.handle_true_false(msg_hash['log_message'])
281
+ if log_message
282
+ @logger.info(msg_hash['cmd_string'], user: msg_hash['username'], scope: @scope)
283
+ end
284
+
226
285
  @interface.write(command)
227
286
 
228
287
  if command.validator and validate
@@ -440,8 +499,7 @@ module OpenC3
440
499
 
441
500
  @queued = false
442
501
  @interface.options.each do |option_name, option_values|
443
- case option_name.upcase
444
- when 'OPTIMIZE_THROUGHPUT'
502
+ if option_name.upcase == 'OPTIMIZE_THROUGHPUT'
445
503
  @queued = true
446
504
  update_interval = option_values[0].to_f
447
505
  EphemeralStoreQueued.instance.set_update_interval(update_interval)
@@ -222,8 +222,7 @@ module OpenC3
222
222
  # Returns if the command was handled
223
223
  def microservice_cmd(topic, msg_id, msg_hash, _redis)
224
224
  command = msg_hash['command']
225
- case command
226
- when 'ADD_TOPICS'
225
+ if command == 'ADD_TOPICS'
227
226
  topics = JSON.parse(msg_hash['topics'])
228
227
  if topics and Array === topics
229
228
  topics.each do |new_topic|
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2022 OpenC3 Inc.
3
+ # Copyright 2024 OpenC3 Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # This program is free software; you can modify and/or redistribute it
@@ -36,8 +36,8 @@ module OpenC3
36
36
  end
37
37
  end
38
38
  raise "Could not determine class filename from '#{cmd_line}'" unless filename
39
- OpenC3.set_working_dir(@work_dir) do
40
- require_relative filename
39
+ OpenC3.set_working_dir(microservice_model.work_dir) do
40
+ require File.join(microservice_model.work_dir, filename)
41
41
  end
42
42
  klass = filename.filename_to_class_name.to_class
43
43
  klass.run(microservice_model.name)
@@ -0,0 +1,31 @@
1
+ require 'openc3/utilities/migration'
2
+ require 'openc3/models/scope_model'
3
+ require 'openc3/models/microservice_model'
4
+
5
+ module OpenC3
6
+ class NoCriticalCmd < Migration
7
+ begin
8
+ require 'openc3-enterprise/models/cmd_authority_model'
9
+ require 'openc3-enterprise/models/critical_cmd_model'
10
+ BASE = false
11
+ rescue LoadError
12
+ BASE = true
13
+ end
14
+
15
+ def self.run
16
+ ScopeModel.get_all_models(scope: nil).each do |scope, scope_model|
17
+ model = MicroserviceModel.get_model(name: "#{scope}__CRITICALCMD__#{scope}", scope: scope)
18
+ if BASE # Only remove the critical command model if we're not enterprise
19
+ model.destroy if model
20
+ else
21
+ model.work_dir = '/openc3-enterprise/lib/openc3-enterprise/microservices'
22
+ model.update
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ unless ENV['OPENC3_NO_MIGRATE']
30
+ OpenC3::NoCriticalCmd.run
31
+ end
@@ -0,0 +1,46 @@
1
+ require 'openc3/utilities/migration'
2
+ require 'openc3/models/scope_model'
3
+ require 'openc3/models/microservice_model'
4
+
5
+ module OpenC3
6
+ class NoTriggerGroups < Migration
7
+ begin
8
+ require 'openc3-enterprise/models/cmd_authority_model'
9
+ require 'openc3-enterprise/models/critical_cmd_model'
10
+ BASE = false
11
+ rescue LoadError
12
+ BASE = true
13
+ end
14
+
15
+ def self.run
16
+ MicroserviceModel.get_all_models(scope: 'DEFAULT').each do |microservice_name, microservice_model|
17
+ if microservice_name =~ /__TRIGGER_GROUP__/
18
+ if BASE
19
+ # Only remove the trigger group microservice if we're not enterprise
20
+ microservice_model.destroy
21
+ else
22
+ # Need to update working dir for Enterprise
23
+ microservice_model.work_dir = '/openc3-enterprise/lib/openc3-enterprise/microservices'
24
+ microservice_model.update
25
+ end
26
+ end
27
+
28
+ if microservice_name =~ /__OPENC3__REACTION/
29
+ # Need to update working dir for Enterprise
30
+ microservice_model.work_dir = '/openc3-enterprise/lib/openc3-enterprise/microservices'
31
+ microservice_model.update
32
+ end
33
+
34
+ if microservice_name =~ /__TIMELINE__/
35
+ # Need to update working dir for Enterprise
36
+ microservice_model.work_dir = '/openc3-enterprise/lib/openc3-enterprise/microservices'
37
+ microservice_model.update
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ unless ENV['OPENC3_NO_MIGRATE']
45
+ OpenC3::NoTriggerGroups.run
46
+ end
@@ -121,7 +121,7 @@ module OpenC3
121
121
 
122
122
  notification = {
123
123
  # start / stop to match SortedModel
124
- 'data' => JSON.generate({'start' => score}),
124
+ 'data' => JSON.generate({'start' => score, 'uuid' => uuid}),
125
125
  'kind' => 'deleted',
126
126
  'type' => 'activity',
127
127
  'timeline' => name
@@ -361,7 +361,7 @@ module OpenC3
361
361
  end
362
362
  end
363
363
  end
364
- notify(kind: 'updated', extra: old_start)
364
+ notify(kind: 'updated', extra: {old_start: old_start, old_uuid: old_uuid})
365
365
  return @start
366
366
  end
367
367
 
@@ -409,7 +409,11 @@ module OpenC3
409
409
  'type' => 'activity',
410
410
  'timeline' => @name
411
411
  }
412
- notification['extra'] = extra unless extra.nil?
412
+ if extra
413
+ extra.each do |key, value|
414
+ notification[key.to_s] = value
415
+ end
416
+ end
413
417
  begin
414
418
  TimelineTopic.write_activity(notification, scope: @scope)
415
419
  rescue StandardError => e
@@ -156,7 +156,13 @@ module OpenC3
156
156
  item_result[1] = item_result[1].intern if item_result[1] # Convert to symbol
157
157
  end
158
158
  else
159
- raise "Item '#{target_name} #{packet_name} #{value_keys[-1]}' does not exist" unless hash.key?(value_keys[-1])
159
+ # We didn't find a value but the packet hash contains the key so the item exists
160
+ # Thus set the result to nil so it comes back like a normal item
161
+ if hash.key?(value_keys[-1])
162
+ item_result[1] = nil
163
+ else
164
+ raise "Item '#{target_name} #{packet_name} #{value_keys[-1]}' does not exist"
165
+ end
160
166
  end
161
167
  end
162
168
  results << item_result
@@ -50,6 +50,7 @@ module OpenC3
50
50
  attr_accessor :work_dir
51
51
  attr_accessor :ports
52
52
  attr_accessor :prefix
53
+ attr_accessor :shard
53
54
 
54
55
  # NOTE: The following three class methods are used by the ModelController
55
56
  # and are reimplemented to enable various Model class methods to work
@@ -121,6 +122,7 @@ module OpenC3
121
122
  env: {},
122
123
  container: nil,
123
124
  prefix: nil,
125
+ shard: 0,
124
126
  scope:
125
127
  )
126
128
  if self.class._get_type == 'INTERFACE'
@@ -158,6 +160,7 @@ module OpenC3
158
160
  @env = env
159
161
  @container = container
160
162
  @prefix = prefix
163
+ @shard = shard.to_i # to_i to handle nil
161
164
  @secrets = secrets
162
165
  end
163
166
 
@@ -222,6 +225,7 @@ module OpenC3
222
225
  'env' => @env,
223
226
  'container' => @container,
224
227
  'prefix' => @prefix,
228
+ 'shard' => @shard,
225
229
  'updated_at' => @updated_at
226
230
  }
227
231
  end
@@ -297,9 +301,7 @@ module OpenC3
297
301
  # Option Name, Secret Name
298
302
  @secret_options << [parameters[3], parameters[1]]
299
303
  end
300
- if ConfigParser.handle_nil(parameters[4])
301
- @secrets[-1] << parameters[4]
302
- end
304
+ @secrets[-1] << ConfigParser.handle_nil(parameters[4])
303
305
 
304
306
  when 'ENV'
305
307
  parser.verify_num_parameters(2, 2, "#{keyword} <Key> <Value>")
@@ -341,6 +343,9 @@ module OpenC3
341
343
  parser.verify_num_parameters(1, 1, "#{keyword} <Route Prefix>")
342
344
  @prefix = parameters[0]
343
345
 
346
+ when 'SHARD'
347
+ parser.verify_num_parameters(1, 1, "#{keyword} <Shard Number Starting from 0>")
348
+ @shard = Integer(parameters[0])
344
349
  else
345
350
  raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Interface/Router: #{keyword} #{parameters.join(" ")}")
346
351
 
@@ -365,6 +370,7 @@ module OpenC3
365
370
  needs_dependencies: @needs_dependencies,
366
371
  secrets: @secrets,
367
372
  prefix: @prefix,
373
+ shard: @shard,
368
374
  scope: @scope
369
375
  )
370
376
  unless validate_only
@@ -44,6 +44,7 @@ module OpenC3
44
44
  attr_accessor :prefix
45
45
  attr_accessor :disable_erb
46
46
  attr_accessor :ignore_changes
47
+ attr_accessor :shard
47
48
 
48
49
  # NOTE: The following three class methods are used by the ModelController
49
50
  # and are reimplemented to enable various Model class methods to work
@@ -105,6 +106,7 @@ module OpenC3
105
106
  prefix: nil,
106
107
  disable_erb: nil,
107
108
  ignore_changes: nil,
109
+ shard: 0,
108
110
  scope:
109
111
  )
110
112
  parts = name.split("__")
@@ -131,6 +133,7 @@ module OpenC3
131
133
  @prefix = prefix
132
134
  @disable_erb = disable_erb
133
135
  @ignore_changes = ignore_changes
136
+ @shard = shard.to_i # to_i to handle nil
134
137
  @bucket = Bucket.getClient()
135
138
  end
136
139
 
@@ -153,7 +156,8 @@ module OpenC3
153
156
  'secrets' => @secrets.as_json(*a),
154
157
  'prefix' => @prefix,
155
158
  'disable_erb' => @disable_erb,
156
- 'ignore_changes' => @ignore_changes
159
+ 'ignore_changes' => @ignore_changes,
160
+ 'shard' => @shard,
157
161
  }
158
162
  end
159
163
 
@@ -215,6 +219,9 @@ module OpenC3
215
219
  if parameters
216
220
  @disable_erb.concat(parameters)
217
221
  end
222
+ when 'SHARD'
223
+ parser.verify_num_parameters(1, 1, "#{keyword} <Shard Number Starting from 0>")
224
+ @shard = Integer(parameters[0])
218
225
  else
219
226
  raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Microservice: #{keyword} #{parameters.join(" ")}")
220
227
  end
@@ -172,6 +172,7 @@ module OpenC3
172
172
  # Undo the actions of deploy and remove the model from OpenC3.
173
173
  # Subclasses must implement this as by default it is a noop.
174
174
  def undeploy
175
+ # empty for a reason
175
176
  end
176
177
 
177
178
  # Delete the model from the Store