2
0

feat(gateway): add in/out gateway nodes

This commit is contained in:
Pierre CLEMENT
2017-12-31 23:10:06 +01:00
parent 5c27c9ff6e
commit 8608f4eed4
6 changed files with 334 additions and 21 deletions

View File

@@ -5,8 +5,11 @@ This module contains the following nodes to provide easy integration of the Xiao
The following devices are currently supported:
* Temperature/humidity sensor
* Aqara temperature/humidity/pressure sensor
* Magnet switch
* Aqara window/door sensor
* Button switch
* Aqara smart wireless switch
* Motion sensor
* Power plug (zigbee)
* Power plug (wifi)
@@ -29,7 +32,7 @@ npm install node-red-contrib-xiaomi-devices
From the Xiaomi configurator screen add your different devices by selecting the type of device and a readable description. The readable discription is used on the different edit screen of the nodes to easily select the device you associate to the node.
Note that the Wifi power plug is not configured through the configurator as it is not connected to the gateway.
Note that the Wifi power plug is not configured through the configurator as it is not connected to the gateway.
The Xiaomi configurator screen with ease of use to configure your different devices.
@@ -37,23 +40,6 @@ The Xiaomi configurator screen with ease of use to configure your different devi
Tip: use the configurator from the side-panel (hamburger menu, configuration nodes) to manage your devices. Node-red doesn't update underlying edit screens if the configuration panel is opened / closed from the edit node screen. (If you do, you need to first close the edit node screen and reopen it by double-clicking the node you want to edit the properties for.)
To receive/send json UDP messages from/to the gateway you need to enable the local LAN mode on the gateway. To receive the json UDP messages in node-red you need to add an udp-node with the correct configuration:
```
Listen for: multicast messages
Group: 224.0.0.50
Local ip: <empty>
On port: 9898 ipv4
Output: String
```
If you want to sent messages to the gateway you need to add an UDP sender, here an example configuration:
```
Send a: UDP message to port: 9898
Address: <ip_of_your_gateway> ipv4
```
This configuration worked for me however I have seen people using different configuration to make UDP work.
Here an example of how to use the different nodes.
@@ -63,4 +49,3 @@ Here an example of how to use the different nodes.
## Roadmap
* ~~Support for other devices like the smart-socket WiFi~~ Done!
* Import (new) devices directly from the gateway

View File

