Web Services SmartApp for multiple switches but only control ONE?

I’ve been following the guide for a web services SmartApp here: http://smartthings.readthedocs.org/en/latest/smartapp-web-services-developers-guide/tutorial-part1.html

It works great - if I wanted to control ALL Switches. Which I don’t. Instead, I’m looking to have the SmartApp have access to 8 switches, and through the POST URL, only manipulate the setting of an individual switch.

Here’s the code I’ve come up with, but it’s giving me an error that the method isn’t supported.

Admittedly it’s a clone of the tutorial, which is a great starting point. Just trying to do a foreach loop of the switches array from the defined switches. if a match is made from the :zoneName URL variable, then do the command (on or off).

Majority of my changes can be found in the updateSwitches function.

    /**
 *  Web Services Tutorial
 *
 *  Copyright 2015 SmartThings
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
definition(
    name: "Web Services Tutorial",
    namespace: "smartthings",
    author: "SmartThings",
    description: "web services tutorial",
    category: "",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    oauth: [displayName: "web services tutorial ", displayLink: "http://localhost:4567"])


preferences {
  section ("Allow external service to control these things...") {
    input "switches", "capability.switch", multiple: true, required: true
  }
}

mappings {
  path("/switches") {
    action: [
      GET: "listSwitches"
    ]
  }
  path("/switches/:zoneName/:command") {
    action: [
      PUT: "updateSwitches"
    ]
  }
}

// returns a list like
// [[name: "kitchen lamp", value: "off"], [name: "bathroom", value: "on"]]
def listSwitches() {

    def status
    def contentType
    def data
    def headers = [:]

    def resp = []
    switches.each {
        resp << [displayNameLabel: it.displayName, name: it.name, value: it.currentValue("switch")]
    }
    return resp
}

void updateSwitches() {
    // use the built-in request object to get the command parameter
    def status
    def contentType
    def data
    def headers = [:]
    
    def command = params.command
    def zoneName = params.zoneName

    if (zoneName) {

        // check that the switch supports the specified command
        // If not, return an error using httpError, providing a HTTP status code.
        switches.each {
            if (!it.hasCommand(command)) {
                httpError(501, "$command is not a valid command for all switches specified")
            } 
        }
        
        // all switches have the comand
        // execute the command on all switches
        // (note we can do this on the array - the command will be invoked on every element
        //switches."$command"()
        switches.each {
        	if ($zoneName == it.displayName) {
            	it."$command"()
            }
        }
    }
}
def installed() {}

def updated() {}
1 Like

That message typically means that the wrong HTTP verb was used to make the web service request. What HTTP method are you using to control the switches? Note that /switches/zone/command URL requires an HTTP PUT - so make sure you aren’t using a GET or POST.

1 Like

Yeah I forgot the PUT in the curl request. Good catch. Does the code seem OK? That’s where I was stuck (thinking my problem was in the code) :smile:

Actually got this bit of code to work, which is pretty cool. Except it doesn’t handle spaces in the device displayName.

    switches.each {
    	log.debug "current loop displayName: ${it.displayName}"
    	
        if (params.zone == it.displayName) {
        	log.debug "Match found: $zoneName"
        	it."$command"()
        }
    }

So I’m trying to work with something like this which is more long-hand, but I’m not sure how to handle the array in Groovy. This isn’t working.

    if (params.zone == "1") { 
    	log.debug "Match found. Attempting to turn ${command} switch."
        log.debug switches["Zone1"].currentValue("switch")
        switches["Zone1"]."$command"() 
    } else if (params.zone == "2") {
    
    } else {
    	log.debug "No matching switch zones found. URL provided zone ${zoneName}."
       httpError(501, "Error: Zone $zoneName was not found as a valid zone switch.")
    }

Clearly switches["Zone1"]."$command"() isn’t correct, do you know how to call a member of the array directly like this?

I’m not clear on exactly what you’re trying to do, but maybe you’re looking for something like:

def theSwitch = switches.find { it.displayName == "Zone1" }
theSwitch."$command"()

I was trying to pluck an item out of the array if it matched, without “foreaching” the array. This could be exactly what I’m looking for! I’ll try it out. Thanks!

I’m new to this but trying to learn. Can you please explain what you mean by the " /switches/zone/command URL " I understand the /switches and /command, it’s the zone that I don’t understand? What exactly is a zone?

I am trying to control lights individually. For testing, I have been trying to turn on/off a light in my house named ‘Family Room Table Lamp’. how do I reference that single lamp in the URL?

This is what I do:


definition(
    name: "HTTP Smart App",
    namespace: "your.namespace",
    author: "Your Name",
    description: "Allow Devices to be controlled via HTTP requests",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    oauth: [displayName: "XXXXXXXXXXXXXXXXX", displayLink: ""])


preferences {
	section ("Allow external service to control these things...") {
    input "switches", "capability.switch", multiple: true, required: true
    }
}

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

	initialize()
}

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

	unsubscribe()
	initialize()
}

def initialize() {
	// TODO: subscribe to attributes, devices, locations, etc.
    createSubscriptions()
}

def createSubscriptions()
{
	subscribe(switches, "switch", switchHandler)
}

def switchHandler(evt) {
	log.debug "Switch state changed: ${evt.device}"
    // Do more stuff here when the switch generates an event
}


mappings {
  path("/switches/:sname/:command") {
    action: [
      PUT: "updateSwitches",
      POST: "updateSwitches"
    ]
  }
}

void updateSwitches() {
    
    def command = params.command
    def sname = params.sname
    
    log.info "Issued command $command for switch $sname"
    
	def theSwitch = switches.find { it.displayName == "${sname}" }
    log.debug "Selected switch ${theSwitch}"
    def switchCurrent = theSwitch.currentValue("switch")
    log.debug "Current value: ${switchCurrent}"
    
    switch(command) {
       	case "on":
			theSwitch.on()
	        break
	    case "off":
	        theSwitch.off()
	        break
	    case "toggle":
	     	if(switchCurrent == "on")
	            {theSwitch.off()}
	        else
	            {theSwitch.on()}
    	    break
	    default:
	        httpError(400, "$command is not a valid command for the specified switch")
	}
}

In this way I can send an “on”, “off” or “toggle” command for a particular switch (sname).

Surround your code paste (before and after the block of code) with three back quotes (```) on a line by itself.

Done.

Thank you!!

1 Like

I’m stumped.

I copied your code, although I did make a change to the mappings from /switches/:command/:sname to /switches/:sname/:command since that seemed more sensible. And I used curl to send this request (edited to hide the sensitive bits)

curl -H “Authorization: Bearer [secret]” -X PUT “https://graph-na02-useast1.api.smartthings.com/api/smartapps/installations/[client id]/switches/Family Room Table Lamp/off”

But nothing happens. I don’t get any errors.

The zone was within the context of the custom SmartApp code listed in the first post of this thread.

The example by @carlovn uses the same HTTP Web Service SmartApp concept but with a different path of /switches/:command/:sname which implies that there is a command and sname variable as part of his SmartApp’s path.

Keep in mind the order of the parameters… his example would be something like:

https://graph-na02-useast1.api.smartthings.com/api/smartapps/installations/{installationID}/switches/off/My Cool Switch

Couple of things to help you troubleshoot:

  1. After publishing your smart app for yourself, did you open it on your phone in the Smartthings app to add the switches you want to control?
  2. You can add logging to the updateSwitches function in your smart app and then check the “Live Logging” tab in the IDE while issuing the curl command to see what happens. (log.debug “executing updateSwitches ${sname} ${command}”)
  3. If all else fails, maybe share your code. It will enable us to better help you.

I have updated my post above with a shortened version of my complete app, just in case it might help. And for what it’s worth, I started out the same way as you. I am not a groovy developer. I got most of what I needed from either the documentation, Google or this community.

Thanks for your help. I truly appreciate it.

Maybe it would help if I described what I am trying to accomplish. I have a device in my house called a MasterLink Gateway, it is made by Bang & Olufsen and is used to act as the bridge between the B&O MasterLink network of audio/video devices and a home automation system. The idea is that using the MLGW you can control things like lights, blinds, curtains, windows, etc from the same remote you use to control your TV and stereo.
For non-B&O products, the gateway allows you to create custom drivers. Those drivers can be used to make requests to web services.
So my goal is to figure out how to control individual lights by sending requests to the web API.

As of now, my code is essentially your code, but here it is.

 *  MLGW Test
 *
 *  Copyright 2018 Kent Rigby
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
definition(
    name: "MLGW Test",
    namespace: "KentRigby",
    author: "Kent Rigby",
    description: "This will be used to test sending commands through a MasterLink Gateway",
    category: "Convenience",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")


preferences {
  section ("Allow external service to control these things...") {
    input "switches", "capability.switch", multiple: true, required: true
  }
}

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

	initialize()
}

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

	unsubscribe()
	initialize()
}

def initialize() {
	// TODO: subscribe to attributes, devices, locations, etc.
    createSubscriptions()
}

def createSubscriptions()
{
	subscribe(switches, "switch", switchHandler)
}

def switchHandler(evt) {
	log.debug "Switch state changed: ${evt.device}"
    // Do more stuff here when the switch generates an event
}

mappings {
  path("/switches") {
    action: [
      GET: "listSwitches"
    ]
  }
  path("/switches/:command") {
    action: [
      PUT: "updateSwitches"
    ]
  }
  path("/switches/:command/:sname") {
    action: [
      PUT: "updateSwitches",
      POST: "updateSwitches"
    ]
  }
}

def listSwitches() {
    def resp = []
    switches.each {
      resp << [name: it.displayName, value: it.currentValue("switch")]
    }
    return resp
}

void updateSwitches() {
    
    def command = params.command
    def sname = params.sname
    
    log.info "Issued command $command for switch $sname"
    
	def theSwitch = switches.find { it.displayName == "${sname}" }
    log.debug "Selected switch ${theSwitch}"
    def switchCurrent = theSwitch.currentValue("switch")
    log.debug "Current value: ${switchCurrent}"
    
    switch(command) {
       	case "on":
			theSwitch.on()
	        break
	    case "off":
	        theSwitch.off()
	        break
	    case "toggle":
	     	if(switchCurrent == "on")
	            {theSwitch.off()}
	        else
	            {theSwitch.on()}
    	    break
	    default:
	        httpError(400, "$command is not a valid command for the specified switch")
	}
}

And here is the web service call using curl:
curl H "Authorization: Bearer (removed)" -X PUT "https://graph-na02-useast1.api.smartthings.com/api/smartapps/installations/(removed)/switches/Family Room Table Lamp/off"

Kent, did you set up OAuth for your app? I don’t see that line in your app definition, but maybe you have just removed it for privacy reasons.

And when you execute the curl command, do you see anything in “Live Logging”? Keep in mind you have to open the “Live Logging” tab before you execute any commands.