From 825043dd0ebcfaf2d5d80394e98e67d7235581e0 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Thu, 23 Jan 2025 17:31:58 -0700 Subject: [PATCH 1/4] Document using globals() in python expressions --- docs.openc3.com/docs/guides/scripting-api.md | 67 +++++++++++++------- openc3.code-workspace | 3 + openc3/python/openc3/script/api_shared.py | 14 ++-- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/docs.openc3.com/docs/guides/scripting-api.md b/docs.openc3.com/docs/guides/scripting-api.md index 8909847424..5e3b424396 100644 --- a/docs.openc3.com/docs/guides/scripting-api.md +++ b/docs.openc3.com/docs/guides/scripting-api.md @@ -1216,19 +1216,27 @@ Now this evaluates to `'yes' == 'yes'` which is true so the check passes. Ruby / Python Syntax: ```ruby -check_expression("") +check_expression("", ) ``` -| Parameter | Description | -| ---------- | -------------------------- | -| Expression | An expression to evaluate. | +| Parameter | Description | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Expression | An expression to evaluate. | +| Context | The context to call eval against. Context in Ruby is typically binding() while in Python it is globals(). Note that to use COSMOS APIs like tlm() in python you must pass globals(). | -Ruby / Python Example: +Ruby Example: ```ruby check_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0") ``` +Python Example: + +```python +# Note that for Python we need to pass globals() to be able to use COSMOS API methods like tlm() +check_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0", globals()) +``` + ### check_exception Executes a method and expects an exception to be raised. If the method does not raise an exception, a CheckError is raised. @@ -1914,24 +1922,31 @@ success = wait_tolerance("INST HEALTH_STATUS COLLECTS", 10.0, 5.0, 10, type='RAW Pauses the script until an expression is evaluated to be true or a timeout occurs. If a timeout occurs the script will continue. This method can be used to perform more complicated comparisons than using wait as shown in the example. Note that on a timeout, wait_expression does not stop the script, usually [wait_check_expression](#wait_check_expression) is a better choice. -Syntax: +Ruby / Python Syntax: ```ruby # Returns true or false based on the whether the expression is true or false -success = wait_expression("", , , quiet) +success = wait_expression("", , , , quiet) ``` -| Parameter | Description | -| ------------ | -------------------------------------------------------------------------------------------------------------- | -| Expression | A ruby expression to evaluate. | -| Timeout | Timeout in seconds. Script will proceed if the wait statement times out waiting for the comparison to be true. | -| Polling Rate | How often the comparison is evaluated in seconds. Defaults to 0.25 if not specified. | -| quiet | Named parameter indicating whether to log the result. Defaults to true. | +| Parameter | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Expression | A ruby expression to evaluate. | +| Timeout | Timeout in seconds. Script will proceed if the wait statement times out waiting for the comparison to be true. | +| Polling Rate | How often the comparison is evaluated in seconds. Defaults to 0.25 if not specified. | +| Context | The context to call eval against. Context in Ruby is typically binding() while in Python it is globals(). Note that to use COSMOS APIs like tlm() in python you must pass globals(). | +| quiet | Named parameter indicating whether to log the result. Defaults to false which means to log. | -Ruby / Python Example: +Ruby Example: ```ruby -success = wait_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0", 10) +success = wait_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0", 10, 0.25, nil, quiet: true) +``` + +Python Example: + +```python +success = wait_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0", 10, 0.25, globals(), quiet=True) ``` ### wait_packet @@ -2039,21 +2054,29 @@ Ruby / Python Syntax: ```ruby # Returns the amount of time elapsed waiting for the expression -elapsed = wait_check_expression("", , ) +elapsed = wait_check_expression("", , , ) ``` -| Parameter | Description | -| ------------ | ----------------------------------------------------------------------------------------------------------- | -| Expression | A ruby expression to evaluate. | -| Timeout | Timeout in seconds. Script will stop if the wait statement times out waiting for the comparison to be true. | -| Polling Rate | How often the comparison is evaluated in seconds. Defaults to 0.25 if not specified. | +| Parameter | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Expression | An expression to evaluate. | +| Timeout | Timeout in seconds. Script will stop if the wait statement times out waiting for the comparison to be true. | +| Polling Rate | How often the comparison is evaluated in seconds. Defaults to 0.25 if not specified. | +| Context | The context to call eval against. Context in Ruby is typically binding() while in Python it is globals(). Note that to use COSMOS APIs like tlm() in python you must pass globals(). | -Ruby / Python Example: +Ruby Example: ```ruby elapsed = wait_check_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0", 10) ``` +Python Example: + +```python +# Note that for Python we need to pass globals() to be able to use COSMOS API methods like tlm() +elapsed = wait_check_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_STATUS TEMP1') > 25.0", 10, 0.25, globals()) +``` + ### wait_check_packet Pauses the script until a certain number of packets have been received. If a timeout occurs the script will stop. diff --git a/openc3.code-workspace b/openc3.code-workspace index 4e858bd353..1e76570a5a 100644 --- a/openc3.code-workspace +++ b/openc3.code-workspace @@ -14,6 +14,9 @@ }, { "path": "../cosmos-enterprise-plugins" + }, + { + "path": "../openc3-cosmos-cfdp" } ], "settings": { diff --git a/openc3/python/openc3/script/api_shared.py b/openc3/python/openc3/script/api_shared.py index 76d0612f47..6e4ba1c2a2 100644 --- a/openc3/python/openc3/script/api_shared.py +++ b/openc3/python/openc3/script/api_shared.py @@ -160,10 +160,10 @@ def check_tolerance(*args, type="CONVERTED", scope=OPENC3_SCOPE): raise CheckError(message) -def check_expression(exp_to_eval, locals=None): +def check_expression(exp_to_eval, context=None): """Check to see if an expression is true without waiting. If the expression is not true, the script will pause.""" - success = _openc3_script_wait_expression(exp_to_eval, 0, DEFAULT_TLM_POLLING_RATE, locals) + success = _openc3_script_wait_expression(exp_to_eval, 0, DEFAULT_TLM_POLLING_RATE, context) if success: print(f"CHECK: {exp_to_eval} is TRUE") else: @@ -340,12 +340,12 @@ def wait_expression( exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, - locals=None, + context=None, quiet=False, ): """Wait on a custom expression to be true""" start_time = time.time() - success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, locals) + success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context) time_diff = time.time() - start_time if not quiet: if success: @@ -993,7 +993,7 @@ def _openc3_script_wait_array_tolerance( ) -def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, locals=None): +def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context=None): """Wait on an expression to be true.""" end_time = time.time() + timeout if not exp_to_eval.isascii(): @@ -1002,7 +1002,7 @@ def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, locals=No try: while True: work_start = time.time() - if eval(exp_to_eval, locals): + if eval(exp_to_eval, context): return True if time.time() >= end_time: break @@ -1017,7 +1017,7 @@ def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, locals=No canceled = openc3_script_sleep(sleep_time) if canceled: - if eval(exp_to_eval, locals): + if eval(exp_to_eval, context): return True else: return None From 08a1e21483b28e0a11df35948d169607ba639c69 Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Sat, 25 Jan 2025 21:24:53 -0700 Subject: [PATCH 2/4] Update disconnect with better wait_check_expression example --- .../openc3-cosmos-demo/targets/INST/procedures/disconnect.rb | 2 +- .../openc3-cosmos-demo/targets/INST2/procedures/disconnect.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST/procedures/disconnect.rb b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST/procedures/disconnect.rb index d102878359..8c97aacd9b 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST/procedures/disconnect.rb +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST/procedures/disconnect.rb @@ -17,5 +17,5 @@ wait_packet("<%= target_name %>","ADCS", 2, 5) wait_check("<%= target_name %> ADCS BIASX == 100", 5) wait_check_tolerance("<%= target_name %> ADCS BIASX", 5, 0.5, 5) -wait_check_expression("true == false", 5) +wait_check_expression("tlm('<%= target_name %> HEALTH_STATUS TEMP1') < 101", 5, 0.25) wait_check_packet("<%= target_name %>","ADCS", 2, 5) diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/disconnect.py b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/disconnect.py index 486b70d134..a2626cf0bc 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/disconnect.py +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/targets/INST2/procedures/disconnect.py @@ -17,5 +17,5 @@ wait_packet("<%= target_name %>", "ADCS", 2, 5) wait_check("<%= target_name %> ADCS BIASX == 100", 5) wait_check_tolerance("<%= target_name %> ADCS BIASX", 5, 0.5, 5) -wait_check_expression("True == False", 5) +wait_check_expression(f"tlm('<%= target_name %> HEALTH_STATUS TEMP1') < 101", 5, 0.25, globals()) wait_check_packet("<%= target_name %>", "ADCS", 2, 5) From e00ca40582d9fd2787dee9978f23350dc4693a9c Mon Sep 17 00:00:00 2001 From: Jason Thomas Date: Mon, 27 Jan 2025 14:08:59 -0700 Subject: [PATCH 3/4] Support both globals and locals in python eval --- docs.openc3.com/docs/guides/scripting-api.md | 119 ++++++++++++------- openc3/python/openc3/api/api_shared.py | 17 +-- openc3/python/openc3/script/api_shared.py | 19 +-- 3 files changed, 96 insertions(+), 59 deletions(-) diff --git a/docs.openc3.com/docs/guides/scripting-api.md b/docs.openc3.com/docs/guides/scripting-api.md index 5e3b424396..28795fbcc8 100644 --- a/docs.openc3.com/docs/guides/scripting-api.md +++ b/docs.openc3.com/docs/guides/scripting-api.md @@ -152,14 +152,14 @@ Prompts the user for input with a question. User input is automatically converte Ruby / Python Syntax: ```ruby -ask("", , ) +ask("", , ) ``` | Parameter | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | question | Question to prompt the user with. | -| blank_or_default | Whether or not to allow empty responses (optional - defaults to false). If a non-boolean value is passed it is used as a default value. | -| password | Whether to treat the entry as a password which is displayed with dots and not logged. Default is false. | +| Blank or Default | Whether or not to allow empty responses (optional - defaults to false). If a non-boolean value is passed it is used as a default value. | +| Password | Whether to treat the entry as a password which is displayed with dots and not logged. Default is false. | Ruby Example: @@ -186,14 +186,14 @@ Prompts the user for input with a question. User input is always returned as a s Ruby / Python Syntax: ```ruby -ask_string("", , ) +ask_string("", , ) ``` | Parameter | Description | | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | question | Question to prompt the user with. | -| blank_or_default | Whether or not to allow empty responses (optional - defaults to false). If a non-boolean value is passed it is used as a default value. | -| password | Whether to treat the entry as a password which is displayed with dots and not logged. Default is false. | +| Blank or Default | Whether or not to allow empty responses (optional - defaults to false). If a non-boolean value is passed it is used as a default value. | +| Password | Whether to treat the entry as a password which is displayed with dots and not logged. Default is false. | Ruby Example: @@ -224,15 +224,15 @@ The message_box, vertical_message_box, and combo_box methods create a message bo Ruby / Python Syntax: ```ruby -message_box("", "