@@ -3,6 +3,7 @@
category: 'config',
defaults: {
name: {value: ""},
ip: {value: ""},
deviceList: {value:[{ sid:"", desc:"", model:"plug"}]},
key: {value: ""}
},
@@ -59,6 +60,13 @@
oneditsave: function() {
var devices = $("#node-config-input-devices").editableList('items');
var node = this;
RED.nodes.eachNode(function(tmpNode) {
if(tmpNode.type.indexOf("xiaomi-gateway") === 0 && tmpNode.gateway == node.id) {
tmpNode.ip = $("#node-config-input-ip").val();
tmpNode.changed = true;
console.log(tmpNode);
}
});
var devicesArray = [];
devices.each(function(i) {
var deviceElement = $(this);
@@ -81,7 +89,11 @@
<label for="node-config-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-config-input-name" placeholder="Name">
</div>
<div class="form-row">
<div class="form-row">
<label for="node-config-input-ip"><i class="icon-tag"></i> IP address (v4 or v6)</label>
<input type="text" id="node-config-input-ip" placeholder="IP">
</div>
<div class="form-row">
<label for="node-config-input-key"><i class="fa fa-ticket"></i> Key/Password</label>
<input type="text" id="node-config-input-key" placeholder="Key">
</div>

View File

@@ -5,6 +5,7 @@ module.exports = function(RED) {
this.name = n.name;
this.deviceList = n.deviceList || [];
this.key = n.key;
this.ip = n.ip;
var node = this;
}

View File

@@ -0,0 +1,139 @@
<!--
Copyright JS Foundation and other contributors, http://js.foundation
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.
-->
<!-- The Input Node -->
<script type="text/x-red" data-template-name="xiaomi-gateway in">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-ip"><i class="icon-tag"></i> IP</label>
<input type="text" id="node-input-ip" placeholder="IP" readonly>
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-gateway in">
<p>A Xiaomi Gateway input node, that produces a <code>msg.payload</code> containing a
string with the gateway message content.
</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('xiaomi-gateway in',{
category: 'xiaomi',
color: '#087F8A',
defaults: {
name: {value:""},
gateway: {value:"", type:"xiaomi-configurator"},
ip: {value:""}
},
inputs:0,
outputs:1,
paletteLabel: "gateway in",
icon: "bridge-dash.png",
label: function() {
return this.name||"xiaomi-gateway";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
function changeGateway() {
var configNodeID = $('#node-input-gateway').val();
if (configNodeID) {
var configNode = RED.nodes.node(configNodeID);
if(configNode) {
if(!this.name) {
$("#node-input-name").val(configNode.name);
}
$('#node-input-ip').val(configNode.ip);
}
}
}
$("#node-input-gateway").change(function () {
changeGateway();
});
}
});
</script>
<!-- The Output Node -->
<script type="text/x-red" data-template-name="xiaomi-gateway out">
<div class="form-row">
<label for="node-input-gateway"><i class="icon-tag"></i> Gateway</label>
<input type="text" id="node-input-gateway" placeholder="xiaomi gateway">
</div>
<div class="form-row">
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-ip"><i class="icon-tag"></i> IP</label>
<input type="text" id="node-input-ip" placeholder="IP" readonly>
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-gateway out">
<p>This node sends <code>msg.payload</code> to the configured Xiaomi Gateway.</p>
</script>
<script type="text/javascript">
RED.nodes.registerType('xiaomi-gateway out',{
category: 'xiaomi',
color: '#087F8A',
defaults: {
name: {value:""},
gateway: {value:"", type:"xiaomi-configurator"},
ip: {value:""}
},
inputs:1,
outputs:0,
paletteLabel: "gateway out",
icon: "bridge-dash.png",
align: "right",
label: function() {
return this.name||"xiaomi-gateway";
},
labelStyle: function() {
return this.name?"node_label_italic":"";
},
oneditprepare: function() {
function changeGateway() {
var configNodeID = $('#node-input-gateway').val();
if (configNodeID) {
var configNode = RED.nodes.node(configNodeID);
if(configNode) {
if(!this.name) {
$("#node-input-name").val(configNode.name);
}
$('#node-input-ip').val(configNode.ip);
}
}
}
$("#node-input-gateway").change(function () {
changeGateway();
});
}
});
</script>

View File

@@ -0,0 +1,175 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* 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 dgram = require('dgram');
var udpInputPortsInUse = {};
// The Input Node
function GatewayIn(n) {
RED.nodes.createNode(this,n);
this.group = "224.0.0.50";
this.port = 9898;
this.iface = null;
this.addr = n.ip;
this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4";
var node = this;
var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var server;
if (!udpInputPortsInUse.hasOwnProperty(this.port)) {
server = dgram.createSocket(opts); // default to udp4
udpInputPortsInUse[this.port] = server;
}
else {
node.warn(RED._("udp.errors.alreadyused",node.port));
server = udpInputPortsInUse[this.port]; // re-use existing
}
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
server.on("error", function (err) {
if ((err.code == "EACCES") && (node.port < 1024)) {
node.error(RED._("udp.errors.access-error"));
} else {
node.error(RED._("udp.errors.error",{error:err.code}));
}
server.close();
});
server.on('message', function (message, remote) {
var msg;
if(remote.address == node.addr) {
msg = { payload: JSON.parse(message.toString('utf8')) };
node.send(msg);
}
});
server.on('listening', function () {
var address = server.address();
node.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
server.setBroadcast(true);
try {
server.setMulticastTTL(128);
server.addMembership(node.group,node.iface);
node.log(RED._("udp.status.mc-group",{group:node.group}));
} catch (e) {
if (e.errno == "EINVAL") {
node.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") {
node.error(RED._("udp.errors.interface"));
} else {
node.error(RED._("udp.errors.error",{error:e.errno}));
}
}
});
node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.port)) {
delete udpInputPortsInUse[node.port];
}
try {
server.close();
node.log(RED._("udp.status.listener-stopped"));
} catch (err) {
//node.error(err);
}
});
try { server.bind(node.port,node.iface); }
catch(e) { } // Don't worry if already bound
}
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) {
res.json(Object.keys(udpInputPortsInUse));
});
RED.nodes.registerType("xiaomi-gateway in",GatewayIn);
// The Output Node
function GatewayOut(n) {
RED.nodes.createNode(this,n);
this.port = 9898;
this.outport = 9898;
this.iface = null;
this.addr = n.ip;
this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4";
this.multicast = false;
var node = this;
var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var sock;
if (udpInputPortsInUse[this.outport]) {
sock = udpInputPortsInUse[this.outport];
}
else {
sock = dgram.createSocket(opts); // default to udp4
sock.on("error", function(err) {
// Any async error will also get reported in the sock.send call.
// This handler is needed to ensure the error marked as handled to
// prevent it going to the global error handler and shutting node-red
// down.
});
udpInputPortsInUse[this.outport] = sock;
}
if (!udpInputPortsInUse[this.outport]) {
sock.bind(node.outport);
node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port}));
} else {
node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
}
node.on("input", function(msg) {
if (msg.hasOwnProperty("payload")) {
var add = node.addr || msg.ip || "";
var por = node.port || msg.port || 0;
if (add === "") {
node.warn(RED._("udp.errors.ip-notset"));
} else if (por === 0) {
node.warn(RED._("udp.errors.port-notset"));
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
node.warn(RED._("udp.errors.port-invalid"));
} else {
var message = Buffer.from(JSON.stringify(msg.payload));
sock.send(message, 0, message.length, por, add, function(err, bytes) {
if (err) {
node.error("udp : "+err,msg);
}
message = null;
});
}
}
});
node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.outport)) {
delete udpInputPortsInUse[node.outport];
}
try {
sock.close();
node.log(RED._("udp.status.output-stopped"));
} catch (err) {
//node.error(err);
}
});
}
RED.nodes.registerType("xiaomi-gateway out", GatewayOut);
}

View File

@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-xiaomi-devices",
"version": "1.0.14",
"version": "1.1.0",
"description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway.",
"repository": {
"type": "git",
@@ -16,6 +16,7 @@
"xiaomi-switch": "node-red-contrib-xiaomi-switch/xiaomi-switch.js",
"xiaomi-socket": "node-red-contrib-xiaomi-socket/xiaomi-socket.js",
"xiaomi-socket-wifi": "node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js",
"xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js",
"xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js"
}
},