Poll or subscribe example (to network events)

Hi all

Total newbie to smartthings and certainly to writing device types.

I’ve searched and searched but cant find any example of how to create a device type that will listen for network events using hub action. I found the below but not sure if this should be done in a device type or a smart app.

I am essentially trying to integrate smarthings with a raspberry pi which will detect motion and send an event or notify smartthings. I have been able to connect with smartthings to the pi (and parsing the results) but I essentially want the communication the other way around (pi to smarthings)

   def someCommand() {
    subscribeAction("/pi/motion")
}

private subscribeAction(path, callbackPath="") {
    log.debug "subscribe($path, $callbackPath)"
    def address = getCallBackAddress() 	//address of hub
    def ip = getHostAddress() 			//address of device to connect to

    def result = new physicalgraph.device.HubAction(
        method: "SUBSCRIBE",
        path: path,
        headers: [
            HOST: ip,
            CALLBACK: "<http://${address}/notify$callbackPath>",
            NT: "upnp:event",
            TIMEOUT: "Second-28800"
        ]
    )

    log.debug "SUBSCRIBE $path"

    return result
}

Many thanks in advance
JJ

Connection to Domoticz defined devices like Blinds and On/Off switches maybe this solution works for you. If not as a solution take a look at the code it does the things you want to do in both directions. Have fun.

1 Like

Hi Martin,

Appreciate you posting the link but i am struggling finding the device types in the git repo.

If you can point me more specifically to a device type that listens for a Subscribe on a path (i.e. can listen to my pi sending events to it).

Many thanks in advance.

Jonathan

Hi Jonathan, sorry for the confusion, these are done in smartapps, not in the device.

In the above case the Smartapp will listen to the events coming in from my Pi running an application that is sending messages/events on behalf of the devices that are being controlled by the Pi.

It will interpret these events where they are coming from and will call the DTH for a device via generateEvent that is present in each DTH, The generateEvent is using sendEvent command to update status of the device.

The call from the Pi needs to be a specific url that contains the access-token and the callback address of your smartapp (combi of apiurl and appid and a part that is in the mappings definition of your smartapp).

I recently went through this…

To get this to work in a DeviceHandler you need to set the network address (DNI) to the MAC address of your raspberry pi (no colons). For some reason, and this isn’t in the documentation anywhere, using device callbacks requires the network address of the virtual device to have the same MAC as the physical device.

Ping your raspberry PI from your development PC type arp -a at a command prompt to dump the ARP table which will give you the MAC of your pi.

1 Like

Thanks Steve, so you have had this working from a DeviceHandler without the need for the SmartApp?

Does this use the poll() method to frequently ping the PI or do you use Subscribe on the HubAction (in any event , I couldnt workout where to call that HubAction)?

I would love to see an example of the DeviceHandler that does it?

Thanks
Jonathan

Yes I did, although I scrapped it and moved the LAN logic over to a service manager SmartApp because SmartThings expects a 1-1 relationship of LAN devices to ST devices when connecting through the DTH. I had multiple LAN devices that needed to connect through my home server so I ran into that limitation after getting the DTH working.

The key, at least for me, was to make sure the DNI of the device in SmartThings matched the MAC address of the server, which in my case was a Windows Server 2012R2 box. A Pi should work exactly the same. I have one here I might be able to test with if you want to share your Pi code too.

The other thing I had to do was make sure I enable the subscribe verb on the web server that the DTH was connecting to. SUBSCRIBE/UNSUBSCRIBE was not enabled by default and also had to be mapped to the php cgi. The last step I did was to have my web app respond the the subscribe request by returning the subscription ID in the http response header. The header should be formatted as follows:

SID: uuid:<generate UUID here>

I found that if the hub did not receive a 200 OK response AND a subscription ID to the SUBSCRIBE request the hub would not pass the LAN events to the parse function.

Once the subscription is complete, you can either execute an HTTP POST or HTTP NOTIFY request to the hub’s callback address. NOTIFY ignores the body of the request. Also note that a POST request will return HTTP 202 on success whereas NOTIFY returns HTTP 200 on success.

