Dynamic UI lists in Indigo 6

Indigo 6 is a popular home automation controller software package on the Mac. Extensibility is one of its main features and it allows users to add a range of features to suit their needs.

Using Python scripting, users can create plugins that provide extended functionality. These plugins can provide a custom configuration UI to the user. Since the documentation around a particular feature - dynamic lists was lacking, I’ve written up my approach here.

Since I live in Canada, the excellent NOAA plugin doesn’t work for me. However Environment Canada provides an XML-based weather data API that we could package into an Indigo plugin. Since the number of Environment Canada station locations is large, I would like the user to select a province first then select locations within that province. This means that I must use a dynamic list for the locations and reload the location list dynamically when the province changes. The solution turned out to be simple. Perhaps it could be even simpler. This is just what I came up with.

Devices.xml configuration

<?xml version="1.0"?>
<Devices>
    <!-- define devices -->
    <Device type="custom" id="station">
        <Name>Weather station</Name>
        <ConfigUI>
            <!-- choose location -->
            <Field id="province" type="menu">
                <Label>Province:</Label>
                <List class="self" filter="" method="listProvinces"/>
                <CallbackMethod>provinceChanged</CallbackMethod>
            </Field>
            <!-- choose location within province -->
            <Field id="location" type="menu">
                <Label>Location:</Label>
                <List class="self" filter="" method="listStations" dynamicReload="true"/>
            </Field>
        </ConfigUI>
    </Device>
</Devices>

In the device configuration I’ve specified a province field and a location field. The former provides a callback method provinceChanged where I can deal with filtering the locations based on the province selection. The other key here is to make the location field dynamically-reloadable (dynamicReload="true".) By doing this, we get another call to the list generator method listStations when the province is selected.

Province selection callback

In plugin.py, I must provide a callback method provinceChanged to save my selection:

def provinceChanged(self, valuesDict, typeId, devId):
    self.selectedProvince = valuesDict['province']

Here’s where the solution might be simpler. The documentation is ambiguous about the status of valuesDict if the device hasn’t been saved yet. Based on that ambiguity, I decided to save the selected province as an instance variable of my Plugin class.

Providing a filtered location list

My dynamic list generator for the locations takes the selected province instance variable into consideration so that when the list is dynamically reloaded, I get a chance to filter the list by province.

def listStations(self, filter="", valuesDict=None, typeId="", targetId=0):
    locations = []
    stations = []
    self.debugLog(u"Generating stations")
    stations = self.locationDB.stationsForProvice(self.selectedProvince)
    for loc in stations:
        option = loc[0]
        city,province = loc[1].encode('utf-8'),loc[2].encode('utf-8')
        stationName = "{0} ({1})".format(city,province)
        locations.append(stationName)
    return locations

I have a suspicion there’s an easier way. If you know of one, let me know and I’ll share it.

Import and tag with Hazel and DEVONthink Pro Office

Hazel and DEVONthink make a great pair as I’ve written before. Using AppleScript, it’s possible to take the import workflow even further by tagging incoming files automatically.

Use case

I download a lot of mp3 files containing pronunciation of words in a language I’ve been learning. I keep a record of these words and tag them appropriately using my hierarchical tagging system.

I’d like to download the files to a directory on the desktop. Keep them there for a few minutes until I’m done working with them, then import the file to DEVONthink Pro Office, tag the file there and delete the original.

Read on to see how the Hazel rule is written, including the AppleScript to make it happen.

Hazel rule

The Hazel rule is simple:

  • Look in a directory called “mp3”.
  • Select files that have the mp3 extension.
  • Allow the file to remain in place for 5 minutes
  • Thereafter, execute the import and tagging script.
  • Finally, delete the original file

Most of the action is in the AppleScript that provides a bridge from Hazel to DEVONthink.

AppleScript import action

Hazel exposes a variable theFile that we can use as we import and tag the file. Here’s the entire AppleScript with comments following.

tell application id "com.devon-technologies.thinkpro2"
    launch
    open database "/Users/alanduncan/Documents/russian.dtBase2"
    set dbID to 7
    set theDatabase to get database with id dbID
    tell theDatabase
        set destGroup to first item of (records whose name is "reference")
        set theRecord to import theFile to destGroup
        tell theRecord
            set {od, AppleScript's text item delimiters} ¬
			   to {AppleScript's text item delimiters, ","}
            set tags to text items of "source_web, type_sound"
            set AppleScript's text item delimiters to od
        end tell
    end tell
end tell

Line 2-3 - Launch DEVONthink and open the receiving database. Line 4-5 - Reference the database by ID. There are other ways to do this. I just happened to find the ID in Script Debugger so I used it. Line 7 - Set a reference to the destination group. Mine is reference. Line 8 - Perform the import returning the imported record. You can import without the return but then you would have to do extra work to find the file you want to tag. Line 10 - Allow us to use a comma-delimited list of tags Line 12 - Set the incoming file’s tags. Mine are as listed; yours would be something else. Line 13 Restore the original text delimiters

That’s it! No you have a mechanism for automatically managing files from the Finder to DEVONthink.