Return value from Device Handler function when called from SmartApp


(www.rboyapps.com - Make your home your butler!) #1

According to my conversation with @slagle when a SmartApp calls a DH the DH should be able to return a value to the SmartApp (when using Parent Child relationships). Funnily it works and doesn’t work for me.

What I specify a return type for the function in the DH it does NOT work, however when I don’t specify a return type for the function it does work.

I’ve whipped up some code to replicate the issue, can someone try it out and let me know what results they get. Install the SmartApp and Device Handler. Open Live Logging, goto your phone and install the SmartApp and see what you get in Live Logging:

DH CODE:

metadata {
    definition (name: "Test Device", namespace: "rboy", author: "RBoy") {
        capability "Polling"
        capability "Refresh"

        // Calls from Parent to Child
        command "saveStuff", ["string"]
        command "readStuff"
        command "readStuffA"
    }

    tiles(scale: 2) {
        standardTile("refresh", "device.status", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
            state "refresh", action:"refresh.refresh", icon:"st.secondary.refresh"
        }
        
        main "refresh"
        details(["refresh"])
    }
}

def initialize() {
    log.trace "Initialize called settings: $settings"
    try {
        if (!state.init) {
            state.init = true
        }
        response(refresh()) // Get the updates
    } catch (e) {
        log.warn "updated() threw $e"
    }
}

def updated() {
    log.trace "Update called settings: $settings"
    try {
        if (!state.init) {
            state.init = true
        }
        response(refresh()) // Get the updates
    } catch (e) {
        log.warn "updated() threw $e"
    }
}

def refresh() {
	log.trace "Refresh called"
}

// PARENT CHILD INTERFACES
def void saveStuff(def stuff) {
    log.debug "SaveStuff called with: $stuff"
    state.stuff = stuff as String
}

def String readStuff() {
    log.debug "ReadStuff called, returning: ${state.stuff}"
    return state.stuff as String
}

def readStuffA() {
    log.debug "ReadStuffA called, returning: ${state.stuff}"
    return state.stuff as String
}

SMARTAPP

definition(
    name: "Test Device Parent",
    namespace: "rboy",
    author: "RBoy",
    description: "Test Parent Child app",
    category: "Safety & Security",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Developers/whole-house-fan.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Developers/whole-house-fan@2x.png",
    singleInstance: false)

preferences {
    page(name: "mainPage")
}

def mainPage() {
    dynamicPage(name: "mainPage", title: "Test Parent Child Device App", install: true, uninstall: true) {
        // Let the user know the current status
        section("Status") {
            def devices = getChildDevices().each { device ->
                log.trace "Found child ${device.displayName}"
                def stuff = device.readStuff()
                paragraph "${device.displayName}: $stuff"
            }
        }

        def physicalHubs = location.hubs.findAll { it.type == physicalgraph.device.HubType.PHYSICAL } // Ignore Virtual hubs
        if (physicalHubs.size() > 1) { // If there is more than one hub then select the hub otherwise we'll the default hub
            section("Hub Selection") {
                paragraph title: "", "Multiple SmartThings Hubs have been detected at this location. Please select the Hub."
                input name: "installHub", type: "hub", title: "Select the Hub", required: true
            }
        }
    }
}

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

def updated()
{
    log.debug "Updated: $settings"

    unsubscribe()
    unschedule()
    initialize()
}

def uninstalled() {
    log.trace "Uninstalled called"
    getChildDevices().each {device ->
        log.info "Deleting Device $device.displayName"
        deleteChildDevice(device.deviceNetworkId)
    }
}

def initialize() {
    def physicalHubs = location.hubs.findAll { it.type == physicalgraph.device.HubType.PHYSICAL } // Ignore Virtual hubs
    log.trace "Selected Hub ID ${installHub?.id}, All Hubs Types: ${location.hubs*.type}, Names: ${location.hubs*.name}, IDs: ${location.hubs*.id}, IPs: ${location.hubs*.localIP}, Total Hubs Found: ${location.hubs.size()}, Physical Hubs Found: ${physicalHubs.size()}"

    try {
        def existingDevices = getChildDevices()
        log.trace "Found devices $existingDevices"
        if(!existingDevices) {
            if ((physicalHubs.size() > 1) && !installHub) {
                log.error "Found more than one physical hub and user has NOT selected a hub in the SmartApp settings"
                throw new RuntimeException("Select Hub in SmartApp settings") // Lets not continue with out this settings
            }
            if (physicalHubs.size() < 1) {
                log.error "NO Physical hubs found at this location, please contact SmartThings support!"
                throw new RuntimeException("No physical hubs found") // Lets not continue with out this settings
            }
            
            (1..5).each {
                def id = 3000 + it
                log.info "Creating Device ID $id on Hub Id ${physicalHubs.size() > 1 ? installHub.id : physicalHubs[0].id}"
                def childDevice = addChildDevice("rboy", "Test Device", id.toString(), (physicalHubs.size() > 1 ? installHub.id : physicalHubs[0].id), [name: "Test Device $id", label: "Test Device $id", completedSetup: true])
            }

            existingDevices = getChildDevices()
        }

        log.trace "Working with devices $existingDevices"
        
        existingDevices.each { device ->
            def stuff = "DeviceID" + device.deviceNetworkId.toString()
            log.trace "Saving stuff to ${device.displayName}: $stuff"
            device.saveStuff(stuff)
            stuff = device.readStuff()
            log.debug "Read stuff from ${device.displayName}: $stuff"
            stuff = device.readStuffA()
            log.debug "Read stuff without return type from ${device.displayName}: $stuff"
        }
    } catch (e) {
        log.error "Error creating device: ${e}"
        throw e // Don't lose the exception here
    }
}

(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #2

Is this behavior documented?

A return value for basic success/fail indication makes sense, and more generalized cases would be useful, but perhaps unnecessarily breaks some of the SmartThings “message passing paradigm” (ie, DHs report information via Attributes using sendEvent()).


(www.rboyapps.com - Make your home your butler!) #3

Did you try it out? Documentation is scanty but Tim thinks it should work. I’m confused why it works without return type and not with in my instance.


( I hate Mondays) #4

My experience with that is that DTHs somehow try to parse the result of the function and execute physical commands based on it. I also found that passing a Map object to the DTH function and havingthe DTH add/update properties of said Map will make them available to the Smart App. In essence, the Map object is passed to the DTH by reference/pointer.


(ActionTiles.com co-founder Terry @ActionTiles; GitHub: @cosmicpuppy) #5

There are definitely layers in between SmartApps and DTHs.

Just because they look like Javs Object Instances with methods, doesn’t means there isn’t a complex framework in-between.

This is why we need much more detailed and accurate Documentation. Nothing can be assumed.


(www.rboyapps.com - Make your home your butler!) #6

This is a brilliant idea. Let me experiment with map objects. You maybe Onto something here @ady624


( I hate Mondays) #7

I use that in that describeAttributes function that I run if the DTH provides it… It works.


(www.rboyapps.com - Make your home your butler!) #8

I didn’t understand. Can you elaborate.