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:
- The code contains a single expression and yields its value
- The meaning of many names is different from a stand-alone script
- Evaluation happens in the context of a particular plugin
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 theindigo.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 accessdevices["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 accessvariables["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. Thusmodules.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 asorg.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 theindigo
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:- If the name was earlier the target of a Python assignment statement, it stands for the last value assigned to it. (It is a local variable.)
-
If it is the name of an Indigo variable, it stands for the current value of that variable. Groups are ignored. This is a shorthand for an attribute of the
variables
container. -
If it is the name of a Python module that could be imported, it stands for that module after importing it. This is a shorthand for an attribute of the
modules
container.
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
- name - the official name of the plugin
- enabled - true if the plugin is active and running. False if it is disabled or altogether missing.
- version - the version of the plugin as reported by Indigo
- location - the path to where the plugin is stored on disk
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
- id - the device's id value (a number)
- name - the device name
- active - true if the device is operating; false if it is disabled or otherwise defunct
- config - a dictionary of the device's configuration values
- io - the underlying Indigo device object as described by the Indigo plugin documentation (be careful with it)
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
- DEBUG - true if debug mode is enabled for the plugin
- device(id) - a function that, given an Indigo device id or name, returns cyin's device object for it
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.