Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions docs.openc3.com/docs/guides/scripting-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -4942,6 +4943,52 @@ set_limits_set("DEFAULT")
</TabItem>
</Tabs>

### delete_limits_set

<span class="badge badge--secondary since-heading">Since 7.3.0</span>

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.

<Tabs groupId="script-language">
<TabItem value="python" label="Python Syntax">

```python
delete_limits_set("<Limits Set Name>")
```

</TabItem>

<TabItem value="ruby" label="Ruby Syntax">

```ruby
delete_limits_set("<Limits Set Name>")
```

</TabItem>
</Tabs>

| Parameter | Description |
| --------------- | --------------------------------- |
| Limits Set Name | Name of the limits set to delete. |

<Tabs groupId="script-language">
<TabItem value="python" label="Python Example">

```python
delete_limits_set("TVAC")
```

</TabItem>

<TabItem value="ruby" label="Ruby Example">

```ruby
delete_limits_set("TVAC")
```

</TabItem>
</Tabs>

### get_limits_set

<span class="badge badge--secondary since-heading">Since 5.0.0</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ***********************************************
Expand Down
38 changes: 38 additions & 0 deletions openc3/lib/openc3/api/limits_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
'get_limits_sets',
'set_limits_set',
'get_limits_set',
'delete_limits_set',
'get_limits_events',
])

Expand Down Expand Up @@ -292,6 +293,43 @@
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'

Check warning on line 304 in openc3/lib/openc3/api/limits_api.rb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a specific exception class instead of raising a string literal.

See more on https://sonarcloud.io/project/issues?id=OpenC3_cosmos&issues=AZ7ce67d0X-JifGOfVEs&open=AZ7ce67d0X-JifGOfVEs&pullRequest=3493
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."

Check warning on line 306 in openc3/lib/openc3/api/limits_api.rb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a specific exception class instead of raising a string literal.

See more on https://sonarcloud.io/project/issues?id=OpenC3_cosmos&issues=AZ7ce67d0X-JifGOfVEt&open=AZ7ce67d0X-JifGOfVEt&pullRequest=3493
end
unless LimitsEventTopic.sets(scope: scope).key?(limits_set)
raise "Limits set '#{limits_set}' does not exist"

Check warning on line 309 in openc3/lib/openc3/api/limits_api.rb

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a specific exception class instead of raising a string literal.

See more on https://sonarcloud.io/project/issues?id=OpenC3_cosmos&issues=AZ7ce67d0X-JifGOfVEu&open=AZ7ce67d0X-JifGOfVEu&pullRequest=3493
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
Expand Down
2 changes: 1 addition & 1 deletion openc3/lib/openc3/script/limits.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions openc3/lib/openc3/topics/limits_event_topic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
36 changes: 36 additions & 0 deletions openc3/python/openc3/api/limits_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"get_limits_sets",
"set_limits_set",
"get_limits_set",
"delete_limits_set",
"get_limits_events",
]
)
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions openc3/python/openc3/script/limits.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions openc3/python/openc3/topics/limits_event_topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
30 changes: 30 additions & 0 deletions openc3/python/test/api/test_limits_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
34 changes: 34 additions & 0 deletions openc3/spec/api/limits_api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion openc3/spec/script/limits_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading