Skip to content

Commit

Permalink
Support both globals and locals in python eval
Browse files Browse the repository at this point in the history
  • Loading branch information
jmthomas committed Jan 27, 2025
1 parent 957cbe2 commit e00ca40
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 59 deletions.
119 changes: 77 additions & 42 deletions docs.openc3.com/docs/guides/scripting-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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:

Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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:

Expand All @@ -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 |
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand Down
17 changes: 9 additions & 8 deletions openc3/python/openc3/api/api_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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():
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading

0 comments on commit e00ca40

Please sign in to comment.