Compare commits
18 Commits
master
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa37eede7c | ||
|
|
8e3a5ccba3 | ||
|
|
a51edb976d | ||
|
|
df64a9521c | ||
|
|
1828e35a4d | ||
|
|
d04f5c1196 | ||
|
|
c850ca9536 | ||
|
|
c5afb43b47 | ||
|
|
50df65d7fc | ||
|
|
b6e311965a | ||
|
|
7bff50da16 | ||
|
|
c81fb2db33 | ||
|
|
6536f0b583 | ||
|
|
83090c4338 | ||
|
|
ee19899d9d | ||
|
|
03af0b8c06 | ||
|
|
f4d54d714b | ||
|
|
c1299336cb |
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
*.iml
|
.DS_Store
|
||||||
|
.idea
|
||||||
/node_modules
|
/node_modules
|
||||||
.log
|
.log
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|||||||
18
README.md
@@ -1,7 +1,8 @@
|
|||||||
# node-red-contrib-mi-devices
|
# node-red-contrib-mi-devices
|
||||||
|
|
||||||
|
__:warning: I will not go further because I don't use node-red anymore and I don't have the time, but feel free to fork, PR & so on. If you want to maintain this module, feel free to ask.__
|
||||||
|
|
||||||
This module contains the following nodes to provide easy integration of the Xiaomi devices into node-red.
|
This module contains the following nodes to provide easy integration of the Xiaomi devices into node-red.
|
||||||
This module is a fork of [Harald Rietman module, node-red-contrib-xiaomi-devices](https://github.com/hrietman/node-red-contrib-xiaomi-devices)
|
|
||||||
|
|
||||||
The following devices are currently supported:
|
The following devices are currently supported:
|
||||||
|
|
||||||
@@ -12,12 +13,11 @@ The following devices are currently supported:
|
|||||||
* Button switch
|
* Button switch
|
||||||
* Aqara smart wireless switch
|
* Aqara smart wireless switch
|
||||||
* Motion sensor
|
* Motion sensor
|
||||||
* Power plug (zigbee)
|
* Aqara Motion sensor
|
||||||
* Power plug (wifi)
|
|
||||||
* Yeelight White (mono)
|
* Yeelight White (mono)
|
||||||
* Yeelight RGB
|
* Yeelight RGB (color)
|
||||||
|
|
||||||
## Preperation
|
## Preparation
|
||||||
|
|
||||||
To interact with the gateway, you need to enable the developer mode, aka LAN mode in the gateway (see below).
|
To interact with the gateway, you need to enable the developer mode, aka LAN mode in the gateway (see below).
|
||||||
|
|
||||||
@@ -30,6 +30,12 @@ Make sure to check his page for compatible devices.
|
|||||||
npm install node-red-contrib-mi-devices
|
npm install node-red-contrib-mi-devices
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Migrating from v1.X.X
|
||||||
|
|
||||||
|
:warning: When I fully rewrote the code, it has been a need to move to other nodes types. So, there is no backward compatibility between 1.X.X and 2.X.X version (thsi is why a v2 has been released..). That also means that you will have to redo all the configurations add replace previous nodes to new ones, sorry for that.
|
||||||
|
|
||||||
|
Last thing, before upgrading to v2, you should remove the previous version, to prevent node-red warn about missing nodes (or delete the `.config.json` file in your userDir, but you might also loose your credentials).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
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.
|
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.
|
||||||
@@ -89,11 +95,9 @@ The lightning icon should be underline un yellow.
|
|||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
* [Harald Rietman node-red module](https://github.com/hrietman/node-red-contrib-xiaomi-devices)
|
|
||||||
* [Domoticz Instructions](https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara))
|
* [Domoticz Instructions](https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara))
|
||||||
* [louisZl Gateway Local API](https://github.com/louisZL/lumi-gateway-local-api)
|
* [louisZl Gateway Local API](https://github.com/louisZL/lumi-gateway-local-api)
|
||||||
* [Domoticz Gateway Code](https://github.com/domoticz/domoticz/blob/development/hardware/XiaomiGateway.cpp)
|
* [Domoticz Gateway Code](https://github.com/domoticz/domoticz/blob/development/hardware/XiaomiGateway.cpp)
|
||||||
* [Node-red UDP nodes](https://github.com/node-red/node-red/blob/master/nodes/core/io/32-udp.js)
|
|
||||||
* [Yeelight specs](http://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf)
|
* [Yeelight specs](http://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|||||||
113
dist/devices/gateway/Gateway.js
vendored
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const events = require("events");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const GatewayServer_1 = require("./GatewayServer");
|
||||||
|
const _1 = require("./");
|
||||||
|
const Color_1 = require("../../utils/Color");
|
||||||
|
class Gateway extends events.EventEmitter {
|
||||||
|
constructor(sid, ip) {
|
||||||
|
super();
|
||||||
|
this.sid = sid;
|
||||||
|
this.ip = ip;
|
||||||
|
this._subdevices = {};
|
||||||
|
}
|
||||||
|
set password(password) {
|
||||||
|
this._password = password;
|
||||||
|
}
|
||||||
|
get key() {
|
||||||
|
if (!this.lastToken || !this._password)
|
||||||
|
return null;
|
||||||
|
var cipher = crypto.createCipheriv('aes-128-cbc', this._password, Gateway.iv);
|
||||||
|
var key = cipher.update(Buffer.from(this.lastToken), "ascii", "hex");
|
||||||
|
cipher.final('hex');
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
if (msg.data) {
|
||||||
|
if (msg.model === "gateway" && msg.sid === this.sid && msg.token) {
|
||||||
|
this.lastToken = msg.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg.isGetIdListAck()) {
|
||||||
|
msg.data.forEach((sid) => {
|
||||||
|
this.read(sid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (msg.isReadAck() || msg.isReport()) {
|
||||||
|
if (!this._subdevices[msg.sid]) {
|
||||||
|
for (let SubDeviceClass of [_1.Magnet, _1.Motion, _1.Switch, _1.Weather]) {
|
||||||
|
if (SubDeviceClass.acceptedModels.indexOf(msg.model) >= 0) {
|
||||||
|
this._subdevices[msg.sid] = new SubDeviceClass(msg.sid, msg.model);
|
||||||
|
this._subdevices[msg.sid].on('values-updated', (sidOrMessage) => {
|
||||||
|
this.emit("subdevice-values-updated", sidOrMessage);
|
||||||
|
});
|
||||||
|
this.emit("subdevice-found", msg.sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this._subdevices[msg.sid]) {
|
||||||
|
this._subdevices[msg.sid].handleMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getSubdevice(sid) {
|
||||||
|
return this._subdevices[sid] || null;
|
||||||
|
}
|
||||||
|
hasSubdevice(sid) {
|
||||||
|
return !!this._subdevices[sid];
|
||||||
|
}
|
||||||
|
getIdList() {
|
||||||
|
this.send({
|
||||||
|
cmd: "get_id_list",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
read(sid) {
|
||||||
|
this.send({ cmd: "read", sid: sid || this.sid });
|
||||||
|
}
|
||||||
|
setLight(brightness, rgb) {
|
||||||
|
this.send({
|
||||||
|
cmd: "write",
|
||||||
|
data: {
|
||||||
|
rgb: Color_1.Color.toValue(rgb.red, rgb.green, rgb.blue, brightness),
|
||||||
|
sid: this.sid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
playSound(musicId, volume) {
|
||||||
|
this.send({
|
||||||
|
cmd: "write",
|
||||||
|
data: {
|
||||||
|
mid: musicId,
|
||||||
|
volume: volume,
|
||||||
|
sid: this.sid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
send(message) {
|
||||||
|
let msg = Object.assign({}, message.payload || message);
|
||||||
|
if (msg.cmd) {
|
||||||
|
msg.sid = message.sid || this.sid;
|
||||||
|
if (msg.gateway) {
|
||||||
|
delete msg.gateway;
|
||||||
|
}
|
||||||
|
if (msg.cmd === "write") {
|
||||||
|
msg.data.key = this.key;
|
||||||
|
}
|
||||||
|
GatewayServer_1.GatewayServer.getInstance().sendToGateway(this.sid, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get subdevices() {
|
||||||
|
return this._subdevices;
|
||||||
|
}
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
sid: this.sid,
|
||||||
|
ip: this.ip,
|
||||||
|
key: this.password,
|
||||||
|
subdevices: this.subdevices
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Gateway.iv = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]);
|
||||||
|
exports.Gateway = Gateway;
|
||||||
30
dist/devices/gateway/GatewayMessage.js
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
class GatewayMessage {
|
||||||
|
constructor(raw) {
|
||||||
|
Object.assign(this, raw);
|
||||||
|
if (raw.port) {
|
||||||
|
this.port = parseInt(raw.port);
|
||||||
|
}
|
||||||
|
if (raw.data) {
|
||||||
|
this.data = JSON.parse(raw.data) || raw.data;
|
||||||
|
}
|
||||||
|
this.timestamp = +new Date;
|
||||||
|
}
|
||||||
|
isHeartbeat() {
|
||||||
|
return this.cmd === "heartbeat";
|
||||||
|
}
|
||||||
|
isIam() {
|
||||||
|
return this.cmd === "iam";
|
||||||
|
}
|
||||||
|
isGetIdListAck() {
|
||||||
|
return this.cmd === "get_id_list_ack";
|
||||||
|
}
|
||||||
|
isReadAck() {
|
||||||
|
return this.cmd === "read_ack";
|
||||||
|
}
|
||||||
|
isReport() {
|
||||||
|
return this.cmd === "report";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.GatewayMessage = GatewayMessage;
|
||||||
2
dist/devices/gateway/GatewayMessageData.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
121
dist/devices/gateway/GatewayServer.js
vendored
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const events = require("events");
|
||||||
|
const dgram = require("dgram");
|
||||||
|
const _1 = require("./");
|
||||||
|
const GatewayMessage_1 = require("./GatewayMessage");
|
||||||
|
class GatewayServer extends events.EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this._gateways = {};
|
||||||
|
this._gatewaysPing = {};
|
||||||
|
}
|
||||||
|
static getInstance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new GatewayServer();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
discover(ipv = 4) {
|
||||||
|
if (this.server) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.server = dgram.createSocket({
|
||||||
|
type: `udp${ipv}`,
|
||||||
|
reuseAddr: true
|
||||||
|
});
|
||||||
|
this.server.on('listening', () => {
|
||||||
|
var address = this.server.address();
|
||||||
|
//this.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
|
||||||
|
this.server.setBroadcast(true);
|
||||||
|
try {
|
||||||
|
this.server.setMulticastTTL(128);
|
||||||
|
this.server.addMembership(GatewayServer.MULTICAST_ADDRESS, null);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
/*if (e.errno == "EINVAL") {
|
||||||
|
this.error(RED._("udp.errors.bad-mcaddress"));
|
||||||
|
} else if (e.errno == "ENODEV") {
|
||||||
|
this.error(RED._("udp.errors.interface"));
|
||||||
|
} else {
|
||||||
|
this.error(RED._("udp.errors.error",{error:e.errno}));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.server.on("error", (err) => {
|
||||||
|
/*if ((err.code == "EACCES") && (this.port < 1024)) {
|
||||||
|
this.error(RED._("udp.errors.access-error"));
|
||||||
|
} else {
|
||||||
|
this.error(RED._("udp.errors.error",{error:err.code}));
|
||||||
|
}*/
|
||||||
|
this.server.close();
|
||||||
|
delete this.server;
|
||||||
|
});
|
||||||
|
this.server.on('message', (message, remote) => {
|
||||||
|
let msg = new GatewayMessage_1.GatewayMessage(JSON.parse(message.toString('utf8')));
|
||||||
|
//console.log(msg);
|
||||||
|
let gatewaySid = null;
|
||||||
|
if ((msg.isHeartbeat() || msg.isIam()) && msg.model === "gateway") {
|
||||||
|
if (!this._gateways[msg.sid]) {
|
||||||
|
this._gateways[msg.sid] = new _1.Gateway(msg.sid, remote.address);
|
||||||
|
this._gateways[msg.sid].getIdList();
|
||||||
|
this.emit("gateway-online", msg.sid);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Any IP update?
|
||||||
|
this._gateways[msg.sid].ip = remote.address;
|
||||||
|
}
|
||||||
|
if (this._gatewaysPing[msg.sid]) {
|
||||||
|
clearTimeout(this._gatewaysPing[msg.sid]);
|
||||||
|
delete this._gatewaysPing[msg.sid];
|
||||||
|
}
|
||||||
|
// Consider the gateway as unreachable after 2 heartbeats missed (1 heartbeat every 10s)
|
||||||
|
this._gatewaysPing[msg.sid] = setTimeout(() => {
|
||||||
|
this.emit("gateway-offline", msg.sid);
|
||||||
|
delete this._gateways[msg.sid];
|
||||||
|
}, 25 * 1000);
|
||||||
|
gatewaySid = msg.sid;
|
||||||
|
}
|
||||||
|
if (!gatewaySid) {
|
||||||
|
gatewaySid = Object.keys(this._gateways).filter((gatewaySid) => this._gateways[gatewaySid].ip === remote.address)[0];
|
||||||
|
}
|
||||||
|
gatewaySid && this._gateways[gatewaySid] && this._gateways[gatewaySid].handleMessage(msg);
|
||||||
|
});
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
this.server.bind(GatewayServer.SERVER_PORT, null);
|
||||||
|
let msg = Buffer.from(JSON.stringify({ cmd: "whois" }));
|
||||||
|
this.server.send(msg, 0, msg.length, GatewayServer.MULTICAST_PORT, GatewayServer.MULTICAST_ADDRESS);
|
||||||
|
resolve(this.server);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
if (this.server) {
|
||||||
|
this.server.close();
|
||||||
|
delete this.server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getGateway(sid) {
|
||||||
|
return this._gateways[sid] || null;
|
||||||
|
}
|
||||||
|
hasGateway(sid) {
|
||||||
|
return !!this._gateways[sid];
|
||||||
|
}
|
||||||
|
get gateways() {
|
||||||
|
return this._gateways;
|
||||||
|
}
|
||||||
|
sendToGateway(sid, message) {
|
||||||
|
if (this.server && this._gateways[sid]) {
|
||||||
|
let msg = Buffer.from(JSON.stringify(message));
|
||||||
|
this.server.send(msg, 0, msg.length, GatewayServer.SERVER_PORT, this._gateways[sid].ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GatewayServer.MULTICAST_ADDRESS = '224.0.0.50';
|
||||||
|
GatewayServer.MULTICAST_PORT = 4321;
|
||||||
|
GatewayServer.SERVER_PORT = 9898;
|
||||||
|
exports.GatewayServer = GatewayServer;
|
||||||
40
dist/devices/gateway/GatewaySubdevice.js
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const events = require("events");
|
||||||
|
class GatewaySubdevice extends events.EventEmitter {
|
||||||
|
constructor(sid, model) {
|
||||||
|
super();
|
||||||
|
this.sid = sid;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
get batteryLevel() {
|
||||||
|
/*
|
||||||
|
When full, CR2032 batteries are between 3 and 3.4V
|
||||||
|
http://farnell.com/datasheets/1496885.pdf
|
||||||
|
*/
|
||||||
|
return this.voltage ? Math.min(Math.round((this.voltage - 2200) / 10), 100) : -1;
|
||||||
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
if (msg.data.voltage) {
|
||||||
|
this.voltage = msg.data.voltage;
|
||||||
|
}
|
||||||
|
this.message = msg;
|
||||||
|
}
|
||||||
|
static get acceptedModels() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
;
|
||||||
|
toJSON() {
|
||||||
|
let json = {};
|
||||||
|
for (let prop of Object.keys(this)) {
|
||||||
|
json[prop] = this[prop];
|
||||||
|
}
|
||||||
|
delete json._events;
|
||||||
|
delete json._eventsCount;
|
||||||
|
delete json._maxListeners;
|
||||||
|
json.batteryLevel = this.batteryLevel;
|
||||||
|
json.internalModel = this.internalModel;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.GatewaySubdevice = GatewaySubdevice;
|
||||||
32
dist/devices/gateway/Magnet.js
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const GatewaySubdevice_1 = require("./GatewaySubdevice");
|
||||||
|
class Magnet extends GatewaySubdevice_1.GatewaySubdevice {
|
||||||
|
static get acceptedModels() {
|
||||||
|
return ['magnet', 'sensor_magnet.aq2'];
|
||||||
|
}
|
||||||
|
get internalModel() {
|
||||||
|
return 'mi.magnet';
|
||||||
|
}
|
||||||
|
isClosed() {
|
||||||
|
return this.status === "close";
|
||||||
|
}
|
||||||
|
isOpened() {
|
||||||
|
return this.status === "open";
|
||||||
|
}
|
||||||
|
isUnkownState() {
|
||||||
|
return this.status === "unkown";
|
||||||
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
if (msg.isReadAck() || msg.isReport()) {
|
||||||
|
let data = msg.data;
|
||||||
|
// TODO: mintime
|
||||||
|
if (this.status !== data.status) {
|
||||||
|
this.status = data.status;
|
||||||
|
this.emit('values-updated', this.sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Magnet = Magnet;
|
||||||
29
dist/devices/gateway/Motion.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const GatewaySubdevice_1 = require("./GatewaySubdevice");
|
||||||
|
class Motion extends GatewaySubdevice_1.GatewaySubdevice {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.lux = 0;
|
||||||
|
}
|
||||||
|
static get acceptedModels() {
|
||||||
|
return ['motion', 'sensor_motion.aq2'];
|
||||||
|
}
|
||||||
|
get internalModel() {
|
||||||
|
return 'mi.motion';
|
||||||
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
if (msg.isReadAck() || msg.isReport()) {
|
||||||
|
let data = msg.data;
|
||||||
|
if (data.lux) {
|
||||||
|
this.lux = parseInt(data.lux);
|
||||||
|
}
|
||||||
|
if (data.status === "motion") {
|
||||||
|
this.lastMotionTimestamp = data.timestamp;
|
||||||
|
this.emit('values-updated', { sid: this.sid, data: { hasMotion: true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Motion = Motion;
|
||||||
15
dist/devices/gateway/Switch.js
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const GatewaySubdevice_1 = require("./GatewaySubdevice");
|
||||||
|
class Switch extends GatewaySubdevice_1.GatewaySubdevice {
|
||||||
|
static get acceptedModels() {
|
||||||
|
return ['switch', 'sensor_switch.aq2'];
|
||||||
|
}
|
||||||
|
get internalModel() {
|
||||||
|
return 'mi.switch';
|
||||||
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Switch = Switch;
|
||||||
36
dist/devices/gateway/Weather.js
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const GatewaySubdevice_1 = require("./GatewaySubdevice");
|
||||||
|
class Weather extends GatewaySubdevice_1.GatewaySubdevice {
|
||||||
|
static get acceptedModels() {
|
||||||
|
return ['sensor_ht', 'weather.v1'];
|
||||||
|
}
|
||||||
|
get internalModel() {
|
||||||
|
return 'mi.weather';
|
||||||
|
}
|
||||||
|
get temperatureInDegrees() {
|
||||||
|
return this.temperature / 100;
|
||||||
|
}
|
||||||
|
get humidityInPercent() {
|
||||||
|
return this.humidity / 100;
|
||||||
|
}
|
||||||
|
get pressureInBar() {
|
||||||
|
return this.pressure / 100000;
|
||||||
|
}
|
||||||
|
get pressureInhPa() {
|
||||||
|
return this.pressure / 100;
|
||||||
|
}
|
||||||
|
handleMessage(msg) {
|
||||||
|
super.handleMessage(msg);
|
||||||
|
if (msg.isReadAck() || msg.isReport()) {
|
||||||
|
let data = msg.data;
|
||||||
|
['temperature', 'humidity', 'pressure'].forEach((dataType) => {
|
||||||
|
if (data[dataType]) {
|
||||||
|
this[dataType] = parseInt(data[dataType]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.emit('values-updated', this.sid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Weather = Weather;
|
||||||
14
dist/devices/gateway/index.js
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"use strict";
|
||||||
|
function __export(m) {
|
||||||
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
__export(require("./"));
|
||||||
|
__export(require("./Gateway"));
|
||||||
|
__export(require("./GatewayMessage"));
|
||||||
|
__export(require("./GatewayServer"));
|
||||||
|
__export(require("./GatewaySubdevice"));
|
||||||
|
__export(require("./Magnet"));
|
||||||
|
__export(require("./Motion"));
|
||||||
|
__export(require("./Switch"));
|
||||||
|
__export(require("./Weather"));
|
||||||
45
dist/devices/yeelight/YeelightServer.js
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const events = require("events");
|
||||||
|
const YeelightSearch = require("yeelight-wifi");
|
||||||
|
class YeelightServer extends events.EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this._bulbs = {};
|
||||||
|
this._bulbsJson = {};
|
||||||
|
}
|
||||||
|
static getInstance() {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new YeelightServer();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
get bulbs() {
|
||||||
|
return this._bulbsJson;
|
||||||
|
}
|
||||||
|
getBulb(sid) {
|
||||||
|
return this._bulbs[sid];
|
||||||
|
}
|
||||||
|
discover() {
|
||||||
|
new Promise(() => {
|
||||||
|
(new YeelightSearch()).on('found', (bulb) => {
|
||||||
|
bulb.sid = parseInt(bulb.id);
|
||||||
|
if (!this._bulbs[bulb.sid]) {
|
||||||
|
this._bulbs[bulb.sid] = bulb;
|
||||||
|
this._bulbsJson[bulb.sid] = YeelightServer.bulbToJSON(bulb);
|
||||||
|
this.emit("yeelight-online", bulb.sid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// TODO: disconected ?
|
||||||
|
}
|
||||||
|
static bulbToJSON(bulb) {
|
||||||
|
return {
|
||||||
|
sid: bulb.sid,
|
||||||
|
ip: bulb.hostname,
|
||||||
|
name: bulb.name,
|
||||||
|
model: bulb.model
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.YeelightServer = YeelightServer;
|
||||||
0
dist/devices/yeelight/index.js
vendored
Normal file
26
dist/nodes/actions/GatewayPlaySound.js
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class GatewayPlaySound {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.mid = parseInt(props.mid);
|
||||||
|
this.volume = parseInt(props.volume);
|
||||||
|
this.setListeners();
|
||||||
|
}
|
||||||
|
setListeners() {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
if (msg.sid) {
|
||||||
|
msg.payload = {
|
||||||
|
action: "playSound",
|
||||||
|
mid: msg.mid || this.mid,
|
||||||
|
volume: msg.volume || this.volume
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-actions gateway_play_sound`, GatewayPlaySound);
|
||||||
|
};
|
||||||
23
dist/nodes/actions/GatewayStopSound.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class GatewayStopSound {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.setListeners();
|
||||||
|
}
|
||||||
|
setListeners() {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
if (msg.sid) {
|
||||||
|
msg.payload = {
|
||||||
|
action: "playSound",
|
||||||
|
mid: 1000
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-actions gateway_stop_sound`, GatewayStopSound);
|
||||||
|
};
|
||||||
24
dist/nodes/actions/Light.js
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class Light {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.color = props.color;
|
||||||
|
this.brightness = props.brightness;
|
||||||
|
this.setListeners();
|
||||||
|
}
|
||||||
|
setListeners() {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
msg.payload = {
|
||||||
|
action: "setLight",
|
||||||
|
color: msg.color || this.color,
|
||||||
|
brightness: msg.brightness || this.brightness
|
||||||
|
};
|
||||||
|
this.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-actions light`, Light);
|
||||||
|
};
|
||||||
20
dist/nodes/actions/ReadAction.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED, type) => {
|
||||||
|
class ReadAction {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
if (msg.sid) {
|
||||||
|
msg.payload = {
|
||||||
|
cmd: this.type.replace(`${constants_1.Constants.NODES_PREFIX}-actions `, ''),
|
||||||
|
sid: msg.sid
|
||||||
|
};
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-actions ${type}`, ReadAction);
|
||||||
|
};
|
||||||
18
dist/nodes/actions/ToggleAction.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED, action) => {
|
||||||
|
class ToggleAction {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.setListeners();
|
||||||
|
}
|
||||||
|
setListeners() {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
msg.payload = { action };
|
||||||
|
this.send(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-actions ${action}`, ToggleAction);
|
||||||
|
};
|
||||||
23
dist/nodes/actions/WriteAction.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED, type) => {
|
||||||
|
class WriteAction {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
if (msg.sid) {
|
||||||
|
msg.payload = {
|
||||||
|
cmd: "write",
|
||||||
|
data: {
|
||||||
|
status: this.type.replace(`${constants_1.Constants.NODES_PREFIX}-actions `, ''),
|
||||||
|
sid: msg.sid
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-actions ${type}`, WriteAction);
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 1016 B After Width: | Height: | Size: 1016 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 601 B After Width: | Height: | Size: 601 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 990 B After Width: | Height: | Size: 990 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1,30 +1,29 @@
|
|||||||
<!-- The Read Node -->
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions read">
|
<script type="text/x-red" data-template-name="mi-devices-actions read">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions read">
|
<script type="text/x-red" data-help-name="mi-devices-actions read">
|
||||||
<p>Ask the gateway to read the report of the input device.</p>
|
<p>Ask the gateway to read the report of the input device.</p>
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
<h3>Inputs</h3>
|
||||||
<dl class="message-properties">
|
<dl class="message-properties">
|
||||||
<dt>sid
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
<span class="property-type">string</span>
|
|
||||||
</dt>
|
|
||||||
<dd>Device <code>sid</code> to ask the report.</dd>
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
<h3>Outputs</h3>
|
||||||
<p class="node-ports">
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
Message to connect to a gateway out node.
|
|
||||||
</p>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions read',{
|
RED.nodes.registerType('mi-devices-actions read',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -35,31 +34,37 @@
|
|||||||
paletteLabel: "read",
|
paletteLabel: "read",
|
||||||
icon: "mi-read.png",
|
icon: "mi-read.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"read";
|
return this.name||"mi-devices read";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The get ids Node -->
|
<script type="text/x-red" data-template-name="mi-devices-actions get_id_list">
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions get_id_list">
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions get_id_list">
|
<script type="text/x-red" data-help-name="mi-devices-actions get_id_list">
|
||||||
<p>Ask the gateway to the list of devices ids.</p>
|
<p>Ask the gateway to the list of devices ids.</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
<h3>Outputs</h3>
|
||||||
<ol class="node-ports">
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
<li>Message to connect to a gateway out node.</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions get_id_list',{
|
RED.nodes.registerType('mi-devices-actions get_id_list',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -70,44 +75,37 @@
|
|||||||
paletteLabel: "get id list",
|
paletteLabel: "get id list",
|
||||||
icon: "mi-list.png",
|
icon: "mi-list.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"get id list";
|
return this.name||"mi-devices get id list";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-actions click">
|
||||||
<!-- The Single click Node -->
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions click">
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions click">
|
<script type="text/x-red" data-help-name="mi-devices-actions click">
|
||||||
<p>Virtual single click for switch.</p>
|
<p>Virtual single click for switch.</p>
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
<h3>Inputs</h3>
|
||||||
<dl class="message-properties">
|
<dl class="message-properties">
|
||||||
<dt>sid
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
<span class="property-type">string</span>
|
|
||||||
</dt>
|
|
||||||
<dd>Device <code>sid</code> to ask the report.</dd>
|
|
||||||
<dt>gateway
|
|
||||||
<span class="property-type">xiaomi-configurator</span>
|
|
||||||
</dt>
|
|
||||||
<dd>Device <code>sid</code> to ask the report.</dd>
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
<h3>Outputs</h3>
|
||||||
<ol class="node-ports">
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
<li>Message to connect to a gateway out node.</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions click',{
|
RED.nodes.registerType('mi-devices-actions click',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -118,43 +116,37 @@
|
|||||||
paletteLabel: "click",
|
paletteLabel: "click",
|
||||||
icon: "mi-click.png",
|
icon: "mi-click.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"click";
|
return this.name||"mi-devices click";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The Double click Node -->
|
<script type="text/x-red" data-template-name="mi-devices-actions double_click">
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions double_click">
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions double_click">
|
<script type="text/x-red" data-help-name="mi-devices-actions double_click">
|
||||||
<p>Virtual double click for switch.</p>
|
<p>Virtual double click for switch.</p>
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
<h3>Inputs</h3>
|
||||||
<dl class="message-properties">
|
<dl class="message-properties">
|
||||||
<dt>sid
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
<span class="property-type">string</span>
|
|
||||||
</dt>
|
|
||||||
<dd>Device <code>sid</code> to ask the report.</dd>
|
|
||||||
<dt>gateway
|
|
||||||
<span class="property-type">xiaomi-configurator</span>
|
|
||||||
</dt>
|
|
||||||
<dd>Device <code>sid</code> to ask the report.</dd>
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
<h3>Outputs</h3>
|
||||||
<ol class="node-ports">
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
<li>Message to connect to a gateway out node.</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions double_click',{
|
RED.nodes.registerType('mi-devices-actions double_click',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -163,17 +155,16 @@
|
|||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:1,
|
outputs:1,
|
||||||
paletteLabel: "double click",
|
paletteLabel: "double click",
|
||||||
icon: "mi-double-click.png",
|
icon: "double-click.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"double click";
|
return this.name||"mi-devices double click";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The Gateway light Node -->
|
<!-- The Gateway light Node -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions gateway_light', {
|
RED.nodes.registerType('mi-devices-actions light', {
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -203,7 +194,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions gateway_light">
|
<script type="text/x-red" data-template-name="mi-devices-actions light">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
@@ -218,7 +209,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions gateway_light">
|
<script type="text/x-red" data-help-name="mi-devices-actions light">
|
||||||
<p>Change the light of the gateway.</p>
|
<p>Change the light of the gateway.</p>
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
<h3>Inputs</h3>
|
||||||
@@ -250,11 +241,9 @@
|
|||||||
<li>Message to connect to a gateway out node.</li>
|
<li>Message to connect to a gateway out node.</li>
|
||||||
</ol>
|
</ol>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The Gateway sound Node -->
|
<!-- The Gateway sound Node -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions gateway_sound', {
|
RED.nodes.registerType('mi-devices-actions gateway_play_sound', {
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -272,7 +261,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions gateway_sound">
|
<script type="text/x-red" data-template-name="mi-devices-actions gateway_play_sound">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
@@ -309,9 +298,10 @@
|
|||||||
<label for="node-input-volume"><i class="icon-tag"></i> Volume</label>
|
<label for="node-input-volume"><i class="icon-tag"></i> Volume</label>
|
||||||
<input type="range" id="node-input-volume" min="0" max="100">
|
<input type="range" id="node-input-volume" min="0" max="100">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions gateway_sound">
|
<script type="text/x-red" data-help-name="mi-devices-actions gateway_play_sound">
|
||||||
<p>Play a sound on the gateway.</p>
|
<p>Play a sound on the gateway.</p>
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
<h3>Inputs</h3>
|
||||||
@@ -330,34 +320,34 @@
|
|||||||
<ol class="node-ports">
|
<ol class="node-ports">
|
||||||
<li>Message to connect to a gateway out node.</li>
|
<li>Message to connect to a gateway out node.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The Gateway stop sound Node -->
|
<!-- The Gateway stop sound Node -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions gateway_stop_sound',{
|
RED.nodes.registerType('mi-devices-actions gateway_stop_sound', {
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
name: {value:""}
|
name: {value: ""}
|
||||||
},
|
},
|
||||||
inputs:1,
|
inputs: 1,
|
||||||
outputs:1,
|
outputs: 1,
|
||||||
paletteLabel: "stop sound",
|
paletteLabel: "stop sound",
|
||||||
icon: "mi-mute.png",
|
icon: "mi-mute.png",
|
||||||
label: function() {
|
label: function () {
|
||||||
return this.name||"stop sound";
|
return this.name || "stop sound";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions gateway_stop_sound">
|
<script type="text/x-red" data-template-name="mi-devices-actions gateway_stop_sound">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions gateway_stop_sound">
|
<script type="text/x-red" data-help-name="mi-devices-actions gateway_stop_sound">
|
||||||
<p>
|
<p>
|
||||||
Stop current playing sound on the gateway.
|
Stop current playing sound on the gateway.
|
||||||
</p>
|
</p>
|
||||||
@@ -366,12 +356,35 @@
|
|||||||
<ol class="node-ports">
|
<ol class="node-ports">
|
||||||
<li>Message to connect to a gateway out node.</li>
|
<li>Message to connect to a gateway out node.</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The "on" Node -->
|
<script type="text/x-red" data-template-name="mi-devices-actions turn_on">
|
||||||
|
<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>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-actions turn_on">
|
||||||
|
<p>Turn device on.</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
|
</script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions on',{
|
RED.nodes.registerType('mi-devices-actions turn_on',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -379,35 +392,40 @@
|
|||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:1,
|
outputs:1,
|
||||||
paletteLabel: "on",
|
paletteLabel: "turn on",
|
||||||
icon: "mi-on.png",
|
icon: "mi-on.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"power on";
|
return this.name||"mi-devices turn on";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions on">
|
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-actions turn_off">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions on">
|
<script type="text/x-red" data-help-name="mi-devices-actions turn_off">
|
||||||
<p>
|
<p>Turn device off.</p>
|
||||||
Turn input device to on.
|
|
||||||
</p>
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
<h3>Outputs</h3>
|
||||||
<ol class="node-ports">
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
<li>Message to connect to a gateway/yeelight out node.</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The "off" Node -->
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions off',{
|
RED.nodes.registerType('mi-devices-actions turn_off',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -415,35 +433,40 @@
|
|||||||
},
|
},
|
||||||
inputs:1,
|
inputs:1,
|
||||||
outputs:1,
|
outputs:1,
|
||||||
paletteLabel: "off",
|
paletteLabel: "turn off",
|
||||||
icon: "mi-off.png",
|
icon: "mi-off.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"power off";
|
return this.name||"mi-devices turn off";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions off">
|
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-actions toggle">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions off">
|
<script type="text/x-red" data-help-name="mi-devices-actions toggle">
|
||||||
<p>
|
<p>Toggle device.</p>
|
||||||
Turn input device to off.
|
|
||||||
</p>
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device <code>sid</code> to ask the report.</dd>
|
||||||
|
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the input device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
<h3>Outputs</h3>
|
||||||
<ol class="node-ports">
|
<p class="node-ports">Message to connect to a gateway out node.</p>
|
||||||
<li>Message to connect to a gateway/yeelight out node.</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- The "toggle" Node -->
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-actions toggle',{
|
RED.nodes.registerType('mi-devices-actions toggle',{
|
||||||
category: 'xiaomi actions',
|
category: 'xiaomi actions',
|
||||||
color: '#64C4CD',
|
color: '#64C4CD',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -454,22 +477,7 @@
|
|||||||
paletteLabel: "toggle",
|
paletteLabel: "toggle",
|
||||||
icon: "mi-toggle.png",
|
icon: "mi-toggle.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
return this.name||"toggle power";
|
return this.name||"mi-devices toggle";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<script type="text/x-red" data-template-name="xiaomi-actions toggle">
|
|
||||||
<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>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-actions toggle">
|
|
||||||
<p>Toggle device.</p>
|
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
|
||||||
<ol class="node-ports">
|
|
||||||
<li>Message to connect to a gateway/yeelight out node.</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
|
||||||
21
dist/nodes/actions/index.js
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"use strict";
|
||||||
|
const ReadAction_1 = require("./ReadAction");
|
||||||
|
const WriteAction_1 = require("./WriteAction");
|
||||||
|
const Light_1 = require("./Light");
|
||||||
|
const GatewayPlaySound_1 = require("./GatewayPlaySound");
|
||||||
|
const GatewayStopSound_1 = require("./GatewayStopSound");
|
||||||
|
const ToggleAction_1 = require("./ToggleAction");
|
||||||
|
module.exports = (RED) => {
|
||||||
|
["read", "get_id_list"].forEach((action) => {
|
||||||
|
ReadAction_1.default(RED, action);
|
||||||
|
});
|
||||||
|
["click", "double_click"].forEach((action) => {
|
||||||
|
WriteAction_1.default(RED, action);
|
||||||
|
});
|
||||||
|
Light_1.default(RED);
|
||||||
|
GatewayPlaySound_1.default(RED);
|
||||||
|
GatewayStopSound_1.default(RED);
|
||||||
|
["turn_on", "turn_off", "toggle"].forEach(action => {
|
||||||
|
ToggleAction_1.default(RED, action);
|
||||||
|
});
|
||||||
|
};
|
||||||
10
dist/nodes/constants.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
class Constants {
|
||||||
|
static get NODES_PREFIX() {
|
||||||
|
let packageJson = require(`${__dirname}/../../package`);
|
||||||
|
return packageJson.config.nodes_prefix;
|
||||||
|
}
|
||||||
|
;
|
||||||
|
}
|
||||||
|
exports.Constants = Constants;
|
||||||
67
dist/nodes/gateway-subdevices/All.js
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
const uniqid = require("uniqid");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class All {
|
||||||
|
static getOnlyModelsValue(input) {
|
||||||
|
var cleanOnlyModels = [];
|
||||||
|
input.forEach((value) => {
|
||||||
|
cleanOnlyModels = cleanOnlyModels.concat(value.split(','));
|
||||||
|
});
|
||||||
|
return cleanOnlyModels;
|
||||||
|
}
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.gatewayConf = RED.nodes.getNode(props.gateway);
|
||||||
|
this.onlyModels = All.getOnlyModelsValue(props.onlyModels || []);
|
||||||
|
this.excludedSids = props.excludedSids;
|
||||||
|
this.setMessageListener();
|
||||||
|
}
|
||||||
|
setMessageListener() {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
if (this.gatewayConf) {
|
||||||
|
// Filter input
|
||||||
|
if (msg.payload && msg.payload.model && msg.payload.sid) {
|
||||||
|
if (!this.isDeviceValid(msg.payload.sid)) {
|
||||||
|
msg = null;
|
||||||
|
}
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let partsId = uniqid();
|
||||||
|
Object.keys(this.gatewayConf.deviceList || {})
|
||||||
|
.filter((sid) => this.isDeviceValid(sid))
|
||||||
|
.forEach((sid, i, subSids) => {
|
||||||
|
let curMsg = Object.assign({}, msg);
|
||||||
|
delete curMsg._msgid;
|
||||||
|
curMsg.parts = {
|
||||||
|
id: partsId,
|
||||||
|
index: i,
|
||||||
|
count: subSids.length,
|
||||||
|
};
|
||||||
|
curMsg.sid = sid;
|
||||||
|
curMsg.gateway = this.gatewayConf;
|
||||||
|
this.send(curMsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isDeviceValid(sid) {
|
||||||
|
if ((!this.onlyModels || this.onlyModels.length == 0) && (!this.excludedSids || this.excludedSids.length == 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let device = this.gatewayConf.deviceList[sid];
|
||||||
|
// Is excluded
|
||||||
|
if ((this.excludedSids && this.excludedSids.length != 0) && this.excludedSids.indexOf(sid) >= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((this.onlyModels && this.onlyModels.length != 0) && this.onlyModels.indexOf(device.internalModel) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-all`, All);
|
||||||
|
};
|
||||||
45
dist/nodes/gateway-subdevices/GatewaySubdevice.js
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED, type) => {
|
||||||
|
class GatewayDevice {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.gateway = RED.nodes.getNode(props.gateway);
|
||||||
|
this.sid = props.sid;
|
||||||
|
this.status({ fill: "grey", shape: "ring", text: "battery - na" });
|
||||||
|
this.setMessageListener();
|
||||||
|
}
|
||||||
|
setMessageListener() {
|
||||||
|
if (this.gateway) {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
let payload = msg.payload;
|
||||||
|
// Input from gateway
|
||||||
|
if (payload.sid) {
|
||||||
|
if (payload.sid == this.sid) {
|
||||||
|
let batteryLevel = payload.batteryLevel;
|
||||||
|
var status = {
|
||||||
|
fill: "green", shape: "dot",
|
||||||
|
text: "battery - " + batteryLevel + "%"
|
||||||
|
};
|
||||||
|
if (batteryLevel < 10) {
|
||||||
|
status.fill = "red";
|
||||||
|
}
|
||||||
|
else if (batteryLevel < 45) {
|
||||||
|
status.fill = "yellow";
|
||||||
|
}
|
||||||
|
this.status(status);
|
||||||
|
this.send([msg]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg.sid = this.sid;
|
||||||
|
msg.gateway = this.gateway;
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-mi.${type}`, GatewayDevice);
|
||||||
|
};
|
||||||
34
dist/nodes/gateway-subdevices/Plug.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class Plug {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.gateway = RED.nodes.getNode(props.gateway);
|
||||||
|
this.status({ fill: "grey", shape: "ring", text: "status" });
|
||||||
|
}
|
||||||
|
setListener() {
|
||||||
|
if (this.gateway) {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
var payload = msg.payload;
|
||||||
|
if (payload.sid) {
|
||||||
|
if (payload.sid == this.sid) {
|
||||||
|
if (payload.data.status && payload.data.status == "on") {
|
||||||
|
this.status({ fill: "green", shape: "dot", text: "on" });
|
||||||
|
}
|
||||||
|
else if (payload.data.status && payload.data.status == "off") {
|
||||||
|
this.status({ fill: "red", shape: "dot", text: "off" });
|
||||||
|
}
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-plug`, Plug);
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 842 B After Width: | Height: | Size: 842 B |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
729
dist/nodes/gateway-subdevices/index.html
vendored
Normal file
@@ -0,0 +1,729 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-all', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""},
|
||||||
|
onlyModels: {value: []},
|
||||||
|
excludedSids: { value: []}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
outputLabels: ["All devices"],
|
||||||
|
paletteLabel: "all",
|
||||||
|
icon: "mi-all.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "xiaomi-all";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
function getOnlyModelsValue(input) {
|
||||||
|
var cleanOnlyModels = [];
|
||||||
|
input.forEach(function(value) {
|
||||||
|
cleanOnlyModels = cleanOnlyModels.concat(value.split(','));
|
||||||
|
});
|
||||||
|
return cleanOnlyModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGateway(gateway, onlyModels, excludedSids) {
|
||||||
|
var configNodeID = gateway || $('#node-input-gateway').val();
|
||||||
|
if (configNodeID) {
|
||||||
|
var configNode = RED.nodes.node(configNodeID);
|
||||||
|
if(configNode) {
|
||||||
|
onlyModels = getOnlyModelsValue(onlyModels || $('#node-input-onlyModels').val() || []);
|
||||||
|
excludedSids = excludedSids || $('#node-input-excludedSids').val() || [];
|
||||||
|
$('#node-input-excludedSids').empty();
|
||||||
|
for (sid in configNode.deviceList) {
|
||||||
|
var device = configNode.deviceList[sid];
|
||||||
|
if (onlyModels.length == 0 || onlyModels.indexOf(device.internalModel) >= 0) {
|
||||||
|
var option = $('<option value="' + sid + '">' + device.name + '</option>');
|
||||||
|
if(excludedSids && excludedSids.indexOf(sid) >= 0) {
|
||||||
|
option.prop('selected', true);
|
||||||
|
}
|
||||||
|
$('#node-input-excludedSids').append(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeGateway(this.gateway, this.onlyModels, this.excludedSids);
|
||||||
|
$("#node-input-gateway, #node-input-onlyModels").change(function () {
|
||||||
|
changeGateway();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
if(!$('#node-input-onlyModels').val()) {
|
||||||
|
this.onlyModels = [];
|
||||||
|
}
|
||||||
|
if(!$('#node-input-excludedSids').val()) {
|
||||||
|
this.excludedSids = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-all">
|
||||||
|
<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>
|
||||||
|
<hr />
|
||||||
|
<h5>Filters</h5>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-onlyModels"><i class="icon-tag"></i> Only</label>
|
||||||
|
<select multiple id="node-input-onlyModels">
|
||||||
|
<option value="mi.weather">Temperature/humidty</option>
|
||||||
|
<option value="mi.motion">Motion</option>
|
||||||
|
<option value="mi.switch">Switches</option>
|
||||||
|
<option value="mi.magnet">Magnets</option>
|
||||||
|
<option value="mi.plug">Plugs</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-excludedSids"><i class="icon-tag"></i> Exclude</label>
|
||||||
|
<select multiple id="node-input-excludedSids" size=10></select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-all">
|
||||||
|
<p>All devices registred in the gateway, except gateway itself.</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">object</span>
|
||||||
|
</dt>
|
||||||
|
<dd>When use as an incoming filter node, <code>sid</code> and <code>model</code> are mandatory.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<li>Devices output
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">array</span></dt>
|
||||||
|
<dd>Array of devices.</dd>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-plug', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: "", required: true},
|
||||||
|
onmsg: {value: ""},
|
||||||
|
offmsg: {value: ""},
|
||||||
|
output: {value: "0"}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
paletteLabel: "plug (zigbee)",
|
||||||
|
icon: "outlet-icon.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "xiaomi-plug";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid').val(node.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGateway(model) {
|
||||||
|
var configNodeID = $('#node-input-gateway').val();
|
||||||
|
if (configNodeID) {
|
||||||
|
var configNode = RED.nodes.node(configNodeID);
|
||||||
|
if(configNode) {
|
||||||
|
$('#node-input-sid').empty();
|
||||||
|
for (key in configNode.deviceList) {
|
||||||
|
var device = configNode.deviceList[key];
|
||||||
|
if (device.model === model) {
|
||||||
|
$('#node-input-sid').append('<option value="' + device.sid + '">' + device.desc + '</option>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#node-input-sid").change(function () {
|
||||||
|
if(!this.name) {
|
||||||
|
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#node-input-gateway").change(function () {
|
||||||
|
changeGateway("plug");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
node.sid = $("#node-input-sid").val();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-plug">
|
||||||
|
<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-sid"><i class="icon-tag"></i> Device</label>
|
||||||
|
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-plug">
|
||||||
|
<p>The Xiaomi plug (zigbee) node</p>
|
||||||
|
|
||||||
|
<p>This is the plug (socket) version which is attached to a Xiaomi gateway. The Wifi version is not yet supported.</p>
|
||||||
|
<p>To switch an output you need to specify the key of the gateway in the gateway configuration; without the key
|
||||||
|
no output can be switched. To retrieve the gateway key consult the Xiaomi Mi Home App.</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">string | json</span>
|
||||||
|
</dt>
|
||||||
|
<dd>When the node is used as filter, gateway <code>plug</code> message of type <code>read_ack</code>, <code>heartbeat</code> or <code>report</code>. Or <code>on</code> or <code>off</code>.</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<li>Control output
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">json</span></dt>
|
||||||
|
<dd>Gateway <code>write_cmd</code> to switch the output on or off.</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<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>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>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-mi.magnet', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: "", required: true}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
paletteLabel: "magnet",
|
||||||
|
icon: "door-icon.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "mi-devices mi.magnet";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid').val(node.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGateway(model) {
|
||||||
|
var configNodeID = $('#node-input-gateway').val();
|
||||||
|
if (configNodeID) {
|
||||||
|
var configNode = RED.nodes.node(configNodeID);
|
||||||
|
if(configNode) {
|
||||||
|
$('#node-input-sid').empty();
|
||||||
|
for (sid in configNode.deviceList) {
|
||||||
|
var device = configNode.deviceList[sid];
|
||||||
|
if (device.internalModel === model) {
|
||||||
|
$('#node-input-sid').append('<option value="' + sid + '">' + device.name + ' - ' + sid + '</option>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#node-input-sid").change(function () {
|
||||||
|
if(!this.name) {
|
||||||
|
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#node-input-gateway").change(function () {
|
||||||
|
changeGateway("mi.magnet");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
node.sid = $("#node-input-sid").val();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-mi.magnet">
|
||||||
|
<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-sid"><i class="icon-tag"></i> Device</label>
|
||||||
|
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-mi.magnet">
|
||||||
|
<p>The Xiaomi contact sensor node</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">object</span>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
||||||
|
<hr>
|
||||||
|
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">object</span></dt>
|
||||||
|
<dd>Data from gateway when used as a filter (see below).</dd>
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device SID.</dd>
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>Details</h4>
|
||||||
|
<p>The incoming message is processed if the input <code>sid</code> matches the configured value for this device.</p>
|
||||||
|
<p>Sample payload after incoming incoming message:</p>
|
||||||
|
<p><pre>{
|
||||||
|
cmd: "read_ack"
|
||||||
|
model: "magnet"
|
||||||
|
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>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-mi.motion', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: "", required: true}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
paletteLabel: "motion",
|
||||||
|
icon: "motion-icon.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "mi-devices mi.motion";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid').val(node.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGateway(model) {
|
||||||
|
var configNodeID = $('#node-input-gateway').val();
|
||||||
|
if (configNodeID) {
|
||||||
|
var configNode = RED.nodes.node(configNodeID);
|
||||||
|
if(configNode) {
|
||||||
|
$('#node-input-sid').empty();
|
||||||
|
for (sid in configNode.deviceList) {
|
||||||
|
var device = configNode.deviceList[sid];
|
||||||
|
if (device.internalModel === model) {
|
||||||
|
$('#node-input-sid').append('<option value="' + sid + '">' + device.name + ' - ' + sid + '</option>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#node-input-sid").change(function () {
|
||||||
|
if(!this.name) {
|
||||||
|
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#node-input-gateway").change(function () {
|
||||||
|
changeGateway("mi.motion");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
node.sid = $("#node-input-sid").val();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-mi.motion">
|
||||||
|
<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-sid"><i class="icon-tag"></i> Device</label>
|
||||||
|
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-mi.motion">
|
||||||
|
<p>The Xiaomi body motion sensor node</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">object</span>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
||||||
|
<hr>
|
||||||
|
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">object</span></dt>
|
||||||
|
<dd>Data from gateway when used as a filter (see below).</dd>
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device SID.</dd>
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>Details</h4>
|
||||||
|
<p>The incoming message is processed if the input <code>sid</code> matches the configured value for this device.</p>
|
||||||
|
<p>Sample payload after incoming incoming message:</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>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-mi.weather', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: "", required: true}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
paletteLabel: "weather",
|
||||||
|
icon: "thermometer-icon.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "mi-devices mi.weather";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid').val(node.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGateway(model) {
|
||||||
|
var configNodeID = $('#node-input-gateway').val();
|
||||||
|
if (configNodeID) {
|
||||||
|
var configNode = RED.nodes.node(configNodeID);
|
||||||
|
if(configNode) {
|
||||||
|
$('#node-input-sid').empty();
|
||||||
|
for (sid in configNode.deviceList) {
|
||||||
|
var device = configNode.deviceList[sid];
|
||||||
|
if (device.internalModel === model) {
|
||||||
|
$('#node-input-sid').append('<option value="' + sid + '">' + device.name + ' - ' + sid + '</option>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#node-input-sid").change(function () {
|
||||||
|
if(!this.name) {
|
||||||
|
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#node-input-gateway").change(function () {
|
||||||
|
changeGateway("mi.weather");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
node.sid = $("#node-input-sid").val();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-mi.weather">
|
||||||
|
<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-sid"><i class="icon-tag"></i> Device</label>
|
||||||
|
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-mi.weather">
|
||||||
|
<p>The Xiaomi Humidity & Temperature sensor node</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">object</span>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
||||||
|
<hr>
|
||||||
|
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">object</span></dt>
|
||||||
|
<dd>Data from gateway when used as a filter (see below).</dd>
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device SID.</dd>
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>Details</h4>
|
||||||
|
<p>The incoming message is processed if the input <code>sid</code> matches the configured value for this device.</p>
|
||||||
|
<p>Sample payload after incoming incoming message:</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 kPa, <code>batteryLevel</code> is a computed percentage of remaining battery.
|
||||||
|
</p>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-mi.switch', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: "", required: true}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
paletteLabel: "switch",
|
||||||
|
icon: "mi-switch.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "mi-devices mi.switch";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid').val(node.sid);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeGateway(model) {
|
||||||
|
var configNodeID = $('#node-input-gateway').val();
|
||||||
|
if (configNodeID) {
|
||||||
|
var configNode = RED.nodes.node(configNodeID);
|
||||||
|
if(configNode) {
|
||||||
|
$('#node-input-sid').empty();
|
||||||
|
for (sid in configNode.deviceList) {
|
||||||
|
var device = configNode.deviceList[sid];
|
||||||
|
if (device.internalModel === model) {
|
||||||
|
$('#node-input-sid').append('<option value="' + sid + '">' + device.name + ' - ' + sid + '</option>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(node.sid) {
|
||||||
|
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#node-input-sid").change(function () {
|
||||||
|
if(!this.name) {
|
||||||
|
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#node-input-gateway").change(function () {
|
||||||
|
changeGateway("mi.switch");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
node.sid = $("#node-input-sid").val();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-mi.switch">
|
||||||
|
<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-sid"><i class="icon-tag"></i> Device</label>
|
||||||
|
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-mi.switch">
|
||||||
|
<p>The Xiaomi Switch sensor node</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">object</span>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
||||||
|
<hr>
|
||||||
|
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">object</span></dt>
|
||||||
|
<dd>Data from gateway when used as a filter (see below).</dd>
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device SID.</dd>
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>mi-devices-gateway configurator</code> object where the device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>Details</h4>
|
||||||
|
<p>The incoming message is processed if the input <code>sid</code> matches the configured value for this device.</p>
|
||||||
|
<p>Sample payload after incoming incoming message:</p>
|
||||||
|
<p><pre>{
|
||||||
|
cmd: "report"
|
||||||
|
model: "switch"
|
||||||
|
sid: "158d000128b124"
|
||||||
|
short_id: 56773
|
||||||
|
data: {
|
||||||
|
status: "click",
|
||||||
|
batteryLevel: 23
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
Where <code>batteryLevel</code> is a computed percentage of remaining battery.
|
||||||
|
</p>
|
||||||
|
</script>
|
||||||
11
dist/nodes/gateway-subdevices/index.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
const All_1 = require("./All");
|
||||||
|
const Plug_1 = require("./Plug");
|
||||||
|
const GatewaySubdevice_1 = require("./GatewaySubdevice");
|
||||||
|
module.exports = (RED) => {
|
||||||
|
All_1.default(RED);
|
||||||
|
Plug_1.default(RED);
|
||||||
|
["magnet", "motion", "sensor", "switch"].forEach((subdeviceType) => {
|
||||||
|
GatewaySubdevice_1.default(RED, subdeviceType);
|
||||||
|
});
|
||||||
|
};
|
||||||
46
dist/nodes/gateway/Gateway.js
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class Gateway {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.gatewayConf = RED.nodes.getNode(props.gateway);
|
||||||
|
this.status({ fill: "red", shape: "ring", text: "offline" });
|
||||||
|
if (this.gatewayConf.gateway) {
|
||||||
|
this.gatewayOnline();
|
||||||
|
}
|
||||||
|
this.gatewayConf.on('gateway-online', () => this.gatewayOnline());
|
||||||
|
this.gatewayConf.on('gateway-offline', () => this.gatewayOffline());
|
||||||
|
this.setMessageListener();
|
||||||
|
}
|
||||||
|
gatewayOnline() {
|
||||||
|
this.status({ fill: "blue", shape: "dot", text: "online" });
|
||||||
|
}
|
||||||
|
gatewayOffline() {
|
||||||
|
this.status({ fill: "red", shape: "ring", text: "offline" });
|
||||||
|
}
|
||||||
|
setMessageListener() {
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
if (this.gatewayConf.gateway) {
|
||||||
|
var payload = msg.payload;
|
||||||
|
// Input from gateway
|
||||||
|
if (payload.sid && payload.sid == this.gatewayConf.gateway.sid) {
|
||||||
|
if (payload.data.rgb) {
|
||||||
|
/*var decomposed = miDevicesUtils.computeColor(payload.data.rgb);
|
||||||
|
payload.data.brightness = decomposed.brightness;
|
||||||
|
payload.data.color = decomposed.color;*/
|
||||||
|
}
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg.sid = this.gatewayConf.gateway.sid;
|
||||||
|
msg.gateway = this.gatewayConf.gateway;
|
||||||
|
this.send(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-gateway`, Gateway);
|
||||||
|
};
|
||||||
58
dist/nodes/gateway/GatewayConfigurator.js
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
const GatewayServer_1 = require("../../devices/gateway/GatewayServer");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class GatewayConfigurator {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
let { sid, key, deviceList } = props;
|
||||||
|
this.sid = sid;
|
||||||
|
this.key = key;
|
||||||
|
this.deviceList = deviceList;
|
||||||
|
let server = GatewayServer_1.GatewayServer.getInstance();
|
||||||
|
if (this.sid) {
|
||||||
|
this.setGateway();
|
||||||
|
}
|
||||||
|
server.on('gateway-online', (sid) => {
|
||||||
|
if (sid === this.sid) {
|
||||||
|
this.setGateway();
|
||||||
|
this.emit('gateway-online');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.on('gateway-offline', (sid) => {
|
||||||
|
if (sid === this.sid) {
|
||||||
|
this._gateway = null;
|
||||||
|
this.emit('gateway-offline');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setGateway() {
|
||||||
|
this._gateway = GatewayServer_1.GatewayServer.getInstance().getGateway(this.sid);
|
||||||
|
if (this._gateway) {
|
||||||
|
this._gateway.password = this.key;
|
||||||
|
this._gateway.on("subdevice-values-updated", (sidOrMessage) => {
|
||||||
|
let sid = sidOrMessage.sid || sidOrMessage;
|
||||||
|
let subdevice = this._gateway.getSubdevice(sid);
|
||||||
|
if (subdevice) {
|
||||||
|
(sidOrMessage.data ? Object.keys(sidOrMessage.data) : []).forEach((key) => {
|
||||||
|
subdevice[key] = sidOrMessage.data[key];
|
||||||
|
});
|
||||||
|
this.emit('subdevice-update', subdevice);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get gateway() {
|
||||||
|
return this._gateway;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-gateway configurator`, GatewayConfigurator, {
|
||||||
|
settings: {
|
||||||
|
miDevicesGatewayConfiguratorDiscoveredGateways: {
|
||||||
|
value: GatewayServer_1.GatewayServer.getInstance().gateways,
|
||||||
|
exportable: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
27
dist/nodes/gateway/GatewayIn.js
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class GatewayIn {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.gatewayConf = RED.nodes.getNode(props.gateway);
|
||||||
|
this.status({ fill: "red", shape: "ring", text: "offline" });
|
||||||
|
if (this.gatewayConf.gateway) {
|
||||||
|
this.gatewayOnline();
|
||||||
|
}
|
||||||
|
this.gatewayConf.on('gateway-online', () => this.gatewayOnline());
|
||||||
|
this.gatewayConf.on('gateway-offline', () => this.gatewayOffline());
|
||||||
|
}
|
||||||
|
gatewayOnline() {
|
||||||
|
this.status({ fill: "blue", shape: "dot", text: "online" });
|
||||||
|
this.gatewayConf.on('subdevice-update', (subdevice) => {
|
||||||
|
this.send({ payload: subdevice });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
gatewayOffline() {
|
||||||
|
this.status({ fill: "red", shape: "ring", text: "offline" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-gateway in`, GatewayIn);
|
||||||
|
};
|
||||||
35
dist/nodes/gateway/GatewayOut.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
const GatewayServer_1 = require("../../devices/gateway/GatewayServer");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class GatewayOut {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.setMessageListener();
|
||||||
|
}
|
||||||
|
setMessageListener() {
|
||||||
|
this.on("input", (msg) => {
|
||||||
|
if (msg.hasOwnProperty("payload") && msg.hasOwnProperty("gateway")) {
|
||||||
|
let gateway = GatewayServer_1.GatewayServer.getInstance().getGateway(msg.gateway.sid);
|
||||||
|
if (gateway) {
|
||||||
|
if (msg.payload.cmd) {
|
||||||
|
gateway.send(msg);
|
||||||
|
}
|
||||||
|
else if (msg.payload.action) {
|
||||||
|
switch (msg.payload.action) {
|
||||||
|
case 'setLight':
|
||||||
|
gateway.setLight(msg.payload.brightness, msg.payload.color);
|
||||||
|
break;
|
||||||
|
case 'playSound':
|
||||||
|
gateway.playSound(msg.payload.mid, msg.payload.volume);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-gateway out`, GatewayOut);
|
||||||
|
};
|
||||||
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
295
dist/nodes/gateway/index.html
vendored
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-gateway configurator', {
|
||||||
|
category: 'config',
|
||||||
|
defaults: {
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: ""},
|
||||||
|
key: { value: "" },
|
||||||
|
deviceList: {value:{}}
|
||||||
|
},
|
||||||
|
paletteLabel: "gateway configurator",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "gateway configurator";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var foundGateways = RED.settings.miDevicesGatewayConfiguratorDiscoveredGateways;
|
||||||
|
Object.keys(foundGateways).forEach(function(sid) {
|
||||||
|
var gateway = foundGateways[sid];
|
||||||
|
$('#discovered-gateways').append('<option value="' + gateway.sid + '">' + gateway.sid + ' - ' + gateway.ip + '</option>');
|
||||||
|
});
|
||||||
|
var node = this;
|
||||||
|
|
||||||
|
var devicesConfig = {
|
||||||
|
"mi.weather": {label:"weather", icon:"icons/node-red-contrib-mi-devices/thermometer-icon.png"},
|
||||||
|
"mi.magnet": {label:"magnet", icon:"icons/node-red-contrib-mi-devices/door-icon.png"},
|
||||||
|
"mi.motion": {label:"motion", icon:"icons/node-red-contrib-mi-devices/motion-icon.png"},
|
||||||
|
"mi.switch": {label:"switch", icon:"icons/node-red-contrib-mi-devices/mi-switch.png"},
|
||||||
|
"mi.plug": {label:"plug zigbee", icon:"icons/node-red-contrib-mi-devices/outlet-icon.png"}
|
||||||
|
};
|
||||||
|
|
||||||
|
$("#node-config-input-subdevices").css('min-height','250px').css('min-width','450px').editableList({
|
||||||
|
addItem: function(container, i, device) {
|
||||||
|
var row = container;
|
||||||
|
$('<label/>',{for:"node-config-input-sid-"+i, style:"margin-left: 3px; width: 15px;vertical-align:middle"}).appendTo(row);
|
||||||
|
var sid = $('<input/>',{id:"node-config-input-sid-"+i,type:"text", placeholder:"SID", style:"width:auto;vertical-align:top"}).appendTo(row);
|
||||||
|
sid.typedInput({
|
||||||
|
default: 'mi.weather',
|
||||||
|
types: Object.keys(devicesConfig).map(function(type) {
|
||||||
|
var cleanType = devicesConfig[type];
|
||||||
|
cleanType.value = type;
|
||||||
|
return cleanType;
|
||||||
|
})
|
||||||
|
});
|
||||||
|
$('<label/>',{for:"node-config-input-desc-"+i, style:"margin-left: 7px; width: 20px;vertical-align:middle"}).html('<i class="fa fa-pencil-square-o"></i>').appendTo(row);
|
||||||
|
var desc = $('<input/>',{id:"node-config-input-desc-"+i, type:"text", placeholder:"name", style:"width:auto;vertical-align:top"}).appendTo(row);
|
||||||
|
sid.typedInput('value', device.sid);
|
||||||
|
sid.typedInput('type', device.internalModel);
|
||||||
|
desc.val(device.name);
|
||||||
|
container.parent().attr("data-sid", device.sid);
|
||||||
|
},
|
||||||
|
removeItem: function(device) {
|
||||||
|
$("#node-config-input-subdevices").find("[data-sid=" + device.sid + "]").remove();
|
||||||
|
},
|
||||||
|
sortable: false,
|
||||||
|
removable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#discovered-gateways').on('change', function() {
|
||||||
|
var sid = $('#discovered-gateways').val();
|
||||||
|
|
||||||
|
$('#input-subdevices > *').remove();
|
||||||
|
var gateway = sid && RED.settings.miDevicesGatewayConfiguratorDiscoveredGateways[sid];
|
||||||
|
$("#node-config-input-sid").val(gateway && gateway.sid);
|
||||||
|
$("#node-config-input-key").val(gateway && gateway.key);
|
||||||
|
|
||||||
|
$("#node-config-input-subdevices").editableList('items').each(function(i, elt) {
|
||||||
|
$("#node-config-input-subdevices").editableList('removeItem', {sid: $(elt).find("#node-config-input-sid-"+i).val()});
|
||||||
|
});
|
||||||
|
var subdevices = gateway && Object.keys(gateway.subdevices).map(function(sid) { return gateway.subdevices[sid]; });
|
||||||
|
subdevices && subdevices.sort(function(a, b) { return a.internalModel > b.internalModel; }).forEach(function(device) {
|
||||||
|
if(!devicesConfig[device.internalModel] || !device.sid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(node.deviceList[device.sid]) {
|
||||||
|
device.name = node.deviceList[device.sid].name;
|
||||||
|
}
|
||||||
|
$("#node-config-input-subdevices").editableList('addItem', {
|
||||||
|
sid: device.sid,
|
||||||
|
internalModel: device.internalModel,
|
||||||
|
name: device.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var listHeight = $("#node-config-input-subdevices").editableList('items').size() * 51 + 50;
|
||||||
|
$("#node-config-input-subdevices").editableList('height', listHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(this.deviceList || {}).forEach(function(sid) {
|
||||||
|
var device = node.deviceList[sid];
|
||||||
|
$("#node-config-input-subdevices").editableList('addItem', {
|
||||||
|
sid: sid,
|
||||||
|
internalModel: device.internalModel,
|
||||||
|
name: device.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var listHeight = $("#node-config-input-subdevices").editableList('items').size() * 51 + 50;
|
||||||
|
$("#node-config-input-subdevices").editableList('height', listHeight);
|
||||||
|
},
|
||||||
|
oneditsave: function() {
|
||||||
|
var node = this;
|
||||||
|
var devices = $("#node-config-input-subdevices").editableList('items');
|
||||||
|
|
||||||
|
devices.each(function(i, elt) {
|
||||||
|
var deviceElement = $(elt);
|
||||||
|
var sid = deviceElement.find("#node-config-input-sid-"+i).val();
|
||||||
|
var desc = deviceElement.find("#node-config-input-desc-"+i).val();
|
||||||
|
var internalModel = deviceElement.find("#node-config-input-sid-"+i).typedInput('type');
|
||||||
|
node.deviceList[sid] = {internalModel: internalModel, name: desc};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-gateway configurator">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="discovered-gateways"><i class="fa fa-search"></i> Found gateways</label>
|
||||||
|
<select id="discovered-gateways">
|
||||||
|
<option>- Select -</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<h4>Gateway</h4>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||||
|
<input type="text" id="node-config-input-name" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-sid"><i class="fa fa-barcode"></i> SID</label>
|
||||||
|
<input type="text" id="node-config-input-sid" placeholder="sid">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-gatewayKey"><i class="fa fa-key"></i> Key</label>
|
||||||
|
<input type="text" id="node-config-input-key" placeholder="Key">
|
||||||
|
</div>
|
||||||
|
<h4>Devices</h4>
|
||||||
|
<div class="form-row node-config-input-subdevices">
|
||||||
|
<ol id="node-config-input-subdevices"></ol>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-gateway configurator">
|
||||||
|
<p>Gateway configuration for Xiaomi nodes.</p>
|
||||||
|
<h3>Details</h3>
|
||||||
|
<p>This configuration node is used by the Xiaomi device nodes. Here you can add
|
||||||
|
devices with their device-id (SID), type and a description.</p>
|
||||||
|
<p>At the moment the following devices are supported:
|
||||||
|
<lu>
|
||||||
|
<li>Humidity & Temperature sensor [sensor ht/]</li>
|
||||||
|
<li>Body motion sensor [motion]</li>
|
||||||
|
<li>Magnet contact sensor [contact]</li>
|
||||||
|
<li>Wall socket plug (zigbee) [plug]</li>
|
||||||
|
<li>Push button [switch]</li>
|
||||||
|
</lu>
|
||||||
|
</p>
|
||||||
|
<p>To be able to receive messages from the Xiaomi gateway, you need to set the gateway
|
||||||
|
in developer mode. Once in developer mode, the gateway sends JSON messages over the network as
|
||||||
|
UDP packages. On the internet their are a lot of guides on how to put the gateway in developer mode.</p>
|
||||||
|
<p>If you want to use the wall sockets, you need to set the key from the gateway. The key can be
|
||||||
|
retrieved via the Xiaomi Home App when in developer mode. Enter the key here and it is used
|
||||||
|
together with the token from the gateway's heartbeat message to recalculate the key to switch
|
||||||
|
the plug. If you do not specify a key, the plug-node can not be used.</p>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-gateway', {
|
||||||
|
category: 'xiaomi',
|
||||||
|
color: '#3FADB5',
|
||||||
|
defaults: {
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"},
|
||||||
|
name: {value: ""}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 1,
|
||||||
|
outputLabels: ["Gateway"],
|
||||||
|
paletteLabel: "gateway",
|
||||||
|
icon: "mijia.png",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "gateway";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-gateway">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-gateway"><i class="fa fa-wrench"></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="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="mi-devices-gateway">
|
||||||
|
<p>The gateway itself.</p>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<li>Devices output
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>gateway <span class="property-type">mi-devices-gateway configurator</span></dt>
|
||||||
|
<dd>The gateway.</dd>
|
||||||
|
</dl>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">json</span></dt>
|
||||||
|
<dd>Data from gateway, with computed data.</dd>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</script>
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-gateway in">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-gateway"><i class="fa fa-wrench"></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="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="mi-devices-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('mi-devices-gateway in',{
|
||||||
|
category: 'xiaomi in out',
|
||||||
|
color: '#087F8A',
|
||||||
|
defaults: {
|
||||||
|
name: {value:""},
|
||||||
|
gateway: {value:"", type:"mi-devices-gateway configurator"}
|
||||||
|
},
|
||||||
|
inputs:0,
|
||||||
|
outputs:1,
|
||||||
|
paletteLabel: "gateway in",
|
||||||
|
icon: "mijia-io.png",
|
||||||
|
label: function() {
|
||||||
|
return this.name||"gateway in";
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-gateway out">
|
||||||
|
<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="mi-devices-gateway out">
|
||||||
|
<p>This node sends <code>msg.payload</code> to <code>msg.gateway</code> Xiaomi Gateway.</p>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-gateway out',{
|
||||||
|
category: 'xiaomi in out',
|
||||||
|
color: '#087F8A',
|
||||||
|
defaults: {
|
||||||
|
name: {value:""}
|
||||||
|
},
|
||||||
|
inputs:1,
|
||||||
|
outputs:0,
|
||||||
|
paletteLabel: "gateway out",
|
||||||
|
icon: "mijia-io.png",
|
||||||
|
align: "right",
|
||||||
|
label: function() {
|
||||||
|
return this.name||"gateway out";
|
||||||
|
},
|
||||||
|
labelStyle: function() {
|
||||||
|
return this.name?"node_label_italic":"";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
13
dist/nodes/gateway/index.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"use strict";
|
||||||
|
const GatewayServer_1 = require("../../devices/gateway/GatewayServer");
|
||||||
|
const GatewayConfigurator_1 = require("./GatewayConfigurator");
|
||||||
|
const Gateway_1 = require("./Gateway");
|
||||||
|
const GatewayIn_1 = require("./GatewayIn");
|
||||||
|
const GatewayOut_1 = require("./GatewayOut");
|
||||||
|
module.exports = (RED) => {
|
||||||
|
GatewayServer_1.GatewayServer.getInstance().discover();
|
||||||
|
GatewayConfigurator_1.default(RED);
|
||||||
|
Gateway_1.default(RED);
|
||||||
|
GatewayIn_1.default(RED);
|
||||||
|
GatewayOut_1.default(RED);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
RED.nodes.registerType('xiaomi-plug-wifi', {
|
RED.nodes.registerType('mi-devices-plug-wifi', {
|
||||||
category: 'xiaomi',
|
category: 'xiaomi',
|
||||||
color: '#3FADB5',
|
color: '#3FADB5',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
paletteLabel: "plug (wifi)",
|
paletteLabel: "plug (wifi)",
|
||||||
icon: "outlet-wifi-icon.png",
|
icon: "outlet-wifi-icon.png",
|
||||||
label: function () {
|
label: function () {
|
||||||
return this.name || "xiaomi-plug-wifi";
|
return this.name || "plug-wifi";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-plug-wifi">
|
<script type="text/x-red" data-template-name="mi-devices-plug-wifi">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
<label for="node-input-name"><i class="icon-tag"></i> Name</label>
|
||||||
<input type="text" id="node-input-name" placeholder="Name">
|
<input type="text" id="node-input-name" placeholder="Name">
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-plug-wifi">
|
<script type="text/x-red" data-help-name="mi-devices-plug-wifi">
|
||||||
<p>The Xiaomi plug (wifi) node</p>
|
<p>The Xiaomi plug (wifi) node</p>
|
||||||
|
|
||||||
<p>This is the wiFi plug (socket). To control the Wifi-Plug, extensive use is made of the miio library created by <a href="https://github.com/aholstenson/miio">Andreas Holstenson</a>. Make sure to check his page for compatible devices.</p>
|
<p>This is the wiFi plug (socket). To control the Wifi-Plug, extensive use is made of the miio library created by <a href="https://github.com/aholstenson/miio">Andreas Holstenson</a>. Make sure to check his page for compatible devices.</p>
|
||||||
139
dist/nodes/plug-wifi/index.js
vendored
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
"use strict";
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
const miio = require("miio");
|
||||||
|
module.exports = (RED) => {
|
||||||
|
var connectionState = "timeout";
|
||||||
|
var retryTimer;
|
||||||
|
var delayedStatusMsgTimer;
|
||||||
|
function XiaomiPlugWifiNode(config) {
|
||||||
|
RED.nodes.createNode(this, config);
|
||||||
|
this.ip = config.ip;
|
||||||
|
this.plug = null;
|
||||||
|
this.status({ fill: "yellow", shape: "dot", text: "connecting" });
|
||||||
|
miio.device({ address: this.ip })
|
||||||
|
.then((plug) => {
|
||||||
|
this.plug = plug;
|
||||||
|
this.status({ fill: "green", shape: "dot", text: "connected" });
|
||||||
|
connectionState = "connected";
|
||||||
|
delayedStatusMsgUpdate();
|
||||||
|
this.plug.on('propertyChanged', (e) => {
|
||||||
|
if (e.property === "power") {
|
||||||
|
if (e.value['0']) {
|
||||||
|
setState("on");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState("off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watchdog();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
connectionState = "reconnecting";
|
||||||
|
watchdog();
|
||||||
|
});
|
||||||
|
this.on('input', (msg) => {
|
||||||
|
var payload = msg.payload;
|
||||||
|
if (connectionState === "connected") {
|
||||||
|
if (payload == 'on') {
|
||||||
|
this.plug.setPower(true);
|
||||||
|
}
|
||||||
|
if (payload == 'off') {
|
||||||
|
this.plug.setPower(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.on('close', (done) => {
|
||||||
|
if (retryTimer) {
|
||||||
|
clearTimeout(retryTimer);
|
||||||
|
}
|
||||||
|
if (delayedStatusMsgTimer) {
|
||||||
|
clearTimeout(delayedStatusMsgTimer);
|
||||||
|
}
|
||||||
|
if (this.plug) {
|
||||||
|
this.plug.destroy();
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
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 = () => {
|
||||||
|
delayedStatusMsgTimer = setTimeout(() => {
|
||||||
|
if (this.plug.power()['0']) {
|
||||||
|
setState("on");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState("off");
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
};
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setState("off");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (connectionState === "reconnecting") {
|
||||||
|
this.status({ fill: "green", shape: "dot", text: "connected" });
|
||||||
|
connectionState = "connected";
|
||||||
|
delayedStatusMsgUpdate();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
connectionState = "reconnecting";
|
||||||
|
if (this.plug) {
|
||||||
|
this.plug.destroy();
|
||||||
|
this.plug = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
var watchdog = () => {
|
||||||
|
var node = this;
|
||||||
|
function retryTimer() {
|
||||||
|
discoverDevice();
|
||||||
|
if (connectionState === "reconnecting") {
|
||||||
|
node.status({ fill: "red", shape: "dot", text: "reconnecting" });
|
||||||
|
}
|
||||||
|
setTimeout(retryTimer, 30000);
|
||||||
|
}
|
||||||
|
setTimeout(retryTimer, 30000);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
process.on('unhandledRejection', function (reason, p) {
|
||||||
|
// console.log("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason);
|
||||||
|
var message = reason + "";
|
||||||
|
if (message.indexOf("Call to device timed out") >= 0) {
|
||||||
|
if (this.plug) {
|
||||||
|
console.log("Issue with miio package; discard plug and reconnect.");
|
||||||
|
this.plug.destroy();
|
||||||
|
this.plug = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-wifi-plug`, XiaomiPlugWifiNode);
|
||||||
|
};
|
||||||
43
dist/nodes/yeelight/YeelightConfigurator.js
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
const YeelightServer_1 = require("../../devices/yeelight/YeelightServer");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class YeelightConfigurator {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
let { sid } = props;
|
||||||
|
this.sid = parseInt(sid);
|
||||||
|
if (this.sid) {
|
||||||
|
this.setBulb();
|
||||||
|
}
|
||||||
|
let server = YeelightServer_1.YeelightServer.getInstance();
|
||||||
|
server.on('yeelight-online', (sid) => {
|
||||||
|
if (sid === this.sid) {
|
||||||
|
this.setBulb();
|
||||||
|
this.emit('bulb-online');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.on('yeelight-offline', (sid) => {
|
||||||
|
if (sid === this.sid) {
|
||||||
|
this._bulb = null;
|
||||||
|
this.emit('bulb-offline');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setBulb() {
|
||||||
|
this._bulb = YeelightServer_1.YeelightServer.getInstance().getBulb(this.sid);
|
||||||
|
}
|
||||||
|
get bulb() {
|
||||||
|
return this._bulb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-yeelight configurator`, YeelightConfigurator, {
|
||||||
|
settings: {
|
||||||
|
miDevicesYeelightConfiguratorDiscoveredBulbs: {
|
||||||
|
value: YeelightServer_1.YeelightServer.getInstance().bulbs,
|
||||||
|
exportable: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
64
dist/nodes/yeelight/YeelightOut.js
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
const constants_1 = require("../constants");
|
||||||
|
exports.default = (RED) => {
|
||||||
|
class YeelightOut {
|
||||||
|
constructor(props) {
|
||||||
|
RED.nodes.createNode(this, props);
|
||||||
|
this.yeelightConf = RED.nodes.getNode(props.yeelight);
|
||||||
|
this.status({ fill: "red", shape: "ring", text: "offline" });
|
||||||
|
if (this.yeelightConf.bulb) {
|
||||||
|
this.yeelightOnline();
|
||||||
|
}
|
||||||
|
this.yeelightConf.on('bulb-online', () => this.yeelightOnline());
|
||||||
|
this.yeelightConf.on('bulb-offline', () => this.yeelightOffline());
|
||||||
|
this.setListener();
|
||||||
|
}
|
||||||
|
yeelightOnline() {
|
||||||
|
this.status({ fill: "blue", shape: "dot", text: "online" });
|
||||||
|
}
|
||||||
|
yeelightOffline() {
|
||||||
|
this.status({ fill: "red", shape: "ring", text: "offline" });
|
||||||
|
}
|
||||||
|
setListener() {
|
||||||
|
this.on("input", (msg) => {
|
||||||
|
let bulb = this.yeelightConf.bulb;
|
||||||
|
if (msg.hasOwnProperty("payload") && bulb) {
|
||||||
|
switch (msg.payload.action) {
|
||||||
|
case 'turn_on':
|
||||||
|
bulb.turnOn();
|
||||||
|
break;
|
||||||
|
case 'turn_off':
|
||||||
|
bulb.turnOff();
|
||||||
|
break;
|
||||||
|
case 'toggle':
|
||||||
|
bulb.toggle();
|
||||||
|
break;
|
||||||
|
case 'setLight':
|
||||||
|
if (msg.payload.color !== undefined) {
|
||||||
|
let rgb = msg.payload.color.blue | (msg.payload.color.green << 8) | (msg.payload.color.red << 16);
|
||||||
|
let hex = '#' + (0x1000000 + rgb).toString(16).slice(1);
|
||||||
|
bulb.setRGB(hex);
|
||||||
|
}
|
||||||
|
(msg.payload.brightness !== undefined) && bulb.setBrightness(Math.max(1, msg.payload.brightness));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/*(<any> this).on('input', (msg) => {
|
||||||
|
if (this.yeelightConf.bulb) {
|
||||||
|
|
||||||
|
|
||||||
|
if(msg.payload.color !== undefined) {
|
||||||
|
// TODO: revoir la couleur
|
||||||
|
this.yeelightConf.bulb.setRGB(msg.payload.color);
|
||||||
|
}
|
||||||
|
if(msg.payload.brightness !== undefined) {
|
||||||
|
this.yeelightConf.bulb.setBrightness(msg.payload.brightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.nodes.registerType(`${constants_1.Constants.NODES_PREFIX}-yeelight out`, YeelightOut);
|
||||||
|
};
|
||||||
BIN
dist/nodes/yeelight/icons/mi-yeelight.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
160
dist/nodes/yeelight/index.html
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-yeelight configurator', {
|
||||||
|
category: 'config',
|
||||||
|
defaults: {
|
||||||
|
name: {value: ""},
|
||||||
|
sid: {value: ""}
|
||||||
|
},
|
||||||
|
label: function () {
|
||||||
|
return this.name || "yeelight conf";
|
||||||
|
},
|
||||||
|
oneditprepare: function() {
|
||||||
|
var foundBulbs = RED.settings.miDevicesYeelightConfiguratorDiscoveredBulbs;
|
||||||
|
Object.keys(foundBulbs).forEach(function(sid) {
|
||||||
|
var bulb = foundBulbs[sid];
|
||||||
|
$('#discovered-bulbs').append('<option value="' + bulb.sid + '">' + (bulb.name || bulb.sid) + ' - ' + bulb.model + ' - ' + bulb.ip + '</option>');
|
||||||
|
});
|
||||||
|
$('#discovered-bulbs').on('change', function() {
|
||||||
|
var sid = $('#discovered-bulbs').val();
|
||||||
|
var bulb = foundBulbs[sid];
|
||||||
|
$("#node-config-input-name").val(bulb && bulb.name);
|
||||||
|
$("#node-config-input-sid").val(bulb && bulb.sid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-yeelight configurator">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="discovered-bulbs"><i class="fa fa-search"></i> Found bulbs</label>
|
||||||
|
<select id="discovered-bulbs">
|
||||||
|
<option>- Select -</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
||||||
|
<input type="text" id="node-config-input-name" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-config-input-sid"><i class="fa fa-barcode"></i> SID</label>
|
||||||
|
<input type="text" id="node-config-input-sid" placeholder="sid">
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-yeelight configurator">
|
||||||
|
<p>Xiaomi Yeelight configuration node.</p>
|
||||||
|
<h3>Details</h3>
|
||||||
|
<p>This configuration node is used by the Yeelight nodes. Here you can add
|
||||||
|
devices with their device-id (SID), type and a description.</p>
|
||||||
|
<p>At the moment the following devices are supported:
|
||||||
|
<lu>
|
||||||
|
<li>Humidity & Temperature sensor [sensor ht/]</li>
|
||||||
|
<li>Body motion sensor [motion]</li>
|
||||||
|
<li>Magnet contact sensor [contact]</li>
|
||||||
|
<li>Wall socket plug (zigbee) [plug]</li>
|
||||||
|
<li>Push button [switch]</li>
|
||||||
|
</lu>
|
||||||
|
</p>
|
||||||
|
<p>To be able to receive messages from the Xiaomi gateway, you need to set the gateway
|
||||||
|
in developer mode. Once in developer mode, the gateway sends JSON messages over the network as
|
||||||
|
UDP packages. On the internet their are a lot of guides on how to put the gateway in developer mode.</p>
|
||||||
|
<p>If you want to use the wall sockets, you need to set the key from the gateway. The key can be
|
||||||
|
retrieved via the Xiaomi Home App when in developer mode. Enter the key here and it is used
|
||||||
|
together with the token from the gateway's heartbeat message to recalculate the key to switch
|
||||||
|
the plug. If you do not specify a key, the plug-node can not be used.</p>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
RED.nodes.registerType('mi-devices-yeelight out', {
|
||||||
|
category: 'xiaomi in out',
|
||||||
|
color: '#087F8A',
|
||||||
|
defaults: {
|
||||||
|
name: {value: ""},
|
||||||
|
yeelight: {value:"", type:"mi-devices-yeelight configurator"}
|
||||||
|
},
|
||||||
|
inputs: 1,
|
||||||
|
outputs: 0,
|
||||||
|
paletteLabel: "yeelight out",
|
||||||
|
icon: "mi-yeelight.png",
|
||||||
|
align: "right",
|
||||||
|
label: function () {
|
||||||
|
return this.name || "yeelight out";
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-template-name="mi-devices-yeelight out">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="node-input-yeelight"><i class="fa fa-wrench"></i> Yeelight</label>
|
||||||
|
<input type="text" id="node-input-yeelight" placeholder="yeelight">
|
||||||
|
</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>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script type="text/x-red" data-help-name="mi-devices-yeelight out">
|
||||||
|
<p>The Xiaomi Yeelight node</p>
|
||||||
|
|
||||||
|
<h3>Inputs</h3>
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload
|
||||||
|
<span class="property-type">object</span>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
||||||
|
<hr>
|
||||||
|
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>.<br>
|
||||||
|
<hr>
|
||||||
|
Input Gateway node produces message of type <code>read_ack</code>, <code>heartbeat</code> or <code>report</code>.
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h3>Outputs</h3>
|
||||||
|
<ol class="node-ports">
|
||||||
|
<dl class="message-properties">
|
||||||
|
<dt>payload <span class="property-type">object</span></dt>
|
||||||
|
<dd>Data from gateway when used as a filter (see below).</dd>
|
||||||
|
<dt>sid <span class="property-type">string</span></dt>
|
||||||
|
<dd>Device SID.</dd>
|
||||||
|
<dt>gateway <span class="property-type">object</span></dt>
|
||||||
|
<dd>The <code>xiaomi-configurator</code> object where the device is registred.</dd>
|
||||||
|
</dl>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4>Details</h4>
|
||||||
|
<p>The incoming message is processed if the input <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",
|
||||||
|
batteryLevel: 23
|
||||||
|
}
|
||||||
|
}</pre></p>
|
||||||
|
|
||||||
|
</script>
|
||||||
9
dist/nodes/yeelight/index.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"use strict";
|
||||||
|
const YeelightServer_1 = require("../../devices/yeelight/YeelightServer");
|
||||||
|
const YeelightConfigurator_1 = require("./YeelightConfigurator");
|
||||||
|
const YeelightOut_1 = require("./YeelightOut");
|
||||||
|
module.exports = (RED) => {
|
||||||
|
YeelightServer_1.YeelightServer.getInstance().discover();
|
||||||
|
YeelightConfigurator_1.default(RED);
|
||||||
|
YeelightOut_1.default(RED);
|
||||||
|
};
|
||||||
23
dist/utils/Color.js
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
class Color {
|
||||||
|
static toValue(red, green, blue, brightness) {
|
||||||
|
return (brightness !== undefined ? 256 * 256 * 256 * brightness : 0) + (256 * 256 * red) + (256 * green) + blue;
|
||||||
|
}
|
||||||
|
static fromValue(rgb) {
|
||||||
|
var blue = rgb % 256;
|
||||||
|
rgb = Math.max(rgb - blue, 0);
|
||||||
|
var green = rgb % (256 * 256);
|
||||||
|
rgb = Math.max(rgb - green, 0);
|
||||||
|
green /= 256;
|
||||||
|
var red = rgb % (256 * 256 * 256);
|
||||||
|
rgb = Math.max(rgb - red, 0);
|
||||||
|
red /= 256 * 256;
|
||||||
|
var brightness = rgb / (256 * 256 * 256);
|
||||||
|
return {
|
||||||
|
brightness: brightness,
|
||||||
|
color: { red: red, green: green, blue: blue }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.Color = Color;
|
||||||
6
dist/utils/index.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
"use strict";
|
||||||
|
function __export(m) {
|
||||||
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
||||||
|
}
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
__export(require("./Color"));
|
||||||
BIN
icons/actions/mi-bulb.png
Normal file
|
After Width: | Height: | Size: 1016 B |
BIN
icons/actions/mi-click.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/actions/mi-double-click.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/actions/mi-list.png
Normal file
|
After Width: | Height: | Size: 601 B |
BIN
icons/actions/mi-mute.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
icons/actions/mi-off.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
icons/actions/mi-on.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/actions/mi-read.png
Normal file
|
After Width: | Height: | Size: 990 B |
BIN
icons/actions/mi-sound.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
icons/actions/mi-toggle.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
icons/gateway-subdevices/door-icon.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
icons/gateway-subdevices/mi-all.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
icons/gateway-subdevices/mi-switch.png
Normal file
|
After Width: | Height: | Size: 842 B |
BIN
icons/gateway-subdevices/motion-icon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
icons/gateway-subdevices/outlet-icon.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
icons/gateway-subdevices/thermometer-icon.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
icons/gateway/mijia-io.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
icons/gateway/mijia.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,185 +0,0 @@
|
|||||||
const miDevicesUtils = require('../src/utils');
|
|
||||||
|
|
||||||
module.exports = (RED) => {
|
|
||||||
/*********************************************
|
|
||||||
Read data from Gateway
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionRead(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
if(msg.sid) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: { status: "click", sid: msg.sid }
|
|
||||||
};
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions click", XiaomiActionSingleClick);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Virtual Double click on a button
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionDoubleClick(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: { status: "double_click", sid: msg.sid }
|
|
||||||
};
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions double_click", XiaomiActionDoubleClick);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Set the gateway light
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionGatewayLight(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
this.color = config.color;
|
|
||||||
this.brightness = config.brightness;
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
let color = msg.color || this.color;
|
|
||||||
let brightness = msg.brightness || this.brightness;
|
|
||||||
if(msg.sid) {
|
|
||||||
let rgb = miDevicesUtils.computeColorValue(color.red, color.green, color.blue, brightness);
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: { rgb: rgb, sid: msg.sid }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg.payload = {
|
|
||||||
color: miDevicesUtils.computeColorValue(color.red, color.green, color.blue),
|
|
||||||
brightness: brightness
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions gateway_light", XiaomiActionGatewayLight);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Play a sound on the gateway
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionGatewaySound(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
this.mid = config.mid;
|
|
||||||
this.volume = config.volume;
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: {
|
|
||||||
mid: parseInt(msg.mid || this.mid),
|
|
||||||
volume: parseInt(msg.volume || this.volume),
|
|
||||||
sid: msg.sid
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions gateway_sound", XiaomiActionGatewaySound);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Stop playing a sound on the gateway
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionGatewayStopSound(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: { mid: 1000, sid: msg.sid }
|
|
||||||
};
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions gateway_stop_sound", XiaomiActionGatewayStopSound);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Turn device on
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionPowerOn(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
if(msg.sid){
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: { status: "on", sid: msg.sid }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg.payload = "on";
|
|
||||||
}
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions on", XiaomiActionPowerOn);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Turn device off
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionPowerOff(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
if(msg.sid){
|
|
||||||
msg.payload = {
|
|
||||||
cmd: "write",
|
|
||||||
data: { status: "off", sid: msg.sid }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg.payload = "off";
|
|
||||||
}
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions off", XiaomiActionPowerOff);
|
|
||||||
|
|
||||||
/*********************************************
|
|
||||||
Toggle device
|
|
||||||
*********************************************/
|
|
||||||
function XiaomiActionToggle(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
msg.payload = "toggle";
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-actions toggle", XiaomiActionToggle);
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
module.exports = (RED) => {
|
|
||||||
function getOnlyModelsValue(input) {
|
|
||||||
var cleanOnlyModels = [];
|
|
||||||
input.forEach((value) => {
|
|
||||||
cleanOnlyModels = cleanOnlyModels.concat(value.split(','));
|
|
||||||
});
|
|
||||||
return cleanOnlyModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
function XiaomiAllNode(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
this.gateway = RED.nodes.getNode(config.gateway);
|
|
||||||
this.onlyModels = getOnlyModelsValue(config.onlyModels || []);
|
|
||||||
this.excludedSids = config.excludedSids;
|
|
||||||
|
|
||||||
|
|
||||||
this.isDeviceValid = (device) => {
|
|
||||||
if((!this.onlyModels || this.onlyModels.length == 0) && (!this.excludedSids || this.excludedSids.length == 0)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Is excluded
|
|
||||||
if((this.excludedSids && this.excludedSids.length != 0) && this.excludedSids.indexOf(device.sid) >= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if((this.onlyModels && this.onlyModels.length != 0) && this.onlyModels.indexOf(device.model) >= 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.gateway) {
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
// Filter input
|
|
||||||
if(msg.payload && msg.payload.model && msg.payload.sid) {
|
|
||||||
if(!this.isDeviceValid(msg.payload)) {
|
|
||||||
msg = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Prepare for request
|
|
||||||
else {
|
|
||||||
msg.payload = this.gateway.deviceList.filter((device) => this.isDeviceValid(device));
|
|
||||||
}
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RED.nodes.registerType("xiaomi-all", XiaomiAllNode);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,131 +0,0 @@
|
|||||||
<script type="text/javascript">
|
|
||||||
RED.nodes.registerType('xiaomi-configurator', {
|
|
||||||
category: 'config',
|
|
||||||
defaults: {
|
|
||||||
name: {value: ""},
|
|
||||||
ip: {value: "", required: true},
|
|
||||||
sid: {value: ""},
|
|
||||||
deviceList: {value:[{ sid:"", desc:"", model:"plug"}]},
|
|
||||||
key: {value: ""}
|
|
||||||
},
|
|
||||||
label: function () {
|
|
||||||
return this.name || "xiaomi-configurator";
|
|
||||||
},
|
|
||||||
oneditprepare: function() {
|
|
||||||
var node = this;
|
|
||||||
|
|
||||||
var tw_sensor_ht = {value:"sensor_ht", label:"sensor ht", icon:"icons/node-red-contrib-mi-devices/thermometer-tw-icon.png"};
|
|
||||||
var tw_magnet = {value:"magnet", label:"contact", icon:"icons/node-red-contrib-mi-devices/door-tw-icon.png"};
|
|
||||||
var tw_motion = {value:"motion", label:"motion", icon:"icons/node-red-contrib-mi-devices/motion-tw-icon.png"};
|
|
||||||
var tw_plug = {value:"plug", label:"plug", icon:"icons/node-red-contrib-mi-devices/outlet-tw-icon.png"};
|
|
||||||
var tw_switch = {value:"switch", label:"switch", icon:"icons/node-red-contrib-mi-devices/mi-tw-switch.png"};
|
|
||||||
|
|
||||||
$("#node-config-input-devices").css('min-height','250px').css('min-width','450px').editableList({
|
|
||||||
addItem: function(container, i, device) {
|
|
||||||
if (!device.hasOwnProperty('model')) {
|
|
||||||
device.model = 'sensor_ht';
|
|
||||||
}
|
|
||||||
var row = $('<div/>').appendTo(container);
|
|
||||||
|
|
||||||
$('<label/>',{for:"node-config-input-sid-"+i, style:"margin-left: 3px; width: 15px;vertical-align:middle"}).appendTo(row);
|
|
||||||
var sid = $('<input/>',{id:"node-config-input-sid-"+i,type:"text", placeholder:"SID", style:"width:auto;vertical-align:top"}).appendTo(row);
|
|
||||||
sid.typedInput({
|
|
||||||
default: 'sensor_ht',
|
|
||||||
types: [tw_sensor_ht, tw_magnet, tw_motion, tw_plug, tw_switch]
|
|
||||||
});
|
|
||||||
|
|
||||||
$('<label/>',{for:"node-config-input-desc-"+i, style:"margin-left: 7px; width: 20px;vertical-align:middle"}).html('<i class="fa fa-pencil-square-o"></i>').appendTo(row);
|
|
||||||
var desc = $('<input/>',{id:"node-config-input-desc-"+i, type:"text", placeholder:"description", style:"width:auto;vertical-align:top"}).appendTo(row);
|
|
||||||
|
|
||||||
sid.typedInput('value', device.sid);
|
|
||||||
sid.typedInput('type', device.model);
|
|
||||||
desc.val(device.desc);
|
|
||||||
},
|
|
||||||
resize: function() {
|
|
||||||
},
|
|
||||||
removeItem: function(opt) {
|
|
||||||
},
|
|
||||||
sortItems: function(rules) {
|
|
||||||
},
|
|
||||||
sortable: true,
|
|
||||||
removable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var i=0;i<this.deviceList.length;i++) {
|
|
||||||
var device = this.deviceList[i];
|
|
||||||
$("#node-config-input-devices").editableList('addItem', device);
|
|
||||||
}
|
|
||||||
var listHeight = $("#node-config-input-devices").editableList('items').size() * 51 + 50;
|
|
||||||
$("#node-config-input-devices").editableList('height', listHeight);
|
|
||||||
},
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
var devicesArray = [];
|
|
||||||
devices.each(function(i) {
|
|
||||||
var deviceElement = $(this);
|
|
||||||
var sid = deviceElement.find("#node-config-input-sid-"+i).val();
|
|
||||||
var desc = deviceElement.find("#node-config-input-desc-"+i).val();
|
|
||||||
var model = deviceElement.find("#node-config-input-sid-"+i).typedInput('type');
|
|
||||||
var d = {};
|
|
||||||
d['sid']=sid;
|
|
||||||
d['desc']=desc;
|
|
||||||
d['model']=model;
|
|
||||||
devicesArray.push(d);
|
|
||||||
});
|
|
||||||
node.deviceList = devicesArray;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-configurator">
|
|
||||||
<div class="form-row">
|
|
||||||
<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">
|
|
||||||
<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-sid"><i class="icon-tag"></i> SID (optional)</label>
|
|
||||||
<input type="text" id="node-config-input-sid" placeholder="sid">
|
|
||||||
</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>
|
|
||||||
<div class="form-row node-config-input-devices">
|
|
||||||
<ol id="node-config-input-devices"></ol>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-configurator">
|
|
||||||
<p>Device configuration for Xiaomi nodes.</p>
|
|
||||||
<h3>Details</h3>
|
|
||||||
<p>This configuration node is used by the Xiaomi device nodes. Here you can add
|
|
||||||
devices with their device-id (SID), type and a description.</p>
|
|
||||||
<p>At the moment the following devices are supported:
|
|
||||||
<lu>
|
|
||||||
<li>Humidity & Temperature sensor [sensor ht/]</li>
|
|
||||||
<li>Body motion sensor [motion]</li>
|
|
||||||
<li>Magnet contact sensor [contact]</li>
|
|
||||||
<li>Wall socket plug (zigbee) [plug]</li>
|
|
||||||
<li>Push button [switch]</li>
|
|
||||||
</lu>
|
|
||||||
</p>
|
|
||||||
<p>To be able to receive messages from the Xiaomi gateway, you need to set the gateway
|
|
||||||
in developer mode. Once in developer mode, the gateway sends JSON messages over the network as
|
|
||||||
UDP packages. On the internet their are a lot of guides on how to put the gateway in developer mode.</p>
|
|
||||||
<p>If you want to use the wall sockets, you need to set the key from the gateway. The key can be
|
|
||||||
retrieved via the Xiaomi Home App when in developer mode. Enter the key here and it is used
|
|
||||||
together with the token from the gateway's heartbeat message to recalculate the key to switch
|
|
||||||
the plug. If you do not specify a key, the plug-node can not be used.</p>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
module.exports = (RED) => {
|
|
||||||
function XiaomiConfiguratorNode(n) {
|
|
||||||
RED.nodes.createNode(this, n);
|
|
||||||
this.name = n.name;
|
|
||||||
this.deviceList = n.deviceList || [];
|
|
||||||
this.key = n.key;
|
|
||||||
this.ip = n.ip;
|
|
||||||
this.sid = this.sid || n.sid;
|
|
||||||
|
|
||||||
var node = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
RED.nodes.registerType("xiaomi-configurator", XiaomiConfiguratorNode);
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
<!-- The Input Node -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
RED.nodes.registerType('xiaomi-gateway', {
|
|
||||||
category: 'xiaomi',
|
|
||||||
color: '#3FADB5',
|
|
||||||
defaults: {
|
|
||||||
gateway: {value:"", type:"xiaomi-configurator"},
|
|
||||||
name: {value: ""}
|
|
||||||
},
|
|
||||||
inputs: 1,
|
|
||||||
outputs: 1,
|
|
||||||
outputLabels: ["Gateway"],
|
|
||||||
paletteLabel: "gateway",
|
|
||||||
icon: "mijia.png",
|
|
||||||
label: function () {
|
|
||||||
return this.name || "xiaomi-gateway";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-gateway">
|
|
||||||
<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>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-gateway">
|
|
||||||
<p>The gateway itself.</p>
|
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
|
||||||
<ol class="node-ports">
|
|
||||||
<li>Devices output
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>gateway <span class="property-type">xiaomi-configurator</span></dt>
|
|
||||||
<dd>The gateway.</dd>
|
|
||||||
</dl>
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload <span class="property-type">json</span></dt>
|
|
||||||
<dd>Data from gateway, with computed data.</dd>
|
|
||||||
</dl>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- 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 in out',
|
|
||||||
color: '#087F8A',
|
|
||||||
defaults: {
|
|
||||||
name: {value:""},
|
|
||||||
gateway: {value:"", type:"xiaomi-configurator"},
|
|
||||||
ip: {value:""}
|
|
||||||
},
|
|
||||||
inputs:0,
|
|
||||||
outputs:1,
|
|
||||||
paletteLabel: "gateway in",
|
|
||||||
icon: "mijia-io.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 in out',
|
|
||||||
color: '#087F8A',
|
|
||||||
defaults: {
|
|
||||||
name: {value:""},
|
|
||||||
gateway: {value:"", type:"xiaomi-configurator"},
|
|
||||||
ip: {value:""}
|
|
||||||
},
|
|
||||||
inputs:1,
|
|
||||||
outputs:0,
|
|
||||||
paletteLabel: "gateway out",
|
|
||||||
icon: "mijia-io.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>
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
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);
|
|
||||||
|
|
||||||
this.status({fill:"red", shape:"ring", text: "offline"});
|
|
||||||
|
|
||||||
if (this.gateway) {
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
var payload = msg.payload;
|
|
||||||
|
|
||||||
// Input from gateway
|
|
||||||
if(payload.sid) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
this.send([msg]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Prepare for request
|
|
||||||
else {
|
|
||||||
msg.gateway = this.gateway;
|
|
||||||
msg.sid = this.gateway.sid;
|
|
||||||
this.send(msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-gateway", XiaomiGatewayNode);
|
|
||||||
|
|
||||||
// The Input Node
|
|
||||||
function GatewayIn(n) {
|
|
||||||
RED.nodes.createNode(this,n);
|
|
||||||
this.gatewayNodeId = n.gateway;
|
|
||||||
this.gateway = RED.nodes.getNode(n.gateway);
|
|
||||||
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";
|
|
||||||
|
|
||||||
this.status({fill:"red", shape:"ring", text: "offline"});
|
|
||||||
|
|
||||||
var opts = {type:this.ipv, reuseAddr:true};
|
|
||||||
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
|
|
||||||
var server;
|
|
||||||
|
|
||||||
if (!udpInputPortsInUse.hasOwnProperty(this.port)) {
|
|
||||||
server = dgram.createSocket(opts); // default to udp4
|
|
||||||
udpInputPortsInUse[this.port] = server;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.warn(RED._("udp.errors.alreadyused",this.port));
|
|
||||||
server = udpInputPortsInUse[this.port]; // re-use existing
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
|
|
||||||
|
|
||||||
server.on("error", (err) => {
|
|
||||||
if ((err.code == "EACCES") && (this.port < 1024)) {
|
|
||||||
this.error(RED._("udp.errors.access-error"));
|
|
||||||
} else {
|
|
||||||
this.error(RED._("udp.errors.error",{error:err.code}));
|
|
||||||
}
|
|
||||||
server.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on('message', (message, remote) => {
|
|
||||||
var msg;
|
|
||||||
if(remote.address == this.addr) {
|
|
||||||
var msg = message.toString('utf8');
|
|
||||||
var jsonMsg = JSON.parse(msg);
|
|
||||||
if(jsonMsg.data) {
|
|
||||||
jsonMsg.data = JSON.parse(jsonMsg.data) || jsonMsg.data;
|
|
||||||
if(jsonMsg.data.voltage) {
|
|
||||||
jsonMsg.data.batteryLevel = miDevicesUtils.computeBatteryLevel(jsonMsg.data.voltage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg = { payload: jsonMsg };
|
|
||||||
if(this.gateway && jsonMsg.data.ip && jsonMsg.data.ip === this.gateway.ip) {
|
|
||||||
if(jsonMsg.token) {
|
|
||||||
this.gateway.lastToken = jsonMsg.token;
|
|
||||||
if(!this.gateway.sid) {
|
|
||||||
this.gateway.sid = jsonMsg.sid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RED.nodes.eachNode((tmpNode) => {
|
|
||||||
if(tmpNode.type.indexOf("xiaomi-gateway") === 0 && tmpNode.gateway == this.gatewayNodeId) {
|
|
||||||
let tmpNodeInst = RED.nodes.getNode(tmpNode.id);
|
|
||||||
if(tmpNode.type === "xiaomi-gateway out" && !this.gateway.lastToken) {
|
|
||||||
tmpNodeInst.status({fill:"yellow", shape:"ring", text: "waiting input"});
|
|
||||||
}
|
|
||||||
tmpNodeInst.status({fill:"blue", shape:"dot", text: "online"});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.send(msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on('listening', () => {
|
|
||||||
var address = server.address();
|
|
||||||
this.log(RED._("udp.status.listener-at",{host:address.address,port:address.port}));
|
|
||||||
server.setBroadcast(true);
|
|
||||||
try {
|
|
||||||
server.setMulticastTTL(128);
|
|
||||||
server.addMembership(this.group,this.iface);
|
|
||||||
this.log(RED._("udp.status.mc-group",{group:this.group}));
|
|
||||||
} catch (e) {
|
|
||||||
if (e.errno == "EINVAL") {
|
|
||||||
this.error(RED._("udp.errors.bad-mcaddress"));
|
|
||||||
} else if (e.errno == "ENODEV") {
|
|
||||||
this.error(RED._("udp.errors.interface"));
|
|
||||||
} else {
|
|
||||||
this.error(RED._("udp.errors.error",{error:e.errno}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on("close", () => {
|
|
||||||
if (udpInputPortsInUse.hasOwnProperty(this.port)) {
|
|
||||||
delete udpInputPortsInUse[this.port];
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
server.close();
|
|
||||||
this.log(RED._("udp.status.listener-stopped"));
|
|
||||||
} catch (err) {
|
|
||||||
//this.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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'), (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;
|
|
||||||
|
|
||||||
this.gatewayNodeId = n.gateway;
|
|
||||||
this.gateway = RED.nodes.getNode(n.gateway);
|
|
||||||
|
|
||||||
this.status({fill:"red", shape:"ring", text: "offline"});
|
|
||||||
|
|
||||||
var opts = {type:this.ipv, reuseAddr:true};
|
|
||||||
if (process.version.indexOf("v0.10") === 0) { opts = this.ipv; }
|
|
||||||
|
|
||||||
var sock;
|
|
||||||
if (udpInputPortsInUse[this.outport]) {
|
|
||||||
sock = udpInputPortsInUse[this.outport];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sock = dgram.createSocket(opts); // default to udp4
|
|
||||||
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
|
|
||||||
// down.
|
|
||||||
});
|
|
||||||
udpInputPortsInUse[this.outport] = sock;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!udpInputPortsInUse[this.outport]) {
|
|
||||||
sock.bind(this.outport);
|
|
||||||
this.log(RED._("udp.status.ready",{outport:this.outport,host:this.addr,port:this.port}));
|
|
||||||
} else {
|
|
||||||
this.log(RED._("udp.status.ready-nolocal",{host:this.addr,port:this.port}));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.on("input", (msg) => {
|
|
||||||
if (msg.hasOwnProperty("payload")) {
|
|
||||||
var add = this.addr || msg.ip || "";
|
|
||||||
var por = this.port || msg.port || 0;
|
|
||||||
if (add === "") {
|
|
||||||
this.warn(RED._("udp.errors.ip-notset"));
|
|
||||||
} else if (por === 0) {
|
|
||||||
this.warn(RED._("udp.errors.port-notset"));
|
|
||||||
} else if (isNaN(por) || (por < 1) || (por > 65535)) {
|
|
||||||
this.warn(RED._("udp.errors.port-invalid"));
|
|
||||||
} else {
|
|
||||||
if(msg.payload.cmd === "write" && !msg.payload.data.key && this.gateway && this.gateway.sid && this.gateway.key && this.gateway.lastToken) {
|
|
||||||
msg.payload.data.key = miDevicesUtils.getGatewayKey(this.gateway.key, this.gateway.lastToken);
|
|
||||||
}
|
|
||||||
var message = Buffer.from(JSON.stringify(msg.payload));
|
|
||||||
sock.send(message, 0, message.length, por, add, (err, bytes) => {
|
|
||||||
if (err) {
|
|
||||||
this.error("udp : "+err,msg);
|
|
||||||
}
|
|
||||||
message = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.on("close", () => {
|
|
||||||
if (udpInputPortsInUse.hasOwnProperty(this.outport)) {
|
|
||||||
delete udpInputPortsInUse[this.outport];
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
sock.close();
|
|
||||||
this.log(RED._("udp.status.output-stopped"));
|
|
||||||
} catch (err) {
|
|
||||||
//this.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-gateway out", GatewayOut);
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<script type="text/javascript">
|
|
||||||
RED.nodes.registerType('xiaomi-ht', {
|
|
||||||
category: 'xiaomi',
|
|
||||||
color: '#3FADB5',
|
|
||||||
defaults: {
|
|
||||||
gateway: {value:"", type:"xiaomi-configurator"},
|
|
||||||
name: {value: ""},
|
|
||||||
sid: {value: "", required: true}
|
|
||||||
},
|
|
||||||
inputs: 1,
|
|
||||||
outputs: 1,
|
|
||||||
paletteLabel: "sensor HT",
|
|
||||||
icon: "thermometer-icon.png",
|
|
||||||
label: function () {
|
|
||||||
return this.name || "xiaomi-ht";
|
|
||||||
},
|
|
||||||
oneditprepare: function() {
|
|
||||||
var node = this;
|
|
||||||
|
|
||||||
if(node.sid) {
|
|
||||||
$('#node-input-sid').val(node.sid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeGateway(model) {
|
|
||||||
var configNodeID = $('#node-input-gateway').val();
|
|
||||||
if (configNodeID) {
|
|
||||||
var configNode = RED.nodes.node(configNodeID);
|
|
||||||
if(configNode) {
|
|
||||||
$('#node-input-sid').empty();
|
|
||||||
for (key in configNode.deviceList) {
|
|
||||||
var device = configNode.deviceList[key];
|
|
||||||
if (device.model === model) {
|
|
||||||
$('#node-input-sid').append('<option value="' + device.sid + '">' + device.desc + '</option>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(node.sid) {
|
|
||||||
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#node-input-sid").change(function () {
|
|
||||||
if(!this.name) {
|
|
||||||
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#node-input-gateway").change(function () {
|
|
||||||
changeGateway("sensor_ht");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
oneditsave: function() {
|
|
||||||
var node = this;
|
|
||||||
node.sid = $("#node-input-sid").val();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-ht">
|
|
||||||
<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-sid"><i class="icon-tag"></i> Device</label>
|
|
||||||
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-ht">
|
|
||||||
<p>The Xiaomi Humidity & Temperature sensor node</p>
|
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload
|
|
||||||
<span class="property-type">object</span>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
|
||||||
<hr>
|
|
||||||
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>.<br>
|
|
||||||
<hr>
|
|
||||||
Input Gateway node produces message of type <code>read_ack</code>, <code>heartbeat</code> or <code>report</code>.
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
|
||||||
<ol class="node-ports">
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload <span class="property-type">object</span></dt>
|
|
||||||
<dd>Data from gateway when used as a filter (see below).</dd>
|
|
||||||
<dt>sid <span class="property-type">string</span></dt>
|
|
||||||
<dd>Device SID.</dd>
|
|
||||||
<dt>gateway <span class="property-type">object</span></dt>
|
|
||||||
<dd>The <code>xiaomi-configurator</code> object where the device is registred.</dd>
|
|
||||||
</dl>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<h4>Details</h4>
|
|
||||||
<p>The incoming message is processed if the input <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>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
const miDevicesUtils = require('../src/utils');
|
|
||||||
|
|
||||||
module.exports = (RED) => {
|
|
||||||
// sensor_ht, weather.v1
|
|
||||||
|
|
||||||
function XiaomiHtNode(config) {
|
|
||||||
RED.nodes.createNode(this, config);
|
|
||||||
this.gateway = RED.nodes.getNode(config.gateway);
|
|
||||||
this.sid = config.sid;
|
|
||||||
|
|
||||||
this.status({fill:"grey", shape:"ring", text:"battery - na"});
|
|
||||||
|
|
||||||
if (this.gateway) {
|
|
||||||
this.on('input', (msg) => {
|
|
||||||
let payload = msg.payload;
|
|
||||||
|
|
||||||
// Input from gateway
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Prepare for request
|
|
||||||
else {
|
|
||||||
miDevicesUtils.prepareForGatewayRequest(this, msg);
|
|
||||||
}
|
|
||||||
this.send(msg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RED.nodes.registerType("xiaomi-ht", XiaomiHtNode);
|
|
||||||
};
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
<script type="text/javascript">
|
|
||||||
RED.nodes.registerType('xiaomi-magnet', {
|
|
||||||
category: 'xiaomi',
|
|
||||||
color: '#3FADB5',
|
|
||||||
defaults: {
|
|
||||||
gateway: {value:"", type:"xiaomi-configurator"},
|
|
||||||
name: {value: ""},
|
|
||||||
sid: {value: "", required: true}
|
|
||||||
},
|
|
||||||
inputs: 1,
|
|
||||||
outputs: 1,
|
|
||||||
paletteLabel: "contact",
|
|
||||||
icon: "door-icon.png",
|
|
||||||
label: function () {
|
|
||||||
return this.name || "xiaomi-magnet";
|
|
||||||
},
|
|
||||||
oneditprepare: function() {
|
|
||||||
var node = this;
|
|
||||||
|
|
||||||
if(node.sid) {
|
|
||||||
$('#node-input-sid').val(node.sid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeGateway(model) {
|
|
||||||
var configNodeID = $('#node-input-gateway').val();
|
|
||||||
if (configNodeID) {
|
|
||||||
var configNode = RED.nodes.node(configNodeID);
|
|
||||||
if(configNode) {
|
|
||||||
$('#node-input-sid').empty();
|
|
||||||
for (key in configNode.deviceList) {
|
|
||||||
var device = configNode.deviceList[key];
|
|
||||||
if (device.model === model) {
|
|
||||||
$('#node-input-sid').append('<option value="' + device.sid + '">' + device.desc + '</option>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(node.sid) {
|
|
||||||
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#node-input-sid").change(function () {
|
|
||||||
if(!this.name) {
|
|
||||||
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#node-input-gateway").change(function () {
|
|
||||||
changeGateway("magnet");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
oneditsave: function() {
|
|
||||||
var node = this;
|
|
||||||
node.sid = $("#node-input-sid").val();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-magnet">
|
|
||||||
<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-sid"><i class="icon-tag"></i> Device</label>
|
|
||||||
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-magnet">
|
|
||||||
<p>The Xiaomi contact sensor node</p>
|
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload
|
|
||||||
<span class="property-type">object</span>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
|
||||||
<hr>
|
|
||||||
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>.<br>
|
|
||||||
<hr>
|
|
||||||
Input Gateway node produces message of type <code>read_ack</code>, <code>heartbeat</code> or <code>report</code>.
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
|
||||||
<ol class="node-ports">
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload <span class="property-type">object</span></dt>
|
|
||||||
<dd>Data from gateway when used as a filter (see below).</dd>
|
|
||||||
<dt>sid <span class="property-type">string</span></dt>
|
|
||||||
<dd>Device SID.</dd>
|
|
||||||
<dt>gateway <span class="property-type">object</span></dt>
|
|
||||||
<dd>The <code>xiaomi-configurator</code> object where the device is registred.</dd>
|
|
||||||
</dl>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<h4>Details</h4>
|
|
||||||
<p>The incoming message is processed if the input <code>sid</code> matches the configured value for this device.</p>
|
|
||||||
<p>Sample message:</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>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
const miDevicesUtils = require('../src/utils');
|
|
||||||
|
|
||||||
module.exports = (RED) => {
|
|
||||||
// magnet, sensor_magnet.aq2
|
|
||||||
function XiaomiMagnetNode(config) {
|
|
||||||
miDevicesUtils.defaultNode(RED, config, this);
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-magnet", XiaomiMagnetNode);
|
|
||||||
};
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
<script type="text/javascript">
|
|
||||||
RED.nodes.registerType('xiaomi-motion', {
|
|
||||||
category: 'xiaomi',
|
|
||||||
color: '#3FADB5',
|
|
||||||
defaults: {
|
|
||||||
gateway: {value:"", type:"xiaomi-configurator"},
|
|
||||||
name: {value: ""},
|
|
||||||
sid: {value: "", required: true},
|
|
||||||
motionmsg: {value: ""},
|
|
||||||
nomotionmsg: {value: ""},
|
|
||||||
output: {value: "0"}
|
|
||||||
},
|
|
||||||
inputs: 1,
|
|
||||||
outputs: 1,
|
|
||||||
paletteLabel: "motion",
|
|
||||||
icon: "motion-icon.png",
|
|
||||||
label: function () {
|
|
||||||
return this.name || "xiaomi-motion";
|
|
||||||
},
|
|
||||||
oneditprepare: function() {
|
|
||||||
var node = this;
|
|
||||||
|
|
||||||
if(node.sid) {
|
|
||||||
$('#node-input-sid').val(node.sid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeGateway(model) {
|
|
||||||
var configNodeID = $('#node-input-gateway').val();
|
|
||||||
if (configNodeID) {
|
|
||||||
var configNode = RED.nodes.node(configNodeID);
|
|
||||||
if(configNode) {
|
|
||||||
$('#node-input-sid').empty();
|
|
||||||
for (key in configNode.deviceList) {
|
|
||||||
var device = configNode.deviceList[key];
|
|
||||||
if (device.model === model) {
|
|
||||||
$('#node-input-sid').append('<option value="' + device.sid + '">' + device.desc + '</option>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(node.sid) {
|
|
||||||
$('#node-input-sid option[value="' + node.sid + '"]').prop('selected', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#node-input-sid").change(function () {
|
|
||||||
if(!this.name) {
|
|
||||||
$("#node-input-name").val($('#node-input-sid option:selected').text());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#node-input-gateway").change(function () {
|
|
||||||
changeGateway("motion");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
oneditsave: function() {
|
|
||||||
var node = this;
|
|
||||||
node.sid = $("#node-input-sid").val();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-template-name="xiaomi-motion">
|
|
||||||
<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-sid"><i class="icon-tag"></i> Device</label>
|
|
||||||
<select id="node-input-sid" placeholder="xiaomi gateway"></select>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="text/x-red" data-help-name="xiaomi-motion">
|
|
||||||
<p>The Xiaomi body motion sensor node</p>
|
|
||||||
|
|
||||||
<h3>Inputs</h3>
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload
|
|
||||||
<span class="property-type">object</span>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
When the message contains a <code>sid</code> field, the node will filter the input and output only if the <code>sid</code> is the device's sid.<br>
|
|
||||||
<hr>
|
|
||||||
If the message doesn't contain a <code>sid</code> field, the node will be used to inject <code>sid</code> and <code>gateway</code> fields in the incoming <code>msg</code>.<br>
|
|
||||||
<hr>
|
|
||||||
Input Gateway node produces message of type <code>read_ack</code>, <code>heartbeat</code> or <code>report</code>.
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
|
|
||||||
<h3>Outputs</h3>
|
|
||||||
<ol class="node-ports">
|
|
||||||
<dl class="message-properties">
|
|
||||||
<dt>payload <span class="property-type">object</span></dt>
|
|
||||||
<dd>Data from gateway when used as a filter (see below).</dd>
|
|
||||||
<dt>sid <span class="property-type">string</span></dt>
|
|
||||||
<dd>Device SID.</dd>
|
|
||||||
<dt>gateway <span class="property-type">object</span></dt>
|
|
||||||
<dd>The <code>xiaomi-configurator</code> object where the device is registred.</dd>
|
|
||||||
</dl>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<h4>Details</h4>
|
|
||||||
<p>The incoming message is processed if the input <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",
|
|
||||||
batteryLevel: 45
|
|
||||||
}
|
|
||||||
}</pre>
|
|
||||||
Where <code>batteryLevel</code> is a computed percentage of remaining battery.
|
|
||||||
</p>
|
|
||||||
</script>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
const miDevicesUtils = require('../src/utils');
|
|
||||||
|
|
||||||
module.exports = (RED) => {
|
|
||||||
// motion
|
|
||||||
function XiaomiMotionNode(config) {
|
|
||||||
miDevicesUtils.defaultNode(RED, config, this);
|
|
||||||
}
|
|
||||||
RED.nodes.registerType("xiaomi-motion", XiaomiMotionNode);
|
|
||||||
};
|
|
||||||