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

@hardillb

I tried the updated code, I got the same thing again. Am I doing something wrong?

~# node wemo-light.js list

events.js:72
        throw er; // Unhandled 'error' event
              ^
TypeError: Cannot read property 'length' of undefined
    at /root/wemo-light.js:257: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

@ahmadtawakol Have you already paired the bulbs with the bridde using the iOS/Android app? The only way I can think of that you will get the error you are seeing is if there are no bulbs paired.

Add the following line to the code just between lines 252 and 253:

console.log(result);

and try again. This won't fix it but it will show the result the bridge is sending back

@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