mirror of
https://github.com/node-red/node-red-nodes.git
synced 2023-10-10 13:36:58 +02:00
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
This commit is contained in:
parent
a8ec48553f
commit
37ba29484a
@ -1,90 +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.
|
||||
-->
|
||||
|
||||
<script type="text/x-red" data-template-name="wemo out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-ipaddr"><i class="fa fa-globe"></i> IP Address</label>
|
||||
<input type="text" id="node-input-ipaddr" placeholder="192.168.1.100">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Expects a msg.payload with either 1/0, on/off, or true/false</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="wemo out">
|
||||
<p>Wemo output node. Expects a <code>msg.payload</code> with either 1/0, on/off or true/false.</p>
|
||||
<p>It doesn't yet do any ip address discovery of the wemo devices.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('wemo out',{
|
||||
category: 'advanced-output',
|
||||
color:"GoldenRod",
|
||||
defaults: {
|
||||
ipaddr: {value:"",required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:1,
|
||||
outputs:0,
|
||||
icon: "light.png",
|
||||
align: "right",
|
||||
label: function() {
|
||||
return this.name||"wemo";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="wemo in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-ipaddr"><i class="fa fa-globe"></i> IP Address</label>
|
||||
<input type="text" id="node-input-ipaddr" placeholder="192.168.1.100">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
<div class="form-tips">Creates a msg.payload with either 1, 0, nc or na.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="wemo in">
|
||||
<p>Wemo input node. Creates a <code>msg.payload</code> with either 1, 0, nc (no change), or na (not available).</p>
|
||||
<p>It doesn't yet do any ip address discovery of the wemo devices.</p>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('wemo in',{
|
||||
category: 'advanced-input',
|
||||
color:"GoldenRod",
|
||||
defaults: {
|
||||
ipaddr: {value:"",required:true},
|
||||
name: {value:""}
|
||||
},
|
||||
inputs:0,
|
||||
outputs:1,
|
||||
icon: "light.png",
|
||||
label: function() {
|
||||
return this.name||"wemo";
|
||||
},
|
||||
labelStyle: function() {
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
@ -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);
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
node-red-node-wemo
|
||||
==================
|
||||
# node-red-contrib-nodes-wemo
|
||||
|
||||
A pair of <a href="http://nodered.org" target="_new">Node-RED</a> nodes to control a <a href="http://www.belkin.com/uk/Products/home-automation/c/wemo-home-automation/" target="_new">Belkin Wemo</a> 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": "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n<e:property>\n<BinaryState>1</BinaryState>\n</e:property>\n</e:propertyset>\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": "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n<e:property>\n<StatusChange><?xml version="1.0" encoding="utf-8"?><StateEvent><DeviceID\navailable="YES">94103EA2B27803ED</DeviceID><CapabilityId>10006</CapabilityId><Value>1</Value></StateEvent>\n</StatusChange>\n</e:property>\n</e:propertyset>\n\n\r",
|
||||
"id": "94103EA2B27803ED",
|
||||
"capability": "10006",
|
||||
"value": "1",
|
||||
"sid": "uuid:e2e5739e-1dd1-11b2-943d-c238ce2bad17",
|
||||
"type": "light",
|
||||
"name": "Bedroom"
|
||||
}
|
||||
```
|
||||
|
||||
Insight
|
||||
|
||||
```
|
||||
{
|
||||
"raw": "<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">\n<e:property>\n<BinaryState>8|1454271649|301|834|56717|1209600|8|1010|638602|12104165</BinaryState>\n</e:property>\n</e:propertyset>\n\n\r",
|
||||
"state": "8",
|
||||
"power": 1.01,
|
||||
"sid": "uuid:ea808ecc-1dd1-11b2-9579-8e5c117d479e",
|
||||
"type": "socket",
|
||||
"name": "WeMo Insight",
|
||||
"id": "221450K1200F5C"
|
||||
}
|
||||
```
|
||||
|
193
hardware/wemo/WeMoNG.html
Normal file
193
hardware/wemo/WeMoNG.html
Normal file
@ -0,0 +1,193 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<script type="text/x-red" data-template-name="wemo in">
|
||||
<div class="form-row">
|
||||
<label for="node-input-topic"><i class="fa fa-tasks"></i> Topic</label>
|
||||
<input type="text" id="node-input-topic" placeholder="Topic">
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-input-device"><i class="fa fa-tasks"></i> Device</label>
|
||||
<input type="text" id="node-input-device" placeholder="Device">
|
||||
</div>
|
||||
<br/>
|
||||
<!-- By convention, most nodes have a 'name' property. The following div -->
|
||||
<!-- provides the necessary field. Should always be the last option -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="wemo in">
|
||||
<p>A node that listens to event notifications from Belkin WeMo devices.</p>
|
||||
<p>Supported devices include:</p>
|
||||
<ul>
|
||||
<li>Sockets</li>
|
||||
<li>Insight Sockets</li>
|
||||
<li>Light Bulbs</li>
|
||||
<li>Light Groups</li>
|
||||
<li>Motion Detector</li>
|
||||
</ul>
|
||||
<p>Sockets will generate msg.payload with values of 0/1 for off or on, all other
|
||||
types will return an object like this:</p>
|
||||
<pre>
|
||||
{
|
||||
name: 'Bedroom light',
|
||||
type: 'light',
|
||||
id: '94103EA2B27803ED',
|
||||
capability: '1006',
|
||||
value: 1
|
||||
}
|
||||
</pre>
|
||||
<p>Current known capabilities</p>
|
||||
<ul>
|
||||
<li>10006 - on/off</li>
|
||||
<li>10008 - brightness</li>
|
||||
</ul>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('wemo in',{
|
||||
category: 'input', // the palette category
|
||||
defaults: { // defines the editable properties of the node
|
||||
name: {value:""}, // along with default values.
|
||||
topic: {value:"wemo", required: true},
|
||||
device: {value:"", type: "wemo-dev"}
|
||||
},
|
||||
color: "LawnGreen",
|
||||
inputs:0, // set the number of inputs - only 0 or 1
|
||||
outputs:1, // set the number of outputs - 0 to n
|
||||
// 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";
|
||||
},
|
||||
labelStyle: function() { // sets the class to apply to the label
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="wemo out">
|
||||
<div class="form-row">
|
||||
<label for="node-input-device"><i class="fa fa-tasks"></i> Device</label>
|
||||
<input type="text" id="node-input-device" placeholder="Device">
|
||||
</div>
|
||||
<br/>
|
||||
<!-- By convention, most nodes have a 'name' property. The following div -->
|
||||
<!-- provides the necessary field. Should always be the last option -->
|
||||
<div class="form-row">
|
||||
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||
<input type="text" id="node-input-name" placeholder="Name">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-help-name="wemo out">
|
||||
<p>A node to control Belkin WeMo Devices</p>
|
||||
<p>Supported devices include:</p>
|
||||
<ul>
|
||||
<li>Sockets</li>
|
||||
<li>Insight Sockets</li>
|
||||
<li>Light Bulbs</li>
|
||||
</ul>
|
||||
<p>Sets the device on or off based on the passed in msg.payload values of:</p>
|
||||
<ul>
|
||||
<li>on/off</li>
|
||||
<li>1/0</li>
|
||||
<li>true/false</li>
|
||||
</ul>
|
||||
<p>for light bulbs it also accepts an object with the following structure</p>
|
||||
<pre>
|
||||
{
|
||||
state: 1,
|
||||
dim: 255,
|
||||
color: '255,255,255',
|
||||
temperature: 25000
|
||||
}
|
||||
</pre>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('wemo out',{
|
||||
category: 'output', // the palette category
|
||||
defaults: { // defines the editable properties of the node
|
||||
name: {value:""}, // along with default values.
|
||||
device: {type: "wemo-dev", required: true}
|
||||
},
|
||||
color: "LawnGreen",
|
||||
inputs: 1, // set the number of inputs - only 0 or 1
|
||||
// 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";
|
||||
},
|
||||
labelStyle: function() { // sets the class to apply to the label
|
||||
return this.name?"node_label_italic":"";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type="text/x-red" data-template-name="wemo-dev">
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-device"><i class="fa fa-tasks"></i> Device</label>
|
||||
<select size="4" id="node-config-input-device">
|
||||
</select>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="form-row">
|
||||
<label for="node-config-input-type"><i class="fa fa-tasks"></i> Type</label>
|
||||
<input type="text" editable="false" id="node-config-input-type" placeholder="Type"/>
|
||||
<input type="hidden" editable="false" id="node-config-input-name"/>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
RED.nodes.registerType('wemo-dev',{
|
||||
category: 'config', // the palette category
|
||||
defaults: { // defines the editable properties of the node
|
||||
device: {value:"", required: true}, // along with default values.
|
||||
name: {value: ""}
|
||||
},
|
||||
label: function() { // sets the default label contents
|
||||
return this.name;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
var devices;
|
||||
$.getJSON('wemoNG/devices', function(data){
|
||||
devices = data;
|
||||
var devs = Object.keys(data);
|
||||
if (devs.length !== 0) {
|
||||
for (var d in devs) {
|
||||
if (dev.hasOwnProperty(d)) {
|
||||
$('<option/>',{
|
||||
'value': devs[d],
|
||||
'text': data[devs[d]].name
|
||||
}).appendTo('#node-config-input-device');
|
||||
console.log(data[devs[d]].name);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$('#node-config-input-device').change(function(){
|
||||
var id = $( "#node-config-input-device option:selected" ).first().val();
|
||||
if (devices) {
|
||||
$('#node-config-input-type').val(devices[id].type);
|
||||
$('#node-config-input-name').val(devices[id].name);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
386
hardware/wemo/WeMoNG.js
Normal file
386
hardware/wemo/WeMoNG.js
Normal file
@ -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("");
|
||||
});
|
||||
|
||||
}
|
BIN
hardware/wemo/icons/belkin.png
Normal file
BIN
hardware/wemo/icons/belkin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 243 B |
367
hardware/wemo/lib/wemo.js
Normal file
367
hardware/wemo/lib/wemo.js
Normal file
@ -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 = [
|
||||
'<?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) {
|
||||
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<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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
_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;
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user