From 825043dd0ebcfaf2d5d80394e98e67d7235581e0 Mon Sep 17 00:00:00 2001 From: Jason Thomas <jason@openc3.com> 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("<Expression>") +check_expression("<Expression>", <Context (optional)>) ``` -| 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("<Expression>", <Timeout>, <Polling Rate (optional)>, quiet) +success = wait_expression("<Expression>", <Timeout>, <Polling Rate (optional)>, <Context (optional)>, 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("<Expression>", <Timeout>, <Polling Rate (optional)>) +elapsed = wait_check_expression("<Expression>", <Timeout>, <Polling Rate (optional)>, <Context (optional)>) ``` -| 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 <jason@openc3.com> 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 <jason@openc3.com> 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("<question>", <blank_or_default>, <password>) +ask("<question>", <Blank or Default>, <Password>) ``` | 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("<question>", <blank_or_default>, <password>) +ask_string("<question>", <Blank or Default>, <Password>) ``` | 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("<message>", "<button text 1>", ...) -vertical_message_box("<message>", "<button text 1>", ...) -combo_box("<message>", "<selection text 1>", ...) +message_box("<Message>", "<button text 1>", ...) +vertical_message_box("<Message>", "<button text 1>", ...) +combo_box("<Message>", "<selection text 1>", ...) ``` | Parameter | Description | | --------------------- | -------------------------------- | -| message | Message to prompt the user with. | -| button/selection text | Text for a button or selection | +| Message | Message to prompt the user with. | +| Button/Selection Text | Text for a button or selection | Ruby Example: @@ -279,7 +279,7 @@ get_target_file("<File Path>", original=False) | Parameter | Description | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| path | The path to the file in the target directory. Should assume to start with a TARGET name, e.g. INST/procedures/proc.rb | +| File Path | The path to the file in the target directory. Should assume to start with a TARGET name, e.g. INST/procedures/proc.rb | | original | Whether to get the original file from the plug-in, or any modifications to the file. Default is false which means to grab the modified file. If the modified file does not exist the API will automatically try to pull the original. | Ruby Example: @@ -316,10 +316,10 @@ Ruby or Python Syntax: put_target_file("<File Path>", "IO or String") ``` -| Parameter | Description | -| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| path | The path to the file in the target directory. Should assume to start with a TARGET name, e.g. INST/procedures/proc.rb. The file can previously exist or not. Note: The original file from the plug-in will not be modified, however existing modified files will be overwritten. | -| data | The data can be an IO object or String | +| Parameter | Description | +| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| File Path | The path to the file in the target directory. Should assume to start with a TARGET name, e.g. INST/procedures/proc.rb. The file can previously exist or not. Note: The original file from the plug-in will not be modified, however existing modified files will be overwritten. | +| IO or String | The data can be an IO object or String | Ruby Example: @@ -355,7 +355,7 @@ delete_target_file("<File Path>") | Parameter | Description | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| path | The path to the file in the target directory. Should assume to start with a TARGET name, e.g. INST/procedures/proc.rb. Note: Only files created with put_target_file can be deleted. Original files from the plugin installation will remain. | +| File Path | The path to the file in the target directory. Should assume to start with a TARGET name, e.g. INST/procedures/proc.rb. Note: Only files created with put_target_file can be deleted. Original files from the plugin installation will remain. | Ruby / Python Example: @@ -375,15 +375,15 @@ Note: COSMOS 5 has deprecated the save_file_dialog and open_directory_dialog met Ruby Syntax: ```ruby -open_file_dialog("<title>", "<message>", filter: "<filter>") -open_files_dialog("<title>", "<message>", filter: "<filter>") +open_file_dialog("<Title>", "<Message>", filter: "<filter>") +open_files_dialog("<Title>", "<Message>", filter: "<filter>") ``` Python Syntax: ```python -open_file_dialog("<title>", "<message>", filter="<filter>") -open_files_dialog("<title>", "<message>", filter="<filter>") +open_file_dialog("<Title>", "<Message>", filter="<filter>") +open_files_dialog("<Title>", "<Message>", filter="<filter>") ``` | Parameter | Description | @@ -436,12 +436,12 @@ Displays a message to the user and waits for them to press an ok button. Ruby / Python Syntax: ```ruby -prompt("<message>") +prompt("<Message>") ``` | Parameter | Description | | --------- | -------------------------------- | -| message | Message to prompt the user with. | +| Message | Message to prompt the user with. | Ruby / Python Example: @@ -1213,16 +1213,24 @@ This evaluates to `yes == 'yes'` which is not valid syntax because the variable Now this evaluates to `'yes' == 'yes'` which is true so the check passes. -Ruby / Python Syntax: +Ruby Syntax: ```ruby -check_expression("<Expression>", <Context (optional)>) +check_expression(exp_to_eval, context = nil) +``` + +Python Syntax: + +```python +check_expression(exp_to_eval, globals=None, locals=None) ``` -| 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(). | +| Parameter | Description | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| exp_to_eval | An expression to evaluate. | +| context (ruby only) | The context to call eval with. Defaults to nil. Context in Ruby is typically binding() and is usually not needed. | +| globals (python only) | The globals to call eval with. Defaults to None. Note that to use COSMOS APIs like tlm() you must pass globals(). | +| locals (python only) | The locals to call eval with. Defaults to None. Note that if you're using local variables in a method you must pass locals(). | Ruby Example: @@ -1233,8 +1241,10 @@ check_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST HEALTH_ST 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()) +def check(value): + # Here we using both tlm() and a local 'value' so we need to pass globals() and locals() + check_expression("tlm('INST HEALTH_STATUS COLLECTS') > value", 5, 0.25, globals(), locals()) +check(5) ``` ### check_exception @@ -1922,20 +1932,42 @@ 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. -Ruby / Python Syntax: +Ruby Syntax: ```ruby -# Returns true or false based on the whether the expression is true or false -success = wait_expression("<Expression>", <Timeout>, <Polling Rate (optional)>, <Context (optional)>, quiet) +# Return true or false based the expression evaluation +wait_expression( + exp_to_eval, + timeout, + polling_rate = DEFAULT_TLM_POLLING_RATE, + context = nil, + quiet: false +) -> boolean ``` -| 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. | +Python Syntax: + +```python +# Return True or False based on the expression evaluation +wait_expression( + exp_to_eval, + timeout, + polling_rate=DEFAULT_TLM_POLLING_RATE, + globals=None, + locals=None, + quiet=False, +) -> bool +``` + +| Parameter | Description | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| expression | An 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 (ruby only) | The context to call eval with. Defaults to nil. Context in Ruby is typically binding() and is usually not needed. | +| globals (python only) | The globals to call eval with. Defaults to None. Note that to use COSMOS APIs like tlm() you must pass globals(). | +| locals (python only) | The locals to call eval with. Defaults to None. Note that if you're using local variables in a method you must pass locals(). | +| quiet | Whether to log the result. Defaults to false which means to log. | Ruby Example: @@ -1946,7 +1978,10 @@ success = wait_expression("tlm('INST HEALTH_STATUS COLLECTS') > 5 and tlm('INST 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) +def check(value): + # Here we using both tlm() and a local 'value' so we need to pass globals() and locals() + return wait_expression("tlm('INST HEALTH_STATUS COLLECTS') > value", 5, 0.25, globals(), locals(), quiet=True) +success = check(5) ``` ### wait_packet diff --git a/openc3/python/openc3/api/api_shared.py b/openc3/python/openc3/api/api_shared.py index 2d66b72f5f..2c2ee21567 100644 --- a/openc3/python/openc3/api/api_shared.py +++ b/openc3/python/openc3/api/api_shared.py @@ -154,10 +154,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, globals=None, locals=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, globals, locals) if success: print(f"CHECK: {exp_to_eval} is TRUE") else: @@ -331,12 +331,13 @@ def wait_expression( exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, + globals=None, locals=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, globals, locals) time_diff = time.time() - start_time if not quiet: if success: @@ -465,10 +466,10 @@ def wait_check_tolerance(*args, type="CONVERTED", scope=OPENC3_SCOPE): return time_diff -def wait_check_expression(exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, context=None): +def wait_check_expression(exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, globals=None, locals=None): """Wait on an expression to be true. On a timeout, the script will pause""" start_time = time.time() - success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context) + success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, globals, locals) time_diff = time.time() - start_time if success: print(f"CHECK: {exp_to_eval} is TRUE after waiting {time_diff:.3f} seconds") @@ -913,7 +914,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, globals=None, locals=None): """Wait on an expression to be true.""" end_time = time.time() + timeout if not exp_to_eval.isascii(): @@ -922,7 +923,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, globals, locals): return True if time.time() >= end_time: break @@ -937,7 +938,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, globals, locals): return True else: return None diff --git a/openc3/python/openc3/script/api_shared.py b/openc3/python/openc3/script/api_shared.py index 6e4ba1c2a2..2ee7fe1a29 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, context=None): +def check_expression(exp_to_eval, globals=None, locals=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, context) + success = _openc3_script_wait_expression(exp_to_eval, 0, DEFAULT_TLM_POLLING_RATE, globals, locals) if success: print(f"CHECK: {exp_to_eval} is TRUE") else: @@ -340,12 +340,13 @@ def wait_expression( exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, - context=None, + globals=None, + locals=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, context) + success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, globals, locals) time_diff = time.time() - start_time if not quiet: if success: @@ -483,10 +484,10 @@ def wait_check_tolerance(*args, type="CONVERTED", scope=OPENC3_SCOPE): return time_diff -def wait_check_expression(exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, context=None): +def wait_check_expression(exp_to_eval, timeout, polling_rate=DEFAULT_TLM_POLLING_RATE, globals=None, locals=None): """Wait on an expression to be true. On a timeout, the script will pause""" start_time = time.time() - success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context) + success = _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, globals, locals) time_diff = time.time() - start_time if success: print(f"CHECK: {exp_to_eval} is TRUE after waiting {time_diff:.3f} seconds") @@ -993,7 +994,7 @@ def _openc3_script_wait_array_tolerance( ) -def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context=None): +def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, globals, locals): """Wait on an expression to be true.""" end_time = time.time() + timeout if not exp_to_eval.isascii(): @@ -1002,7 +1003,7 @@ def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context=N try: while True: work_start = time.time() - if eval(exp_to_eval, context): + if eval(exp_to_eval, globals, locals): return True if time.time() >= end_time: break @@ -1017,7 +1018,7 @@ def _openc3_script_wait_expression(exp_to_eval, timeout, polling_rate, context=N canceled = openc3_script_sleep(sleep_time) if canceled: - if eval(exp_to_eval, context): + if eval(exp_to_eval, globals, locals): return True else: return None From c251df4c107d9f9ddabd1e60cceb8829c496c69d Mon Sep 17 00:00:00 2001 From: Jason Thomas <jason@openc3.com> Date: Mon, 27 Jan 2025 14:28:18 -0700 Subject: [PATCH 4/4] Update wait_check_expression --- docs.openc3.com/docs/guides/scripting-api.md | 38 +++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/docs.openc3.com/docs/guides/scripting-api.md b/docs.openc3.com/docs/guides/scripting-api.md index 28795fbcc8..3da029adcd 100644 --- a/docs.openc3.com/docs/guides/scripting-api.md +++ b/docs.openc3.com/docs/guides/scripting-api.md @@ -2085,19 +2085,39 @@ elapsed = wait_check_tolerance("INST HEALTH_STATUS COLLECTS", 10.0, 5.0, 10, typ Pauses the script until an expression is evaluated to be true or a timeout occurs. If a timeout occurs the script will stop. This method can be used to perform more complicated comparisons than using wait as shown in the example. Also see the syntax notes for [check_expression](#check_expression). -Ruby / Python Syntax: +Ruby Syntax: ```ruby -# Returns the amount of time elapsed waiting for the expression -elapsed = wait_check_expression("<Expression>", <Timeout>, <Polling Rate (optional)>, <Context (optional)>) +# Return time spent waiting for the expression to evaluate to true +wait_check_expression( + exp_to_eval, + timeout, + polling_rate = DEFAULT_TLM_POLLING_RATE, + context = nil +) -> int ``` -| 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(). | +Python Syntax: + +```python +# Return time spent waiting for the expression to evaluate to True +wait_check_expression( + exp_to_eval, + timeout, + polling_rate=DEFAULT_TLM_POLLING_RATE, + globals=None, + locals=None +) -> int +``` + +| Parameter | Description | +| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| expression | An 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 (ruby only) | The context to call eval with. Defaults to nil. Context in Ruby is typically binding() and is usually not needed. | +| globals (python only) | The globals to call eval with. Defaults to None. Note that to use COSMOS APIs like tlm() you must pass globals(). | +| locals (python only) | The locals to call eval with. Defaults to None. Note that if you're using local variables in a method you must pass locals(). | Ruby Example: