Skip to content

Instantly share code, notes, and snippets.

@hardillb
Last active November 3, 2016 08:08
Show Gist options
  • Save hardillb/1279241bb886ee28c05b to your computer and use it in GitHub Desktop.
Save hardillb/1279241bb886ee28c05b to your computer and use it in GitHub Desktop.
Wemo lights example - "npm install wemo-js xml2js" then "node wemo-light.js" for instructions
var wemo = require('wemo-js');
var http = require('http');
var util = require('util');
var xml2js = require('xml2js');
var postbodyheader = [
'<?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>'].join('\n');
var postbodyfooter = ['</s:Body>',
'</s:Envelope>'
].join('\n');
var getenddevs = {};
getenddevs.path = '/upnp/control/bridge1';
getenddevs.action = '"urn:Belkin:service:bridge:1#GetEndDevices"';
getenddevs.body = [
postbodyheader,
'<u:GetEndDevices xmlns:u="urn:Belkin:service:bridge:1">',
'<DevUDN>%s</DevUDN>',
'<ReqListType>PAIRED_LIST</ReqListType>',
'</u:GetEndDevices>',
postbodyfooter
].join('\n');
if (process.argv.length < 3) {
console.log("Help:");
console.log("node wemo-lights.js list - Shows all the available bulbs");
console.log("node wemo-lights.js state <friendly name> - Shows the current state of a given bulb");
console.log("node wemo-lights.js control <friendly name> on|off [0-255] - (dim value only used if state is on)");
console.log("node wemo-lights.js dim <friendly name> <0-255> - setting dim value of an off device will turn it on at new level");
console.log("node wemo-lights.js sleep <friendly name> <time> - time in seconds to dim to off");
process.exit(0);
}
function processOptions(lights) {
if (process.argv[2] === 'list') {
console.log("Lights:");
console.log("Friendly name - id");
for (var j=0; j<lights.length; j++) {
console.log("%s - %s", lights[j].name, lights[j].id);
}
console.log("-------");
console.log("Sockets:");
for (var j=0; j<sockets.length; j++) {
console.log("%s", sockets[j].name);
}
process.exit(0);
} else if (process.argv[2] === 'state') {
for (var j=0; j<lights.length; j++) {
if (lights[j].name === process.argv[3]) {
console.log("%s %s",lights[j].name, lights[j].state);
process.exit(0);
}
if (sockets[j].name === process.argv[3]) {
console.log("%s %s",sockets[j].name, sockets[j].state);
process.exit(0);
}
}
} else if (process.argv[2] === 'control') {
for (var j=0; j<lights.length; j++) {
if (lights[j].name === process.argv[3]) {
var on = 0;
var level = 255;
if ("on" === process.argv[4]) {
on = 1;
}
control(lights[j], on);
if (on === 1 && process.argv.length === 6) {
console.log("dimming to %d", process.argv[5]);
dim(lights[j], process.argv[5]);
}
return;
}
}
for (var j=0; j<sockets.length; j++) {
if (sockets[j].name === process.argv[3]) {
var on = 0;
if ("on" === process.argv[4]) {
on = 1;
}
toggleSocket(sockets[j], on);
return;
}
}
} else if (process.argv[2] === "dim") {
for (var j=0; j<lights.length; j++) {
if (lights[j].name === process.argv[3]) {
var level = process.argv[4];
dim(lights[j],level);
break;
}
}
} else if (process.argv[2] === "sleep") {
for (var j=0; j<lights.length; j++) {
var time = process.argv[4];
sleep(lights[j], time);
}
}
}
function toggleSocket(socket, on) {
var postoptions = {
host: socket.ip,
port: socket.port,
path: "/upnp/control/basicevent1",
method: 'POST',
headers: {
'SOAPACTION': '"urn:Belkin:service:basicevent:1#SetBinaryState"',
'Content-Type': 'text/xml; charset="utf-8"',
'Accept': ''
}
};
var post_request = http.request(postoptions, function(res) {
var data = "";
res.setEncoding('utf8');
res.on('data', function(chunk){
data += chunk
});
res.on('end', function(){
//console.log(data);
});
});
var body = [
postbodyheader,
'<u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">',
'<BinaryState>%s</BinaryState>',
'</u:SetBinaryState>',
postbodyfooter
].join('\n');
post_request.write(util.format(body, on));
post_request.end();
}
function control(light, on) {
setStatus(light, "10006", on);
}
function dim(light, dim) {
setStatus(light, "10008", dim + ":0");
}
function sleep(light, time) {
var date = new Date();
date = date.getTime()/1000;
time = time * 10;
setStatus(light, "30008", time + ":" + date);
}
function setStatus(light, capability, value) {
var setdevstatus = {};
setdevstatus.path = '/upnp/control/bridge1';
setdevstatus.action = '"urn:Belkin:service:bridge:1#SetDeviceStatus"';
setdevstatus.body = [
postbodyheader,
'<u:SetDeviceStatus xmlns:u="urn:Belkin:service:bridge:1">',
'<DeviceStatusList>',
'&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;DeviceStatus&gt;&lt;IsGroupAction&gt;NO&lt;/IsGroupAction&gt;&lt;DeviceID available=&quot;YES&quot;&gt;%s&lt;/DeviceID&gt;&lt;CapabilityID&gt;%s&lt;/CapabilityID&gt;&lt;CapabilityValue&gt;%s&lt;/CapabilityValue&gt;&lt;/DeviceStatus&gt;',
'</DeviceStatusList>',
'</u:SetDeviceStatus>',
postbodyfooter
].join('\n');
var postoptions = {
host: light.ip,
port: light.port,
path: setdevstatus.path,
method: 'POST',
headers: {
'SOAPACTION': setdevstatus.action,
'Content-Type': 'text/xml; charset="utf-8"',
'Accept': ''
}
};
var post_request = http.request(postoptions, function(res) {
var data = "";
res.setEncoding('utf8');
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function(){
//console.log(data);
});
});
post_request.on('error', function (e) {
console.log(e);
console.log("%j", postoptions);
});
//console.log(util.format(setdevstatus.body, light.id, capability, value));
post_request.write(util.format(setdevstatus.body, light.id, capability, value));
post_request.end();
}
var lights = [];
var sockets = [];
var timer;
var client = wemo.Search();
client.on('found', function(device) {
//console.log("%j",device);
if (!timer) {
timer = setTimeout(function() {
client.stop();
processOptions(lights);
}, 2000);
}
if (device.deviceType === "urn:Belkin:device:bridge:1") {
var ip = device.ip;
var port = device.port;
var udn = device.UDN;
var postoptions = {
host: ip,
port: port,
path: getenddevs.path,
method: 'POST',
headers: {
'SOAPACTION': getenddevs.action,
'Content-Type': 'text/xml; charset="utf-8"',
'Accept': ''
}
};
var post_request = http.request(postoptions, function(res) {
var data = "";
res.setEncoding('utf8');
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function(){
xml2js.parseString(data, function(err, result) {
if (!err) {
var list = result["s:Envelope"]["s:Body"][0]["u:GetEndDevicesResponse"][0].DeviceLists[0];
xml2js.parseString(list, function(err, result2) {
if (!err) {
var devinfo = result2.DeviceLists.DeviceList[0].DeviceInfos[0].DeviceInfo
for (var i=0; i<devinfo.length; i++) {
//console.log("%s[%s]:\t\t%s",devinfo[i].FriendlyName[0], devinfo[i].DeviceID[0], devinfo[i].CurrentState[0]);
var light = {
"ip": ip,
"port": port,
"udn": udn,
"name": devinfo[i].FriendlyName[0],
"id": devinfo[i].DeviceID[0],
"state": devinfo[i].CurrentState[0]
};
lights.push(light);
}
} else {
console.log(err);
console.log(data);
}
});
}
});
});
});
post_request.write(util.format(getenddevs.body, udn));
post_request.end();
} else if (device.deviceType === 'urn:Belkin:device:controllee:1') {
//console.log("%s", device.friendlyName);
var socket = {
"ip": device.ip,
"port": device.port,
"name": device.friendlyName,
"type": "socket"
};
var postoptions = {
host: socket.ip,
port: socket.port,
path: "/upnp/control/basicevent1",
method: 'POST',
headers: {
'SOAPACTION': '"urn:Belkin:service:basicevent:1#GetBinaryState"',
'Content-Type': 'text/xml; charset="utf-8"',
'Accept': ''
}
};
var post_request = http.request(postoptions, function(res) {
var data = "";
res.setEncoding('utf8');
res.on('data', function(chunk){
data += chunk
});
res.on('end', function(){
xml2js.parseString(data, function(err, result) {
if (!err) {
var state = result['s:Envelope']['s:Body'][0]['u:GetBinaryStateResponse'][0].BinaryState[0];
socket.state = state;
sockets.push(socket);
} else {
console.log(err);
console.log(data);
}
})
});
});
var body = [
postbodyheader,
'<u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">',
'</u:GetBinaryState>',
postbodyfooter
].join('\n');
post_request.write(body);
post_request.end();
}
});
@ahmadtawakol
Copy link

