mirror of
https://github.com/node-red/node-red-nodes.git
synced 2023-10-10 13:36:58 +02:00
46cc9bb906
* 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 * Adds proper url length detection * fix up the extra line added in the merge * Fix some jshint errors More jshint fixes fix jshint last jshint fix Fix last jshint error load of tiny listing fixes, undef, unused, etc fix slight wrinkle in new Wemo node Fix wemo package name for npm. Add contribution.md to node-red-nodes to make it more obvious. correct spelling in readme.md revert .jshintrc Added subtree and walker nodes. (#200) Thanks @mikakaraila - very useful. slight edits to snmp to pass jslint-ing Bump underlying serial port nam version correct BBB callback response to check exists To Fix #198 * Adds proper url length detection fix up the extra line added in the merge * Added some more error checking in discovery * Adds proper url length detection* fix up the extra line added in the merge * Adds proper url length detection * fix up the extra line added in the merge Add line output mode for Daemon node to address #202 Email rework (#195) * Rework of Node-RED email nodes tidy up email listing - no code changes add latest email node dips and update email node libs - bump version update package version for nodes tests add a few tests geohash, smooth, base64, msgpack correct path of source file Thanks @Ltrlg Fix typo in node-red-node-smooth (#205) node-red-node-physical-web: Added msg.advertising = true/false to enable/disable advertising (#204) * Added msg.advertising = true/false to enable/disable advertising * Fixed comparison with bool Don't use get_compass as it breaks get_orientation add envelope to email node options as part of node-red#875 Physical-web: Added status (found, updated and lost) as msg.topic (#206) Added msg.advertising = true/false to enable/disable advertising Added status (lost, updated, found) as msg.topic, if not already set Added checkbox to allow duplicates or not. * Re-added optional topic to avoid breaking compatibility * Cleaner topic slection add a v0.3 serial port to help with node5/6 let PI try to enable sensorTag access on install Add cc and bcc options to email node to close #209 updates to email node to better handle different mail types. Still not great - but does the basics… To close Better email node parsing (again) Fix indent fixed formating * Fix heading in README.md Fix node name in README.md
377 lines
12 KiB
JavaScript
377 lines
12 KiB
JavaScript
/**
|
|
* 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 = [
|
|
'<?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');
|
|
|
|
var getcapabilities = {};
|
|
getcapabilities.path = '/upnp/control/bridge1';
|
|
getcapabilities.action = '"urn:Belkin:service:bridge:1#GetCapabilityProfileIDList"';
|
|
getcapabilities.body = [
|
|
postbodyheader,
|
|
'<u:GetCapabilityProfileIDList xmlns:u="urn:Belkin:service:bridge:1">',
|
|
'<DevUDN>%s</DevUDN>',
|
|
'</u:GetCapabilityProfileIDList>',
|
|
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) {
|
|
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) {
|
|
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++) {
|
|
var light = {
|
|
"ip": ip,
|
|
"port": port,
|
|
"udn": device.UDN,
|
|
"name": devinfo[i].FriendlyName[0],
|
|
"id": devinfo[i].DeviceID[0],
|
|
"capabilities": devinfo[i].CapabilityIDs[0],
|
|
"state": devinfo[i].CurrentState[0],
|
|
"type": "light",
|
|
"device": device
|
|
};
|
|
var key = device.serialNumber + "-" + light.id;
|
|
if (!_wemo.devices[key]){
|
|
_wemo.devices[key] = light;
|
|
_wemo.emit('discovered', key);
|
|
} else {
|
|
_wemo.devices[key] = light;
|
|
}
|
|
}
|
|
var groupinfo = result2.DeviceLists.DeviceList[0].GroupInfos;
|
|
if (groupinfo) {
|
|
for(var i=0; i<groupinfo.length; i++) {
|
|
var group = {
|
|
"ip": ip,
|
|
"port": port,
|
|
"udn": device.UDN,
|
|
"name": groupinfo[i].GroupInfo[0].GroupName[0],
|
|
"id": groupinfo[i].GroupInfo[0].GroupID[0],
|
|
"capabilities": groupinfo[i].GroupInfo[0].GroupCapabilityIDs[0],
|
|
"state": groupinfo[i].GroupInfo[0].GroupCapabilityValues[0],
|
|
"type": "light group",
|
|
"lights": [],
|
|
"device": device
|
|
}
|
|
for(var j=0; j<groupinfo[i].GroupInfo[0].DeviceInfos[0].DeviceInfo.length; j++) {
|
|
group.lights.push(groupinfo[i].GroupInfo[0].DeviceInfos[0].DeviceInfo[j].DeviceID[0]);
|
|
}
|
|
}
|
|
var key = device.serialNumber + "-" + group.id;
|
|
if (!_wemo.devices[key]) {
|
|
_wemo.devices[key] = group;
|
|
_wemo.emit('discovered', key);
|
|
} else {
|
|
_wemo.devices[key] = group;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
post_request.write(util.format(getenddevs.body, udn));
|
|
post_request.end();
|
|
|
|
} else if (device.deviceType.indexOf('urn:Belkin:device') != -1) {
|
|
//socket
|
|
var socket = {
|
|
"ip": location.hostname,
|
|
"port": location.port,
|
|
"name": device.friendlyName,
|
|
"type": "socket",
|
|
"device": device
|
|
};
|
|
if (!_wemo.devices[device.serialNumber]) {
|
|
_wemo.devices[device.serialNumber] = socket;
|
|
_wemo.emit('discovered',device.serialNumber);
|
|
} else {
|
|
_wemo.devices[device.serialNumber] = socket;
|
|
}
|
|
} else {
|
|
//other stuff
|
|
//console.log( device.ip + ' -' + device.deviceType);
|
|
}
|
|
} else {
|
|
console.error("failed to parse respose from " + location.href);
|
|
console.error(err);
|
|
}
|
|
});
|
|
} else {
|
|
console.error("Failed to GET info from " + location.href);
|
|
console.error(err);
|
|
}
|
|
});
|
|
});
|
|
_wemo._client.search(urn);
|
|
setTimeout(function(){
|
|
//console.log("stopping");
|
|
_wemo._client._stop();
|
|
//console.log("%j", devices);
|
|
}, 10000);
|
|
}
|
|
|
|
WeMoNG.prototype.get = function get(deviceID) {
|
|
return this.devices[deviceID];
|
|
}
|
|
|
|
WeMoNG.prototype.toggleSocket = 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);
|
|
});
|
|
});
|
|
|
|
post_request.on('error', function (e) {
|
|
console.log(e);
|
|
console.log("%j", postoptions);
|
|
});
|
|
|
|
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();
|
|
}
|
|
|
|
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,
|
|
'<u:SetDeviceStatus xmlns:u="urn:Belkin:service:bridge:1">',
|
|
'<DeviceStatusList>',
|
|
'<?xml version="1.0" encoding="UTF-8"?><DeviceStatus><IsGroupAction>NO</IsGroupAction><DeviceID available="YES">%s</DeviceID><CapabilityID>%s</CapabilityID><CapabilityValue>%s</CapabilityValue></DeviceStatus>',
|
|
'</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();
|
|
}
|
|
|
|
//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; |