Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| indigo_2022.1_documentation:plugin_scripting_tutorial [2022/03/20 00:43] – [Turn on the device office desk lamp:] - Minor change davel17 | indigo_2022.1_documentation:plugin_scripting_tutorial [2025/04/14 20:10] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Indigo Scripting Tutorial ====== | ||
| + | ===== Talking to the Indigo Server ===== | ||
| + | |||
| + | The [[plugin_guide# | ||
| + | |||
| + | To launch the Terminal utility application (inside //''/ | ||
| + | |||
| + | Note: you'll want to read through the [[object_model_reference# | ||
| + | ===== Example Code Snippets ===== | ||
| + | |||
| + | Below are some sample scripts you can copy/paste directly into the IPH window (opened when following the directions above). Keep in mind, these are only examples, so be sure to read the full [[object_model_reference|Indigo Object Model (IOM) Reference]]. | ||
| + | |||
| + | <color red> | ||
| + | |||
| + | ==== Device Examples ==== | ||
| + | |||
| + | ===Turn on the device " | ||
| + | < | ||
| + | indigo.device.turnOn(1234567890) # Preferred, where the number is the ID of " | ||
| + | |||
| + | # OR the less preferred way because it will break if you rename the device | ||
| + | indigo.device.turnOn(" | ||
| + | </ | ||
| + | ===Duplicate the device " | ||
| + | < | ||
| + | indigo.device.duplicate(1234567890, | ||
| + | </ | ||
| + | ===In 4 seconds turn on the device " | ||
| + | < | ||
| + | indigo.device.turnOn(1234567890, | ||
| + | </ | ||
| + | ===Turn off all devices:=== | ||
| + | < | ||
| + | indigo.device.allOff() | ||
| + | </ | ||
| + | |||
| + | ===Count the number of device modules:=== | ||
| + | < | ||
| + | indigo.devices.len() | ||
| + | </ | ||
| + | |||
| + | ===Count the number of dimmable device modules:=== | ||
| + | < | ||
| + | indigo.devices.len(filter=" | ||
| + | </ | ||
| + | |||
| + | ===Count the number of devices defined by all of our plugin types:=== | ||
| + | < | ||
| + | indigo.devices.len(filter=" | ||
| + | </ | ||
| + | |||
| + | ===Count the number of irBlaster type devices defined by our plugin:=== | ||
| + | < | ||
| + | indigo.devices.len(filter=" | ||
| + | </ | ||
| + | |||
| + | ===Get the on state of a device if it has the onState property (uses Python' | ||
| + | < | ||
| + | lamp = indigo.devices[1234567890] # where the number is the ID of " | ||
| + | if hasattr(lamp, | ||
| + | isOn = lamp.onState | ||
| + | </ | ||
| + | ===Get the class of a device (uses Python' | ||
| + | < | ||
| + | lamp = indigo.devices[1234567890] # where the number is the ID of " | ||
| + | if lamp.__class__ == indigo.DimmerDevice: | ||
| + | theBrightness = lamp.brightness | ||
| + | </ | ||
| + | ===Turn on a light only if it's been off for longer than 1 minute:=== | ||
| + | < | ||
| + | from datetime import datetime | ||
| + | lamp = indigo.devices[91776575] # ID of " | ||
| + | timeDelta = datetime.now() - lamp.lastChanged | ||
| + | if not lamp.onState and timeDelta.seconds > 60: | ||
| + | indigo.device.turnOn(91776575) # ID of " | ||
| + | </ | ||
| + | === Access a custom device state === | ||
| + | < | ||
| + | # get the device | ||
| + | dev = indigo.devices[23989834] | ||
| + | # access the state through the states property, use the key | ||
| + | # that's displayed in the Custom States tile on the main window | ||
| + | # when you have a custom device selected | ||
| + | print(dev.states[" | ||
| + | |||
| + | # show all the device states: | ||
| + | print(dev.states) | ||
| + | </ | ||
| + | ==== Variable Examples ==== | ||
| + | |||
| + | === Create a new Indigo variable named fooMonster, change its value multiple times, and delete it: === | ||
| + | < | ||
| + | newVar = indigo.variable.create(" | ||
| + | indigo.variable.updateValue(newVar, | ||
| + | indigo.variable.updateValue(newVar, | ||
| + | indigo.variable.delete(newVar) | ||
| + | </ | ||
| + | |||
| + | === Getting a variable object and using its value: === | ||
| + | < | ||
| + | if myVar.value == " | ||
| + | indigo.server.log(" | ||
| + | </ | ||
| + | |||
| + | |||
| + | === Duplicating a variable: === | ||
| + | < | ||
| + | indigo.variable.duplicate(" | ||
| + | </ | ||
| + | |||
| + | === Using a variable value in an HTTP GET === | ||
| + | |||
| + | < | ||
| + | my_var = indigo.variables[1893500335] # always use variable ID rather than name | ||
| + | query_args = {" | ||
| + | reply = requests.get(" | ||
| + | </ | ||
| + | === Setting a variable to Python types === | ||
| + | |||
| + | Since Indigo variable values are always strings, you have to convert anything that's not a string. It's safest to use the str() method: | ||
| + | |||
| + | < | ||
| + | my_ascii_string = "ASCII String" | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | |||
| + | my_unicode_string = " | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | |||
| + | my_integer = 1 | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | |||
| + | my_float = 1.0 | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | |||
| + | my_list = [" | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | |||
| + | my_dictionary = {" | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | </ | ||
| + | |||
| + | Any Python object that can be converted to a string can then be inserted into an Indigo variable. <color red> | ||
| + | |||
| + | < | ||
| + | import json | ||
| + | my_list = [" | ||
| + | my_dictionary = {" | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | # string will be something like: ' | ||
| + | my_new_dictionary = json.loads(indigo.variables[1234567].value) | ||
| + | # | ||
| + | my_new_dictionary[" | ||
| + | # results in: [' | ||
| + | </ | ||
| + | |||
| + | Custom Python classes that you create can implement the %%__str__(self)%% method. Take this simple example: | ||
| + | |||
| + | < | ||
| + | class myCustomClass: | ||
| + | def __init__(self): | ||
| + | self.a=1 | ||
| + | self.b=2 | ||
| + | def __str__(self): | ||
| + | outputString = f" | ||
| + | return outputString | ||
| + | </ | ||
| + | |||
| + | If you have an instance of that class, then you can create a string representation of the class to insert into an Indigo variable: | ||
| + | |||
| + | < | ||
| + | cl = myCustomClass() | ||
| + | indigo.variable.updateValue(1234567, | ||
| + | # the value of the variable in Indigo will look like this: 'a:1, b:2' not including the quotes | ||
| + | </ | ||
| + | |||
| + | For further information on Python classes, [[http:// | ||
| + | ==== Date and Time Examples ==== | ||
| + | |||
| + | === Get the current server time: === | ||
| + | < | ||
| + | indigo.server.getTime() | ||
| + | </ | ||
| + | |||
| + | ===Calculate the sunset time in 1 week:=== | ||
| + | < | ||
| + | import datetime | ||
| + | one_week = indigo.server.getTime().date() + datetime.timedelta(days=7) | ||
| + | indigo.server.calculateSunset(one_week) | ||
| + | </ | ||
| + | |||
| + | ==== Action Group Examples ==== | ||
| + | |||
| + | ===Execute Action Group 12345678: | ||
| + | < | ||
| + | indigo.actionGroup.execute(12345678) | ||
| + | </ | ||
| + | |||
| + | |||
| + | ==== Log Examples ==== | ||
| + | |||
| + | ===Log to the Indigo Event Log window all the attributes/ | ||
| + | < | ||
| + | lamp = indigo.devices[" | ||
| + | indigo.server.log(f" | ||
| + | </ | ||
| + | ===Log to the Indigo Event Log window using different levels of logging== | ||
| + | < | ||
| + | import logging | ||
| + | indigo.server.log(" | ||
| + | indigo.server.log(" | ||
| + | indigo.server.log(" | ||
| + | |||
| + | # Equivalent of above server API calls that instead use the plugin instances default logger instance: | ||
| + | self.logger.info(" | ||
| + | self.logger.warn(" | ||
| + | self.logger.error(" | ||
| + | </ | ||
| + | |||
| + | ===Print the last 5 Event Log entries:=== | ||
| + | < | ||
| + | logList = indigo.server.getEventLogList(lineCount=5) | ||
| + | print(logList) | ||
| + | </ | ||
| + | |||
| + | ==== Folder Examples ==== | ||
| + | |||
| + | ===Iterate over a list of all device folders=== | ||
| + | < | ||
| + | for folder in indigo.devices.folders: | ||
| + | print(f" | ||
| + | </ | ||
| + | |||
| + | ===Create a trigger folder named "My Triggers" | ||
| + | < | ||
| + | try: | ||
| + | myFolder = indigo.triggers.folder.create(" | ||
| + | except ValueError as e: | ||
| + | if e.message == " | ||
| + | # a folder with that name already exists so just get it | ||
| + | myFolder = indigo.triggers.folders[" | ||
| + | else: | ||
| + | # you'll probably want to do something else to make myFolder a valid folder | ||
| + | myFolder = None | ||
| + | </ | ||
| + | |||
| + | ===Make a folder visible in remote clients (IWS, Indigo Touch, etc.)=== | ||
| + | < | ||
| + | indigo.devices.folder.displayInRemoteUI(123, | ||
| + | </ | ||
| + | |||
| + | ===Get the folder containing a device=== | ||
| + | < | ||
| + | lamp = indigo.devices[" | ||
| + | # An object that's not in a folder will have a folder id of 0, which isn't a valid folder | ||
| + | # so we need to make sure it's a valid folder ID first | ||
| + | if lamp.folderId != 0: | ||
| + | lampsFolder = indigo.devices.folders[lamp.folderId] | ||
| + | else: | ||
| + | lampsFolder = None | ||
| + | </ | ||
| + | |||
| + | ==== Miscellaneous Examples ==== | ||
| + | |||
| + | ===Get a list of all serial ports, excluding any Bluetooth ports:=== | ||
| + | < | ||
| + | indigo.server.getSerialPorts(filter=" | ||
| + | </ | ||
| + | |||
| + | === Sending emails === | ||
| + | < | ||
| + | indigo.server.sendEmailTo(" | ||
| + | |||
| + | # Putting a variable' | ||
| + | theVar = indigo.variables[928734897] | ||
| + | theSubject = f"The value of {theVar.name}" | ||
| + | theBody = f"The value of {theVar.name} is now {theVar.value}" | ||
| + | indigo.server.sendEmailTo(" | ||
| + | |||
| + | # Putting device data into the subject and body | ||
| + | theDevice = indigo.devices[980532604] | ||
| + | theSubject = f" | ||
| + | theBody = f" | ||
| + | indigo.server.sendEmailTo(" | ||
| + | </ | ||
| + | |||
| + | ===== Starting the host from an existing terminal window ===== | ||
| + | |||
| + | If you already have a Terminal shell running, you can launch it directly. To start the IPH in interactive mode just execute the following inside the Terminal: | ||
| + | |||
| + | < | ||
| + | |||
| + | {{pluginhost_prompt.png? | ||
| + | |||
| + | As shown, the IPH will automatically connect to the IndigoServer running on the same Mac and will show the server' | ||
| + | |||
| + | Next, let's tell the Indigo Server to log a message to the Event Log window (again, via the Terminal application): | ||
| + | < | ||
| + | |||
| + | {{pluginhost_helloworld.png? | ||
| + | ===== Connecting Remotely over SSH ===== | ||
| + | |||
| + | If you have SSH configured so you can remotely connect to your Mac running the Indigo Server, then you can use SSH to start the IPH interactively anywhere using the syntax: | ||
| + | |||
| + | < | ||
| + | |||
| + | ===== What Else Can it Do? ===== | ||
| + | |||
| + | The IPH gives you full access to the [[object_model_reference|Indigo Object Model (IOM)]] providing access to create/ | ||
| + | |||
| + | ===== Executing Indigo Commands Directly ===== | ||
| + | |||
| + | In addition to communicating interactively with Indigo via the shell, you can also send direct python commands to Indigo via the IPH. For example, to get the current brightness of the device " | ||
| + | |||
| + | < | ||
| + | |||
| + | Or to toggle the device " | ||
| + | |||
| + | < | ||
| + | indigo.device.toggle(" | ||
| + | indigo.device.toggle(" | ||
| + | indigo.device.toggle(" | ||
| + | '</ | ||
| + | |||
| + | Note when your commands are executed the indigo module is already loaded and connected and you can execute standard python code (loops, conditionals, | ||
| + | |||
| + | //Caveat:// Each call creates a new IPH (indigo-host) process which must establish a connection to the Indigo Server. Although this is relatively fast, calling it multiple times a second is not recommended. | ||
| + | |||
| + | ===== Executing Indigo Python Files ===== | ||
| + | |||
| + | The IPH can also be used to execute Indigo python (.py) files, like this: | ||
| + | |||
| + | < | ||
| + | |||
| + | //Caveat:// Each call creates a new IPH (indigo-host) process which must establish a connection to the Indigo Server. Although this is relatively fast, calling it multiple times a second is not recommended. | ||
| + | |||
| + | ===== Executing AppleScript ===== | ||
| + | |||
| + | Often times, you may find yourself wanting to execute an AppleScript from Python. You want to send some parameters to the AppleScript and you want to get results back in some format. There' | ||
| + | |||
| + | This is a great pattern for calling AppleScripts from Python, specifically a great way to integrate other scriptable Mac app data with Indigo. | ||
| + | |||
| + | ===== Shared Classes and Methods in Python Files (Python Modules) ===== | ||
| + | |||
| + | You may install Python modules/ | ||
| + | |||
| + | /// | ||
| + | |||
| + | Any Python files in this directory that define methods and/or classes will be available to any Python script whenever the interpreter is loaded (note that it's the Library directory at the top level of your boot disk, not the one in your user folder). These are referred to as Python modules. | ||
| + | |||
| + | So, you can create files of classes, functions, etc. you want to share between all Python scripts using these mechanisms. If you add/change something in that directory while the Indigo Server is running, you'll need to tell the server to reload - select the '' | ||
| + | |||
| + | Files added to the generic module location can also import the entire IOM - IF the script that's actually running is started by Indigo. Here's a simple script that you can use that will safely import the IOM and will show a specific error when used from a Python script that's not started by Indigo: | ||
| + | |||
| + | < | ||
| + | indigo_attachments.py | ||
| + | |||
| + | In this file you can insert any methods and classes that you define. | ||
| + | They will be shared by all Python scripts - you can even import the | ||
| + | IOM (as shown below) but if you do then you'll only be able to import | ||
| + | this script in Python processes started by Indigo. If you don't need | ||
| + | the IOM then skip the import and it'll work in any Python script | ||
| + | no matter where it's run from. | ||
| + | """ | ||
| + | |||
| + | try: | ||
| + | import indigo | ||
| + | except ImportError: | ||
| + | print(" | ||
| + | raise ImportError | ||
| + | from datetime import datetime | ||
| + | |||
| + | def log(message, | ||
| + | # Create a log line with the date/ | ||
| + | log_line = f" | ||
| + | # Write the log line with the label. If you didn't pass in a label | ||
| + | # then the default label will be used. | ||
| + | indigo.server.log(log_line, | ||
| + | </ | ||
| + | |||
| + | Then, when you want to use any of the classes/ | ||
| + | |||
| + | < | ||
| + | indigo_attachments.log(" | ||
| + | |||
| + | What you'll see in the Event Log: | ||
| + | |||
| + | < | ||
| + | |||
| + | If you try to run this script in a normal python session, you'll see the print statement followed by the ImportError: | ||
| + | |||
| + | < | ||
| + | The indigo module can only be used by scripts started from within Indigo | ||
| + | Traceback (most recent call last): | ||
| + | File " | ||
| + | import indigo_attachments | ||
| + | File "/ | ||
| + | raise ImportError | ||
| + | ImportError</ | ||
| + | ===== Scripting Indigo Plugins ===== | ||
| + | |||
| + | Indigo plugins are also scriptable. Because plugin-defined devices look almost identical to built-in devices, you can get (but not set) state information from them (see [[#device examples]] above for a lot of examples). You can also get some of the other properties for a plugin. Most importantly you can execute plugin-defined actions, which is how you'd set their state values. | ||
| + | |||
| + | The first step is to get an instance of the plugin: | ||
| + | |||
| + | < | ||
| + | iTunesPlugin = indigo.server.getPlugin(iTunesId)</ | ||
| + | |||
| + | This will **always** return to you a plugin object, defined with the following properties: | ||
| + | |||
| + | ^ Property ^ Type ^ Description ^ | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |||
| + | |||
| + | There are also a couple of methods defined by this object. | ||
| + | |||
| + | ^ Method ^ Description ^ | ||
| + | |< | ||
| + | |'' | ||
| + | |'' | ||
| + | |'' | ||
| + | |< | ||
| + | |< | ||
| + | |||
| + | Plugin developers should [[indigo_2022.1_documentation: | ||
| + | |||
| + | ==== Examples ==== | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | === Restart a Plugin === | ||
| + | |||
| + | There may be instances where you want to restart a plugin. This is how you do that from a script: | ||
| + | |||
| + | < | ||
| + | airfoil_plugin = indigo.server.getPlugin(airfoil_id) | ||
| + | if airfoil_plugin(): | ||
| + | airfoil_plugin.restart()</ | ||
| + | |||
| + | //Caveat//: For safety reasons, we do not provide the ability to enable a plugin that is disabled, or disable a plugin that is enabled. | ||
| + | |||
| + | Check the documentation for each plugin for the necessary information and more examples. | ||