Cynics at Large Expressions in Indigo Plugins

Using Expressions in Indigo Plugins

Cynical Plugins, like all Indigo plugins, have configuration values that you set in dialogs. For fields with string values, you usually just type that string into a text field. (Then there's checkboxes and menus and all those modern conveniences. But we're talking about strings here, even if they hold numbers.)

In addition to simple text, the Cynical Indigo plugins allow you to put formulas into many of these text fields. These formulas are taken as rules for calculating a value, rather than as the value itself. A formula is evaluated every time the plugin needs to use its value for something, and each evaluation can produce a different result. Every field labeled (dynamic) in a plugin's documentation has this capability, and this page explains how this all works.

Cynical plugin formulas are based on Python expressions - formulas as the Python programming language understands them. In fact, they are Python expressions. If you do not know Python and do not wish to learn, I've written a very simple tutorial to help you along. It might even help get you started if you do.

For the rest of this page, we'll assume that you know enough Python to write "Hello, World!" and you know how to use the Web to look up more.

Overview

You place a formula into a dynamic configuration text field by writing an equal sign "=" as its first character. That equal sign indicates that the rest of the text is a formula to be evaluated, rather than text to be taken as itself. Either way, the field yields a value - either the constant string, or the result of evaluating the formula.

A plugin formula is Python code - it is directly evaluated by the Python interpreter, and the entire power of Python is at your disposal. There are some differences between the Python code in a separate file, or in an Indigo Python script action, and what we're doing here:

All the power of Python is at your disposal - if it fits into a single expression. You can call functions, instantiate classes, even import modules.

The Meaning of Names

When a plugin formula is evaluated, Python's namespace contains a standard set of names already bound and ready to be used. Individual fields may define additional names to add context; those fields will document them explicitly. This section describes what is common to all plugin formulas.

plugin

This is the plugin itself, a subclass of the indigo.pluginBase that represents the entire plugin to Indigo. It has all the attributes described in the Indigo plugin documentation.

devices

This is a container for all the devices currently defined by this plugin only. Use container access devices["Frodo the hobbit"] to name a particular device. You can also use the device's id number instead of its name. If the name happens to be a valid Python identifier, you can also use attribute notation devices.frobo. See here for what to do with a device object.

variables

This is a container for all Indigo variables currently defined. Use container access variables["HouseOccupied"] to get the value of a particular variable. You can also use the variable's id number instead of its name. If the name happens to be a valid Python identifier, you can also use attribute notation variables.last_tick. The value of an Indigo variable is always a string.

modules

This is a container for all possible Python modules that could be imported at this point. Naming an attribute or element of this container imports the module and yields its value. Thus modules.time.time() yields the current time (in seconds from the Epoch), and modules.sys.version yields the version of the Python system in use.

plugins

This is a container for all plugins currently known to Indigo. You must use the reverse domain identifier for the plugin, such as org.cynic.indigo.gcnet for Cynical Network. If you use the current plugin's identifier, you'll get the same as just saying plugin. Note that the plugin you get is not necessarily loaded or running. See here for what to do with them.

device

If the field belongs to the configuration of a device, this is that device. If the field belongs to an action and that action applies to a device, it is that device. Otherwise this name is undefined.

indigo

This is the indigo object representing Indigo inside the plugin. It behaves exactly as documented in the Indigo plugin documentation.

Other Names

Any other name is resolved as follows: In other words, local variables win over indigo variables, and both win over module names. If none of these apply, the name is in error. When in doubt, use the appropriate container to get what you mean.

Plugin Objects

The plugin object represents the whole plugin in operation. This is a subclass of indigo.pluginBase and has all the attributes and methods defined in the Indigo plugin documentation. Useful attributes of plugin include

In addition, all plugin preferences can be named as attributes of the plugin object.

If you pick a plugin out of the plugins container (and it's not the current one), then only enabled and name are useful.

Device Objects

The device variable and the contents of the devices container yield device objects. These are not subclasses of Indigo's device class; they are defined by the cyin framework. Attributes of device objects include

In addition, all of the device's configuration values and state values can be named as attributes of the device object.

The cyin Module

The name cyin is bound to the cyin module. This is the module that makes much of the Cynical plugin magic happen. Among its contents you may find

Error Handling

If Python signals any error during evaluation of a plugin formula, evaluation stops and an error message is written to the Indigo log. The operation we were trying to accomplish is abandonned - an action is not taken; an event is not triggered; a device does not function; etc. The plugin will usually survive this - control returns to Indigo, and its next request to the plugin will be handled normally in the hope that this time things will work out better. Examine the error message and improve your formula.

Script Fields

Some fields are explicitly meant to hold Python scripts - multiple lines of Python statements that are executed for their effects, not their values. Anything allowed by Python can be used in such scripts - you can assign to local variables, define functions and classes, etc. You can assign to Indigo variables (the variable is created if needed, and the value is converted to a string if necessary). Assignment to an otherwise unknown name creates a local variable.

Script fields always contain Python code; they don't start with an equal sign. A plain string wouldn't make any sense for them.

Each Cynical plugin has a Do Script action whose sole purpose is to execute a plugin script. This is how you can write Python code that runs in the context of a particular plugin, and thus has direct access to its devices and their implementation features.

Unfortunately, Indigo's configuration UI has currently no way to ask for a field that displays multiple lines, let alone one that grows as you type into it. This is a limitation of Indigo, not of the underlying mechanism. You can work around that, for now, by assembling your script in any text editor and then copy-and-pasting it into a script field.

Formulas and Scripts from Outside a Plugin

Recall that plugin formulas and scripts are focused on one particular plugin, with special access to its plugin, devices, etc. A formula or script placed into a field for that plugin (including its devices, events, and actions) will automatically apply to it.

To run a plugin formula or script for a plugin from outside that plugin, you must send it a Perform Script action. The action identifier is scripting, and the script itself is sent as text item script. You may send a device name or id number in the device item; if you do, it will become the meaning of the device name inside the script.

One way to run a plugin script is to use the Indigo Plugin Host script and tell it something like this:


plugin = indigo.server.getPlugin("org.cynic.indigo.gcnet")	# which plugin to use
plugin.executeAction("scripting", 0, {'script': """
# your Python code goes here. For example:
import time
debug("Hi there! It is now precisely %s." % time.asctime())
"""})

Do keep in mind that this only works in the Cynical Plugins.

Security Considerations

The Indigo plugin is not trying to defend itself against nasty, subversive formulas or scripts. You can most definitely mess up the plugin's operation with ill-advised function calls, and you can also tell Indigo to do unsafe things. Plugin formulas have access to the Macintosh filesystem and the network. Act responsibly and do not blindly use formulas provided by others.


About This Area 29 Oct 2019 19:52