@hardiilb Yes I have paired the bulbs and I can control them from the wemo app just fine. This is what I'm getting now after adding the above line:

~# node wemo-light.js list
{ 's:Envelope': 
   { '$': 
      { 'xmlns:s': 'http://schemas.xmlsoap.org/soap/envelope/',
        's:encodingStyle': 'http://schemas.xmlsoap.org/soap/encoding/' },
     's:Body': [ [Object] ] } }

events.js:72
        throw er; // Unhandled 'error' event
              ^
TypeError: Cannot read property 'length' of undefined
    at /root/wemo-light.js:258:31
    at Parser.<anonymous> (/root/node_modules/xml2js/lib/xml2js.js:384:20)
    at Parser.emit (events.js:95:17)
    at Object.onclosetag (/root/node_modules/xml2js/lib/xml2js.js:348:26)
    at emit (/root/node_modules/xml2js/node_modules/sax/lib/sax.js:615:33)
    at emitNode (/root/node_modules/xml2js/node_modules/sax/lib/sax.js:620:3)
    at closeTag (/root/node_modules/xml2js/node_modules/sax/lib/sax.js:861:5)
    at Object.write (/root/node_modules/xml2js/node_modules/sax/lib/sax.js:1294:29)
    at Parser.exports.Parser.Parser.parseString (/root/node_modules/xml2js/lib/xml2js.js:403:31)
    at Parser.parseString (/root/node_modules/xml2js/lib/xml2js.js:6:61)

