2
0

refactor: code clean up

This commit is contained in:
Pierre CLEMENT
2018-01-03 12:12:45 +01:00
parent a02969a0dd
commit a33d616516
24 changed files with 371 additions and 1005 deletions

View File

@@ -76,8 +76,14 @@ If you change here something, you lose your password!
- [ ] Integrate Yeelight
- [ ] Handle Xiaomi Cube
- [ ] Add device SID in output
- [ ] Code cleanup
- [ ] Add filter on "all" node
- [ ] Set action status when no token available
- [ ] Add gateway status
- [ ] Update icons
- [ ] Refactor socket and set on/off actions
- [X] Add device SID in output
- [X] Remove different output styles
- [X] Code cleanup
## Sources

View File

@@ -1,154 +1,106 @@
module.exports = function(RED) {
"use strict";
var miDevicesUtils = require('../utils');
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
/*********************************************
Read data from Gateway
*********************************************/
function XiaomiActionRead(config) {
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function(msg) {
this.on('input', (msg) => {
if(msg.sid) {
msg.payload = {
cmd: "read",
sid: msg.sid
};
node.send(msg);
msg.payload = { cmd: "read", sid: msg.sid };
this.send(msg);
}
});
}
RED.nodes.registerType("xiaomi-actions read", XiaomiActionRead);
/*********************************************
Get registred ids of devices on gateway
*********************************************/
function XiaomiActionGetIdList(config) {
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function(msg) {
msg.payload = {
cmd: "get_id_list"
};
this.on('input', (msg) => {
msg.payload = { cmd: "get_id_list" };
node.send(msg);
});
}
RED.nodes.registerType("xiaomi-actions get_id_list", XiaomiActionGetIdList);
/*********************************************
Virtual single click on a button
*********************************************/
function XiaomiActionSingleClick(config) {
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function(msg) {
if(msg.gateway && msg.sid && msg.gateway.key && msg.gateway.lastToken) {
msg.payload = {
cmd: "write",
data: {
status: "click",
sid: msg.sid,
key: miDevicesUtils.getGatewayKey(msg.gateway.key, msg.gateway.lastToken)
}
};
node.send(msg);
}
this.on('input', (msg) => {
this.gateway = msg.gateway;
miDevicesUtils.sendWritePayloadToGateway(this, msg, {status: "click", sid: msg.sid});
});
}
RED.nodes.registerType("xiaomi-actions click", XiaomiActionSingleClick);
/*********************************************
Virtual Double click on a button
*********************************************/
function XiaomiActionDoubleClick(config) {
RED.nodes.createNode(this, config);
var node = this;
node.on('input', function(msg) {
if(msg.gateway && msg.sid && msg.gateway.key && msg.gateway.lastToken) {
msg.payload = {
cmd: "write",
data: {
status: "double_click",
sid: msg.sid,
key: miDevicesUtils.getGatewayKey(msg.gateway.key, msg.gateway.lastToken)
}
};
node.send(msg);
}
this.on('input', (msg) => {
miDevicesUtils.sendWritePayloadToGateway(this, msg, {status: "double_click", sid: msg.sid});
});
}
RED.nodes.registerType("xiaomi-actions double_click", XiaomiActionDoubleClick);
/*********************************************
Set the gateway light
*********************************************/
function XiaomiActionGatewayLight(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.color = RED.nodes.getNode(config.color);
this.brightness = RED.nodes.getNode(config.brightness);
var node = this;
node.on('input', function(msg) {
if(node.gateway && node.gateway.sid && node.gateway.key && node.gateway.lastToken) {
var color = msg.color || node.color;
var brightness = msg.brightness || node.brightness;
var rgb = miDevicesUtils.computeColorValue(brightness, color.red, color.green, color.blue);
msg.payload = {
cmd: "write",
data: {
rgb: rgb,
sid: node.gateway.sid,
key: miDevicesUtils.getGatewayKey(node.gateway.key, node.gateway.lastToken)
}
};
node.send(msg);
}
this.on('input', (msg) => {
let color = msg.color || this.color;
let brightness = msg.brightness || this.brightness;
let rgb = miDevicesUtils.computeColorValue(brightness, color.red, color.green, color.blue);
miDevicesUtils.sendWritePayloadToGateway(this, msg, {rgb: rgb});
});
}
RED.nodes.registerType("xiaomi-actions gateway_light", XiaomiActionGatewayLight);
/*********************************************
Play a sound on the gateway
*********************************************/
function XiaomiActionGatewaySound(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.mid = config.mid;
this.volume = config.volume;
var node = this;
node.on('input', function(msg) {
if(node.gateway && node.gateway.sid && node.gateway.key && node.gateway.lastToken) {
msg.payload = {
cmd: "write",
data: {
mid: parseInt(msg.mid || node.mid),
volume: parseInt(msg.volume || node.volume),
sid: node.gateway.sid,
key: miDevicesUtils.getGatewayKey(node.gateway.key, node.gateway.lastToken)
}
};
node.send(msg);
}
this.on('input', (msg) => {
miDevicesUtils.sendWritePayloadToGateway(this, msg, {
mid: parseInt(msg.mid || this.mid),
volume: parseInt(msg.volume || this.volume)
});
});
}
RED.nodes.registerType("xiaomi-actions gateway_sound", XiaomiActionGatewaySound);
/*********************************************
Stop playing a sound on the gateway
*********************************************/
function XiaomiActionGatewayStopSound(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
var node = this;
node.on('input', function(msg) {
if(node.gateway && node.gateway.sid && node.gateway.key && node.gateway.lastToken) {
msg.payload = {
cmd: "write",
data: {
mid: 1000,
sid: node.gateway.sid,
key: miDevicesUtils.getGatewayKey(node.gateway.key, node.gateway.lastToken)
}
};
node.send(msg);
}
this.on('input', function(msg) {
miDevicesUtils.sendWritePayloadToGateway(this, msg, {
mid: 1000
});
});
}
RED.nodes.registerType("xiaomi-actions gateway_stop_sound", XiaomiActionGatewayStopSound);

View File

@@ -40,4 +40,13 @@
</dl>
</li>
</ol>
<h4>Details</h4>
<p>Sample payload:</p>
<p><pre>[
{sid: "128d0901db1fa8", desc: "Door sensor" model: "magnet"},
{sid: "151d0401ab2491", desc: "Heat sensor", model: "sensor_ht"},
{sid: "658d030171427c", desc: "Button", model: "switch"}
]</pre>
</p>
</script>

View File

@@ -1,18 +1,12 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var miDevicesUtils = require('../utils');
module.exports = (RED) => {
function XiaomiAllNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
var node = this;
if (this.gateway) {
node.on('input', function(msg) {
msg.payload = node.gateway.deviceList;
node.send(msg);
this.on('input', (msg) => {
msg.payload = this.gateway.deviceList;
this.send(msg);
});
}
}

View File

@@ -3,7 +3,7 @@
category: 'config',
defaults: {
name: {value: ""},
ip: {value: ""},
ip: {value: "", required: true},
sid: {value: ""},
deviceList: {value:[{ sid:"", desc:"", model:"plug"}]},
key: {value: ""}

View File

@@ -1,5 +1,4 @@
module.exports = function(RED) {
module.exports = (RED) => {
function XiaomiConfiguratorNode(n) {
RED.nodes.createNode(this, n);
this.name = n.name;
@@ -12,5 +11,4 @@ module.exports = function(RED) {
}
RED.nodes.registerType("xiaomi-configurator", XiaomiConfiguratorNode);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -11,7 +11,7 @@
outputs: 1,
outputLabels: ["Gateway"],
paletteLabel: "gateway",
icon: "gateway-icon.png",
icon: "mijia.png",
label: function () {
return this.name || "xiaomi-gateway";
}

View File

@@ -1,42 +1,39 @@
module.exports = function(RED) {
"use strict";
var dgram = require('dgram');
var miDevicesUtils = require('../utils');
const dgram = require('dgram'); // Given by udp node
const miDevicesUtils = require('../src/utils');
// UDP node copy/paste...
module.exports = (RED) => {
var udpInputPortsInUse = {};
function XiaomiGatewayNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
var node = this;
if (this.gateway) {
node.on('input', function(msg) {
this.on('input', (msg) => {
// var payload = JSON.parse(msg);
var payload = msg.payload;
node.log("Received message from: " + payload.model + " sid: " + payload.sid + " payload: " + payload.data);
//this.log("Received message from: " + payload.model + " sid: " + payload.sid + " payload: " + payload.data);
// Input from gateway
if(payload.sid) {
if (payload.sid == node.gateway.sid && ["gateway"].indexOf(payload.model) >= 0) {
if (payload.sid == this.gateway.sid) {
if(payload.data.rgb) {
var decomposed = miDevicesUtils.computeColor(payload.data.rgb);
payload.data.brightness = decomposed.brightness;
payload.data.color = decomposed.color;
}
node.send([msg]);
this.send([msg]);
}
}
// Prepare for request
else {
msg.gateway = node.gateway;
msg.sid = node.gateway.sid;
node.send(msg);
msg.gateway = this.gateway;
msg.sid = this.gateway.sid;
this.send(msg);
}
});
node.on("close", function() {
});
}
}
RED.nodes.registerType("xiaomi-gateway", XiaomiGatewayNode);
@@ -50,10 +47,9 @@ module.exports = function(RED) {
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 opts = {type:this.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
var server;
if (!udpInputPortsInUse.hasOwnProperty(this.port)) {
@@ -61,24 +57,24 @@ module.exports = function(RED) {
udpInputPortsInUse[this.port] = server;
}
else {
node.warn(RED._("udp.errors.alreadyused",node.port));
this.warn(RED._("udp.errors.alreadyused",this.port));
server = udpInputPortsInUse[this.port]; // re-use existing
}
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
server.on("error", function (err) {
if ((err.code == "EACCES") && (node.port < 1024)) {
node.error(RED._("udp.errors.access-error"));
server.on("error", (err) => {
if ((err.code == "EACCES") && (this.port < 1024)) {
this.error(RED._("udp.errors.access-error"));
} else {
node.error(RED._("udp.errors.error",{error:err.code}));
this.error(RED._("udp.errors.error",{error:err.code}));
}
server.close();
});
server.on('message', function (message, remote) {
server.on('message', (message, remote) => {
var msg;
if(remote.address == node.addr) {
if(remote.address == this.addr) {
var msg = message.toString('utf8');
var jsonMsg = JSON.parse(msg);
if(jsonMsg.data) {
@@ -88,51 +84,51 @@ module.exports = function(RED) {
}
}
msg = { payload: jsonMsg };
if(jsonMsg.token && node.gateway && jsonMsg.data.ip && jsonMsg.data.ip === node.gateway.ip) {
node.gateway.lastToken = jsonMsg.token;
if(!node.gateway.sid) {
node.gateway.sid = jsonMsg.sid;
if(jsonMsg.token && this.gateway && jsonMsg.data.ip && jsonMsg.data.ip === this.gateway.ip) {
this.gateway.lastToken = jsonMsg.token;
if(!this.gateway.sid) {
this.gateway.sid = jsonMsg.sid;
}
}
node.send(msg);
this.send(msg);
}
});
server.on('listening', function () {
server.on('listening', () => {
var address = server.address();
node.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
this.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}));
server.addMembership(this.group,this.iface);
this.log(RED._("udp.status.mc-group",{group:this.group}));
} catch (e) {
if (e.errno == "EINVAL") {
node.error(RED._("udp.errors.bad-mcaddress"));
this.error(RED._("udp.errors.bad-mcaddress"));
} else if (e.errno == "ENODEV") {
node.error(RED._("udp.errors.interface"));
this.error(RED._("udp.errors.interface"));
} else {
node.error(RED._("udp.errors.error",{error:e.errno}));
this.error(RED._("udp.errors.error",{error:e.errno}));
}
}
});
node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.port)) {
delete udpInputPortsInUse[node.port];
this.on("close", () => {
if (udpInputPortsInUse.hasOwnProperty(this.port)) {
delete udpInputPortsInUse[this.port];
}
try {
server.close();
node.log(RED._("udp.status.listener-stopped"));
this.log(RED._("udp.status.listener-stopped"));
} catch (err) {
//node.error(err);
//this.error(err);
}
});
try { server.bind(node.port,node.iface); }
try { server.bind(this.port, this.iface); }
catch(e) { } // Don't worry if already bound
}
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) {
RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), (req,res) => {
res.json(Object.keys(udpInputPortsInUse));
});
RED.nodes.registerType("xiaomi-gateway in",GatewayIn);
@@ -147,10 +143,9 @@ module.exports = function(RED) {
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 opts = {type:this.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
var sock;
if (udpInputPortsInUse[this.outport]) {
@@ -158,7 +153,7 @@ module.exports = function(RED) {
}
else {
sock = dgram.createSocket(opts); // default to udp4
sock.on("error", function(err) {
sock.on("error", (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
@@ -168,27 +163,27 @@ module.exports = function(RED) {
}
if (!udpInputPortsInUse[this.outport]) {
sock.bind(node.outport);
node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port}));
sock.bind(this.outport);
this.log(RED._("udp.status.ready",{outport:this.outport,host:this.addr,port:this.port}));
} else {
node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
this.log(RED._("udp.status.ready-nolocal",{host:this.addr,port:this.port}));
}
node.on("input", function(msg) {
this.on("input", (msg) => {
if (msg.hasOwnProperty("payload")) {
var add = node.addr || msg.ip || "";
var por = node.port || msg.port || 0;
var add = this.addr || msg.ip || "";
var por = this.port || msg.port || 0;
if (add === "") {
node.warn(RED._("udp.errors.ip-notset"));
this.warn(RED._("udp.errors.ip-notset"));
} else if (por === 0) {
node.warn(RED._("udp.errors.port-notset"));
this.warn(RED._("udp.errors.port-notset"));
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
node.warn(RED._("udp.errors.port-invalid"));
this.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) {
sock.send(message, 0, message.length, por, add, (err, bytes) => {
if (err) {
node.error("udp : "+err,msg);
this.error("udp : "+err,msg);
}
message = null;
});
@@ -196,15 +191,15 @@ module.exports = function(RED) {
}
});
node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.outport)) {
delete udpInputPortsInUse[node.outport];
this.on("close", () => {
if (udpInputPortsInUse.hasOwnProperty(this.outport)) {
delete udpInputPortsInUse[this.outport];
}
try {
sock.close();
node.log(RED._("udp.status.output-stopped"));
this.log(RED._("udp.status.output-stopped"));
} catch (err) {
//node.error(err);
//this.error(err);
}
});
}

View File

@@ -5,16 +5,10 @@
defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""},
sid: {value: "", required: true},
temperature: {value: "{{temperature}}"},
humidity: {value: "{{humidity}}"},
pressure: {value: "{{pressure}}"},
divide: {value: true},
output: {value: "0"}
sid: {value: "", required: true}
},
inputs: 1,
outputs: 3,
outputLabels: ["Temperature","Humidity","Pressure"],
outputs: 1,
paletteLabel: "sensor HT",
icon: "thermometer-icon.png",
label: function () {
@@ -55,16 +49,7 @@
changeGateway("sensor_ht");
});
$("#node-input-output").change(function () {
if ($(this).val() == "2") {
$(".node-input-msg").show();
} else {
$(".node-input-msg").hide();
}
});
$(".node-input-msg").hide();
$("#node-input-output").val(node.output);
},
oneditsave: function() {
var node = this;
@@ -86,31 +71,6 @@
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
<div class="form-row">
<label for="node-input-output"><i class="icon-tag"></i> Output</label>
<select id="node-input-output" style="width:70%;">
<option value="0">Full data</option>
<option value="1">Just values</option>
<option value="2">Template</option>
</select>
</div>
<div class="form-row node-input-msg">
<label for="node-input-temperature"><i class="fa fa-thermometer-three-quarters"></i> Temperature</label>
<input type="text" id="node-input-temperature">
</div>
<div class="form-row node-input-msg">
<label for="node-input-humidity"><i class="fa fa-mixcloud"></i> Humidity</label>
<input type="text" id="node-input-humidity"/>
</div>
<div class="form-row node-input-msg">
<label for="node-input-pressure"><i class="fa fa-tachometer"></i> Pressure</label>
<input type="text" id="node-input-pressure"/>
</div>
<div class="form-row node-input-msg">
<label>&nbsp;</label>
<i></i>&nbsp;&nbsp;<input type="checkbox" id="node-input-divide" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-divide" style="width: 70%;">Divide values by 100</label>
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-ht">
@@ -126,59 +86,29 @@
<h3>Outputs</h3>
<ol class="node-ports">
<li>Temperature output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<li>Humidity output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Data from gateway, see below.</dd>
</dl>
</ol>
<h3>Details</h3>
<p>The incoming json message is parsed if the type model is <code>sensor_ht</code> or <code>weather.v1</code> and
the <code>sid</code> matches the configured value for this device.</p>
<p>Three output types are supported:
<ul>
<li>Full data</li>
<li>Just values</li>
<li>Template</li>
</ul>
<h4>Details</h4>
<p>The incoming message is processed if the <code>sid</code> matches the configured value for this device.</p>
<p>Sample payload from Aqara (which brings pressure):</p>
<p><pre>{
cmd: "read_ack"
model: "weather.v1"
sid: "158d00010b7f1b"
short_id: 8451
data: {
voltage:3005,
temperature:23.25,
humidity:56.99,
pressure:981.26,
batteryLevel: 34
}
}</pre>
Where <code>humidy</code> is in percents, <code>pressure</code> in hPa, <code>batteryLevel</code> is a computed percentage of remaining battery.
</p>
<h4>Full data</h4>
<p>Passes the complete json object received from the gateway on output 1. Use this if you need the raw data.</p>
<h4>Just values</h4>
<p>Only passes the string values for temperature on output 1 and humidity on output 2.</p>
<h4>Template</h4>
<p>Use your own template to pass the values on. The template can contain <a href="http://mustache.github.io/mustache.5.html">mustache-style</a> tags.
Any property from the data section of the full object can be used. As the HT sensor is sending data in thousends, you can check this box to have it in hundreds.
Here an template example to send the temperature to homebridge-mqtt:
<pre>
{
"name":"Temperatuur woonkamer",
"characteristic":"CurrentTemperature",
"value":{{temperature}}
}</pre></p>
<p>Sample message:</p>
<p><pre>
{
cmd: "read_ack"
model: "sensor_ht"
sid: "158d00010b7f1b"
short_id: 8451
data: "{
"voltage":3005,
"temperature":"2325",
"humidity":"5699",
"pressure":"98126"
}"
}</pre></p>
</script>

View File

@@ -1,97 +1,39 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var dgram = require('dgram');
var miDevicesUtils = require('../utils');
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
function XiaomiHtNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.sid = config.sid;
this.output = config.output;
this.temperature = config.temperature;
this.humidity = config.humidity;
this.divide = config.divide;
var node = this;
node.status({fill:"grey", shape:"ring", text:"battery - na"});
this.status({fill:"grey", shape:"ring", text:"battery - na"});
if (this.gateway) {
node.on('input', function(msg) {
// var payload = JSON.parse(msg);
var payload = msg.payload;
node.log("Received message from: " + payload.model + " sid: " + payload.sid + " payload: " + payload.data);
this.on('input', (msg) => {
let payload = msg.payload;
// Input from gateway
if(payload.sid) {
if (payload.sid == node.sid && ["sensor_ht", "weather.v1"].indexOf(payload.model) >= 0) {
var data = payload.data;
miDevicesUtils.setStatus(node, data);
if (node.output == "0") {
node.send([msg]);
} else if (node.output == "1") {
var temp = null;
var humidity = null;
var pressure = null;
if (data.temperature) {
temp = {"payload": data.temperature};
if (payload.sid) {
if (payload.sid == this.sid) {
miDevicesUtils.setStatus(this, payload.data);
["temperature", "humidity", "pressure"].forEach((dataType) => {
if(payload.data[dataType]) {
payload.data[dataType] = parseInt(payload.data[dataType])/100;
}
if (data.humidity) {
humidity = {"payload": data.humidity};
}
if (data.pressure) {
pressure = {"payload": data.pressure};
}
node.send([temp, humidity, pressure]);
} else if (node.output == "2") {
var temp = null;
var humidity = null;
var pressure = null;
if (data.temperature) {
if (this.divide) {
data.temperature = String(data.temperature / 100);
}
temp = {"payload": mustache.render(node.temperature, data)}
}
if (data.humidity) {
if (this.divide) {
data.humidity = String(data.humidity / 100);
}
humidity = {"payload": mustache.render(node.humidity, data)}
}
if (data.pressure) {
if (this.divide) {
data.pressure = String(data.pressure / 100);
}
pressure = {"payload": mustache.render(node.pressure, data)}
}
node.send([temp, humidity, pressure]);
}
});
}
else {
msg = null;
}
}
// Prepare for request
else {
miDevicesUtils.prepareForGatewayRequest(node, msg);
node.send(msg);
miDevicesUtils.prepareForGatewayRequest(this, msg);
}
this.send(msg);
});
node.on("close", function() {
});
} else {
// no gateway configured
}
}
RED.nodes.registerType("xiaomi-ht", XiaomiHtNode);
}
};

View File

@@ -5,14 +5,10 @@
defaults: {
gateway: {value:"", type:"xiaomi-configurator"},
name: {value: ""},
sid: {value: "", required: true},
openmsg: {value: ""},
closemsg: {value: ""},
output: {value: "0"}
sid: {value: "", required: true}
},
inputs: 1,
outputs: 1,
outputLabels: ["Status"],
paletteLabel: "contact",
icon: "door-icon.png",
label: function () {
@@ -52,14 +48,6 @@
$("#node-input-gateway").change(function () {
changeGateway("magnet");
});
$("#node-input-output").change(function () {
if ($(this).val() == "2") {
$(".node-input-msg").show();
} else {
$(".node-input-msg").hide();
}
});
},
oneditsave: function() {
var node = this;
@@ -81,22 +69,6 @@
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
<div class="form-row">
<label for="node-input-output"><i class="icon-tag"></i> Output</label>
<select id="node-input-output" style="width:70%;">
<option value="0">Full data</option>
<option value="1">Just values</option>
<option value="2">Template</option>
</select>
</div>
<div class="form-row node-input-msg">
<label for="node-input-openmsg"><i class="fa fa-circle-o-notch"></i> Open msg</label>
<input type="text" id="node-input-openmsg">
</div>
<div class="form-row node-input-msg">
<label for="node-input-closemsg"><i class="fa fa-circle-o"></i> Close msg</label>
<input type="text" id="node-input-closemsg">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-magnet">
@@ -112,44 +84,27 @@
<h3>Outputs</h3>
<ol class="node-ports">
<li>State output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Data from gateway, see below.</dd>
</dl>
</ol>
<h3>Details</h3>
<p>The incoming json message is parsed if the type model is <code>magnet</code> or <code>sensor_magnet.aq2</code> and
the <code>sid</code> matches the configured value for this device.</p>
<p>Three output types are supported:
<ul>
<li>Full data</li>
<li>Just values</li>
<li>Template</li>
</ul>
</p>
<h4>Full data</h4>
<p>Passes the complete json object received from the gateway. Use this if you need the raw data.</p>
<h4>Just values</h4>
<p>Only passes the sting values <code>open</code> or <code>close</code>.</p>
<h4>Template</h4>
<p>Use your own template to pass the values on. The template can contain <a href="http://mustache.github.io/mustache.5.html">mustache-style</a> tags.
Any property from the data section of the full object can be used.</p>
<p>The incoming message is processed if the <code>sid</code> matches the configured value for this device.</p>
<p>Sample message:</p>
<p><pre>
{
cmd: "read_ack"
model: "magnet"
sid: "158d000112fb5d"
short_id: 50301
data: "{
"voltage":3015,
"status":"close"
}"
}</pre></p>
<p><pre>{
cmd: "read_ack"
model: "sensor_magnet.aq2"
sid: "158d000112fb5d"
short_id: 50301
data: {
voltage: 3015,
status: "close",
batteryLevel: 23
}
}</pre>
Where <code>status</code> can be <code>"open"</code> or <code>"close"</code>, <code>batteryLevel</code> is a computed percentage of remaining battery.
</p>
</script>

View File

@@ -1,78 +1,8 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var miDevicesUtils = require('../utils');
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
function XiaomiMagnetNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.sid = config.sid;
this.output = config.output;
this.openmsg = config.openmsg;
this.closemsg = config.closemsg;
var node = this;
var state = "";
// node.status({fill:"yellow", shape:"dot", text:"unknown state"});
node.status({fill:"grey", shape:"ring", text:"battery - na"});
if (this.gateway) {
node.on('input', function(msg) {
// var payload = JSON.parse(msg);
var payload = msg.payload;
if(payload.sid) {
if (payload.sid == node.sid && ["magnet", "sensor_magnet.aq2"].indexOf(payload.model) >= 0) {
var data = payload.data;
// if (data.status && data.status == "open") {
// node.status({fill:"green", shape:"dot", text:"open"});
// state = "open";
// } else if (data.status && data.status == "close") {
// node.status({fill:"red", shape:"dot", text:"closed"});
// state = "closed";
// }
miDevicesUtils.setStatus(node, data);
if (node.output == "0") {
node.send([msg]);
} else if (node.output == "1") {
var status = null;
if (data.status) {
status = {"payload": data.status};
}
node.send([status]);
} else if (node.output == "2") {
var status = null;
if (data.status === 'open') {
status = {"payload": mustache.render(node.openmsg, data)}
} else {
status = {"payload": mustache.render(node.closemsg, data)}
}
node.send([status]);
}
}
}
// Prepare for request
else {
miDevicesUtils.prepareForGatewayRequest(node, msg);
node.send(msg);
}
});
node.on("close", function() {
});
} else {
// no gateway configured
}
miDevicesUtils.defaultNode(RED, config, this);
}
RED.nodes.registerType("xiaomi-magnet", XiaomiMagnetNode);
}
};

View File

@@ -11,8 +11,7 @@
output: {value: "0"}
},
inputs: 1,
outputs: 2,
outputLabels: ["Status"],
outputs: 1,
paletteLabel: "motion",
icon: "motion-icon.png",
label: function () {
@@ -52,14 +51,6 @@
$("#node-input-gateway").change(function () {
changeGateway("magnet");
});
$("#node-input-output").change(function () {
if ($(this).val() == "2") {
$(".node-input-msg").show();
} else {
$(".node-input-msg").hide();
}
});
},
oneditsave: function() {
var node = this;
@@ -81,22 +72,6 @@
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
<div class="form-row">
<label for="node-input-output"><i class="icon-tag"></i> Output</label>
<select id="node-input-output" style="width:70%;">
<option value="0">Full data</option>
<option value="1">Just values</option>
<option value="2">Template</option>
</select>
</div>
<div class="form-row node-input-msg">
<label for="node-input-motionmsg"><i class="fa fa-rss"></i> Motion msg</label>
<input type="text" id="node-input-motionmsg">
</div>
<div class="form-row node-input-msg">
<label for="node-input-nomotionmsg"><i class="fa fa-circle"></i> No motion msg</label>
<input type="text" id="node-input-nomotionmsg">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-motion">
@@ -112,49 +87,26 @@
<h3>Outputs</h3>
<ol class="node-ports">
<li>Motion output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<li>Duration output
<dl class="message-properties">
<dt>payload <span class="property-type">json</span></dt>
<dd>raw data</dd>
</dl>
</li>
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Data from gateway, see below.</dd>
</dl>
</ol>
<h3>Details</h3>
<p>The incoming json message is parsed if the type model is <code>motion</code> and
the <code>sid</code> matches the configured value for this device.</p>
<p>Three output types are supported for output 1:
<ul>
<li>Full data</li>
<li>Just values</li>
<li>Template</li>
</ul>
</p>
<h4>Full data</h4>
<p>Passes the complete json object received from the gateway. Use this if you need the raw data.</p>
<h4>Just values</h4>
<p>Only passes the values <code>motion</code> or <code>no_motion</code>. In case of <code>no_motion</code> also the duration object is passed on output 2.</p>
<h4>Template</h4>
<p>Use your own template to pass the values on. The template can contain <a href="http://mustache.github.io/mustache.5.html">mustache-style</a> tags.
Any property from the data section of the full object can be used.</p>
<h4>Details</h4>
<p>The incoming message is processed if the <code>sid</code> matches the configured value for this device.</p>
<p>Sample message:</p>
<p><pre>
{
cmd: "read_ack"
model: "motion"
sid: "158d00015ef56c"
short_id: 21672
data: "{
"voltage":3035,
"status":"motion"
}"
}</pre></p>
<p><pre>{
cmd: "read_ack"
model: "motion"
sid: "158d00015ef56c"
short_id: 21672
data: {
voltage: 3035,
status: "motion",
batteryLevel: 45
}
}</pre>
Where <code>batteryLevel</code> is a computed percentage of remaining battery.
</p>
</script>

View File

@@ -1,84 +1,8 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var miDevicesUtils = require('../utils');
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
function XiaomiMotionNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.sid = config.sid;
this.output = config.output;
this.motionmsg = config.motionmsg;
this.nomotionmsg = config.nomotionmsg;
var node = this;
var state = "";
// node.status({fill:"yellow", shape:"dot", text:"unknown state"});
node.status({fill:"grey", shape:"ring", text:"battery - na"});
if (this.gateway) {
node.on('input', function(msg) {
// var payload = JSON.parse(msg);
var payload = msg.payload;
if (payload.sid) {
if (payload.sid == node.sid && payload.model == "motion") {
var data = payload.data;
// if (data.status && data.status == "open") {
// node.status({fill:"green", shape:"dot", text:"open"});
// state = "open";
// } else if (data.status && data.status == "close") {
// node.status({fill:"red", shape:"dot", text:"closed"});
// state = "closed";
// }
miDevicesUtils.setStatus(node, data);
if (node.output == "0") {
node.send([msg]);
} else if (node.output == "1") {
var status = null;
var duration = null;
if (data.status) {
status = {"payload": data.status};
}
if (data.no_motion) {
status = {"payload": "no_motion"};
duration = {"payload": {"no_motion": data.no_motion}};
}
node.send([[status], [duration]]);
} else if (node.output == "2") {
var status = null;
if (data.status === 'motion') {
status = {"payload": mustache.render(node.motionmsg, data)}
} else {
status = {"payload": mustache.render(node.nomotionmsg, data)}
}
node.send([status]);
}
}
}
// Prepare for request
else {
miDevicesUtils.prepareForGatewayRequest(node, msg);
node.send(msg);
}
});
node.on("close", function() {
});
} else {
// no gateway configured
}
miDevicesUtils.defaultNode(RED, config, this);
}
RED.nodes.registerType("xiaomi-motion", XiaomiMotionNode);
}
};

View File

@@ -16,21 +16,6 @@
icon: "outlet-wifi-icon.png",
label: function () {
return this.name || "xiaomi-plug-wifi";
},
oneditprepare: function() {
var node = this;
$("#node-input-output").change(function () {
if ($(this).val() == "2") {
$(".node-input-msg").show();
} else {
$(".node-input-msg").hide();
}
});
},
oneditsave: function() {
}
});
</script>
@@ -44,22 +29,6 @@
<label for="node-input-ip"><i class="icon-tag"></i> Ip</label>
<input type="text" id="node-input-ip" placeholder="ip address">
</div>
<div class="form-row">
<label for="node-input-output"><i class="icon-tag"></i> Output</label>
<select id="node-input-output" style="width:70%;">
<option value="0">Full data</option>
<option value="1">Just values</option>
<option value="2">Template</option>
</select>
</div>
<div class="form-row node-input-msg">
<label for="node-input-onmsg"><i class="fa fa-power-off"></i> On msg</label>
<input type="text" id="node-input-onmsg">
</div>
<div class="form-row node-input-msg">
<label for="node-input-offmsg"><i class="fa fa-power-off"></i> Off msg</label>
<input type="text" id="node-input-offmsg">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-plug-wifi">
@@ -77,43 +46,22 @@
<h3>Outputs</h3>
<ol class="node-ports">
<li>Status output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Data from gateway, see below.</dd>
</dl>
</ol>
<h3>Details</h3>
<h4>Details</h4>
<p>On the input you can send the string <code>on</code> to switch the plug on. To turn it off just send the string <code>off</code></p>
<p>Output 1 reports the status</p>
<p>Three output types are supported:
<ul>
<li>Full data</li>
<li>Just values</li>
<li>Template</li>
</ul>
</p>
<h4>Full data</h4>
<p>Passes a json object with detailed information about the plug. Use this if you need the raw data.</p>
<h4>Just values</h4>
<p>Only passes the values <code>on</code> or <code>off</code></p>
<h4>Template</h4>
<p>Use your own template to pass the values on. The template can contain <a href="http://mustache.github.io/mustache.5.html">mustache-style</a> tags.
Any property from the data section of the full object can be used.</p>
<p>Sample message full data:</p>
<p><pre>
{
"type": "power-plug",
"model": "chuangmi.plug.m1",
"capabilities: [ {'0': "power-channels"} ],
"address": "192.168.178.31",
"port": 54321,
"power": { '0': false }
}"
}</pre></p>
<p><pre>{
type: "power-plug",
model: "chuangmi.plug.m1",
capabilities: [ {"0": "power-channels"} ],
address: "192.168.178.31",
port: 54321,
power: { "0": false },
state: "on"
}</pre></p>
</script>

View File

@@ -1,8 +1,6 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var crypto = require("crypto");
var miio = require("miio");
const miio = require("miio");
module.exports = (RED) => {
var connectionState = "timeout";
var retryTimer;
var delayedStatusMsgTimer;
@@ -11,23 +9,18 @@ module.exports = function(RED) {
function XiaomiPlugWifiNode(config) {
RED.nodes.createNode(this, config);
this.ip = config.ip;
this.output = config.output;
this.onmsg = config.onmsg;
this.offmsg = config.offmsg;
this.plug = null;
var node = this;
this.status({fill: "yellow", shape: "dot", text: "connecting"});
node.status({fill: "yellow", shape: "dot", text: "connecting"});
miio.device({address: node.ip})
.then(function (plug) {
node.plug = plug;
node.status({fill:"green", shape:"dot", text:"connected"});
miio.device({address: this.ip})
.then((plug) => {
this.plug = plug;
this.status({fill:"green", shape:"dot", text:"connected"});
connectionState = "connected";
delayedStatusMsgUpdate(node);
node.plug.on('propertyChanged', function(e) {
this.plug.on('propertyChanged', (e) => {
if (e.property === "power") {
if (e.value['0']) {
setState("on");
@@ -38,73 +31,58 @@ module.exports = function(RED) {
});
watchdog();
})
.catch(function (error) {
.catch((error) => {
connectionState = "reconnecting";
watchdog();
})
node.on('input', function (msg) {
this.on('input', (msg) => {
var payload = msg.payload;
if (connectionState === "connected") {
if (payload == 'on') {
node.plug.setPower(true);
this.plug.setPower(true);
}
if (payload == 'off') {
node.plug.setPower(false);
this.plug.setPower(false);
}
}
});
node.on('close', function (done) {
this.on('close', (done) => {
if (retryTimer) {
clearTimeout(retryTimer);
}
if (delayedStatusMsgTimer) {
clearTimeout(delayedStatusMsgTimer);
}
if (node.plug) {
node.plug.destroy();
if (this.plug) {
this.plug.destroy();
}
done();
});
var setState = function(state) {
if (node.plug) {
var status = null;
var info = {"payload": {
"id": node.plug.id,
"type": node.plug.type,
"model": node.plug.model,
"capabilities": node.plug.capabilities,
"address": node.plug.address,
"port": node.plug.port,
"power": node.plug.power()
}};
if (state === "on") {
node.status({fill:"green", shape:"dot", text:"on"});
status = {"payload": mustache.render(node.onmsg, info.payload)}
}
if (state === "off") {
node.status({fill:"red", shape:"dot", text:"off"});
status = {"payload": mustache.render(node.offmsg, info.payload)}
}
if (node.output == 0) {
status = info;
} else if (node.output == "1") {
status = {"payload": state}
} else if (node.output == "2") {
// do nothing, just send status parsed with mustache
}
node.send([status]);
var setState = (state) => {
if (this.plug) {
let status = {
payload: {
id: this.plug.id,
type: this.plug.type,
model: this.plug.model,
capabilities: this.plug.capabilities,
address: this.plug.address,
port: this.plug.port,
power: this.plug.power(),
state: state
}
};
this.send(status);
}
};
var delayedStatusMsgUpdate = function() {
delayedStatusMsgTimer = setTimeout(function() {
if (node.plug.power()['0']) {
var delayedStatusMsgUpdate = () => {
delayedStatusMsgTimer = setTimeout(() => {
if (this.plug.power()['0']) {
setState("on");
} else {
setState("off");
@@ -112,12 +90,12 @@ module.exports = function(RED) {
}, 1500);
};
var discoverDevice = function() {
miio.device({address: node.ip})
.then(function (plug) {
if (node.plug == null) {
node.plug = plug;
node.plug.on('propertyChanged', function(e) {
var discoverDevice = () => {
miio.device({address: this.ip})
.then((plug) => {
if (this.plug == null) {
this.plug = plug;
this.plug.on('propertyChanged', (e) => {
if (e.property === "power") {
if (e.value['0']) {
setState("on");
@@ -128,28 +106,30 @@ module.exports = function(RED) {
});
}
if (connectionState === "reconnecting") {
node.status({fill:"green", shape:"dot", text:"connected"});
this.status({fill:"green", shape:"dot", text:"connected"});
connectionState = "connected";
delayedStatusMsgUpdate();
}
})
.catch(function (error) {
.catch((error) => {
connectionState = "reconnecting";
if (node.plug) {
node.plug.destroy();
node.plug = null;
if (this.plug) {
this.plug.destroy();
this.plug = null;
}
})
};
var watchdog = function() {
setTimeout(function retryTimer() {
var watchdog = () => {
var node = this;
function retryTimer() {
discoverDevice();
if (connectionState === "reconnecting") {
node.status({fill: "red", shape: "dot", text: "reconnecting"});
}
setTimeout(retryTimer, 30000);
}, 30000);
}
setTimeout(retryTimer, 30000);
}
}

View File

@@ -11,8 +11,7 @@
output: {value: "0"}
},
inputs: 1,
outputs: 2,
outputLabels: ["Status","Control"],
outputs: 1,
paletteLabel: "plug (zigbee)",
icon: "outlet-icon.png",
label: function () {
@@ -26,14 +25,6 @@
// Get the config node using the ID:
var configNode = RED.nodes.node(configNodeID);
$("#node-input-output").change(function () {
if ($(this).val() == "2") {
$(".node-input-msg").show();
} else {
$(".node-input-msg").hide();
}
});
$("#node-input-gateway").change(function () {
});
@@ -66,22 +57,6 @@
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
<div class="form-row">
<label for="node-input-output"><i class="icon-tag"></i> Output</label>
<select id="node-input-output" style="width:70%;">
<option value="0">Full data</option>
<option value="1">Just values</option>
<option value="2">Template</option>
</select>
</div>
<div class="form-row node-input-msg">
<label for="node-input-onmsg"><i class="fa fa-power-off"></i> On msg</label>
<input type="text" id="node-input-onmsg">
</div>
<div class="form-row node-input-msg">
<label for="node-input-offmsg"><i class="fa fa-power-off"></i> Off msg</label>
<input type="text" id="node-input-offmsg">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-plug">
@@ -115,41 +90,22 @@
</li>
</ol>
<h3>Details</h3>
<h4>Details</h4>
<p>The incoming json message is parsed if the type model is <code>plug</code> and
the <code>sid</code> matches the configured value for this device.</p>
<p>On the input you can send the string <code>on</code> to switch the plug on. To turn it off just send the string <code>off</code></p>
<p>Output 1 reports the status, output 2 is the write command for the plug.</p>
<p>Three output types are supported:
<ul>
<li>Full data</li>
<li>Just values</li>
<li>Template</li>
</ul>
</p>
<h4>Full data</h4>
<p>Passes the complete json object received from the gateway on output 1. Use this if you need the raw data.</p>
<h4>Just values</h4>
<p>Only passes the values <code>on</code> or <code>off</code> on output 1.</p>
<h4>Template</h4>
<p>Use your own template to pass the values on. The template can contain <a href="http://mustache.github.io/mustache.5.html">mustache-style</a> tags.
Any property from the data section of the full object can be used.</p>
<p>Sample message:</p>
<p><pre>
{
cmd: "write_ack"
model: "plug"
sid: "158d00012f1fb5"
short_id: 47414
data: "{
"voltage":3600,
"status":"off",
"inuse":"0",
"power_consumed":"4000",
"load_power":"0"
}"
}</pre></p>
<p><pre>{
cmd: "write_ack"
model: "plug"
sid: "158d00012f1fb5"
short_id: 47414
data: {
voltage:3600,
status:"off",
inuse:"0",
power_consumed:"4000",
load_power:"0"
}
}</pre></p>
</script>

View File

@@ -1,26 +1,19 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var crypto = require("crypto");
const crypto = require("crypto");
module.exports = (RED) => {
function XiaomiPlugNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.sid = config.sid;
this.output = config.output;
this.onmsg = config.onmsg;
this.offmsg = config.offmsg;
this.key = this.gateway.key;
var node = this;
var currentToken = "";
var state = "";
node.status({fill:"yellow", shape:"ring", text:"waiting for key"});
this.status({fill:"yellow", shape:"ring", text:"waiting for key"});
if (this.gateway && this.key != "") {
node.on('input', function(msg) {
// var payload = JSON.parse(msg);
this.on('input', (msg) => {
var payload = msg.payload;
if (payload.cmd == "heartbeat" && payload.model == "gateway") {
@@ -70,37 +63,12 @@ module.exports = function(RED) {
state = "off";
}
if (node.output == "0") {
msg.payload = payload;
node.send([msg]);
} else if (node.output == "1") {
var status = null;
if (data.status) {
status = {"payload": data.status};
}
node.send([status]);
} else if (node.output == "2") {
var status = null;
if (data.status === 'on') {
status = {"payload": mustache.render(node.onmsg, data)}
} else {
status = {"payload": mustache.render(node.offmsg, data)}
}
node.send([status]);
}
msg.payload = payload;
node.send([msg]);
}
});
node.on("close", function() {
});
} else {
// no gateway configured
if (this.key == "") {
node.status({fill:"red", shape:"dot", text:"no key configured"});
}
} else if (this.key == "") {
node.status({fill:"red", shape:"dot", text:"no key configured"});
}
}

View File

@@ -11,8 +11,7 @@
output: {value: "0"}
},
inputs: 1,
outputs: 2,
outputLabels: ["Single-click", "Double-click"],
outputs: 1,
paletteLabel: "switch",
icon: "light-icon.png",
label: function () {
@@ -53,14 +52,6 @@
changeGateway("switch");
});
$("#node-input-output").change(function () {
if ($(this).val() == "2") {
$(".node-input-msg").show();
} else {
$(".node-input-msg").hide();
}
});
$(".node-input-msg").hide();
$("#node-input-output").val(node.output);
},
@@ -84,22 +75,6 @@
<label for="node-input-sid"><i class="icon-tag"></i> Device</label>
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
</div>
<div class="form-row">
<label for="node-input-output"><i class="icon-tag"></i> Output</label>
<select id="node-input-output" style="width:70%;">
<option value="0">Full data</option>
<option value="1">Just values</option>
<option value="2">Template</option>
</select>
</div>
<div class="form-row node-input-msg">
<label for="node-input-outmsg"><i class="fa fa-hand-pointer-o"></i> Single-click</label>
<input type="text" id="node-input-outmsg">
</div>
<div class="form-row node-input-msg">
<label for="node-input-outmsgdbcl"><i class="fa fa-hand-peace-o"></i> Double-click</label>
<input type="text" id="node-input-outmsgdbcl">
</div>
</script>
<script type="text/x-red" data-help-name="xiaomi-switch">
@@ -115,49 +90,24 @@
<h3>Outputs</h3>
<ol class="node-ports">
<li>Single click output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<li>Double click output
<dl class="message-properties">
<dt>payload <span class="property-type">string | json</span></dt>
<dd>raw data, value or template.</dd>
</dl>
</li>
<dl class="message-properties">
<dt>payload <span class="property-type">object</span></dt>
<dd>Data from gateway, see below.</dd>
</dl>
</ol>
<h3>Details</h3>
<p>The incoming json message is parsed if the type model is <code>switch</code> or <code>sensor_switch.aq2</code> and
the <code>sid</code> matches the configured value for this device.</p>
<p>Three output types are supported:
<ul>
<li>Full data</li>
<li>Just values</li>
<li>Template</li>
</ul>
</p>
<h4>Full data</h4>
<p>Passes the complete json object received from the gateway on output 1. Use this if you need the raw data.</p>
<h4>Just values</h4>
<p>Only passes the string values <code>single_click</code> on output 1 and <code>double_click</code> on output 2.</p>
<h4>Template</h4>
<p>Use your own template to pass the values on. The template can contain <a href="http://mustache.github.io/mustache.5.html">mustache-style</a> tags.
Any property from the data section of the full object can be used.</p>
<h4>Details</h4>
<p>The incoming message is processed if the <code>sid</code> matches the configured value for this device.</p>
<p>Sample message:</p>
<p><pre>
{
cmd: "report"
model: "switch"
sid: "158d000128b124"
short_id: 56773
data: "{
"status":"click"
}"
}</pre></p>
<p><pre>{
cmd: "report"
model: "switch"
sid: "158d000128b124"
short_id: 56773
data: {
status: "click",
batteryLevel: 23
}
}</pre></p>
</script>

View File

@@ -1,71 +1,8 @@
module.exports = function(RED) {
"use strict";
var mustache = require("mustache");
var miDevicesUtils = require('../utils');
const miDevicesUtils = require('../src/utils');
module.exports = (RED) => {
function XiaomiSwitchNode(config) {
RED.nodes.createNode(this, config);
this.gateway = RED.nodes.getNode(config.gateway);
this.sid = config.sid;
this.output = config.output;
this.outmsg = config.outmsg;
this.outmsgdbcl = config.outmsgdbcl;
var node = this;
node.status({fill:"grey", shape:"ring", text:"battery - na"});
if (this.gateway) {
node.on('input', function(msg) {
// var payload = JSON.parse(msg);
var payload = msg.payload;
// Input from gateway
if (payload.sid) {
if (payload.sid == node.sid && ["switch", "sensor_switch.aq2"].indexOf(payload.model) >= 0) {
var data = payload.data;
miDevicesUtils.setStatus(node, data);
if (node.output == "0") {
node.send([msg]);
} else if (node.output == "1") {
var status = null;
if (data.status) {
status = {"payload": data.status};
}
node.send([status]);
} else if (node.output == "2") {
var status = null;
if (data.status && data.status == "click") {
status = {"payload": mustache.render(node.outmsg, data)}
node.send([[status],[]]);
}
if (data.status && data.status == "double_click") {
status = {"payload": mustache.render(node.outmsgdbcl, data)}
node.send([[],[status]]);
}
}
}
}
// Prepare for request
else {
miDevicesUtils.prepareForGatewayRequest(node, msg);
node.send(msg);
}
});
node.on("close", function() {
});
} else {
// no gateway configured
}
miDevicesUtils.defaultNode(RED, config, this);
}
RED.nodes.registerType("xiaomi-switch", XiaomiSwitchNode);
}
};

View File

@@ -1,6 +1,6 @@
{
"name": "node-red-contrib-mi-devices",
"version": "1.0.0",
"version": "1.0.1",
"description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway, and the Gateway itself.",
"repository": {
"type": "git",
@@ -32,7 +32,9 @@
},
"dependencies": {
"cryptojs": "^2.5.3",
"mustache": "^2.3.0",
"miio": "0.13.0"
},
"engines": {
"node" : ">=4.4.5"
}
}

View File

@@ -7,7 +7,7 @@ module.exports = {
When full, CR2032 batteries are between 3 and 3.4V
http://farnell.com/datasheets/1496885.pdf
*/
return Math.min(Math.round((voltage - 2200) / 14), 100);
return Math.min(Math.round((voltage - 2200) / 10), 100);
},
setStatus: function(node, data) {
if (data.voltage) {
@@ -39,6 +39,18 @@ module.exports = {
return key;
},
sendWritePayloadToGateway: function(node, msg, data) {
let gateway = node.gateway;
if(gateway && gateway.sid && gateway.key && gateway.lastToken) {
data.sid = data.sid || gateway.sid;
data.key = this.getGatewayKey(gateway.key, gateway.lastToken);
msg.payload = {
cmd: "write",
data: data
};
node.send(msg);
}
},
prepareForGatewayRequest: function(node, msg) {
msg.sid = node.sid;
msg.gateway = node.gateway;
@@ -64,5 +76,31 @@ module.exports = {
brightness: brightness,
color: { red: red, green: green, blue: blue }
};
},
defaultNode: function(RED, config, node) {
RED.nodes.createNode(node, config);
node.gateway = RED.nodes.getNode(config.gateway);
node.sid = config.sid;
node.status({fill:"grey", shape:"ring", text:"battery - na"});
if (node.gateway) {
node.on('input', (msg) => {
let payload = msg.payload;
// Input from gateway
if (payload.sid) {
if (payload.sid == node.sid) {
miDevicesUtils.setStatus(node, payload.data);
node.send([msg]);
}
}
// Prepare for request
else {
miDevicesUtils.prepareForGatewayRequest(node, msg);
node.send(msg);
}
});
}
}
}