In the previous post, Amazon Echo and Home Automation, I talked about deciphering the UPnP protocol that the Amazon Echo uses to find and control Belkin WeMo switches. This is an update to that hack that improves the integration and makes it easier to do. The code is provided.
No More Dedicated IP Addresses
When the Amazon Echo searches for WeMo devices, the answer from each switch is formatted as an HTTP response with several headers. One of those headers is LOCATION.
HTTP/1.1 200 OK ## copy CACHE-CONTROL: max-age=86400 DATE: Mon, 22 Jun 2015 17:24:01 GMT EXT: LOCATION: http://192.168.5.190:49153/setup.xml OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01 01-NLS: 905bfa3c-1dd2-11b2-8928-fd8aebaf491c SERVER: Unspecified, UPnP/1.0, Unspecified X-User-Agent: redsonic ST: urn:Belkin:device:** USN: uuid:Socket-1_0-221517K0101769::urn:Belkin:device:**
It turns out that the Amazon Echo does respect the LOCATION header. This is very good news. It means that a single machine with one IP address can emulate many virtual switches just by having each one use a different port.
One Port Is Not Enough
It would be even nicer if many virtual switches could all share the same port, each one using a different URL. This would have made it more practical for situations where port forwarding was needed. But in a home environment where the Amazon Echo and the machine hosting the virtual switches are probably on the same network anyway, this isn’t really that big a deal.
The reason it doesn’t work is because the Amazon Echo is programmed to know the specific URL for sending on and off commands to the switches. It does not get this URL from the device definition file (setup.xml). So the Echo is going to use the same URL for all devices, and the only way to differentiate them is by their port number.
Much Simpler setup.xml
Because the Amazon Echo has hard-coded knowledge about WeMo devices, it doesn’t need to get very much configuration info from the device definition file. In fact, it ignores the large majority of it and works just fine if most of the file isn’t even there. In my tests, I was able to reduce the setup.xml contents from 133 lines down to 11 lines with everything still working. The 11 remaining lines are all required by the UPnP specification and aren’t specific to Belkin. All of the optional content authored by Belkin is unnecessary. (There are several elements required by the specification that the Echo doesn’t check for, so I left those out, too.)
The best result of this testing is that I can now publish the code because it no longer contains a copy of the setup.xml from the WeMo devices. The code is fully operational using just required UPnP elements.
<?xml version="1.0"?> <root> <device> <deviceType>urn:MakerMusings:device:controllee:1</deviceType> <friendlyName>%(device_name)s</friendlyName> <manufacturer>Belkin International Inc.</manufacturer> <modelName>Emulated Socket</modelName> <modelNumber>3.1415</modelNumber> <UDN>uuid:Socket-1_0-%(device_serial)s</UDN> </device> </root>
Status Code Is All You Need
When the Amazon Echo send an on or off command to the switch, the actual WeMo devices return a blob of SOAP-formatted content in the body of the HTTP response. The Echo ignores this content and reacts only to the status code. If it gets back a 200 status code, it says, “OK”. If it gets back an error code or nothing at all, then it tells you it couldn’t perform your request. So that’s the other piece of data that comes from WeMo devices that our virtual switches don’t need to copy.
Real WeMo response:
HTTP/1.1 200 OK CONTENT-LENGTH: 295 CONTENT-TYPE: text/xml; charset="utf-8" DATE: Mon, 22 Jun 2015 22:45:57 GMT EXT: SERVER: Unspecified, UPnP/1.0, Unspecified X-User-Agent: redsonic <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body> <u:SetBinaryStateResponse xmlns:u="urn:Belkin:service:basicevent:1"> <CountdownEndTime>0</CountdownEndTime> </u:SetBinaryStateResponse> </s:Body> </s:Envelope>
Virtual switch response that works just fine:
HTTP/1.1 200 OK CONTENT-LENGTH: 0 CONTENT-TYPE: text/xml; charset="utf-8" DATE: Mon, 22 Jun 2015 22:45:57 GMT EXT: SERVER: Unspecified, UPnP/1.0, Unspecified X-User-Agent: redsonic
So Where’s the Code?
The code is right here on GitHub.
Here’s the part that you’ll want to look at and edit before you run it:
class rest_api_handler(object): def __init__(self, on_cmd, off_cmd): self.on_cmd = on_cmd self.off_cmd = off_cmd def on(self): r = requests.get(self.on_cmd) return r.status_code == 200 def off(self): r = requests.get(self.off_cmd) return r.status_code == 200 FAUXMOS = [ ['office lights', rest_api_handler('http://192.168.5.4/ha-api?cmd=on&a=office', 'http://192.168.5.4/ha-api?cmd=off&a=office')], ['kitchen lights', rest_api_handler('http://192.168.5.4/ha-api?cmd=on&a=kitchen', 'http://192.168.5.4/ha-api?cmd=off&a=kitchen')], ]
The virtual switches expect an object that has on() and off() methods. The class I provided as an example just makes a request to a URL that you define. If you have a simple web API for your home automation, then this may work great for you. If not, then you’ll want to make your own class and run any code you want in your on() and off() methods.
If you use the example class, you will need the python-requests library. All the other libraries used are part of the standard Python installation.