The documentation could really use improvement in this area.

Hi Steve, thanks for your response. It certainly sounds as though you have put the time in to this. I am literally getting stumped at every step… ;-(

I’m not sure I understand the 1-1 relationship requirement. Surely it would make sense to have 1-1 between the LAN device and the Device (with a DTH backing it up)… Anyhow, …

Thats a great shout on the SUBSCRIBE/UNSUBSCRIBE verb being enabled on the PI. I suppose thats another thing i need to look into. Where is the SID generated?

Another thing, how did you view what the hub was receiving in order to verify it needed a 200 response and the SID.

I wholeheartedly agree with the fact that the documentation is rubbish.

Thanks again for your support.

jonathan

The subscription ID or SID is a uuid that you generate on the sever side. These can be static, I see no need to generate random uuids. As far as the response codes go, that was just the result of a lot of google and community searching. I couldn’t figure out why the hub responded differently to the different verbs.

To explain the 1:1 relationship a little better, think of using your Raspberry Pi as a garage door controller controlling 2 garage doors. You would in theory, have a relay for each door to trigger the opener. Since you would want to present the garage doors separately in ST, you would have to create 2 virtual devices with each device communicating with the Pi. But since you cannot have 2 devices with the same DNI this approach no longer works.

Unfortunately for me I didn’t learn that the DNI must be identical to the MAC on the server before events hit the parse function until I had built the whole thing.

Ok… I think I have an understanding of everything i need to do to get it working with just a DTH (and Device).

I need to get my pi responding to the SUBSCRIBE / UNSUBCRIBE verbs and then i can call NOTIFY or POST (to the Hub call back address ) when I get some trigger from the GPIO.

I have put a diagram together to check my understanding:

The only bit that is missing for me is how or where I trigger the subscribe code. As per the example below, where would someCommand be called? I could put it on a button in the app but i want the device to automatically listen.

def someCommand() {
    subscribeAction("/path/of/event")
}

private subscribeAction(path, callbackPath="") {
    log.trace "subscribe($path, $callbackPath)"
    def address = getCallBackAddress()
    def ip = getHostAddress()

    def result = new physicalgraph.device.HubAction(
        method: "SUBSCRIBE",
        path: path,
        headers: [
            HOST: ip,
            CALLBACK: "<http://${address}/notify$callbackPath>",
            NT: "upnp:event",
            TIMEOUT: "Second-28800"
        ]
    )

    log.trace "SUBSCRIBE $path"

    return result
}

I really do appreciate your help.
Cheers
JJ

Jonathan.

Did you manage to get this working? I try to call the HubAction with SUBSCRIBE method, but there is no call generated to my LAN device. Is this a HTTP call or UDP call?

Any help is appreciated.

Regards
Juvs

Nice! I was just going to bump this thread too. Trying to get this to work but there isn’t much in documentation and this is the only thread I could find, but there isn’t any posted resolution or working example code…

@Jonathan_Jones or @SteveWhite, appreciate any help!

I pm’d @pstuart, got this reply. Should’ve just tagged him here so it would’ve been available to you as well:

I found this MQTT DTH he mentioned:

That’s the reason this is so confusing… Well, I just want to be clear because I saw a lot of threads proposing so many ways to try to achieve this.

First thing, first: According to ST documentation there’s only two types for connected devices (aside ZWave and Zigbee), Cloud-connected and LAN-connected, personally I thing they works for different scenarios.

Cloud-connected is more like WeMo Belkin Products, where they communicate over the Internet with the Belkin servers and then this servers comunicates with your Hub, thats why you have to do the OAuth process to authorize your Belkin account to comunicate with your Hub, in my case I dont want this kind of interation between my devices and ST.

LAN-connected is like the SonOff Wifi Switch integration, where the local devices are connected only throught your local network, this more straight forward, but the challenge is the implementation of SSDP over UPnP, but this is not the only option you have to communicate with your device, you can use SOAP or REST request (in my case I use REST)

So, focus on LAN-connected devices, I already cover this points:

  1. Register the device throught the Service Manager (SmartApp) using UDP packages based on UPnP messages, this works ok.

  2. Send messages from the Hub to the device using REST request and parsing JSON responses to update the state of the Device in the Hub, this works ok.

But my main challenge is to fire an event into the Hub when my device gets some change condition to notify about it.

Careful here, just to be clear, this is different of the option of using a polling action or “checkInterval” call for retrive the state of my device, the requeriment is to notify directly to the Hub.

So, the only documented information seems to be this, but is not clear and is confusing.

http://docs.smartthings.com/en/latest/cloud-and-lan-connected-device-types-developers-guide/building-lan-connected-device-types/building-the-device-type.html#subscribing-to-device-events

I think is possible, I just to need more reading about UPnP standard, check this link for some possible way to solve this:

Cheers
Juvenal

Hi all. I found LAN notifications quite easy once you factor in the somewhat lacking documentation. I created a system to allow my older DCS-2330L cameras which integrate with ST to act as motion sensors. What I did was configure them to send an e-mail to an local e-mail server than parses the name of the camera from the subject line then issues a LAN notification to a SmartApp which triggers the virtual motion sensors. The system works surprisingly well…

The code in the SmartApp is surprisingly simple:

First you must subscribe to LAN events in the SmartApp’s initialize function.

def initialize()
{
	log.info "Shackrats Camera Motion Child Integration Initialized"
	subscribe(location, null, processLANEvent, [filterEvents:false])
}

Then you have a function to handle the event notification.

def processLANEvent(evt)
{
	def msg = parseLanMessage(evt.value)
	def body = msg.body
	if (body)
    {
		def sluper = new JsonSlurper();
		def json = sluper.parseText(body)
        if (json.status == "motion active" && json.devicename == "${camera}")
		{
			log.info "Motion Detected on ${json.devicename}"
			motionStart()
            runIn(motionTimeout + 4, motionStop)
            runIn(motionTimeout + 10, motionCleanup)
		}
	}
}

I am using the JSON Slurper to read the data in the LAN notification.

import groovy.json.JsonSlurper

The PHP file on the server that issues the motion LAN notifications is also quite simple.

<?php 
function stMotionPush($devicename)
{
	$url = "http://<HUB IP ADDRESS>/motion";
	
	$data = array(
		'devicename'	=> $devicename,
		'status'				=> 'motion active'
	);

$data = json_encode($data);
	// request via cURL
	$c = curl_init($url);	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($c, CURLOPT_PORT, 39500);
	curl_setopt($c, CURLOPT_POST, 1);

	curl_setopt($c, CURLOPT_POSTFIELDS,     $data ); 
 	curl_setopt($c, CURLOPT_HTTPHEADER,     array('Content-Type: text/html', 'Content-Length: ' . strlen($data) ));

	$response = curl_exec($c);
}
?>

Thanks @SteveWhite for the info! I think my purposes might be even simpler, let me know what you think. I use Blue Iris to monitor my cameras for motion, and an option for alerts on motion include sending a command to a web address. I currently have it going to a IFTTT Maker channel, which then turns on my virtual motion sensor, and other’s have use OAuth in an app to have the Blue Iris alert send a command straight to the smartapp. But it’s basic html, not JSON.

So can I use a command like “http://HUB_IP_ADDRESS/motion/devicename/active” and then another with “inactive” on the end, then map/parse it out?

Hi there

No I never got the subscribe working. I ended getting my device to communicate with the hub instead.

Sorry for the delay in replying.

Hi Steve
I’m having an issue getting a subscribe reply back from a device and I believe it’s something to do with the http server the smartapp/hub is talking to.
I can see the GET /filename.xml HTTP/1.1 reach the device, but I never see the xml reply back in the cloud. It works to other hardware devices, just not this one.
The xml file is accessible if I try from a web browser.
Could you tell me what other command/data/verbs I should be providing on the web server, other than the usual “HTTP/1.1 200 OK\n” , "Content-Type: text/xml\n"
Your comments regarding SUBSCRIBE/UNSUBSCRIBE and SID have me perplexed.

Thanks
David