@hardillb
Copy link
Author

OK, that didn't give me what I hoped, change the added line to:

console.log('%j',result["s:Envelope"]["s:Body"][0]);

@ahmadtawakol
Copy link

{"u:GetEndDevicesResponse":[{"$":{"xmlns:u":"urn:Belkin:service:bridge:1"},"DeviceLists":["<?xml version=\"1.0\" encoding=\"utf-8\"?><DeviceLists><DeviceList><DeviceListType>Paired</DeviceListType><DeviceInfos /><GroupInfos><GroupInfo><GroupID>1418677505</GroupID><GroupName>Bar</GroupName><GroupCapabilityIDs>10006,10008,30008,30009,3000A</GroupCapabilityIDs><GroupCapabilityValues>0,45:0,0:0,,</GroupCapabilityValues><DeviceInfos><DeviceInfo><DeviceIndex>0</DeviceIndex><DeviceID>94103EA2B2770460</DeviceID><FriendlyName>Bar 1</FriendlyName><IconVersion>1</IconVersion><FirmwareVersion>7E</FirmwareVersion><CapabilityIDs>10006,10008,30008,30009,3000A</CapabilityIDs><CurrentState>0,45:0,0:0,,</CurrentState><Manufacturer>MRVL</Manufacturer><ModelCode>MZ100</ModelCode><WeMoCertified>YES</WeMoCertified></DeviceInfo><DeviceInfo><DeviceIndex>1</DeviceIndex><DeviceID>B4750E1B95784CB1</DeviceID><FriendlyName>Bar 2</FriendlyName><IconVersion>1</IconVersion><FirmwareVersion>7E</FirmwareVersion><CapabilityIDs>10006,10008,30008,30009,3000A</CapabilityIDs><CurrentState>0,45:0,0:0,,</CurrentState><Manufacturer>MRVL</Manufacturer><ModelCode>MZ100</ModelCode><WeMoCertified>YES</WeMoCertified></DeviceInfo></DeviceInfos></GroupInfo></GroupInfos></DeviceList></DeviceLists>\n"]}]}

@hardillb
Copy link
Author

Ahh, The difference is you have all your bulbs in a group. I've not done much with groups yet. I'm working on a new WEMO node, that should support lights and event notifications (state change updates). When I get some time to look at groups I'll update this.

@hardillb
Copy link
Author

amadtawakol I have updated the script to support controlling groups (no dimming yet, but should be easy to add) it's here: https://gist.github.com/hardillb/ffa9b458109fb8af7d0f

@rmitsos
Copy link

rmitsos commented Sep 3, 2015

Hi

Is it normal that it takes about 3-4 seconds for sending a command ?

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment