Plugin Formulas

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 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 Device Objects 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 value of plugin. Note that the plugin you get is not necessarily loaded or running. See 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:

  • 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.

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

  • 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 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

  • 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) 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

  • 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.