"use strict" var events = require('events'); var util = require('util'); var Client = require('node-ssdp').Client; var xml2js = require('xml2js'); var request = require('request'); var http = require('http'); var url = require('url'); var Q = require('q'); var urn = 'urn:Belkin:service:basicevent:1'; var postbodyheader = [ '', '', ''].join('\n'); var postbodyfooter = ['', '' ].join('\n'); var getenddevs = {}; getenddevs.path = '/upnp/control/bridge1'; getenddevs.action = '"urn:Belkin:service:bridge:1#GetEndDevices"'; getenddevs.body = [ postbodyheader, '', '%s', 'PAIRED_LIST', '', postbodyfooter ].join('\n'); var getcapabilities = {}; getcapabilities.path = '/upnp/control/bridge1'; getcapabilities.action = '"urn:Belkin:service:bridge:1#GetCapabilityProfileIDList"'; getcapabilities.body = [ postbodyheader, '', '%s', '', postbodyfooter ].join('\n'); var WeMoNG = function () { this.devices = {}; this._client; this._interval; events.EventEmitter.call(this); } util.inherits(WeMoNG, events.EventEmitter); WeMoNG.prototype.start = function start() { //console.log("searching"); var _wemo = this; _wemo.setMaxListeners(0); _wemo._client = new Client(); _wemo._client.setMaxListeners(0); _wemo._client.on('response', function (headers, statusCode, rinfo) { var location = url.parse(headers.LOCATION); var port = location.port; request.get(location.href, function(err, res, xml) { if (!err) { xml2js.parseString(xml, function(err, json) { if (!err) { var device = { ip: location.hostname, port: location.port }; for (var key in json.root.device[0]) { device[key] = json.root.device[0][key][0]; } if (device.deviceType == "urn:Belkin:device:bridge:1") { //console.log( device.ip + ' -' + device.deviceType); 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) { if (result["s:Envelope"]) { 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', '%s', '', postbodyfooter ].join('\n'); post_request.write(util.format(body, on)); post_request.end(); } WeMoNG.prototype.setStatus = function setStatus(light, capability, value) { var setdevstatus = {}; setdevstatus.path = '/upnp/control/bridge1'; setdevstatus.action = '"urn:Belkin:service:bridge:1#SetDeviceStatus"'; setdevstatus.body = [ postbodyheader, '', '', '<?xml version="1.0" encoding="UTF-8"?><DeviceStatus><IsGroupAction>NO</IsGroupAction><DeviceID available="YES">%s</DeviceID><CapabilityID>%s</CapabilityID><CapabilityValue>%s</CapabilityValue></DeviceStatus>', '', '', 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(); } //need to promisify this so it returns WeMoNG.prototype.parseEvent = function parseEvent(evt) { var msg = {}; msg.raw = evt; var def = Q.defer(); xml2js.parseString(evt, function(err, res){ if (!err) { var prop = res['e:propertyset']['e:property'][0]; if (prop.hasOwnProperty('StatusChange')) { xml2js.parseString(prop['StatusChange'][0], function(err, res){ if (!err && res != null) { msg.id = res['StateEvent']['DeviceID'][0]['_']; msg.capability = res['StateEvent']['CapabilityId'][0]; msg.value = res['StateEvent']['Value'][0]; def.resolve(msg); } }); } else if (prop.hasOwnProperty('BinaryState')) { msg.state = prop['BinaryState'][0]; if (msg.state.length > 1) { var parts = msg.state.split('|'); msg.state = parts[0]; msg.power = parts[7]/1000; } def.resolve(msg); } else { console.log("unhandled wemo event type \n%s", util.inspect(prop, {depth:null})); } } else { //error } }); return def.promise; } // Based on https://github.com/theycallmeswift/hue.js/blob/master/lib/helpers.js // TODO: Needs to be tweaked for more accurate color representation WeMoNG.prototype.rgb2xy = function rgb2xy(red, green, blue) { var xyz; var rgb = [red / 255, green / 255, blue / 255]; for (var i = 0; i < 3; i++) { if (rgb[i] > 0.04045) { rgb[i] = Math.pow(((rgb[i] + 0.055) / 1.055), 2.4); } else { rgb[i] /= 12.92; } rgb[i] = rgb[i] * 100; } xyz = [ rgb[0] * 0.4124 + rgb[1] * 0.3576 + rgb[2] * 0.1805, rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722, rgb[0] * 0.0193 + rgb[1] * 0.1192 + rgb[2] * 0.9505 ]; return [ xyz[0] / (xyz[0] + xyz[1] + xyz[2]) * 65535, xyz[1] / (xyz[0] + xyz[1] + xyz[2]) * 65535 ]; }; module.exports = WeMoNG;