Multiple hubaction commands in one function and parsing the response

I am trying to write a device handler for my router to be able to enable/disable wifi and guest network from ST.
The router is Netgear and it has a soap api fortunately. I found the messages needed to pass to the router.

But I need to send 3 soap commands to the router to make one change.
So I put all of these in separate functions and I call them from one action bound to a device handler’s button tile.

They don’t run in the correct order and I need to get the response of each commands sent to the hub.

how can I do that ?

my sample code for 3 actions are below. GuestWirelessOff is the main function called when button is pressed.

def GuestWirelessOff() {
	log.debug "Executing 'GuestWirelessOff'"
	// TODO: handle 'GuestWirelessOff' command
return [authrouter1(), configStarted1(), gwoff1() ]
}

private authrouter1() {
new physicalgraph.device.HubAction("delay 1000")
log.debug "authrouter"
    def userpassascii = "${username}:${password}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = ip         
        try {
    def hubaction = new physicalgraph.device.HubSoapAction(
	path:    "/soap/server_sa/",
	urn:     'urn:NETGEAR-ROUTER:service:ParentalControl:1',
	action:  "Authenticate",
	body:    ["NewPassword":"password", "NewUsername":"admin" ],
	headers: [Host:"192.168.254.1:80", Authorization: userpass, CONNECTION: "close"],)
        log.debug hubaction
        hubaction
    }
    catch (Exception e) {
        log.debug "Hit Exception on $hubAction"
        log.debug e
    }

	
}

private gwoff1() {
new physicalgraph.device.HubAction("delay 1000")
    def userpassascii = "${username}:${password}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = ip 
            try {
   	def hubaction = new physicalgraph.device.HubSoapAction(
	path:    "/soap/server_sa/",
	urn:     'urn:NETGEAR-ROUTER:service:WLANConfiguration:1',
	action:  "SetGuestAccessEnabled",
	body:    ["NewGuestAccessEnabled":"0" ],
	headers: [Host:"192.168.254.1:80", Authorization: userpass, CONNECTION: "close"],)
        log.debug hubaction
        hubaction
    }
    catch (Exception e) {
        log.debug "Hit Exception on $hubAction"
        log.debug e
    }
}

private configStarted1() {
new physicalgraph.device.HubAction("delay 1000")
    def userpassascii = "${username}:${password}"
	def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
    def host = ip 
            try {
   	def hubaction = new physicalgraph.device.HubSoapAction(
	path:    "/soap/server_sa/",
	urn:     'urn:NETGEAR-ROUTER:service:DeviceConfig:1',
	action:  "ConfigurationStarted",
	body:    ["NewSessionID":"58DEE6006A88A967E89A" ],
	headers: [Host:"192.168.254.1:80", Authorization: userpass, CONNECTION: "close"],)
        log.debug hubaction
        hubaction
    }
    catch (Exception e) {
        log.debug "Hit Exception on $hubAction"
        log.debug e
    }
}

I believe you can specify a callback handler different that the default parse. I saw that the other day in a code portion here: Connect any wired alarm system to SmartThings with Konnected - funded on Kickstarter in 2 hrs!

I can’t find it. is it in one of the scripts ?
actually I have several example scripts doing that but I can’t reverse engineer them.

It should be in

where the author manages the lan discovery

Hi. I’m the creator of Konnected. Thanks for the mention, and glad you learned something from my code.

Yes, you can pass a [callback: "myFunctionName"] to hubAction to call a custom callback function. Keep in mind that hubAction is asynchronous, so if you need your three functions to be run in order, I would suggest that each hubAction call specifies the next in sequence as the callback. This will ensure that they’re run in order.

2 Likes

thanks. but I actually have some other problem right now.
parsing may wait. First I need to solve how to send envelope-header in soap request.
any idea on that ?

I bookmarked your code indeed as i want to improve my own porting with a lan discovery and you are the first one in this forum providing a clear way to do it. Million thanks!

1 Like

Dude… it took me 5 sec to search “soap” in this forum an hit the below, I am not sure why you don’t find it.
There are many people writing things years before us (and better than us) so the first thing in this forum is to search a bit before posting. Another source of inspiration is to get the smartthings public GitHub on your laptop and grep/find in the hundreds of sources how people/smartthings team do stuff.

1 Like

it seems like a solution. Now I just need to find how to calculate content-length.
how did you find this solution ?
since my problem is about hubsoapaction, I always searched with keyword “hubsoapaction”
and if I search with just the short keyword “soap” , there are lots of threads. (mostly unrelated)

well… the time you didn’t spend looking in is the same I spent to fibd it. I can tell you, not more than 5sec, that was the 3rd entry.
Anyway, since the solution is availAble, that would be great you click the notch box to indicate it, so others can get quicker to this post and solution. You can also reward people answering you by using the likes. That’s how it works here and allow everyone to progress in the forum activities.

good luck

1 Like

well, I tried several formats but I could not succeed sending an http packet to the router (I get captures from its interface)
the packet does not reach the host. but if I use hubsoapaction all packets reach the router.

this is one of the formats I tried:

private gwoff1() {
def turnOn 
try {
turnOn = new physicalgraph.device.HubAction("""POST /soap/server_sa/ HTTP/1.0
SOAPAction: "urn:NETGEAR-ROUTER:service:WLANConfiguration:1#SetGuestAccessEnabled"
content-type: text/xml;charset=utf-8
HOST: 192.168.254.1
User-Agent: SOAP Toolkit 3.0
connection: keep-Alive
Cache-Control: no-cache
Pragma: no-cache
content-length: 598

<?xml version="1.0" encoding="UTF-8" standalone="no"?><SOAP-ENV:Envelope xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema" xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAPSDK3="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header>
<SessionID>58DEE6006A88A967E89A</SessionID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<M1:SetGuestAccessEnabled xmlns:M1="urn:NETGEAR-ROUTER:service:WLANConfiguration:1">
<NewGuestAccessEnabled>0</NewGuestAccessEnabled>
</M1:SetGuestAccessEnabled>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
}
    catch (Exception e) {
        log.debug e
    }
    return turnOn
}

do you see the problem in it ?

Check here: http://docs.smartthings.com/en/latest/ref-docs/hubaction-ref.html

At the bottom of the page, it’s said:

Note
A Device Handler’s parse() method can also return a HubAction object, in addition to the above-described usage by explicitly calling sendHubCommand.
See Building the Device Type for more information.

Normally, in ST, you create an event (like by createEvent and I guess this is also true for sendHubCommand) and you return the event if you are in a command (parse, on, off, etc…). If you are in private method, I think you have to send the hubcommand explicitly.

So try to sendHubCommand( turnOn ) at the end of your gwoff1()
or
just have a sendhHubCommand ( new physicalgraph.device.HubAction("""POST /soap/server_sa/ HTTP/1.0
SOAPAction: "urn:NETGEAR-ROUTER:service etc…)

Also in order to make sure your gwoff1 is called, add a log,debug “in gwoff1” so you can check in the live logging that it is called.

Let me know

1 Like

Thanks. I will try that. But I had read that HubSoapAction is just an extension of HubAction
And when I replace HubAction with HubSoapAction it sends the packet.
Actually, I don’t suspect the code but I suspect that something is missing to direct it to my router.
We just have the HOST: 192.168.254.1 header , is that enough ?

I tried adding the log.debug and it is showing up in logging.

HOST: 192.168.254. ?

In the example I gave you, the guy used getHostAddress() returned string

Please follow that too. We all convert our IPs and ports following this function.

private getHostAddress() {
	def ip = getDataValue("ip")
	def port = getDataValue("port")

	if (!ip || !port) {
		def parts = device.deviceNetworkId.split(":")
		if (parts.length == 2) {
			ip = parts[0]
			port = parts[1]
		} else {
			log.warn "Can't figure out ip and port for device: ${device.id}"
		}
	}
	log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
	return convertHexToIP(ip) + ":" + convertHexToInt(port)
}
1 Like

yes, but the IP is static in my case. at least for the trial.
if it works, I will make it parametric. this should be the destination address , right ?

no that’s not the point. You have to encode the address in hexa, remove the dot etc…

here is a code I use in a device I wrote:

def refresh() {
log.debug “Executing refresh”

def host = internal_ip 
def port = internal_port
def hosthex = convertIPtoHex(host)
def porthex = convertPortToHex(port)
//log.debug "The device id before update is: $device.deviceNetworkId"
device.deviceNetworkId = "$hosthex:$porthex" 

//log.debug "The device id configured is: $device.deviceNetworkId"

def path = internal_query_path
//log.debug "path is: $path"

def headers = [:] 
headers.put("HOST", "$host:$port")

try {
def hubAction = new physicalgraph.device.HubAction(
method: “GET”,
path: path,
headers: headers
)
state.requestCounter=1
return hubAction

}
catch (Exception e) {
	log.debug "Hit Exception $e on $hubAction"
}

}

You have to encode the IP and port using this function. This is also in the ST documentation

And you can check this: What Happened to deviceNetworkId? - #122 by heythisisnate

Example, for my device having 192.168.1.49 and port 80, the string that you pass to the sendhubcommand is: c0a80131:0050

1 Like

actually in the code you just posted, I see “$host:$port” is passed to the header HOST
That’s not the hex format. It’s just 192.168.1.49:80
so what am I missing ?

haha… that’s the end of the week…

Yes you are correct.

However, you have to setup the device.deviceNetworkId otherwise you won’t receive the answer and this one needs to be encoded.

Sorry for the confusion

1 Like

I have the network ID in device profile set as C0A8FE01:0050
But shall I have to put this somewhere also in my code ?
I don’t see it anywhere in code examples.

in other device handlers I see these 2 parameters:
String dni = null, // Remove this when using device.deviceNetworkId
[callback: callbackParse], // Remove this when using device.deviceNetworkId

as in

    def hubAction = new physicalgraph.device.HubAction(
        method: "GET",
        path: uri,
        headers: [HOST:getHostAddress(useIP)],
        String dni = null, // Remove this when using device.deviceNetworkId
        [callback: callbackParse], // Remove this when using device.deviceNetworkId
    )

maybe I should try them ?

Can you have this check?

internal_ip contains the IP string like “192.168.1.49”
internal_port contains the port string like “80”

Then enter your device setting and save them so that the updated method is called. Then you should see the soap going to the IP. I just tested the below on my a device I use to hack and test and its parse function was called so it means the request went to the IP:port and got answered by my device.
If I don’t return the turnOn at the end of the method, nothing will happen

def updated() {
log.debug “Executing ‘updated’”
def host = internal_ip
def port = internal_port

def turnOn = new physicalgraph.device.HubAction(“”"POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: “urn:Belkin:service:basicevent:1#SetBinaryState”
Host: ${host}:${port}
Content-Type: text/xml
Content-Length: 333

<?xml version=“1.0”?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV=“http://schemas.xmlsoap.org/soap/envelope/” SOAP-ENV:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
SOAP-ENV:Body
<m:SetBinaryState xmlns:m=“urn:Belkin:service:basicevent:1”>
1
</m:SetBinaryState>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>“”", physicalgraph.device.Protocol.LAN)

return turnOn

}

You can also replace the return turnOn by sendHubCommand( turnOn)

It worked too on my side,

1 Like