diff --git a/hardware/wemo/README.md b/hardware/wemo/README.md
index 3f5fe7f1..dd49ff71 100644
--- a/hardware/wemo/README.md
+++ b/hardware/wemo/README.md
@@ -24,7 +24,7 @@ The node accecpts the following inputs
* Strings on/off
* integers 1/0
* boolean true/false
- * an Object like this (lights only, coming soon)
+ * an Object like this (lights only & color control is still work in the progress)
```
{
state: 1,
diff --git a/hardware/wemo/WeMoNG.html b/hardware/wemo/WeMoNG.html
index f0e8e11d..b9cec3ab 100644
--- a/hardware/wemo/WeMoNG.html
+++ b/hardware/wemo/WeMoNG.html
@@ -29,8 +29,9 @@
Light Groups
Motion Detector
- Sockets will generate msg.payload with values of 0/1 for off or on, all other
- types will return an object like this:
+ Sockets will generate msg.payload with values of 0/1/8 for off or on
+ (8 is on but at standby load for insight sockets), lightswill return an
+ object like this:
{
name: 'Bedroom light',
@@ -44,6 +45,8 @@
- 10006 - on/off
- 10008 - brightness
+ - 10300 - color
+ - 30301 - color temperature
@@ -53,7 +56,8 @@
defaults: { // defines the editable properties of the node
name: {value:""}, // along with default values.
topic: {value:"wemo", required: true},
- device: {value:"", type: "wemo-dev"}
+ device: {value:"", type: "wemo-dev"},
+ label: {value:""}
},
color: "LawnGreen",
inputs:0, // set the number of inputs - only 0 or 1
@@ -61,10 +65,17 @@
// set the icon (held in icons dir below where you save the node)
icon: "belkin.png", // saved in icons/myicon.png
label: function() { // sets the default label contents
- return this.name||"wemo";
+ if (this.name){
+ return this.name;
+ } else {
+ return this.label||"wemo";
+ }
},
labelStyle: function() { // sets the class to apply to the label
return this.name?"node_label_italic":"";
+ },
+ oneditsave: function(){
+ this.label = $('#node-input-device option:selected').text();
}
});
@@ -106,6 +117,7 @@
temperature: 25000
}
+ color control is still a work in progress, but the rest should work
+
+
+
+
+
+
diff --git a/hardware/wemo/WeMoNG.js b/hardware/wemo/WeMoNG.js
index 827b91ab..96ccbf7c 100644
--- a/hardware/wemo/WeMoNG.js
+++ b/hardware/wemo/WeMoNG.js
@@ -45,7 +45,7 @@ module.exports = function(RED) {
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 subscriptions[subs[s]];
delete sub2dev[sub.sid];
subscribe({dev: subs[s]});
}
@@ -58,7 +58,7 @@ module.exports = function(RED) {
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 subscriptions[subs[s]];
delete sub2dev[sub.sid];
subscribe({dev: subs[s]});
});
@@ -231,6 +231,7 @@ module.exports = function(RED) {
}
var on = 0;
+ var capability = '10006';
if (typeof msg.payload === 'string') {
if (msg.payload == 'on' || msg.payload == '1' || msg.payload == 'true') {
on = 1;
@@ -238,44 +239,53 @@ module.exports = function(RED) {
else if (msg.payload === 'toggle') {
on = 2;
}
- }
- else if (typeof msg.payload === 'number') {
+ } else if (typeof msg.payload === 'number') {
if (msg.payload >= 0 && msg.payload < 3) {
on = msg.payload;
}
- }
- else if (typeof msg.payload === 'object') {
+ } else if (typeof msg.payload === 'object') {
//object need to get complicated here
- if (msg.payload.state && typeof msg.payload.state === 'number') {
+ if (msg.payload.hasOwnProperty('state') && typeof msg.payload.state === 'number') {
if (dev.type === 'socket') {
- if (msg.payload >= 0 && msg.payload < 2) {
+ if (msg.payload.state >= 0 && msg.payload.state < 2) {
on = msg.payload.state;
}
}
else if (dev.type === 'light' || dev.type === 'group') {
- if (msg.payload >= 0 && msg.payload < 3) {
- on = msg.payload.state;
+ // if (msg.payload.state >= 0 && msg.payload.state < 3) {
+ // on = msg.payload.state;
+ // }
+ var keys = Object.keys(msg.payload);
+ var caps = [];
+ var states = [];
+ for (var i=0; i 0) {
+ capability = caps.join(',');
+ on = states.join(',');
}
}
}
- }
- else if (typeof msg.payload === 'boolean') {
+ } else if (typeof msg.payload === 'boolean') {
if (msg.payload) {
on = 1;
}
}
- if (dev.type === 'socket') {
+ 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);
+ } else if (dev.type === 'light') {
+ wemo.setStatus(dev,capability, on);
+ } else {
+ //console.log('group');
+ wemo.setStatus(dev, capability, on);
}
});
};
@@ -366,6 +376,76 @@ module.exports = function(RED) {
};
RED.nodes.registerType('wemo in', wemoNGEvent);
+ var wemoNGLookup = 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'});
+
+ 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;
+ }
+
+ //console.log(dev.type);
+ if (dev.type === 'light' || dev.type === 'group') {
+ //console.log("light");
+ wemo.getLightStatus(dev)
+ .then(function(status){
+ // if (status.available) {
+ var caps = status.capabilities.split(',');
+ var vals = status.state.split(',');
+ for (var i=0; i',
'',
''].join('\n');
-
var postbodyfooter = ['',
''
].join('\n');
-
var getenddevs = {};
getenddevs.path = '/upnp/control/bridge1';
getenddevs.action = '"urn:Belkin:service:bridge:1#GetEndDevices"';
@@ -45,6 +44,58 @@ getcapabilities.body = [
postbodyfooter
].join('\n');
+var getDevStatus = {
+ method: 'POST',
+ path: '/upnp/control/bridge1',
+ action: '"urn:Belkin:service:bridge:1#GetDeviceStatus"',
+ body: [
+ postbodyheader,
+ '',
+ '%s',
+ '',
+ postbodyfooter
+ ].join('\n')
+};
+
+var getSocketState = {
+ method: 'POST',
+ path: '/upnp/control/basicevent1',
+ action: '"urn:Belkin:service:basicevent:1#GetBinaryState"',
+ body: [
+ postbodyheader,
+ '',
+ '',
+ postbodyfooter
+ ].join('\n')
+}
+
+
+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>%s</IsGroupAction><DeviceID available="YES">%s</DeviceID><CapabilityID>%s</CapabilityID><CapabilityValue>%s</CapabilityValue></DeviceStatus>',
+ '',
+ '',
+ postbodyfooter
+].join('\n');
+
+var capabilityMap = {
+ '10006': 'state',
+ '10008': 'dim',
+ '10300': 'color',
+ '30301': 'temperature'
+ };
+
+var reverseCapabilityMap = {
+ 'state': '10006',
+ 'dim': '10008',
+ 'color': '10300',
+ 'temperature': '30301'
+ };
var WeMoNG = function () {
this.devices = {};
@@ -52,6 +103,10 @@ var WeMoNG = function () {
this._interval;
events.EventEmitter.call(this);
+ this.capabilityMap = capabilityMap;
+
+ this.reverseCapabilityMap = reverseCapabilityMap;
+
}
util.inherits(WeMoNG, events.EventEmitter);
@@ -68,7 +123,7 @@ WeMoNG.prototype.start = function start() {
request.get(location.href, function(err, res, xml) {
if (!err) {
xml2js.parseString(xml, function(err, json) {
- if (!err) {
+ if (!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];
@@ -248,20 +303,114 @@ WeMoNG.prototype.toggleSocket = function toggleSocket(socket, 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');
+WeMoNG.prototype.getSocketStatus = function getSocketStatus(socket) {
+ var postoptions = {
+ host: socket.ip,
+ port: socket.port,
+ path: getSocketState.path,
+ method: getSocketState.method,
+ headers: {
+ 'SOAPACTION': getSocketState.action,
+ 'Content-Type': 'text/xml; charset="utf-8"',
+ 'Accept': ''
+ }
+ }
+ var def = Q.defer();
+
+ 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 status = result["s:Envelope"]["s:Body"][0]["u:GetBinaryStateResponse"][0]["BinaryState"][0];
+ status = parseInt(status);
+ def.resolve(status);
+ }
+ });
+ });
+ });
+
+ post_request.on('error', function (e) {
+ console.log(e);
+ console.log("%j", postoptions);
+ def.reject();
+ });
+
+ post_request.write(getSocketState.body);
+ post_request.end();
+
+ return def.promise;
+};
+
+WeMoNG.prototype.getLightStatus = function getLightStatus(light) {
+ var postoptions = {
+ host: light.ip,
+ port: light.port,
+ path: getDevStatus.path,
+ method: getDevStatus.method,
+ headers: {
+ 'SOAPACTION': getDevStatus.action,
+ 'Content-Type': 'text/xml; charset="utf-8"',
+ 'Accept': ''
+ }
+ };
+
+ var def = Q.defer();
+
+ 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 status = result["s:Envelope"]["s:Body"][0]["u:GetDeviceStatusResponse"][0].DeviceStatusList[0];
+ xml2js.parseString(status, function(err,result2){
+ if (!err) {
+ var available = result2['DeviceStatusList']['DeviceStatus'][0]['DeviceID'][0]['$'].available === 'YES';
+ var state = result2['DeviceStatusList']['DeviceStatus'][0]['CapabilityValue'][0];
+ var capabilities = result2['DeviceStatusList']['DeviceStatus'][0]['CapabilityID'][0];
+ var obj = {
+ available: available,
+ state: state,
+ capabilities: capabilities
+ };
+ def.resolve(obj);
+ } else {
+ console.log("err");
+ }
+ });
+ }
+ } else {
+ console.log("err");
+ }
+ });
+ });
+ });
+
+ post_request.on('error', function (e) {
+ console.log(e);
+ console.log("%j", postoptions);
+ def.reject();
+ });
+
+ post_request.write(util.format(getDevStatus.body, light.id));
+ post_request.end();
+
+ return def.promise;
+}
+
+WeMoNG.prototype.setStatus = function setStatus(light, capability, value) {
var postoptions = {
host: light.ip,
port: light.port,
@@ -269,7 +418,7 @@ WeMoNG.prototype.setStatus = function setStatus(light, capability, value) {
method: 'POST',
headers: {
'SOAPACTION': setdevstatus.action,
- 'Content-Type': 'text/xml; charset="utf-8"',
+ 'Content-Type': 'text/xml; charset="utf-8"',
'Accept': ''
}
};
@@ -293,7 +442,7 @@ WeMoNG.prototype.setStatus = function setStatus(light, capability, value) {
//console.log(util.format(setdevstatus.body, light.id, capability, value));
- post_request.write(util.format(setdevstatus.body, light.id, capability, value));
+ post_request.write(util.format(setdevstatus.body, light.type === 'light'?'NO':'YES',light.id, capability, value));
post_request.end();
}
@@ -312,6 +461,7 @@ WeMoNG.prototype.parseEvent = function parseEvent(evt) {
if (!err && res != null) {
msg.id = res['StateEvent']['DeviceID'][0]['_'];
msg.capability = res['StateEvent']['CapabilityId'][0];
+ msg.capabilityName = capabilityMap[msg.capability];
msg.value = res['StateEvent']['Value'][0];
def.resolve(msg);
}
@@ -364,4 +514,27 @@ WeMoNG.prototype.rgb2xy = function rgb2xy(red, green, blue) {
];
};
+//http://stackoverflow.com/questions/22894498/philips-hue-convert-xy-from-api-to-hex-or-rgb
+WeMoNG.prototype.xy2rgb = function xy2rgb(x,y,bri) {
+ z = 1.0 - x - y;
+ Y = bri / 255.0; // Brightness of lamp
+ X = (Y / y) * x;
+ Z = (Y / y) * z;
+ r = X * 1.612 - Y * 0.203 - Z * 0.302;
+ g = -X * 0.509 + Y * 1.412 + Z * 0.066;
+ b = X * 0.026 - Y * 0.072 + Z * 0.962;
+ r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
+ g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
+ b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
+ maxValue = Math.max(r,g,b);
+ r /= maxValue;
+ g /= maxValue;
+ b /= maxValue;
+ r = r * 255; if (r < 0) { r = 255 };
+ g = g * 255; if (g < 0) { g = 255 };
+ b = b * 255; if (b < 0) { b = 255 };
+
+ return [r,g,b];
+};
+
module.exports = WeMoNG;
diff --git a/hardware/wemo/package.json b/hardware/wemo/package.json
index f6632636..9ba06250 100644
--- a/hardware/wemo/package.json
+++ b/hardware/wemo/package.json
@@ -1,6 +1,6 @@
{
"name": "node-red-node-wemo",
- "version": "0.1.10",
+ "version": "0.1.11",
"description": "Input and Output nodes for Belkin WeMo devices",
"repository": "https://github.com/node-red/node-red-nodes/tree/master/hardware",
"main": "WeMoNG.js",
@@ -9,7 +9,8 @@
},
"keywords": [
"node-red",
- "wemo"
+ "wemo",
+ "belkin"
],
"author": {
"email": "hardillb@gmail.com",
@@ -18,7 +19,7 @@
},
"license": "APACHE-2.0",
"dependencies": {
- "node-ssdp": "~2.6.3",
+ "node-ssdp": "~2.9.1",
"request": "~2.74.0",
"xml2js": "~0.4.13",
"util": "~0.10.3",