I looked around for information and reverse engineering the wemo app didn't pause any problem: I reversed engineer lots of programs in my youth (in the 80s that's how you learned programming and how things work, including MS-DOS) and decompiling Java apps rates among the easiest things to do and analyse.
I discovered that the communication between the phone on the local network and the wemo is done via XML over HTTP (aka SOAP) on 2 possible ports (49153 and 49154). To talk to it I just needed to send the right packet to the known IP of the wemo and voila.
To switch on the wemo, you just need to send:
<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:SetBinaryState></s:Body></s:Envelope>
For action Belkin:service:basicevent:1#SetBinaryState.
I used PHP to program the pis because it's the fastest for me when I want to have web interfaces, so to switch on the wemo I used the following code:
$server = "http://192.168.0.35";
$ports = Array(49153,49154);
"Accept: ",
"Content-type: text/xml;charset=\"utf-8\"",
"SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""
);
$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:SetBinaryState></s:Body></s:Envelope>';
$url = $server.":".$ports[$_REQUEST["port"]]."/upnp/control/basicevent1";
$defaults = array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
CURLOPT_USERAGENT => '',
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 4,
);
$ch = curl_init();
curl_setopt_array($ch, $defaults);
$result = curl_exec($ch);
// Deal with results here
curl_close($ch);
Dead easy heh? To switch it off, you just need to change the value set BinaryState value to 0:
$server = "http://192.168.0.35";
$ports = Array(49153,49154);
$headers = array(
"Accept: ",
"Content-type: text/xml;charset=\"utf-8\"",
"SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""
);
$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>0</BinaryState></u:SetBinaryState></s:Body></s:Envelope>';
$url = $server.":".$ports[$_REQUEST["port"]]."/upnp/control/basicevent1";
$defaults = array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
CURLOPT_USERAGENT => '',
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 4,
);
$ch = curl_init();
curl_setopt_array($ch, $defaults);
$result = curl_exec($ch);
// Deal with results here
curl_close($ch);
The port to use comes from the HTTP request (not sanitised, you need to make sure you don't accept any old crap from the interwebs). It is selected after running a test to see which port is currently used by the wemo (it changes regularly). To do that, you can request the BinaryState of your wemo using action Belkin:service:basicevent:1#GetBinaryState and change the port in case of failure:
$server = "http://192.168.0.35";
$ports = Array(49153,49154);
$headers = array(
"Accept: ",
"Content-type: text/xml;charset=\"utf-8\"",
"SOAPACTION: \"urn:Belkin:service:basicevent:1#GetBinaryState\""
);
$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:GetBinaryState></s:Body></s:Envelope>';
$powerstate = "";
$portindex = 0;
while (trim($powerstate)=="" && $portindex<count($ports)) {
$url1 = $server.":".$ports[$portindex]."/upnp/control/basicevent1";
$defaults = array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
CURLOPT_USERAGENT => '',
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_URL => $url1,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 4,
);
$ch = curl_init();
curl_setopt_array($ch, $defaults);
$powerstate = curl_exec($ch);
if (trim($powerstate)=="") {
$portindex++;
}
}
// Now we've tested the ports until we found a working one or failed on all of them. We salso know that if $portindex is outside the $port array we failed to communicate. We can take action.
$power = substr($powerstate, stripos($powerstate,"BinaryState>") + strlen("BinaryState>"),1);
$ports = Array(49153,49154);
"Accept: ",
"Content-type: text/xml;charset=\"utf-8\"",
"SOAPACTION: \"urn:Belkin:service:basicevent:1#SetBinaryState\""
);
$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>0</BinaryState></u:SetBinaryState></s:Body></s:Envelope>';
$url = $server.":".$ports[$_REQUEST["port"]]."/upnp/control/basicevent1";
$defaults = array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
CURLOPT_USERAGENT => '',
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_URL => $url,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 4,
);
$ch = curl_init();
curl_setopt_array($ch, $defaults);
$result = curl_exec($ch);
// Deal with results here
curl_close($ch);
The port to use comes from the HTTP request (not sanitised, you need to make sure you don't accept any old crap from the interwebs). It is selected after running a test to see which port is currently used by the wemo (it changes regularly). To do that, you can request the BinaryState of your wemo using action Belkin:service:basicevent:1#GetBinaryState and change the port in case of failure:
$server = "http://192.168.0.35";
$ports = Array(49153,49154);
$headers = array(
"Accept: ",
"Content-type: text/xml;charset=\"utf-8\"",
"SOAPACTION: \"urn:Belkin:service:basicevent:1#GetBinaryState\""
);
$post = '<?xml version="1.0" encoding="utf-8"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"><BinaryState>1</BinaryState></u:GetBinaryState></s:Body></s:Envelope>';
$powerstate = "";
$portindex = 0;
while (trim($powerstate)=="" && $portindex<count($ports)) {
$url1 = $server.":".$ports[$portindex]."/upnp/control/basicevent1";
$defaults = array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
CURLOPT_USERAGENT => '',
CURLOPT_POSTFIELDS => $post,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POST => 1,
CURLOPT_HEADER => 0,
CURLOPT_URL => $url1,
CURLOPT_FRESH_CONNECT => 1,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_FORBID_REUSE => 1,
CURLOPT_TIMEOUT => 4,
);
$ch = curl_init();
curl_setopt_array($ch, $defaults);
$powerstate = curl_exec($ch);
if (trim($powerstate)=="") {
$portindex++;
}
}
// Now we've tested the ports until we found a working one or failed on all of them. We salso know that if $portindex is outside the $port array we failed to communicate. We can take action.
$power = substr($powerstate, stripos($powerstate,"BinaryState>") + strlen("BinaryState>"),1);
At this point, $power==0 of the wemo is off, 1 if it's on, empty if we failed to communicate with the wemo (but we already know that from the value of $portindex). It's up to you to take action and communicate to the user.
With that sorted, I could read the state, start, and stop my boiler as I wanted. Now I just needed to know when to take action.
Next step: reading and storing temperatures
No comments:
Post a Comment