From 37ba29484a6789a7077a047d3fefae2970e9d091 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Sat, 9 Apr 2016 17:59:36 +0100 Subject: [PATCH] Update WeMo node to WeMo-NG (#199) * Replacing the original wemo node with the wemo-ng node * Added install instructions * Fix name of wemo out node * Fix some jshint errors More jshint fixes fix jshint last jshint fix Fix last jshint error --- hardware/wemo/60-wemo.html | 90 -------- hardware/wemo/60-wemo.js | 60 ----- hardware/wemo/README.md | 76 ++++++- hardware/wemo/WeMoNG.html | 193 +++++++++++++++++ hardware/wemo/WeMoNG.js | 386 +++++++++++++++++++++++++++++++++ hardware/wemo/icons/belkin.png | Bin 0 -> 243 bytes hardware/wemo/lib/wemo.js | 367 +++++++++++++++++++++++++++++++ hardware/wemo/package.json | 53 +++-- 8 files changed, 1044 insertions(+), 181 deletions(-) delete mode 100644 hardware/wemo/60-wemo.html delete mode 100644 hardware/wemo/60-wemo.js create mode 100644 hardware/wemo/WeMoNG.html create mode 100644 hardware/wemo/WeMoNG.js create mode 100644 hardware/wemo/icons/belkin.png create mode 100644 hardware/wemo/lib/wemo.js diff --git a/hardware/wemo/60-wemo.html b/hardware/wemo/60-wemo.html deleted file mode 100644 index cec3bd61..00000000 --- a/hardware/wemo/60-wemo.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - diff --git a/hardware/wemo/60-wemo.js b/hardware/wemo/60-wemo.js deleted file mode 100644 index f11383e0..00000000 --- a/hardware/wemo/60-wemo.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2013 IBM Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -module.exports = function(RED) { - "use strict"; - var Wemo = require('wemo'); - - function WemoOut(n) { - RED.nodes.createNode(this,n); - this.ipaddr = n.ipaddr; - this.wemoSwitch = new Wemo(n.ipaddr); - var node = this; - - this.on("input", function(msg) { - var state = 0; - if ( msg.payload == 1 || msg.payload === true || msg.payload == "on" ) { state = 1; } - node.wemoSwitch.setBinaryState(state, function(err, result) { - if (err) { node.warn(err); } - //else { node.log(result); } - }); - }); - } - RED.nodes.registerType("wemo out",WemoOut); - - function WemoIn(n) { - RED.nodes.createNode(this,n); - this.ipaddr = n.ipaddr; - this.wemoSwitch = new Wemo(n.ipaddr); - this.wemoSwitch.state = 0; - var node = this; - - var tick = setInterval(function() { - node.wemoSwitch.getBinaryState(function(err, result) { - if (err) { node.warn(err); } - if (parseInt(result) != node.wemoSwitch.state) { - node.wemoSwitch.state = parseInt(result); - node.send({payload:node.wemoSwitch.state,topic:"wemo/"+node.ipaddr}); - } - }); - }, 2000); - - this.on("close", function() { - clearInterval(tick); - }); - } - RED.nodes.registerType("wemo in",WemoIn); -} diff --git a/hardware/wemo/README.md b/hardware/wemo/README.md index 8b9b4ab2..57e4a44f 100644 --- a/hardware/wemo/README.md +++ b/hardware/wemo/README.md @@ -1,7 +1,8 @@ -node-red-node-wemo -================== +# node-red-contrib-nodes-wemo -A pair of Node-RED nodes to control a Belkin Wemo set of devices. +A set of Node-RED nodes for working with Belkin WeMo devices. + +These nodes use the uPnP discovery so may not discover your devices if you have a firewall enabled Install ------- @@ -11,16 +12,71 @@ Run the following command in your Node-RED user directory - typically `~/.node-r npm install node-red-node-wemo -Usage ------ +## Output node -It doesn't yet do any ip address discovery of the wemo devices. +The output node switches a socket, a light or group of lights on or off -### Wemo output node. +This should be backward compatible with the pervious version of this node but will benefit +from opening the config dialog and selecting the node you want. -Expects a `msg.payload` with either 1/0, on/off or true/false. +The node accecpts the following inputs + * Strings on/off + * integers 1/0 + * boolean true/false + * an Object like this (lights only, coming soon) + ``` + { + state: 1, + dim: 255, + color: '255,255,255', + temperature: 25000 + } + ``` -### Wemo input node. +## Input Node -Creates a `msg.payload` with either 1, 0, nc (no change), or na (not available). +The new input node is now based on uPnP notifications instead of polling. This means messages +will only be set when an actual change occurs in on the device. This means the node will not +send regular no-change messages. + +The output varies depending on the type of device but examples for sockets look like this: + +``` + { + "raw": "\n\n1\n\n\n\n\r", + "state": "1", + "sid": "uuid:e2c4586c-1dd1-11b2-8f61-b535035ae35d", + "type": "socket", + "name": "Bedroom Switch", + "id": "221448K1100085" + } +``` + +And a lightblub can look like this: + +``` + { + "raw": "\n\n<?xml version="1.0" encoding="utf-8"?><StateEvent><DeviceID\navailable="YES">94103EA2B27803ED</DeviceID><CapabilityId>10006</CapabilityId><Value>1</Value></StateEvent>\n\n\n\n\n\r", + "id": "94103EA2B27803ED", + "capability": "10006", + "value": "1", + "sid": "uuid:e2e5739e-1dd1-11b2-943d-c238ce2bad17", + "type": "light", + "name": "Bedroom" + } +``` + +Insight + +``` + { + "raw": "\n\n8|1454271649|301|834|56717|1209600|8|1010|638602|12104165\n\n\n\n\r", + "state": "8", + "power": 1.01, + "sid": "uuid:ea808ecc-1dd1-11b2-9579-8e5c117d479e", + "type": "socket", + "name": "WeMo Insight", + "id": "221450K1200F5C" + } +``` diff --git a/hardware/wemo/WeMoNG.html b/hardware/wemo/WeMoNG.html new file mode 100644 index 00000000..71c82e34 --- /dev/null +++ b/hardware/wemo/WeMoNG.html @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hardware/wemo/WeMoNG.js b/hardware/wemo/WeMoNG.js new file mode 100644 index 00000000..f3292810 --- /dev/null +++ b/hardware/wemo/WeMoNG.js @@ -0,0 +1,386 @@ +/** + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +var WeMoNG = require('./lib/wemo.js'); + +var wemo = new WeMoNG(); + +//this won't work as there is no way to stop it... +//but is that a problem? +var interval = setInterval(wemo.start.bind(wemo), 60000); +wemo.start(); + +module.exports = function(RED) { + "use strict"; + var util = require('util'); + var ip = require('ip'); + var bodyParser = require('body-parser'); + var http = require('http'); + var os = require('os'); + + var settings = RED.settings; + + var subscriptions = {}; + var sub2dev = {}; + + var resubscribe = function() { + + var subs = Object.keys(subscriptions); + for (var s in subs) { + if (subs.hasOwnProperty(s)) { + var sub = subscriptions[subs[s]]; + var dev = wemo.get(subs[s]); + var reSubOptions = { + host: dev.ip, + port: dev.port, + path: dev.device.UDN.indexOf('Bridge-1_0') < 0 ? '/upnp/event/basicevent1': '/upnp/event/bridge1', + method: 'SUBSCRIBE', + headers: { + 'SID': sub.sid, + 'TIMEOUT': 'Second-300' + } + }; + + var resub_request = http.request(reSubOptions, function(res) { + //shoudl raise an error if needed + if (res.statusCode != 200) { + console.log("problem with resubscription %s - %s", res.statusCode, res.statusMessage); + console.log("opts - %s", util.inspect(reSubOptions)); + console.log("dev - %s", util.inspect(dev)); + delete subscriptions[dev]; + delete sub2dev[sub.sid]; + subscribe({dev: subs[s]}); + } else { + // console.log("resubscription good %s", res.statusCode); + // console.log("dev - %s", util.inspect(dev)); + } + }); + + resub_request.on('error', function(){ + //console.log("failed to resubscribe to %s", dev.name ); + //need to find a way to resubsribe + delete subscriptions[dev]; + delete sub2dev[sub.sid]; + subscribe({dev: subs[s]}); + }); + + resub_request.end(); + + } + } + + } + + setInterval(resubscribe, 200000); + + var subscribe = function(node) { + var dev = node.dev; + var device = wemo.get(dev); + if (device){ + if (subscriptions[dev]) { + //exists + subscriptions[dev].count++; + } else { + //new + + var ipAddr; + //device.ip + var interfaces = os.networkInterfaces(); + var interfaceNames = Object.keys(interfaces); + for (var name in interfaceNames) { + if (interfaceNames.hasOwnProperty(name)) { + var addrs = interfaces[interfaceNames[name]]; + for (var add in addrs) { + if (addrs[add].netmask){ + //node 0.12 or better + if (!addrs[add].internal && addrs[add].family == 'IPv4') { + if (ip.isEqual(ip.mask(addrs[add].address,addrs[add].netmask),ip.mask(device.ip,addrs[add].netmask))) { + ipAddr = addrs[add].address; + break; + } + } + } else { + //node 0.10 not great but best we can do + if (!addrs[add].internal && addrs[add].family == 'IPv4') { + ipAddr = addrs[add].address; + break; + } + } + } + if (ipAddr) { + break; + } + } + } + + var callback_url = 'http://' + ipAddr + ':' + settings.uiPort; + if(settings.httpAdminRoot) { + callback_url += settings.httpAdminRoot; + } + + if (callback_url.lastIndexOf('/') != (callback_url.length -1)) { + callback_url += '/'; + } + + callback_url += 'wemoNG/notification'; + + console.log("Callback URL = %s",callback_url); + + var subscribeOptions = { + host: device.ip, + port: device.port, + path: device.device.UDN.indexOf('Bridge-1_0') < 0 ? '/upnp/event/basicevent1': '/upnp/event/bridge1', + method: 'SUBSCRIBE', + headers: { + 'CALLBACK': '<' + callback_url + '>', + 'NT': 'upnp:event', + 'TIMEOUT': 'Second-300' + } + }; + + //console.log(util.inspect(subscribeOptions)); + + var sub_request = http.request(subscribeOptions, function(res) { + //console.log("subscribe: %s - %s", device.name, res.statusCode); + if (res.statusCode == 200) { + subscriptions[dev] = {'count': 1, 'sid': res.headers.sid}; + sub2dev[res.headers.sid] = dev; + } else { + console.log("failed to subsrcibe"); + } + }); + + sub_request.end(); + } + } + } + + function unsubscribe(node) { + var dev = node.dev; + if (subscriptions[dev]) { + if (subscriptions[dev].count == 1) { + var sid = subscriptions[dev].sid; + + var device = wemo.get(dev); + //need to unsubsribe properly here + var unSubOpts = { + host: device.ip, + port: device.port, + path: device.device.UDN.indexOf('Bridge-1_0') < 0 ? '/upnp/event/basicevent1': '/upnp/event/bridge1', + method: 'UNSUBSCRIBE', + headers: { + 'SID': sid + } + }; + + //console.log(util.inspect(unSubOpts)); + + var unSubreq = http.request(unSubOpts, function(res){ + //console.log("unsubscribe: %s \n %s", device.name, res.statusCode); + delete subscriptions[dev]; + delete sub2dev[sid]; + }); + + unSubreq.end(); + + } else { + subscriptions[dev].count--; + } + } else { + //shouldn't ever get here + } + } + + var wemoNGConfig = function(n) { + RED.nodes.createNode(this,n); + this.device = n.device; + } + RED.nodes.registerType("wemo-dev", wemoNGConfig); + + var wemoNGNode = function(n) { + RED.nodes.createNode(this,n); + var node = this; + node.device = n.device; + node.name = n.name; + node.dev = RED.nodes.getNode(node.device).device; + node.status({fill:"red",shape:"dot",text:"searching"}); + + //console.log("Control - %j" ,this.dev); + if (!wemo.get(node.dev)){ + wemo.on('discovered', function(d){ + if (node.dev === d) { + node.status({fill:"green",shape:"dot",text:"found"}); + } + }); + } else { + node.status({fill:"green",shape:"dot",text:"found"}); + } + + node.on('input', function(msg){ + var dev = wemo.get(node.dev); + + if (!dev) { + //need to show that dev not currently found + console.log("no device found"); + return; + } + + var on = 0; + if (typeof msg.payload === 'string') { + if (msg.payload == 'on' || msg.payload == '1' || msg.payload == 'true') { + on = 1; + } else if (msg.payload === 'toggle') { + on = 2; + } + } else if (typeof msg.payload === 'number') { + if (msg.payload >= 0 && msg.payload < 3) { + on = msg.payload; + } + } else if (typeof msg.payload === 'object') { + //object need to get complicated here + if (msg.payload.state && typeof msg.payload.state === 'number') { + if (dev.type === 'socket') { + if (msg.payload >= 0 && msg.payload < 2) { + on = msg.payload.state + } + } else if (dev.type === 'light' || dev.type === 'group') { + if (msg.payload >= 0 && msg.payload < 3) { + on = msg.payload.state; + } + } + } + } else if (typeof msg.payload === 'boolean') { + if (msg.payload) { + on = 1; + } + } + + if (dev.type === 'socket') { + //console.log("socket"); + wemo.toggleSocket(dev, on); + } else if (dev.type === 'light`') { + //console.log("light"); + wemo.setStatus(dev,"10006", on); + } else { + console.log("group"); + wemo.setStatus(dev, "10006", on); + } + }); + } + RED.nodes.registerType("wemo out", wemoNGNode); + + var wemoNGEvent = function(n) { + RED.nodes.createNode(this,n); + var node = this; + node.ipaddr = n.ipaddr; + node.device = n.device; + node.name = n.name; + node.topic = n.topic; + node.dev = RED.nodes.getNode(node.device).device; + + node.status({fill:"red",shape:"dot",text:"searching"}); + + var onEvent = function(notification){ + var d = sub2dev[notification.sid]; + if (d == node.dev) { + var dd = wemo.get(node.dev); + notification.type = dd.type; + notification.name = dd.name; + if (!notification.id) { + notification.id = node.dev; + } + + var msg = { + topic: node.topic ? node.topic : 'wemo', + payload: notification + }; + + switch (notification.type){ + case 'light': + case 'group': + if (dd.id === notification.id) { + node.send(msg); + } + break; + case 'socket': + node.send(msg); + break; + default: + } + + } + }; + + wemo.on('event', onEvent); + + if (node.dev) { + //subscribe to events + if (wemo.get(node.dev)) { + node.status({fill:"green",shape:"dot",text:"found"}); + subscribe(node); + } else { + wemo.on('discovered', function(d){ + if (node.dev === d) { + node.status({fill:"green",shape:"dot",text:"found"}); + subscribe(node); + } + }); + } + } else if (node.ipaddr) { + //legacy + var devices = Object.keys(wemo.devices); + for (var d in devices) { + if (devices.hasOwnProperty(d)) { + var device = devices[d]; + if (device.ip === node.ipaddr) { + node.dev = device.id; + node.status({fill:"green",shape:"circle",text:"reconfigure"}); + subscribe(node); + break; + } + } + } + } + + + node.on('close', function(done){ + //should un subscribe from events + wemo.removeListener('event', onEvent); + unsubscribe(node); + done(); + }); + } + RED.nodes.registerType("wemo in", wemoNGEvent) + + RED.httpAdmin.get('/wemoNG/devices', function(req,res){ + res.json(wemo.devices); + + }); + + RED.httpAdmin.use('/wemoNG/notification',bodyParser.raw({type: 'text/xml'})); + + RED.httpAdmin.notify('/wemoNG/notification', function(req, res){ + var notification = { + 'sid': req.headers.sid + }; + //console.log("Incoming Event %s", req.body.toString()); + wemo.parseEvent(req.body.toString()).then(function(evt){ + evt.sid = notification.sid; + wemo.emit('event',evt); + }); + res.send(""); + }); + +} \ No newline at end of file diff --git a/hardware/wemo/icons/belkin.png b/hardware/wemo/icons/belkin.png new file mode 100644 index 0000000000000000000000000000000000000000..44e7033d2b64ee21f22892dfd6f0a7ceffb11107 GIT binary patch literal 243 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz1!3HFCgzU0`6kC$Fy9wy$!fk$L9 z0|U1(2s1Lwnj--eWH0gbb!ETLB_^gXSWtQSDNsl@GbEzKIX^cyHLnE7WngeFN=+r>mdKI;Vst05)bvd;kCd literal 0 HcmV?d00001 diff --git a/hardware/wemo/lib/wemo.js b/hardware/wemo/lib/wemo.js new file mode 100644 index 00000000..d3822fed --- /dev/null +++ b/hardware/wemo/lib/wemo.js @@ -0,0 +1,367 @@ +/** + * Copyright 2015 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +"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._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) { + xml2js.parseString(xml, function(err, json) { + 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) { + 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; \ No newline at end of file diff --git a/hardware/wemo/package.json b/hardware/wemo/package.json index 5d31c60a..9717360f 100644 --- a/hardware/wemo/package.json +++ b/hardware/wemo/package.json @@ -1,24 +1,35 @@ { - "name" : "node-red-node-wemo", - "version" : "0.0.3", - "description" : "A Node-RED node to control a Belkin Wemo set of devices.", - "dependencies" : { - "wemo" : "0.2.*" - }, - "repository" : { - "type":"git", - "url":"https://github.com/node-red/node-red-nodes/tree/master/hardware/wemo" - }, - "license": "Apache-2.0", - "keywords": [ "node-red", "wemo" ], - "node-red" : { - "nodes" : { - "wemo": "60-wemo.js" - } - }, - "author": { - "name": "Dave Conway-Jones", - "email": "ceejay@vnet.ibm.com", - "url": "http://nodered.org" + "name": "node-red-nodes-wemo", + "version": "0.1.4", + "description": "Input and Output nodes for Belkin WeMo devices", + "repository": "https://github.com/node-red/node-red-nodes/tree/master/hardware", + "main": "WeMoNG.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "node-red", + "wemo" + ], + "author": { + "email": "hardillb@gmail.com", + "name": "Benjamin Hardill", + "url": "http://www.hardill.me.uk/wordpress/" + }, + "license": "APACHE-2.0", + "dependencies": { + "node-ssdp": "~2.6.3", + "request": "~2.65.0", + "xml2js": "~0.4.13", + "util": "~0.10.3", + "url": "~0.11.0", + "ip": "~1.0.1", + "body-parser": "~1.14.1", + "q": "~1.4.1" + }, + "node-red": { + "nodes": { + "wemo": "WeMoNG.js" } + } }