First SmartThings SmartApp, part I

Step one of my automation project is to get data out of my SmartThings system and into my home server. As a first step I want to log the temperature readings from the sensors.

After a lot of trial and error I managed to get ST to post HTTP messages to my home server. It took a combination of reading documentation, forum posts, example code and plain old guesswork. This is how I did it, and my tips for getting started with SmartApps. Some of them might be obvious, especially to experienced programmers, but I know from the forums that plenty of ST users are new to coding and might find this useful.


Tip 1:


Start small and build up. Follow the tutorial to get the boiler plate code set up in the IDE and start by subscribing to one event type. Have your event handler do nothing but log that it’s been called. If you write too much code to start off with, you’ll have trouble figuring out why it’s not working. If you can get a simple debug log message at least you know you’ve managed to set everything up properly.

With any new system it’s tempting to jump in at the deep end and try to do it all at once, but every system has its own way of getting code to install and run. Making sure you can do that, and finding the error messages when things do go wrong, is the first hurdle.

Here’s my first pass (definition stuff excluded for brevity):

preferences {

    section("Select motion sensors") {

        input("sensors", "capability.motionSensor",
                          required: true, multiple: true)
    }
}

def installed() {
    log.debug "Installed with settings: ${settings}"
    initialize()
}

def updated() {
    log.debug "Updated with settings: ${settings}"
    unsubscribe()
    initialize()
}

def initialize() {
    subscribe(sensors, "motion", evtHandlerMotion)
}

def evtHandlerMotion(evt) {
    log.debug("Motion Event Handler called")
}

Once that’s installed and running you should get a log message every time the motion sensor activates. Make sure you know where the logs are - the link is just below the box in the IDE where you enter your code and can also be found via the live logging tab.

Tip 2:


Motion sensors are great debugging tools. You may be wondering why I started off by subscribing to a motion sensor when my end goal is to get temperature.

Temperature events are triggered only when the temperature changes. You can’t predict when they’ll occur, so they’re not a lot of use for testing. You don’t want to make a change to your event handling code and then have to wait indefinitely to see if it works.

Once I realised this I switched to the door sensor and spent a while making changes to my code and then walking to the front door to open it and trigger an event. Then it hit me. I just put a motion sensor on my desk, subscribed to motion events, and triggered them by waving my hand over it. In hindsight it was a ridiculously obvious solution, and maybe I was just having a slow day not to hit on it sooner!

Tip 3:


Handle exceptions. This one will be blindingly obvious to experienced programmers, but it’s easy to forget when you’re looking at the sample code, especially if you’re not used to languages with that kind of try/catch exception handling block.

The general advice for using try/catch exception handling instead of if/else error handling is that you should use exceptions for - as the name suggests - exceptional circumstances. In particular they shouldn’t be used to catch programmer error.

That’s fine when you’re running code from the command line and unhandled exceptions get printed to screen or you can get to the actual application logs. With SmartThings your code will silently fail unless you explicitly catch the exceptions, so wrapping your code in try/catch is the only way to see what’s going on when things don’t work. The IDE will pick up syntax errors when you try to save your code, but it won’t help with runtime errors. I’m new to Groovy, and I was making mistakes such as not correctly declaring objects before calling methods on them. The only way to pick up on those sort of errors is to wrap your code in a try/catch block:

def evtHandlerMotion(evt) {
    log.debug("Motion Event Handler called")
    try {
          /* all my code here */
   } catch(e) {
     log.debug(“Exception $e”)
   }

This kind of overall try/catch isn’t really best practice but it’s the only way to see what’s going on in ST.

Tip 4:


Hard code stuff. Another deviation from best practice, but remember, we’re learning here and we want to minimise the number of things to debug at once.

I wanted to post an http message to my server. Ideally my SmartApp would have a configuration setting to enter the name or IP address of the server, but from reading around the forums I wasn’t even sure if what I wanted to do was possible. It made sense to hardcode the IP address and URL in my function call rather than spend time figuring out how to capture it and store it.

Once I got the actual call working, it was then worth figuring out how to capture the server details as parameters.

Tip 5:


Don’t expect the documentation to have all the answers.

Sadly, the ST documentation is a little lacking and you’ll find yourself scouring the forums for examples. Some of the forum posts are fairly old and seem to refer to deprecated features.

I wanted to send a message over my local LAN. The SmartApps documentation refers to the httpPut method, but that’s a dead end. SmartApps run in the SmartThings cloud so they have no direct access to your network and the http methods can only be used to call internet facing service.

After a bit of searching around I found that what I need to do was use hubAction to get the SmartApp to tell the hub to run a local action on my network. Most of the documentation refers to using hubAction within a device type rather than a SmartApp - it’s what ST uses to manage physical devices that are LAN connected.

I found various references in the forums to using hubAction within a SmartApp, but I had to literally stumble across the answer that worked for me - the sendHubCommand function. The official documentation only refers to it in the context of discovering uPnP devices, and refers to parameters that no longer seem to be needed.

In the end it turns out that you can send a LAN command by doing something like this:

def action = new physicalgraph.device.HubAction([
    method: "POST",
    path: "/testing/smartthings",
    body: “body text goes here",
    headers: [
        HOST: “192.168.1.1:80"
        ]])

sendHubCommand(action)

You’d never guess it from the documentation or half the forum posts though. Apparently it was once necessary to convert the IP address and port value to hex but this is no longer necessary, and most of the examples add a couple of extra parameters to the sendHubCommand call.

This post is already getting quite long, so I’ll put it all together in part 2 with the code to actually take the event details and post them. Hopefully this little writeup has been useful, even if experienced programmers may think I’m teaching them to suck eggs.

Labels: , ,