diff --git a/README.md b/README.md
index b551c84..ee78323 100644
--- a/README.md
+++ b/README.md
@@ -12,8 +12,6 @@ The following devices are currently supported:
* Aqara smart wireless switch
* Motion sensor
* Aqara Motion sensor
-* Power plug (zigbee)
-* Power plug (wifi)
* Yeelight White (mono)
* Yeelight RGB (color)
diff --git a/dist/devices/gateway/Gateway.js b/dist/devices/gateway/Gateway.js
new file mode 100644
index 0000000..9ce7046
--- /dev/null
+++ b/dist/devices/gateway/Gateway.js
@@ -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;
diff --git a/dist/devices/gateway/GatewayMessage.js b/dist/devices/gateway/GatewayMessage.js
new file mode 100644
index 0000000..b90d398
--- /dev/null
+++ b/dist/devices/gateway/GatewayMessage.js
@@ -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;
diff --git a/dist/devices/gateway/GatewayMessageData.js b/dist/devices/gateway/GatewayMessageData.js
new file mode 100644
index 0000000..c8ad2e5
--- /dev/null
+++ b/dist/devices/gateway/GatewayMessageData.js
@@ -0,0 +1,2 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
diff --git a/dist/devices/gateway/GatewayServer.js b/dist/devices/gateway/GatewayServer.js
new file mode 100644
index 0000000..6cc4a3f
--- /dev/null
+++ b/dist/devices/gateway/GatewayServer.js
@@ -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;
diff --git a/dist/devices/gateway/GatewaySubdevice.js b/dist/devices/gateway/GatewaySubdevice.js
new file mode 100644
index 0000000..cb4aa50
--- /dev/null
+++ b/dist/devices/gateway/GatewaySubdevice.js
@@ -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;
diff --git a/dist/devices/gateway/Magnet.js b/dist/devices/gateway/Magnet.js
new file mode 100644
index 0000000..6641a7e
--- /dev/null
+++ b/dist/devices/gateway/Magnet.js
@@ -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;
+ // mintime
+ if (this.status !== data.status) {
+ this.status = data.status;
+ this.emit('values-updated', this.sid);
+ }
+ }
+ }
+}
+exports.Magnet = Magnet;
diff --git a/dist/devices/gateway/Motion.js b/dist/devices/gateway/Motion.js
new file mode 100644
index 0000000..49d9cac
--- /dev/null
+++ b/dist/devices/gateway/Motion.js
@@ -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;
diff --git a/dist/devices/gateway/Switch.js b/dist/devices/gateway/Switch.js
new file mode 100644
index 0000000..06bc156
--- /dev/null
+++ b/dist/devices/gateway/Switch.js
@@ -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;
diff --git a/dist/devices/gateway/Weather.js b/dist/devices/gateway/Weather.js
new file mode 100644
index 0000000..c836a20
--- /dev/null
+++ b/dist/devices/gateway/Weather.js
@@ -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;
diff --git a/dist/devices/gateway/index.js b/dist/devices/gateway/index.js
new file mode 100644
index 0000000..969aef2
--- /dev/null
+++ b/dist/devices/gateway/index.js
@@ -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"));
diff --git a/dist/devices/yeelight/YeelightServer.js b/dist/devices/yeelight/YeelightServer.js
new file mode 100644
index 0000000..569bea9
--- /dev/null
+++ b/dist/devices/yeelight/YeelightServer.js
@@ -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;
diff --git a/dist/devices/yeelight/index.js b/dist/devices/yeelight/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/dist/nodes/actions/GatewayPlaySound.js b/dist/nodes/actions/GatewayPlaySound.js
new file mode 100644
index 0000000..5756947
--- /dev/null
+++ b/dist/nodes/actions/GatewayPlaySound.js
@@ -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);
+};
diff --git a/dist/nodes/actions/GatewayStopSound.js b/dist/nodes/actions/GatewayStopSound.js
new file mode 100644
index 0000000..08431f4
--- /dev/null
+++ b/dist/nodes/actions/GatewayStopSound.js
@@ -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);
+};
diff --git a/dist/nodes/actions/Light.js b/dist/nodes/actions/Light.js
new file mode 100644
index 0000000..544ac4c
--- /dev/null
+++ b/dist/nodes/actions/Light.js
@@ -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);
+};
diff --git a/dist/nodes/actions/ReadAction.js b/dist/nodes/actions/ReadAction.js
new file mode 100644
index 0000000..8bcf4cb
--- /dev/null
+++ b/dist/nodes/actions/ReadAction.js
@@ -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);
+};
diff --git a/dist/nodes/actions/ToggleAction.js b/dist/nodes/actions/ToggleAction.js
new file mode 100644
index 0000000..cdcd479
--- /dev/null
+++ b/dist/nodes/actions/ToggleAction.js
@@ -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);
+};
diff --git a/dist/nodes/actions/WriteAction.js b/dist/nodes/actions/WriteAction.js
new file mode 100644
index 0000000..839f1fa
--- /dev/null
+++ b/dist/nodes/actions/WriteAction.js
@@ -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);
+};
diff --git a/dist/nodes/actions/icons/mi-bulb.png b/dist/nodes/actions/icons/mi-bulb.png
new file mode 100644
index 0000000..5eae146
Binary files /dev/null and b/dist/nodes/actions/icons/mi-bulb.png differ
diff --git a/dist/nodes/actions/icons/mi-click.png b/dist/nodes/actions/icons/mi-click.png
new file mode 100644
index 0000000..f04c2cf
Binary files /dev/null and b/dist/nodes/actions/icons/mi-click.png differ
diff --git a/dist/nodes/actions/icons/mi-double-click.png b/dist/nodes/actions/icons/mi-double-click.png
new file mode 100644
index 0000000..23313ed
Binary files /dev/null and b/dist/nodes/actions/icons/mi-double-click.png differ
diff --git a/dist/nodes/actions/icons/mi-list.png b/dist/nodes/actions/icons/mi-list.png
new file mode 100644
index 0000000..af9bb2f
Binary files /dev/null and b/dist/nodes/actions/icons/mi-list.png differ
diff --git a/dist/nodes/actions/icons/mi-mute.png b/dist/nodes/actions/icons/mi-mute.png
new file mode 100644
index 0000000..0a97e87
Binary files /dev/null and b/dist/nodes/actions/icons/mi-mute.png differ
diff --git a/dist/nodes/actions/icons/mi-off.png b/dist/nodes/actions/icons/mi-off.png
new file mode 100644
index 0000000..2bce1bb
Binary files /dev/null and b/dist/nodes/actions/icons/mi-off.png differ
diff --git a/dist/nodes/actions/icons/mi-on.png b/dist/nodes/actions/icons/mi-on.png
new file mode 100644
index 0000000..ff48563
Binary files /dev/null and b/dist/nodes/actions/icons/mi-on.png differ
diff --git a/dist/nodes/actions/icons/mi-read.png b/dist/nodes/actions/icons/mi-read.png
new file mode 100644
index 0000000..3acc601
Binary files /dev/null and b/dist/nodes/actions/icons/mi-read.png differ
diff --git a/dist/nodes/actions/icons/mi-sound.png b/dist/nodes/actions/icons/mi-sound.png
new file mode 100644
index 0000000..5327f59
Binary files /dev/null and b/dist/nodes/actions/icons/mi-sound.png differ
diff --git a/dist/nodes/actions/icons/mi-toggle.png b/dist/nodes/actions/icons/mi-toggle.png
new file mode 100644
index 0000000..dbebc29
Binary files /dev/null and b/dist/nodes/actions/icons/mi-toggle.png differ
diff --git a/dist/nodes/actions/index.html b/dist/nodes/actions/index.html
new file mode 100644
index 0000000..7172968
--- /dev/null
+++ b/dist/nodes/actions/index.html
@@ -0,0 +1,483 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/nodes/actions/index.js b/dist/nodes/actions/index.js
new file mode 100644
index 0000000..cfb4f3b
--- /dev/null
+++ b/dist/nodes/actions/index.js
@@ -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);
+ });
+};
diff --git a/dist/nodes/constants.js b/dist/nodes/constants.js
new file mode 100644
index 0000000..b24b684
--- /dev/null
+++ b/dist/nodes/constants.js
@@ -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;
diff --git a/dist/nodes/gateway-subdevices/All.js b/dist/nodes/gateway-subdevices/All.js
new file mode 100644
index 0000000..5dd74c7
--- /dev/null
+++ b/dist/nodes/gateway-subdevices/All.js
@@ -0,0 +1,59 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+const constants_1 = require("../constants");
+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.gateway = RED.nodes.getNode(props.gateway);
+ this.onlyModels = All.getOnlyModelsValue(props.onlyModels || []);
+ this.excludedSids = props.excludedSids;
+ this.setMessageListener();
+ }
+ setMessageListener() {
+ this.on('input', (msg) => {
+ if (this.gateway) {
+ // Filter input
+ if (msg.payload && msg.payload.model && msg.payload.sid) {
+ if (!this.isDeviceValid(msg.payload)) {
+ msg = null;
+ }
+ this.send(msg);
+ }
+ else {
+ Object.keys(this.gateway.deviceList || {})
+ .filter((sid) => this.isDeviceValid(sid))
+ .forEach((sid) => {
+ let curMsg = Object.assign({}, msg);
+ curMsg.sid = sid;
+ curMsg.gateway = this.gateway;
+ this.send(curMsg);
+ });
+ }
+ }
+ });
+ }
+ isDeviceValid(sid) {
+ if ((!this.onlyModels || this.onlyModels.length == 0) && (!this.excludedSids || this.excludedSids.length == 0)) {
+ return true;
+ }
+ let device = this.gateway.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);
+};
diff --git a/dist/nodes/gateway-subdevices/GatewaySubdevice.js b/dist/nodes/gateway-subdevices/GatewaySubdevice.js
new file mode 100644
index 0000000..3fad8ea
--- /dev/null
+++ b/dist/nodes/gateway-subdevices/GatewaySubdevice.js
@@ -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}-${type}`, GatewayDevice);
+};
diff --git a/dist/nodes/gateway-subdevices/Plug.js b/dist/nodes/gateway-subdevices/Plug.js
new file mode 100644
index 0000000..0d6d9d1
--- /dev/null
+++ b/dist/nodes/gateway-subdevices/Plug.js
@@ -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);
+};
diff --git a/dist/nodes/gateway-subdevices/icons/door-icon.png b/dist/nodes/gateway-subdevices/icons/door-icon.png
new file mode 100644
index 0000000..f33e892
Binary files /dev/null and b/dist/nodes/gateway-subdevices/icons/door-icon.png differ
diff --git a/dist/nodes/gateway-subdevices/icons/mi-all.png b/dist/nodes/gateway-subdevices/icons/mi-all.png
new file mode 100644
index 0000000..b2c29af
Binary files /dev/null and b/dist/nodes/gateway-subdevices/icons/mi-all.png differ
diff --git a/dist/nodes/gateway-subdevices/icons/mi-switch.png b/dist/nodes/gateway-subdevices/icons/mi-switch.png
new file mode 100644
index 0000000..8ee44b2
Binary files /dev/null and b/dist/nodes/gateway-subdevices/icons/mi-switch.png differ
diff --git a/dist/nodes/gateway-subdevices/icons/motion-icon.png b/dist/nodes/gateway-subdevices/icons/motion-icon.png
new file mode 100644
index 0000000..86c7e68
Binary files /dev/null and b/dist/nodes/gateway-subdevices/icons/motion-icon.png differ
diff --git a/dist/nodes/gateway-subdevices/icons/outlet-icon.png b/dist/nodes/gateway-subdevices/icons/outlet-icon.png
new file mode 100644
index 0000000..f9c7e4a
Binary files /dev/null and b/dist/nodes/gateway-subdevices/icons/outlet-icon.png differ
diff --git a/dist/nodes/gateway-subdevices/icons/thermometer-icon.png b/dist/nodes/gateway-subdevices/icons/thermometer-icon.png
new file mode 100644
index 0000000..b25be7d
Binary files /dev/null and b/dist/nodes/gateway-subdevices/icons/thermometer-icon.png differ
diff --git a/dist/nodes/gateway-subdevices/index.html b/dist/nodes/gateway-subdevices/index.html
new file mode 100644
index 0000000..7f2db42
--- /dev/null
+++ b/dist/nodes/gateway-subdevices/index.html
@@ -0,0 +1,729 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/nodes/gateway-subdevices/index.js b/dist/nodes/gateway-subdevices/index.js
new file mode 100644
index 0000000..824ec81
--- /dev/null
+++ b/dist/nodes/gateway-subdevices/index.js
@@ -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);
+ });
+};
diff --git a/dist/nodes/gateway/Gateway.js b/dist/nodes/gateway/Gateway.js
new file mode 100644
index 0000000..7c40bf2
--- /dev/null
+++ b/dist/nodes/gateway/Gateway.js
@@ -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);
+};
diff --git a/dist/nodes/gateway/GatewayConfigurator.js b/dist/nodes/gateway/GatewayConfigurator.js
new file mode 100644
index 0000000..8575750
--- /dev/null
+++ b/dist/nodes/gateway/GatewayConfigurator.js
@@ -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
+ }
+ }
+ });
+};
diff --git a/dist/nodes/gateway/GatewayIn.js b/dist/nodes/gateway/GatewayIn.js
new file mode 100644
index 0000000..ab4f0d2
--- /dev/null
+++ b/dist/nodes/gateway/GatewayIn.js
@@ -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);
+};
diff --git a/dist/nodes/gateway/GatewayOut.js b/dist/nodes/gateway/GatewayOut.js
new file mode 100644
index 0000000..d60b012
--- /dev/null
+++ b/dist/nodes/gateway/GatewayOut.js
@@ -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);
+};
diff --git a/dist/nodes/gateway/icons/mijia-io.png b/dist/nodes/gateway/icons/mijia-io.png
new file mode 100644
index 0000000..7058afb
Binary files /dev/null and b/dist/nodes/gateway/icons/mijia-io.png differ
diff --git a/dist/nodes/gateway/icons/mijia.png b/dist/nodes/gateway/icons/mijia.png
new file mode 100644
index 0000000..7f73eae
Binary files /dev/null and b/dist/nodes/gateway/icons/mijia.png differ
diff --git a/dist/nodes/gateway/index.html b/dist/nodes/gateway/index.html
new file mode 100644
index 0000000..aba746d
--- /dev/null
+++ b/dist/nodes/gateway/index.html
@@ -0,0 +1,295 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dist/nodes/gateway/index.js b/dist/nodes/gateway/index.js
new file mode 100644
index 0000000..84df444
--- /dev/null
+++ b/dist/nodes/gateway/index.js
@@ -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);
+};
diff --git a/dist/nodes/plug-wifi/index.html b/dist/nodes/plug-wifi/index.html
new file mode 100644
index 0000000..ffcb4f9
--- /dev/null
+++ b/dist/nodes/plug-wifi/index.html
@@ -0,0 +1,67 @@
+
+
+
+
+
diff --git a/dist/nodes/plug-wifi/index.js b/dist/nodes/plug-wifi/index.js
new file mode 100644
index 0000000..1ca5165
--- /dev/null
+++ b/dist/nodes/plug-wifi/index.js
@@ -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);
+};
diff --git a/dist/nodes/yeelight/YeelightConfigurator.js b/dist/nodes/yeelight/YeelightConfigurator.js
new file mode 100644
index 0000000..644c881
--- /dev/null
+++ b/dist/nodes/yeelight/YeelightConfigurator.js
@@ -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
+ }
+ }
+ });
+};
diff --git a/dist/nodes/yeelight/YeelightOut.js b/dist/nodes/yeelight/YeelightOut.js
new file mode 100644
index 0000000..aba8d98
--- /dev/null
+++ b/dist/nodes/yeelight/YeelightOut.js
@@ -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;
+ }
+ }
+ });
+ /*( 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);
+};
diff --git a/dist/nodes/yeelight/icons/mi-yeelight.png b/dist/nodes/yeelight/icons/mi-yeelight.png
new file mode 100644
index 0000000..0ba6ad3
Binary files /dev/null and b/dist/nodes/yeelight/icons/mi-yeelight.png differ
diff --git a/dist/nodes/yeelight/index.html b/dist/nodes/yeelight/index.html
new file mode 100644
index 0000000..56a9214
--- /dev/null
+++ b/dist/nodes/yeelight/index.html
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/nodes/yeelight/index.js b/dist/nodes/yeelight/index.js
new file mode 100644
index 0000000..b0b659f
--- /dev/null
+++ b/dist/nodes/yeelight/index.js
@@ -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);
+};
diff --git a/dist/utils/Color.js b/dist/utils/Color.js
new file mode 100644
index 0000000..a528617
--- /dev/null
+++ b/dist/utils/Color.js
@@ -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;
diff --git a/dist/utils/index.js b/dist/utils/index.js
new file mode 100644
index 0000000..a5b6e36
--- /dev/null
+++ b/dist/utils/index.js
@@ -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"));
diff --git a/flows-overview.json b/flows-overview.json
index 18d45ac..a432797 100644
--- a/flows-overview.json
+++ b/flows-overview.json
@@ -1 +1 @@
-[{"id":"b7132358.47476","type":"tab","label":"Mi Devices Overview","disabled":false,"info":""},{"id":"37ac569b.b3ef02","type":"xiaomi-gateway in","z":"b7132358.47476","name":"","gateway":"","ip":"","x":100,"y":220,"wires":[["b7bdc18a.4b45","94672654.872fb","b03e7c0c.d344e","540ce4ae.b5a26c","a6d2ae60.7ee088","bb36a54.ac665d8","3229d6fd.1e35fa"]]},{"id":"b7bdc18a.4b45","type":"xiaomi-ht","z":"b7132358.47476","gateway":"","name":"","sid":"","x":420,"y":140,"wires":[["37052c54.e2b62c"]]},{"id":"94672654.872fb","type":"xiaomi-magnet","z":"b7132358.47476","gateway":"","name":"","sid":"","x":440,"y":180,"wires":[["37052c54.e2b62c"]]},{"id":"b03e7c0c.d344e","type":"xiaomi-motion","z":"b7132358.47476","gateway":"","name":"","sid":"","motionmsg":"","nomotionmsg":"","output":"0","x":440,"y":220,"wires":[["37052c54.e2b62c"]]},{"id":"540ce4ae.b5a26c","type":"xiaomi-switch","z":"b7132358.47476","gateway":"","name":"","sid":"","x":440,"y":260,"wires":[["37052c54.e2b62c"]]},{"id":"a6d2ae60.7ee088","type":"xiaomi-gateway","z":"b7132358.47476","gateway":"","name":"","x":440,"y":300,"wires":[["37052c54.e2b62c"]]},{"id":"bb36a54.ac665d8","type":"xiaomi-plug","z":"b7132358.47476","gateway":"","name":"","sid":"","onmsg":"","offmsg":"","output":"0","x":430,"y":340,"wires":[["37052c54.e2b62c"]]},{"id":"37052c54.e2b62c","type":"debug","z":"b7132358.47476","name":"","active":true,"console":"false","complete":"true","x":710,"y":220,"wires":[]},{"id":"a2db7256.81c91","type":"xiaomi-ht","z":"b7132358.47476","gateway":"","name":"","sid":"","x":480,"y":560,"wires":[["ee6734f2.6e1e6"]]},{"id":"1f15867.488737a","type":"xiaomi-magnet","z":"b7132358.47476","gateway":"","name":"","sid":"","x":500,"y":600,"wires":[["ee6734f2.6e1e6"]]},{"id":"36a62693.5c11b2","type":"xiaomi-motion","z":"b7132358.47476","gateway":"","name":"","sid":"","motionmsg":"","nomotionmsg":"","output":"0","x":500,"y":640,"wires":[["ee6734f2.6e1e6"]]},{"id":"79681c1e.90ebdc","type":"xiaomi-switch","z":"b7132358.47476","gateway":"","name":"","sid":"","x":500,"y":700,"wires":[["ee6734f2.6e1e6","9e60c17d.8a64a8","6d2591a6.89d498"]]},{"id":"d5fb64b6.8e94e8","type":"xiaomi-gateway","z":"b7132358.47476","gateway":"","name":"","x":500,"y":760,"wires":[["ee6734f2.6e1e6","ff64227b.22f8a","92cbad8e.098568","520452ff.0f57e4","87e164af.8c453"]]},{"id":"6f3e29cc.ab17e","type":"inject","z":"b7132358.47476","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":100,"y":700,"wires":[["a2db7256.81c91","1f15867.488737a","36a62693.5c11b2","79681c1e.90ebdc","d5fb64b6.8e94e8","3ae7a6cd.805cda","61d3e354.cc9f7c","f9105b74.d9fc5","48720e7b.df608","2e3bfe95.2f5a9a","39683881.e496b8","1e24075e.7c64a9","e3216d45.144648"]]},{"id":"3ae7a6cd.805cda","type":"xiaomi-plug","z":"b7132358.47476","gateway":"","name":"","sid":"","onmsg":"","offmsg":"","output":"0","x":490,"y":820,"wires":[["ee6734f2.6e1e6","b46eec04.6a401","53516e47.bdb338"]]},{"id":"61d3e354.cc9f7c","type":"xiaomi-plug-wifi","z":"b7132358.47476","name":"","ip":"","onmsg":"","offmsg":"","output":"0","x":500,"y":880,"wires":[[]]},{"id":"ee6734f2.6e1e6","type":"xiaomi-actions read","z":"b7132358.47476","name":"","x":810,"y":580,"wires":[["1aa456d0.dea0e9"]]},{"id":"ff64227b.22f8a","type":"xiaomi-actions get_id_list","z":"b7132358.47476","name":"","x":820,"y":800,"wires":[["1aa456d0.dea0e9"]]},{"id":"92cbad8e.098568","type":"xiaomi-actions gateway_light","z":"b7132358.47476","name":"","brightness":100,"hexRgbColor":"#ffffff","color":{"red":255,"green":255,"blue":255},"x":820,"y":840,"wires":[["1aa456d0.dea0e9"]]},{"id":"520452ff.0f57e4","type":"xiaomi-actions gateway_sound","z":"b7132358.47476","name":"","mid":"","volume":"","x":830,"y":880,"wires":[["1aa456d0.dea0e9"]]},{"id":"87e164af.8c453","type":"xiaomi-actions gateway_stop_sound","z":"b7132358.47476","name":"","x":830,"y":920,"wires":[["1aa456d0.dea0e9"]]},{"id":"b46eec04.6a401","type":"xiaomi-actions on","z":"b7132358.47476","name":"","x":820,"y":980,"wires":[["1aa456d0.dea0e9"]]},{"id":"53516e47.bdb338","type":"xiaomi-actions off","z":"b7132358.47476","name":"","x":820,"y":1020,"wires":[["1aa456d0.dea0e9"]]},{"id":"9e60c17d.8a64a8","type":"xiaomi-actions click","z":"b7132358.47476","name":"","x":810,"y":680,"wires":[["1aa456d0.dea0e9"]]},{"id":"6d2591a6.89d498","type":"xiaomi-actions double_click","z":"b7132358.47476","name":"","x":830,"y":720,"wires":[["1aa456d0.dea0e9"]]},{"id":"1aa456d0.dea0e9","type":"xiaomi-gateway out","z":"b7132358.47476","name":"","gateway":"","ip":"","x":1200,"y":780,"wires":[]},{"id":"f9105b74.d9fc5","type":"xiaomi-actions on","z":"b7132358.47476","name":"","x":280,"y":860,"wires":[["61d3e354.cc9f7c"]]},{"id":"48720e7b.df608","type":"xiaomi-actions off","z":"b7132358.47476","name":"","x":280,"y":900,"wires":[["61d3e354.cc9f7c"]]},{"id":"29bb58e2.28de","type":"comment","z":"b7132358.47476","name":"Filter incoming","info":"","x":420,"y":40,"wires":[]},{"id":"33967b37.6d4e44","type":"comment","z":"b7132358.47476","name":"Outgoing","info":"","x":420,"y":500,"wires":[]},{"id":"a39ea960.771b88","type":"xiaomi-yeelight out","z":"b7132358.47476","name":"","ip":"","port":55443,"x":490,"y":1040,"wires":[]},{"id":"2e3bfe95.2f5a9a","type":"xiaomi-actions on","z":"b7132358.47476","name":"","x":280,"y":980,"wires":[["a39ea960.771b88"]]},{"id":"39683881.e496b8","type":"xiaomi-actions off","z":"b7132358.47476","name":"","x":280,"y":1020,"wires":[["a39ea960.771b88"]]},{"id":"1e24075e.7c64a9","type":"xiaomi-actions toggle","z":"b7132358.47476","name":"","x":290,"y":1060,"wires":[["a39ea960.771b88"]]},{"id":"e3216d45.144648","type":"xiaomi-actions gateway_light","z":"b7132358.47476","name":"","brightness":100,"hexRgbColor":"#ffffff","color":{"red":255,"green":255,"blue":255},"x":280,"y":1100,"wires":[["a39ea960.771b88"]]},{"id":"3229d6fd.1e35fa","type":"xiaomi-all","z":"b7132358.47476","gateway":"","name":"","onlyModels":[],"excludedSids":[],"x":420,"y":100,"wires":[["37052c54.e2b62c"]]}]
\ No newline at end of file
+[{"id":"c26e5d65.2ba2f","type":"tab","label":"Mi Devices Overview","disabled":false,"info":""},{"id":"dc0246f8.77bab8","type":"debug","z":"c26e5d65.2ba2f","name":"","active":true,"console":"false","complete":"true","x":710,"y":220,"wires":[]},{"id":"9d3513ae.0cb99","type":"inject","z":"c26e5d65.2ba2f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":98.5714340209961,"y":622.8571577072144,"wires":[["b109ba00.876218","2f4f3b9c.8a33dc","26fba522.5a10ca","c01e5a36.4cf8a","198c4c4f.4a0e2c","f173be1d.ec8e","b4ecbe6b.11bb18","cf39b156.791e68","7e7a6423.d08204"]]},{"id":"6299b44b.364bd4","type":"comment","z":"c26e5d65.2ba2f","name":"Filter incoming","info":"","x":420,"y":40,"wires":[]},{"id":"42ef97c5.c5c0d8","type":"comment","z":"c26e5d65.2ba2f","name":"Outgoing","info":"","x":418.57143211364746,"y":457.1428575515747,"wires":[]},{"id":"fc699927.573898","type":"mi-devices-gateway in","z":"c26e5d65.2ba2f","name":"","gateway":"","x":89.5,"y":227,"wires":[["20b19f26.614d9","935e31dd.7598c8","2d26be73.a95cf2","76949f24.3362f","8ff4f2d9.3bb058","30f884ad.d891f4","7f4fa8ce.a2abb"]]},{"id":"20b19f26.614d9","type":"mi-devices-all","z":"c26e5d65.2ba2f","gateway":"","name":"","onlyModels":[],"excludedSids":[],"x":426.5,"y":97,"wires":[["dc0246f8.77bab8"]]},{"id":"935e31dd.7598c8","type":"mi-devices-mi.weather","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":430.5,"y":138,"wires":[["dc0246f8.77bab8"]]},{"id":"2d26be73.a95cf2","type":"mi-devices-magnet","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":433.5,"y":179,"wires":[["dc0246f8.77bab8"]]},{"id":"76949f24.3362f","type":"mi-devices-mi.motion","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":429.5,"y":217,"wires":[["dc0246f8.77bab8"]]},{"id":"8ff4f2d9.3bb058","type":"mi-devices-mi.switch","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":427.5,"y":256,"wires":[["dc0246f8.77bab8"]]},{"id":"30f884ad.d891f4","type":"mi-devices-gateway","z":"c26e5d65.2ba2f","gateway":"","name":"","x":418.5,"y":295,"wires":[["dc0246f8.77bab8"]]},{"id":"7f4fa8ce.a2abb","type":"mi-devices-plug","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","onmsg":"","offmsg":"","output":"0","x":416.5,"y":333,"wires":[["dc0246f8.77bab8"]]},{"id":"c8141d3d.1b5248","type":"mi-devices-gateway out","z":"c26e5d65.2ba2f","name":"","x":1018.0714340209961,"y":694.8571577072144,"wires":[]},{"id":"b109ba00.876218","type":"mi-devices-mi.weather","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":397.0714340209961,"y":514.8571577072144,"wires":[["47c4031.d1a7f7c"]]},{"id":"47c4031.d1a7f7c","type":"mi-devices-actions read","z":"c26e5d65.2ba2f","name":"","x":678.0714340209961,"y":519.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"2f4f3b9c.8a33dc","type":"mi-devices-magnet","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":389.0714340209961,"y":552.8571577072144,"wires":[["47c4031.d1a7f7c"]]},{"id":"26fba522.5a10ca","type":"mi-devices-mi.motion","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":399.0714340209961,"y":593.8571577072144,"wires":[["47c4031.d1a7f7c"]]},{"id":"c01e5a36.4cf8a","type":"mi-devices-mi.switch","z":"c26e5d65.2ba2f","gateway":"","name":"","sid":"","x":399.0714340209961,"y":632.8571577072144,"wires":[["47c4031.d1a7f7c","d15c2b2e.8e4d48","d61e07d.3171b78"]]},{"id":"d15c2b2e.8e4d48","type":"mi-devices-actions click","z":"c26e5d65.2ba2f","name":"","x":673.0714340209961,"y":612.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"d61e07d.3171b78","type":"mi-devices-actions double_click","z":"c26e5d65.2ba2f","name":"","x":701.0714340209961,"y":649.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"198c4c4f.4a0e2c","type":"mi-devices-gateway","z":"c26e5d65.2ba2f","gateway":"","name":"","x":361.0714340209961,"y":706.8571577072144,"wires":[["47c4031.d1a7f7c","2d3b6309.eb4df4","bb2c9352.8b7c3","d31c9974.bcb9c8","dc7a7421.cd3ca8"]]},{"id":"2d3b6309.eb4df4","type":"mi-devices-actions get_id_list","z":"c26e5d65.2ba2f","name":"","x":692.0714340209961,"y":720.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"bb2c9352.8b7c3","type":"mi-devices-actions light","z":"c26e5d65.2ba2f","name":"","brightness":100,"hexRgbColor":"#ffffff","color":{"red":255,"green":255,"blue":255},"x":654.0714340209961,"y":761.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"d31c9974.bcb9c8","type":"mi-devices-actions gateway_play_sound","z":"c26e5d65.2ba2f","name":"","mid":"","volume":"","x":664.0714340209961,"y":801.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"dc7a7421.cd3ca8","type":"mi-devices-actions gateway_stop_sound","z":"c26e5d65.2ba2f","name":"","x":665.0714340209961,"y":839.8571577072144,"wires":[["c8141d3d.1b5248"]]},{"id":"629b3888.01683","type":"mi-devices-yeelight out","z":"c26e5d65.2ba2f","name":"","yeelight":"","x":725.0714340209961,"y":968.8571577072144,"wires":[]},{"id":"f173be1d.ec8e","type":"mi-devices-actions turn_on","z":"c26e5d65.2ba2f","name":"","x":390.0714340209961,"y":897.8571577072144,"wires":[["629b3888.01683"]]},{"id":"b4ecbe6b.11bb18","type":"mi-devices-actions turn_off","z":"c26e5d65.2ba2f","name":"","x":391.0714340209961,"y":938.8571577072144,"wires":[["629b3888.01683"]]},{"id":"cf39b156.791e68","type":"mi-devices-actions toggle","z":"c26e5d65.2ba2f","name":"","x":392.0714340209961,"y":981.8571577072144,"wires":[["629b3888.01683"]]},{"id":"7e7a6423.d08204","type":"mi-devices-actions light","z":"c26e5d65.2ba2f","name":"","brightness":100,"hexRgbColor":"#ffffff","color":{"red":255,"green":255,"blue":255},"x":363.0714340209961,"y":1021.8571577072144,"wires":[["629b3888.01683"]]}]
\ No newline at end of file
diff --git a/flows-sample.json b/flows-sample.json
index 8f71180..5d35a73 100644
--- a/flows-sample.json
+++ b/flows-sample.json
@@ -1 +1 @@
-[{"id":"42c840ac.0d4088","type":"tab","label":"Mi Devices Sample","disabled":false,"info":""},{"id":"a2d5e2c3.b92fd","type":"comment","z":"42c840ac.0d4088","name":"Get all sensors and gateway statuses","info":"","x":390,"y":40,"wires":[]},{"id":"c53c673b.414f28","type":"inject","z":"42c840ac.0d4088","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"x":107.14285714285711,"y":95,"wires":[["41eb0695.e63408","eafddced.3b04a"]]},{"id":"41eb0695.e63408","type":"xiaomi-all","z":"42c840ac.0d4088","gateway":"","name":"","x":300,"y":100,"wires":[["91c1bb66.39253"]]},{"id":"eafddced.3b04a","type":"xiaomi-gateway","z":"42c840ac.0d4088","gateway":"","name":"","x":320,"y":160,"wires":[["d19fb980.133f68"]]},{"id":"91c1bb66.39253","type":"split","z":"42c840ac.0d4088","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":490,"y":100,"wires":[["3185cd39.74c96a"]]},{"id":"3185cd39.74c96a","type":"change","z":"42c840ac.0d4088","name":"set id","rules":[{"t":"set","p":"sid","pt":"msg","to":"payload.sid","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":650,"y":100,"wires":[["d19fb980.133f68"]]},{"id":"d19fb980.133f68","type":"xiaomi-actions read","z":"42c840ac.0d4088","name":"","x":810,"y":100,"wires":[["257a96ea.456742"]]},{"id":"257a96ea.456742","type":"xiaomi-gateway out","z":"42c840ac.0d4088","name":"","gateway":"","ip":"","x":1000,"y":100,"wires":[]},{"id":"15a7a55c.ee2a13","type":"comment","z":"42c840ac.0d4088","name":"Check if a window at least one window open","info":"","x":410,"y":300,"wires":[]},{"id":"6444fdcc.e8d0b4","type":"xiaomi-all","z":"42c840ac.0d4088","gateway":"","name":"","x":300,"y":360,"wires":[["f3ac9439.133d68","47f2046.6bea47c"]]},{"id":"c04374e6.58de3","type":"split","z":"42c840ac.0d4088","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":360,"wires":[["97dc0a89.8f2808"]]},{"id":"f3ac9439.133d68","type":"function","z":"42c840ac.0d4088","name":"filter windows","func":"let windowSensors = msg.payload.filter((e) => {\n return e.model === \"magnet\";\n});\nmsg.payload = windowSensors;\nreturn msg;","outputs":1,"noerr":0,"x":460,"y":360,"wires":[["c04374e6.58de3"]]},{"id":"97dc0a89.8f2808","type":"change","z":"42c840ac.0d4088","name":"set id","rules":[{"t":"set","p":"sid","pt":"msg","to":"payload.sid","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":770,"y":360,"wires":[["c92e4522.5f451"]]},{"id":"c92e4522.5f451","type":"xiaomi-actions read","z":"42c840ac.0d4088","name":"","x":910,"y":360,"wires":[["faa7cd47.2f0ed"]]},{"id":"faa7cd47.2f0ed","type":"xiaomi-gateway out","z":"42c840ac.0d4088","name":"","gateway":"","ip":"","x":1100,"y":360,"wires":[]},{"id":"a354a2db.a472c8","type":"xiaomi-gateway in","z":"42c840ac.0d4088","name":"","gateway":"","ip":"","x":100,"y":480,"wires":[["c888e01a.97754"]]},{"id":"c888e01a.97754","type":"function","z":"42c840ac.0d4088","name":"set window sensor value","func":"if ([\"magnet\", \"sensor_magnet.aq2\"].indexOf(msg.payload.model) >= 0 && msg.payload.sid !== \"158d0001ab1fa8\") {\n let globalKey = `windowSensorStatus-${msg.payload.sid}`;\n global.set(globalKey, msg.payload.data.status);\n}\n","outputs":"0","noerr":0,"x":330,"y":480,"wires":[]},{"id":"b6ce1add.fd8098","type":"function","z":"42c840ac.0d4088","name":"get window sensors values","func":"let windowSensors = {};\nmsg.payload.filter((e) => {\n return e.model === \"magnet\";\n}).forEach((e) => {\n let globalKey = `windowSensorStatus-${e.sid}`;\n let value = global.get(globalKey);\n if(!value || value == \"open\") {\n windowSensors[e.sid] = value || \"na\";\n }\n});\n\nmsg.payload = windowSensors;\nif(Object.keys(windowSensors).length) {\n return [msg, null];\n}\nreturn [null, msg];","outputs":"2","noerr":0,"x":680,"y":420,"wires":[[],[]],"outputLabels":["at least one window is open","all windows are close"]},{"id":"47f2046.6bea47c","type":"delay","z":"42c840ac.0d4088","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":450,"y":420,"wires":[["b6ce1add.fd8098"]]},{"id":"d85b4cea.a9e338","type":"comment","z":"42c840ac.0d4088","name":"Doorbell","info":"","x":300,"y":580,"wires":[]},{"id":"f1a76c90.712d08","type":"xiaomi-gateway in","z":"42c840ac.0d4088","name":"","gateway":"","ip":"","x":100,"y":660,"wires":[["2418c093.cc257"]]},{"id":"b11d36c7.eac58","type":"function","z":"42c840ac.0d4088","name":"is click","func":"if(msg.payload.cmd === \"report\" && msg.payload.data.status == \"click\") {\n return msg;\n}\nreturn null;","outputs":"1","noerr":0,"x":470,"y":660,"wires":[["f7756ac2.ebcec","b82c564e.b4d968","65bfdf1e.559d48"]]},{"id":"f7756ac2.ebcec","type":"xiaomi-actions gateway_sound","z":"42c840ac.0d4088","name":"","mid":"10","volume":"20","x":650,"y":640,"wires":[["d9796e1c.9da33"]]},{"id":"d9796e1c.9da33","type":"xiaomi-gateway out","z":"42c840ac.0d4088","name":"","gateway":"","ip":"","x":1260,"y":640,"wires":[]},{"id":"c2d7b15f.511b9","type":"template","z":"42c840ac.0d4088","name":"off","field":"brightness","fieldType":"msg","format":"handlebars","syntax":"plain","template":"0","output":"str","x":850,"y":800,"wires":[["c580b5dc.f26a8"]]},{"id":"b82c564e.b4d968","type":"template","z":"42c840ac.0d4088","name":"on","field":"brightness","fieldType":"msg","format":"handlebars","syntax":"plain","template":"100","output":"str","x":850,"y":760,"wires":[["c580b5dc.f26a8"]]},{"id":"c580b5dc.f26a8","type":"xiaomi-actions gateway_light","z":"42c840ac.0d4088","name":"","x":1000,"y":760,"wires":[["d9796e1c.9da33"]]},{"id":"65bfdf1e.559d48","type":"delay","z":"42c840ac.0d4088","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":720,"wires":[["c2d7b15f.511b9","71efc952.99aa1"]]},{"id":"71efc952.99aa1","type":"delay","z":"42c840ac.0d4088","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":760,"wires":[["b82c564e.b4d968","9de20a84.4b1798"]]},{"id":"9de20a84.4b1798","type":"delay","z":"42c840ac.0d4088","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":800,"wires":[["c2d7b15f.511b9","c6e951d9.77c5"]]},{"id":"c6e951d9.77c5","type":"delay","z":"42c840ac.0d4088","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":840,"wires":[["b82c564e.b4d968","6b737819.10c298"]]},{"id":"6b737819.10c298","type":"delay","z":"42c840ac.0d4088","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":880,"wires":[["c2d7b15f.511b9"]]},{"id":"8b00845a.d05ae","type":"comment","z":"42c840ac.0d4088","name":"gateway light flick 3 times","info":"","x":890,"y":720,"wires":[]},{"id":"2418c093.cc257","type":"xiaomi-switch","z":"42c840ac.0d4088","gateway":"","name":"","sid":"","x":300,"y":660,"wires":[["b11d36c7.eac58"]]}]
\ No newline at end of file
+[{"id":"a7780153.e80948","type":"tab","label":"Mi Devices Sample","disabled":false,"info":""},{"id":"e0c11cb8.d19f4","type":"comment","z":"a7780153.e80948","name":"Get all sensors and gateway statuses","info":"","x":390,"y":40,"wires":[]},{"id":"5c5e4db4.e10a84","type":"inject","z":"a7780153.e80948","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"x":107.14285714285711,"y":95,"wires":[["e6a4831a.203a","39a33c8b.2f070c"]]},{"id":"10c513e0.173424","type":"split","z":"a7780153.e80948","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":473,"y":95,"wires":[["22260d64.88cc4a"]]},{"id":"22260d64.88cc4a","type":"change","z":"a7780153.e80948","name":"set id","rules":[{"t":"set","p":"sid","pt":"msg","to":"payload.sid","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":629.9999732971191,"y":95.71428489685059,"wires":[["6958c446.7387dc"]]},{"id":"6fd23b34.f00c84","type":"comment","z":"a7780153.e80948","name":"Check if a window at least one window open","info":"","x":410,"y":300,"wires":[]},{"id":"8aad0e7d.af726","type":"split","z":"a7780153.e80948","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":630,"y":360,"wires":[["f11942f4.1940a8"]]},{"id":"cebd416e.e8ec7","type":"function","z":"a7780153.e80948","name":"filter windows","func":"let windowSensors = msg.payload.filter((e) => {\n return e.model === \"magnet\";\n});\nmsg.payload = windowSensors;\nreturn msg;","outputs":1,"noerr":0,"x":460,"y":360,"wires":[["8aad0e7d.af726"]]},{"id":"f11942f4.1940a8","type":"change","z":"a7780153.e80948","name":"set id","rules":[{"t":"set","p":"sid","pt":"msg","to":"payload.sid","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":770,"y":360,"wires":[["7ab840ba.050d1"]]},{"id":"eba1f751.2155e","type":"function","z":"a7780153.e80948","name":"set window sensor value","func":"if ([\"magnet\", \"sensor_magnet.aq2\"].indexOf(msg.payload.model) >= 0 && msg.payload.sid !== \"158d0001ab1fa8\") {\n let globalKey = `windowSensorStatus-${msg.payload.sid}`;\n global.set(globalKey, msg.payload.data.status);\n}\n","outputs":"0","noerr":0,"x":330,"y":480,"wires":[]},{"id":"fa8d5a4c.2b24","type":"function","z":"a7780153.e80948","name":"get window sensors values","func":"let windowSensors = {};\nmsg.payload.filter((e) => {\n return e.model === \"magnet\";\n}).forEach((e) => {\n let globalKey = `windowSensorStatus-${e.sid}`;\n let value = global.get(globalKey);\n if(!value || value == \"open\") {\n windowSensors[e.sid] = value || \"na\";\n }\n});\n\nmsg.payload = windowSensors;\nif(Object.keys(windowSensors).length) {\n return [msg, null];\n}\nreturn [null, msg];","outputs":"2","noerr":0,"x":680,"y":420,"wires":[[],[]],"outputLabels":["at least one window is open","all windows are close"]},{"id":"66e760b4.3aa808","type":"delay","z":"a7780153.e80948","name":"","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":450,"y":420,"wires":[["fa8d5a4c.2b24"]]},{"id":"82b6724e.158a6","type":"comment","z":"a7780153.e80948","name":"Doorbell","info":"","x":300,"y":580,"wires":[]},{"id":"b6eaa4f1.ab95e8","type":"function","z":"a7780153.e80948","name":"is click","func":"if(msg.payload.cmd === \"report\" && msg.payload.data.status == \"click\") {\n return msg;\n}\nreturn null;","outputs":"1","noerr":0,"x":470,"y":660,"wires":[["5b3e12d2.2bb634","5799ab4.8121954","53df8b56.14a174"]]},{"id":"24e5aa3d.e505be","type":"template","z":"a7780153.e80948","name":"off","field":"brightness","fieldType":"msg","format":"handlebars","syntax":"plain","template":"0","output":"str","x":850,"y":800,"wires":[["589890fb.6f3c6"]]},{"id":"5b3e12d2.2bb634","type":"template","z":"a7780153.e80948","name":"on","field":"brightness","fieldType":"msg","format":"handlebars","syntax":"plain","template":"100","output":"str","x":850,"y":760,"wires":[["589890fb.6f3c6"]]},{"id":"5799ab4.8121954","type":"delay","z":"a7780153.e80948","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":720,"wires":[["24e5aa3d.e505be","7bede046.d2ea48"]]},{"id":"7bede046.d2ea48","type":"delay","z":"a7780153.e80948","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":760,"wires":[["5b3e12d2.2bb634","cda882cb.ba62b8"]]},{"id":"cda882cb.ba62b8","type":"delay","z":"a7780153.e80948","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":800,"wires":[["24e5aa3d.e505be","50b3208b.2cd0b"]]},{"id":"50b3208b.2cd0b","type":"delay","z":"a7780153.e80948","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":840,"wires":[["5b3e12d2.2bb634","3913f36d.5871ac"]]},{"id":"3913f36d.5871ac","type":"delay","z":"a7780153.e80948","name":"500ms","pauseType":"delay","timeout":"500","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":650,"y":880,"wires":[["24e5aa3d.e505be"]]},{"id":"4c598791.b9cef","type":"comment","z":"a7780153.e80948","name":"gateway light flick 3 times","info":"","x":890,"y":720,"wires":[]},{"id":"44f42181.0266","type":"mi-devices-gateway out","z":"a7780153.e80948","name":"","x":1047.8571853637695,"y":94.28570747375488,"wires":[]},{"id":"e6a4831a.203a","type":"mi-devices-all","z":"a7780153.e80948","gateway":"","name":"","onlyModels":[],"excludedSids":[],"x":282.14285469055176,"y":95.14285659790039,"wires":[["10c513e0.173424"]]},{"id":"39a33c8b.2f070c","type":"mi-devices-gateway","z":"a7780153.e80948","gateway":"","name":"","x":282.14286041259766,"y":145.71428203582764,"wires":[["6958c446.7387dc"]]},{"id":"6958c446.7387dc","type":"mi-devices-actions read","z":"a7780153.e80948","name":"","x":862.1428680419922,"y":94.28571510314941,"wires":[["44f42181.0266"]]},{"id":"f023637d.a64158","type":"mi-devices-gateway out","z":"a7780153.e80948","name":"","x":1128.5714988708496,"y":358.5714454650879,"wires":[]},{"id":"7ab840ba.050d1","type":"mi-devices-actions read","z":"a7780153.e80948","name":"","x":947.1428833007812,"y":360,"wires":[["f023637d.a64158"]]},{"id":"e6580bd9.860678","type":"mi-devices-all","z":"a7780153.e80948","gateway":"","name":"","onlyModels":[],"excludedSids":[],"x":239.42857360839844,"y":360.9999761581421,"wires":[["cebd416e.e8ec7","66e760b4.3aa808"]]},{"id":"27a909ad.31cbe6","type":"mi-devices-gateway in","z":"a7780153.e80948","name":"","gateway":"","x":79.28571428571428,"y":480.5714285714285,"wires":[["eba1f751.2155e"]]},{"id":"2ee4ea88.00928e","type":"mi-devices-gateway in","z":"a7780153.e80948","name":"","gateway":"","x":79.28571319580078,"y":659.9999618530273,"wires":[["38730c56.824c1c"]]},{"id":"38730c56.824c1c","type":"mi-devices-mi.switch","z":"a7780153.e80948","gateway":"","name":"","sid":"","x":282.42857360839844,"y":659.9999618530273,"wires":[["b6eaa4f1.ab95e8"]]},{"id":"53df8b56.14a174","type":"mi-devices-actions gateway_play_sound","z":"a7780153.e80948","name":"","mid":"","volume":"","x":782.1428571428571,"y":624.2857142857142,"wires":[["dbdde19e.ecc4f"]]},{"id":"589890fb.6f3c6","type":"mi-devices-actions light","z":"a7780153.e80948","name":"","brightness":100,"hexRgbColor":"#ffffff","color":{"red":255,"green":255,"blue":255},"x":1067.8571243286133,"y":775.7142715454102,"wires":[["dbdde19e.ecc4f"]]},{"id":"dbdde19e.ecc4f","type":"mi-devices-gateway out","z":"a7780153.e80948","name":"","x":1250.7144012451172,"y":652.8572006225586,"wires":[]}]
\ No newline at end of file
diff --git a/resources/mi-configurator.png b/resources/mi-configurator.png
index 2147aec..ff2d806 100644
Binary files a/resources/mi-configurator.png and b/resources/mi-configurator.png differ
diff --git a/resources/mi-devices-overview.png b/resources/mi-devices-overview.png
index 01f428c..0729316 100644
Binary files a/resources/mi-devices-overview.png and b/resources/mi-devices-overview.png differ
diff --git a/resources/mi-devices-sample.png b/resources/mi-devices-sample.png
index 6c26fff..da3218a 100644
Binary files a/resources/mi-devices-sample.png and b/resources/mi-devices-sample.png differ