diff --git a/docs.openc3.com/docs/guides/scripting-api.md b/docs.openc3.com/docs/guides/scripting-api.md
index 1abe4625fc..2d516d851b 100644
--- a/docs.openc3.com/docs/guides/scripting-api.md
+++ b/docs.openc3.com/docs/guides/scripting-api.md
@@ -3847,6 +3847,7 @@ packets.sort(key=lambda p: int(p['time']))
id, packets = get_packets(id)
packets.sort_by! { |p| p['time'].to_i }
```
+
:::
Returns a two element array containing the updated id and an array of packet hashes/dictionaries. Each packet hash/dictionary contains the following keys:
@@ -4942,6 +4943,52 @@ set_limits_set("DEFAULT")
+### delete_limits_set
+
+Since 7.3.0
+
+Deletes a limits set and removes it from all telemetry items. The DEFAULT limits set and the currently active limits set cannot be deleted. Use [set_limits_set](#set_limits_set) to change the active set before deleting it. Use [get_limits_sets](#get_limits_sets) to get the available limit set names.
+
+
+
+
+```python
+delete_limits_set("")
+```
+
+
+
+
+
+```ruby
+delete_limits_set("")
+```
+
+
+
+
+| Parameter | Description |
+| --------------- | --------------------------------- |
+| Limits Set Name | Name of the limits set to delete. |
+
+
+
+
+```python
+delete_limits_set("TVAC")
+```
+
+
+
+
+
+```ruby
+delete_limits_set("TVAC")
+```
+
+
+
+
### get_limits_set
Since 5.0.0
diff --git a/openc3-cosmos-init/plugins/packages/openc3-js-common/src/services/openc3Api.js b/openc3-cosmos-init/plugins/packages/openc3-js-common/src/services/openc3Api.js
index 10fdab08ec..b55d1fb411 100644
--- a/openc3-cosmos-init/plugins/packages/openc3-js-common/src/services/openc3Api.js
+++ b/openc3-cosmos-init/plugins/packages/openc3-js-common/src/services/openc3Api.js
@@ -364,6 +364,10 @@ export default class OpenC3Api {
return this.exec('set_limits_set', [limits_set])
}
+ delete_limits_set(limits_set) {
+ return this.exec('delete_limits_set', [limits_set])
+ }
+
// ***********************************************
// End CmdTlmServer APIs
// ***********************************************
diff --git a/openc3/lib/openc3/api/limits_api.rb b/openc3/lib/openc3/api/limits_api.rb
index 9300cb7d76..f2d4697c41 100644
--- a/openc3/lib/openc3/api/limits_api.rb
+++ b/openc3/lib/openc3/api/limits_api.rb
@@ -35,6 +35,7 @@ module Api
'get_limits_sets',
'set_limits_set',
'get_limits_set',
+ 'delete_limits_set',
'get_limits_events',
])
@@ -292,6 +293,43 @@ def set_limits_set(limits_set, manual: false, scope: $openc3_scope, token: $open
time_nsec: Time.now.to_nsec_from_epoch, message: message }, scope: scope)
end
+ # Deletes a limits set and removes it from all telemetry items. The DEFAULT
+ # limits set and the currently active limits set cannot be deleted. Use
+ # set_limits_set to change the active set before deleting it.
+ #
+ # @param limits_set [String] The name of the limits set to delete
+ def delete_limits_set(limits_set, manual: false, scope: $openc3_scope, token: $openc3_token)
+ authorize(permission: 'tlm_set', manual: manual, scope: scope, token: token)
+ limits_set = limits_set.to_s
+ raise "Cannot delete the DEFAULT limits set" if limits_set == 'DEFAULT'
+ if limits_set == LimitsEventTopic.current_set(scope: scope)
+ raise "Cannot delete the current limits set '#{limits_set}'. Use set_limits_set to change the current set first."
+ end
+ unless LimitsEventTopic.sets(scope: scope).key?(limits_set)
+ raise "Limits set '#{limits_set}' does not exist"
+ end
+
+ # Remove the limits set from every telemetry item definition
+ TargetModel.names(scope: scope).each do |target_name|
+ TargetModel.packets(target_name, type: :TLM, scope: scope).each do |packet|
+ modified = false
+ packet['items'].each do |item|
+ if item['limits'] && item['limits'].key?(limits_set)
+ item['limits'].delete(limits_set)
+ modified = true
+ end
+ end
+ TargetModel.set_packet(target_name, packet['packet_name'], packet, scope: scope) if modified
+ end
+ end
+
+ # Remove the limits set from Redis (limits_sets and current_limits_settings)
+ LimitsEventTopic.delete_set(limits_set, scope: scope)
+
+ message = "Deleting Limits Set: #{limits_set}"
+ Logger.info(message, scope: scope)
+ end
+
# Returns the active limits set that applies to all telemetry
#
# @return [String] The current limits set
diff --git a/openc3/lib/openc3/script/limits.rb b/openc3/lib/openc3/script/limits.rb
index 929e07409f..cba06e007c 100644
--- a/openc3/lib/openc3/script/limits.rb
+++ b/openc3/lib/openc3/script/limits.rb
@@ -20,7 +20,7 @@ module Script
private
# Define all the modification methods such that we can disconnect them
- %i(enable_limits disable_limits set_limits enable_limits_group disable_limits_group set_limits_set).each do |method_name|
+ %i(enable_limits disable_limits set_limits enable_limits_group disable_limits_group set_limits_set delete_limits_set).each do |method_name|
define_method(method_name) do |*args, **kw_args, &block|
kw_args[:scope] = $openc3_scope unless kw_args[:scope]
if $disconnect
diff --git a/openc3/lib/openc3/topics/limits_event_topic.rb b/openc3/lib/openc3/topics/limits_event_topic.rb
index 109cdfb247..ca8a376f5f 100644
--- a/openc3/lib/openc3/topics/limits_event_topic.rb
+++ b/openc3/lib/openc3/topics/limits_event_topic.rb
@@ -173,6 +173,28 @@ def self.delete(target_name, packet_name = nil, scope:)
end
end
+ # Removes a limits set from the limits_sets hash and from the
+ # current_limits_settings of every item. Note that the items themselves
+ # (in the TargetModel packet definitions) are cleaned up in the
+ # delete_limits_set API. Running microservices will continue to hold the
+ # set in memory until they restart and resync from current_limits_settings.
+ def self.delete_set(set_name, scope:)
+ set_name = set_name.to_s
+ limits_settings = Store.hgetall("#{scope}__current_limits_settings")
+ # Collect all changed items and write them back in a single hmset to
+ # avoid a Redis round trip per item (this hash can be large)
+ updates = {}
+ limits_settings.each do |item, settings|
+ settings = JSON.parse(settings, allow_nan: true, create_additions: true)
+ if settings.key?(set_name)
+ settings.delete(set_name)
+ updates[item] = JSON.generate(settings, allow_nan: true)
+ end
+ end
+ Store.hmset("#{scope}__current_limits_settings", *updates.flatten) unless updates.empty?
+ Store.hdel("#{scope}__limits_sets", set_name)
+ end
+
# Update the local System based on overall state
def self.sync_system(scope:)
all_limits_settings = Store.hgetall("#{scope}__current_limits_settings")
diff --git a/openc3/python/openc3/api/limits_api.py b/openc3/python/openc3/api/limits_api.py
index 7ed4d56b18..eaac1d26ea 100644
--- a/openc3/python/openc3/api/limits_api.py
+++ b/openc3/python/openc3/api/limits_api.py
@@ -37,6 +37,7 @@
"get_limits_sets",
"set_limits_set",
"get_limits_set",
+ "delete_limits_set",
"get_limits_events",
]
)
@@ -369,6 +370,41 @@ def set_limits_set(limits_set, scope=OPENC3_SCOPE):
)
+# Deletes a limits set and removes it from all telemetry items. The DEFAULT
+# limits set and the currently active limits set cannot be deleted. Use
+# set_limits_set to change the active set before deleting it.
+#
+# @param limits_set [String] The name of the limits set to delete
+def delete_limits_set(limits_set, scope=OPENC3_SCOPE):
+ authorize(permission="tlm_set", scope=scope)
+ limits_set = str(limits_set)
+ if limits_set == "DEFAULT":
+ raise RuntimeError("Cannot delete the DEFAULT limits set")
+ if limits_set == LimitsEventTopic.current_set(scope=scope):
+ raise RuntimeError(
+ f"Cannot delete the current limits set '{limits_set}'. Use set_limits_set to change the current set first."
+ )
+ if limits_set not in LimitsEventTopic.sets(scope=scope):
+ raise RuntimeError(f"Limits set '{limits_set}' does not exist")
+
+ # Remove the limits set from every telemetry item definition
+ for target_name in TargetModel.names(scope=scope):
+ for packet in TargetModel.packets(target_name, type="TLM", scope=scope):
+ modified = False
+ for item in packet["items"]:
+ if item["limits"] and limits_set in item["limits"]:
+ del item["limits"][limits_set]
+ modified = True
+ if modified:
+ TargetModel.set_packet(target_name, packet["packet_name"], packet, scope=scope)
+
+ # Remove the limits set from Redis (limits_sets and current_limits_settings)
+ LimitsEventTopic.delete_set(limits_set, scope=scope)
+
+ message = f"Deleting Limits Set: {limits_set}"
+ Logger.info(message, scope=scope)
+
+
# Returns the active limits set that applies to all telemetry
#
# @return [String] The current limits set
diff --git a/openc3/python/openc3/script/limits.py b/openc3/python/openc3/script/limits.py
index 8fd5283b40..4a7c7e03c2 100644
--- a/openc3/python/openc3/script/limits.py
+++ b/openc3/python/openc3/script/limits.py
@@ -20,6 +20,7 @@
"enable_limits_group",
"disable_limits_group",
"set_limits_set",
+ "delete_limits_set",
]
# Define all the modification methods such that we can disconnect them
diff --git a/openc3/python/openc3/topics/limits_event_topic.py b/openc3/python/openc3/topics/limits_event_topic.py
index cf38b59b23..4f6a1f2c79 100644
--- a/openc3/python/openc3/topics/limits_event_topic.py
+++ b/openc3/python/openc3/topics/limits_event_topic.py
@@ -194,6 +194,29 @@ def delete(cls, target_name, packet_name=None, scope=None):
if re.match(rf"^{target_name}__", item):
Store.hdel(f"{scope}__current_limits_settings", item)
+ # Removes a limits set from the limits_sets hash and from the
+ # current_limits_settings of every item. Note that the items themselves
+ # (in the TargetModel packet definitions) are cleaned up in the
+ # delete_limits_set API. Running microservices will continue to hold the
+ # set in memory until they restart and resync from current_limits_settings.
+ @classmethod
+ def delete_set(cls, set_name, scope):
+ set_name = str(set_name)
+ limits_settings = Store.hgetall(f"{scope}__current_limits_settings")
+ # decode the binary string keys to strings
+ limits_settings = {k.decode(): v for (k, v) in limits_settings.items()}
+ # Collect all changed items and write them back in a single hset to
+ # avoid a Redis round trip per item (this hash can be large)
+ updates = {}
+ for item, settings in limits_settings.items():
+ settings = json.loads(settings)
+ if set_name in settings:
+ del settings[set_name]
+ updates[item] = json.dumps(settings)
+ if updates:
+ Store.hset(f"{scope}__current_limits_settings", mapping=updates)
+ Store.hdel(f"{scope}__limits_sets", set_name)
+
# Update the local System based on overall state
@classmethod
def sync_system(cls, scope):
diff --git a/openc3/python/test/api/test_limits_api.py b/openc3/python/test/api/test_limits_api.py
index a540fa008b..6d511cf53e 100644
--- a/openc3/python/test/api/test_limits_api.py
+++ b/openc3/python/test/api/test_limits_api.py
@@ -259,6 +259,36 @@ def test_gets_and_set_the_active_limits_set(self):
set_limits_set("DEFAULT")
self.assertEqual(get_limits_set(), "DEFAULT")
+ def test_delete_limits_set_complains_about_default(self):
+ with self.assertRaisesRegex(RuntimeError, "Cannot delete the DEFAULT limits set"):
+ delete_limits_set("DEFAULT")
+
+ def test_delete_limits_set_complains_about_current_set(self):
+ set_limits_set("TVAC")
+ with self.assertRaisesRegex(RuntimeError, "Cannot delete the current limits set 'TVAC'"):
+ delete_limits_set("TVAC")
+
+ def test_delete_limits_set_complains_about_non_existent_set(self):
+ with self.assertRaisesRegex(RuntimeError, "Limits set 'NOPE' does not exist"):
+ delete_limits_set("NOPE")
+
+ def test_delete_limits_set_removes_set_from_all_items(self):
+ self.assertEqual(get_limits_sets(), ["DEFAULT", "TVAC"])
+ self.assertIn("TVAC", get_limits("INST", "HEALTH_STATUS", "TEMP1").keys())
+
+ delete_limits_set("TVAC")
+
+ self.assertEqual(get_limits_sets(), ["DEFAULT"])
+ self.assertNotIn("TVAC", get_limits("INST", "HEALTH_STATUS", "TEMP1").keys())
+ self.assertIn("DEFAULT", get_limits("INST", "HEALTH_STATUS", "TEMP1").keys())
+
+ def test_delete_limits_set_created_with_set_limits(self):
+ set_limits("INST", "HEALTH_STATUS", "TEMP1", 0.0, 10.0, 20.0, 30.0) # creates CUSTOM
+ self.assertIn("CUSTOM", get_limits_sets())
+ delete_limits_set("CUSTOM")
+ self.assertNotIn("CUSTOM", get_limits_sets())
+ self.assertNotIn("CUSTOM", get_limits("INST", "HEALTH_STATUS", "TEMP1").keys())
+
def test_get_limits_events_returns_an_offset_and_limits_event_hash(self):
# Load the events topic with two events ... only the last should be returned
event = {
diff --git a/openc3/spec/api/limits_api_spec.rb b/openc3/spec/api/limits_api_spec.rb
index 47b6cba84b..2be59d7023 100644
--- a/openc3/spec/api/limits_api_spec.rb
+++ b/openc3/spec/api/limits_api_spec.rb
@@ -194,6 +194,40 @@ def with_decom_microservice
end
end
+ describe "delete_limits_set" do
+ it "complains about deleting the DEFAULT limits set" do
+ expect { @api.delete_limits_set("DEFAULT") }.to raise_error(RuntimeError, "Cannot delete the DEFAULT limits set")
+ end
+
+ it "complains about deleting the current limits set" do
+ @api.set_limits_set("TVAC")
+ expect { @api.delete_limits_set("TVAC") }.to raise_error(RuntimeError, /Cannot delete the current limits set 'TVAC'/)
+ end
+
+ it "complains about non-existent limits sets" do
+ expect { @api.delete_limits_set("NOPE") }.to raise_error(RuntimeError, "Limits set 'NOPE' does not exist")
+ end
+
+ it "deletes a limits set and removes it from all items" do
+ expect(@api.get_limits_sets).to eql ['DEFAULT', 'TVAC']
+ expect(@api.get_limits("INST", "HEALTH_STATUS", "TEMP1").keys).to include('TVAC')
+
+ @api.delete_limits_set("TVAC")
+
+ expect(@api.get_limits_sets).to eql ['DEFAULT']
+ expect(@api.get_limits("INST", "HEALTH_STATUS", "TEMP1").keys).to_not include('TVAC')
+ expect(@api.get_limits("INST", "HEALTH_STATUS", "TEMP1").keys).to include('DEFAULT')
+ end
+
+ it "deletes a limits set created with set_limits" do
+ @api.set_limits("INST", "HEALTH_STATUS", "TEMP1", 0.0, 10.0, 20.0, 30.0) # creates CUSTOM
+ expect(@api.get_limits_sets).to include('CUSTOM')
+ @api.delete_limits_set("CUSTOM")
+ expect(@api.get_limits_sets).to_not include('CUSTOM')
+ expect(@api.get_limits("INST", "HEALTH_STATUS", "TEMP1").keys).to_not include('CUSTOM')
+ end
+ end
+
describe "get_limits_events" do
it "returns empty array with no events" do
events = @api.get_limits_events()
diff --git a/openc3/spec/script/limits_spec.rb b/openc3/spec/script/limits_spec.rb
index 85446a20e6..c58660fc46 100644
--- a/openc3/spec/script/limits_spec.rb
+++ b/openc3/spec/script/limits_spec.rb
@@ -51,7 +51,7 @@ module OpenC3
get_limits_events()
# These methods are simply logged in disconnect mode and don't go through
- setters = %i(enable_limits disable_limits set_limits enable_limits_group disable_limits_group set_limits_set)
+ setters = %i(enable_limits disable_limits set_limits enable_limits_group disable_limits_group set_limits_set delete_limits_set)
setters.each do |method_name|
if state == 'connected'
expect(@proxy).to receive(method_name)