diff --git a/icons/devices/door-icon.png b/icons/devices/door-icon.png new file mode 100644 index 0000000..f33e892 Binary files /dev/null and b/icons/devices/door-icon.png differ diff --git a/icons/devices/mi-all.png b/icons/devices/mi-all.png new file mode 100644 index 0000000..b2c29af Binary files /dev/null and b/icons/devices/mi-all.png differ diff --git a/icons/devices/mi-switch.png b/icons/devices/mi-switch.png new file mode 100644 index 0000000..8ee44b2 Binary files /dev/null and b/icons/devices/mi-switch.png differ diff --git a/icons/devices/motion-icon.png b/icons/devices/motion-icon.png new file mode 100644 index 0000000..86c7e68 Binary files /dev/null and b/icons/devices/motion-icon.png differ diff --git a/icons/devices/outlet-icon.png b/icons/devices/outlet-icon.png new file mode 100644 index 0000000..f9c7e4a Binary files /dev/null and b/icons/devices/outlet-icon.png differ diff --git a/icons/devices/thermometer-icon.png b/icons/devices/thermometer-icon.png new file mode 100644 index 0000000..b25be7d Binary files /dev/null and b/icons/devices/thermometer-icon.png differ diff --git a/icons/gateway/mijia-io.png b/icons/gateway/mijia-io.png new file mode 100644 index 0000000..7058afb Binary files /dev/null and b/icons/gateway/mijia-io.png differ diff --git a/icons/gateway/mijia.png b/icons/gateway/mijia.png new file mode 100644 index 0000000..7f73eae Binary files /dev/null and b/icons/gateway/mijia.png differ diff --git a/node-red-contrib-xiaomi-configurator/icons/door-tw-icon.png b/node-red-contrib-xiaomi-configurator/icons/door-tw-icon.png deleted file mode 100644 index 859d2ee..0000000 Binary files a/node-red-contrib-xiaomi-configurator/icons/door-tw-icon.png and /dev/null differ diff --git a/node-red-contrib-xiaomi-configurator/icons/mi-tw-switch.png b/node-red-contrib-xiaomi-configurator/icons/mi-tw-switch.png deleted file mode 100644 index 9040029..0000000 Binary files a/node-red-contrib-xiaomi-configurator/icons/mi-tw-switch.png and /dev/null differ diff --git a/node-red-contrib-xiaomi-configurator/icons/motion-tw-icon.png b/node-red-contrib-xiaomi-configurator/icons/motion-tw-icon.png deleted file mode 100644 index 010a006..0000000 Binary files a/node-red-contrib-xiaomi-configurator/icons/motion-tw-icon.png and /dev/null differ diff --git a/node-red-contrib-xiaomi-configurator/icons/outlet-tw-icon.png b/node-red-contrib-xiaomi-configurator/icons/outlet-tw-icon.png deleted file mode 100644 index 6faa09e..0000000 Binary files a/node-red-contrib-xiaomi-configurator/icons/outlet-tw-icon.png and /dev/null differ diff --git a/node-red-contrib-xiaomi-configurator/icons/switch-tw-icon.png b/node-red-contrib-xiaomi-configurator/icons/switch-tw-icon.png deleted file mode 100644 index a9ef156..0000000 Binary files a/node-red-contrib-xiaomi-configurator/icons/switch-tw-icon.png and /dev/null differ diff --git a/node-red-contrib-xiaomi-configurator/icons/thermometer-tw-icon.png b/node-red-contrib-xiaomi-configurator/icons/thermometer-tw-icon.png deleted file mode 100644 index dfe2cb7..0000000 Binary files a/node-red-contrib-xiaomi-configurator/icons/thermometer-tw-icon.png and /dev/null differ diff --git a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html b/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html deleted file mode 100644 index 5ebba6b..0000000 --- a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - diff --git a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js b/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js deleted file mode 100644 index efb0c53..0000000 --- a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js +++ /dev/null @@ -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); -} diff --git a/package.json b/package.json index f6c69b9..d2dc66b 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "clean": "rimraf dist", "build": "npm run clean && npm run build:ts && npm run build:ejs && npm run build:icons", "build:ts": "tsc --allowUnreachableCode -p .", - "build:ejs": "npm run build:ejs:indexes", + "build:ejs": "npm run build:ejs:indexes && npm run build:ejs:devices", "build:ejs:indexes": "ejs-cli --base-dir src/ --options \"{\\\"NODES_PREFIX\\\": \\\"mi-devices\\\"}\" \"**/index.ejs\" --out dist/", "build:ejs:devices": "ejs-cli --base-dir src/ --options \"{\\\"NODES_PREFIX\\\": \\\"mi-devices\\\"}\" \"nodes/devices/*.ejs\" --out dist/", - "build:icons": "npm run build:icons:yeelight", + "build:icons": "npm run build:icons:gateway && npm run build:icons:devices && npm run build:icons:actions && npm run build:icons:yeelight", "build:icons:gateway": "cp -pr icons/gateway dist/nodes/gateway/icons", "build:icons:devices": "cp -pr icons/devices dist/nodes/devices/icons", "build:icons:actions": "cp -pr icons/actions dist/nodes/actions/icons", @@ -28,15 +28,14 @@ ], "node-red": { "nodes": { - "xiaomi-ht": "node-red-contrib-xiaomi-ht/xiaomi-ht.js", - "xiaomi-magnet": "node-red-contrib-xiaomi-magnet/xiaomi-magnet.js", - "xiaomi-motion": "node-red-contrib-xiaomi-motion/xiaomi-motion.js", - "xiaomi-switch": "node-red-contrib-xiaomi-switch/xiaomi-switch.js", + "xiaomi-ht": "dist/nodes/devices/Sensor.js", + "xiaomi-magnet": "dist/nodes/devices/Magnet.js", + "xiaomi-motion": "dist/nodes/devices/Motion.js", + "xiaomi-switch": "dist/nodes/devices/Switch.js", "xiaomi-socket": "node-red-contrib-xiaomi-socket/xiaomi-socket.js", "xiaomi-socket-wifi": "node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js", - "xiaomi-all": "node-red-contrib-xiaomi-all/xiaomi-all.js", - "xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js", - "xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js", + "xiaomi-all": "dist/nodes/devices/All.js", + "xiaomi-gateway": "dist/nodes/gateway/index.js", "xiaomi-actions": "node-red-contrib-xiaomi-actions/xiaomi-actions.js", "xiaomi-yeelight": "dist/nodes/yeelight/index.js" } diff --git a/src/nodes/gateway/Gateway.ejs b/src/nodes/gateway/Gateway.ejs new file mode 100644 index 0000000..7ca4a7b --- /dev/null +++ b/src/nodes/gateway/Gateway.ejs @@ -0,0 +1,47 @@ + + + + + \ No newline at end of file diff --git a/src/nodes/gateway/Gateway.ts b/src/nodes/gateway/Gateway.ts new file mode 100644 index 0000000..ae5e456 --- /dev/null +++ b/src/nodes/gateway/Gateway.ts @@ -0,0 +1,62 @@ +import { Red, Node, NodeProperties } from "node-red"; +import { LumiAqara } from "../../../typings/index"; +import { Constants } from "../constants"; + +export interface IGatewayNode extends Node { + gatewayConf:any; + gateway: LumiAqara.Gateway; + + setGateway(gateway:LumiAqara.Gateway); +} + +export default (RED:Red) => { + class Gateway { + protected gatewayConf: any; + protected gateway: LumiAqara.Gateway; + + constructor(props:NodeProperties){ + RED.nodes.createNode( this, props); + this.gatewayConf = RED.nodes.getNode(( props).gateway); + this.gateway = null; + + ( this).status({fill:"red", shape:"ring", text: "offline"}); + this.setMessageListener(); + } + + protected setMessageListener() { + ( this).on('input', (msg) => { + if (this.gateway) { + var payload = msg.payload; + + // Input from gateway + if(payload.sid && 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.sid = this.gateway.sid; + ( this).send(msg); + } + } + }); + } + + setGateway(gateway:LumiAqara.Gateway) { + this.gateway = gateway; + this.gateway.setPassword(this.gatewayConf.password); + ( this).status({fill:"blue", shape:"dot", text: "online"}); + + this.gateway.on('offline', () => { + this.gateway = null; + ( this).status({fill:"red", shape:"ring", text: "offline"}); + }); + }; + } + + RED.nodes.registerType(`${Constants.NODES_PREFIX}-gateway`, Gateway); +}; \ No newline at end of file diff --git a/src/nodes/gateway/GatewayConfigurator.ejs b/src/nodes/gateway/GatewayConfigurator.ejs new file mode 100644 index 0000000..54ee938 --- /dev/null +++ b/src/nodes/gateway/GatewayConfigurator.ejs @@ -0,0 +1,120 @@ + + + + + diff --git a/src/nodes/gateway/GatewayConfigurator.ts b/src/nodes/gateway/GatewayConfigurator.ts new file mode 100644 index 0000000..43561a8 --- /dev/null +++ b/src/nodes/gateway/GatewayConfigurator.ts @@ -0,0 +1,44 @@ +import { Red, Node, NodeProperties, NodeStatus, ClearNodeStatus } from "node-red"; +import { Constants } from "../constants"; +import { Searcher } from "./Searcher"; +import { LumiAqara } from "../../../typings/index"; + +export interface IGatewayConfiguratorNode extends Node { + ip:string; + sid:number; + gateway: LumiAqara.Gateway; + + on(event: "gatewayFound", listener: () => void): any; +} + +export default (RED:Red) => { + class GatewayConfigurator { + ip:string; + sid:number; + _gateway:LumiAqara.Gateway; + + constructor(props: NodeProperties) { + RED.nodes.createNode( this, props); + let {ip, sid} = props; + this.sid = sid; + this.ip = ip; + } + + set gateway(gateway:LumiAqara.Gateway) { + this._gateway = gateway; + this._gateway.setPassword(( this).credentials.key); + ( this).emit('gatewayFound'); + } + + get gateway() { + return this._gateway; + } + } + + RED.nodes.registerType(`${Constants.NODES_PREFIX}-gateway configurator`, GatewayConfigurator, { + settings: { + miDevicesGatewayConfiguratorDiscoveredGateways: { value: Searcher.gateways, exportable: true } + }, + credentials: { key: {type:"text"} } + }); +}; diff --git a/src/nodes/gateway/GatewayIn.ejs b/src/nodes/gateway/GatewayIn.ejs new file mode 100644 index 0000000..caf3f31 --- /dev/null +++ b/src/nodes/gateway/GatewayIn.ejs @@ -0,0 +1,55 @@ + + + + + \ No newline at end of file diff --git a/src/nodes/gateway/GatewayIn.ts b/src/nodes/gateway/GatewayIn.ts new file mode 100644 index 0000000..c34cecd --- /dev/null +++ b/src/nodes/gateway/GatewayIn.ts @@ -0,0 +1,70 @@ +import {Red, Node, NodeProperties} from 'node-red'; +import {LumiAqara} from '../../../typings'; +import { Constants } from '../constants'; + +export interface IGatewayInNode extends Node { + gatewayConf:any; + gateway: LumiAqara.Gateway; + + setGateway(gateway:LumiAqara.Gateway); +} + +export default (RED:Red) => { + class GatewayIn { + protected gatewayConf: any; + protected gateway: LumiAqara.Gateway; + + constructor(props:NodeProperties) { + RED.nodes.createNode( this, props); + this.gatewayConf = RED.nodes.getNode(( props).gateway); + (this).status({fill:"red", shape:"ring", text: "offline"}); + } + + setGateway(gateway:LumiAqara.Gateway) { + this.gateway = gateway; + this.gateway.setPassword(this.gatewayConf.password); + (this).status({fill:"blue", shape:"dot", text: "online"}); + + this.gateway.on('offline', () => { + this.gateway = null; + (this).status({fill:"red", shape:"ring", text: "offline"}); + }); + + this.gateway.on('subdevice', (device) => { + device.sid = device.getSid(); + device.type = device.getType(); + device.data = { + voltage: device.getBatteryVoltage(), + batteryLevel: device.getBatteryPercentage() + }; + switch (device.type) { + case 'magnet': + device.data.status = device.isOpen() ? 'open' : 'close'; + break; + case 'switch': + device.on('click', () => { + // Saaad + }); + break; + case 'motion': + break; + case 'sensor': + device.data.temperature = device.getTemperature(); + device.data.humidity = device.getHumidity(); + device.data.pressure = device.getPressure(); + break; + case 'leak': + break; + case 'cube': + break; + }; + + (this).send({ + payload: device + }); + }); + } + } + + RED.nodes.registerType(`${Constants.NODES_PREFIX}-gateway in`, GatewayIn); +}; \ No newline at end of file diff --git a/src/nodes/gateway/GatewayOut.ejs b/src/nodes/gateway/GatewayOut.ejs new file mode 100644 index 0000000..d415e01 --- /dev/null +++ b/src/nodes/gateway/GatewayOut.ejs @@ -0,0 +1,54 @@ + + + + + \ No newline at end of file diff --git a/src/nodes/gateway/GatewayOut.ts b/src/nodes/gateway/GatewayOut.ts new file mode 100644 index 0000000..1748987 --- /dev/null +++ b/src/nodes/gateway/GatewayOut.ts @@ -0,0 +1,49 @@ +import { Red, NodeProperties } from "node-red"; +import { LumiAqara } from "../../../typings/index"; +import { Constants } from "../constants"; + +export interface IGatewayOutNode extends Node { + gatewayConf:any; + gateway: LumiAqara.Gateway; + + setGateway(gateway:LumiAqara.Gateway); +} + +export default (RED:Red) => { + class GatewayOut { + protected gatewayConf: any; + protected gateway: LumiAqara.Gateway; + + constructor(props:NodeProperties) { + RED.nodes.createNode( this, props); + this.gatewayConf= RED.nodes.getNode(( props).gateway); + ( this).status({fill:"red", shape:"ring", text: "offline"}); + + this.setMessageListener(); + } + + protected setMessageListener() { + ( this).on("input", (msg) => { + if (msg.hasOwnProperty("payload") && this.gateway) { + if(msg.payload.cmd === "write" && !msg.payload.data.key && this.gateway && this.gateway.sid && this.gateway._key) { + msg.payload.data.key = this.gateway._key; + } + this.gateway._sendUnicast(JSON.stringify(msg.payload)); + } + }); + } + + setGateway(gateway) { + this.gateway = gateway; + this.gateway.setPassword(this.gatewayConf.password); + ( this).status({fill:"blue", shape:"dot", text: "online"}); + + this.gateway.on('offline', () => { + this.gateway = null; + ( this).status({fill:"red", shape:"ring", text: "offline"}); + }); + } + } + + RED.nodes.registerType(`${Constants.NODES_PREFIX}-gateway out`, GatewayOut); +}; \ No newline at end of file diff --git a/src/nodes/gateway/Searcher.ts b/src/nodes/gateway/Searcher.ts new file mode 100644 index 0000000..7cde419 --- /dev/null +++ b/src/nodes/gateway/Searcher.ts @@ -0,0 +1,42 @@ +import { Red } from "node-red"; +import * as LumiAqara from 'lumi-aqara'; + +import { Constants } from "../constants"; +import { IGatewayConfiguratorNode } from "./GatewayConfigurator"; + +export class Searcher { + static _gateways:LumiAqara.Gateway[] = []; + + static discover(RED:Red) { + new Promise(() => { + const aqara = new LumiAqara(); + + aqara.on('gateway', (gateway:LumiAqara.Gateway) => { + let frontGateway = { + sid: gateway.sid, + ip: gateway.ip, + subdevices: [] + }; + this._gateways.push(frontGateway); + gateway.on('subdevice', (device:LumiAqara.SubDevice) => { + frontGateway.subdevices.push({ + sid: device.getSid(), + type: device.getType() + }); + }); + RED.nodes.eachNode((tmpNode) => { + if(tmpNode.type.indexOf(`${Constants.NODES_PREFIX}-gateway configurator`) === 0) { + let tmpNodeInst = RED.nodes.getNode(tmpNode.id); + if(tmpNodeInst && (tmpNodeInst.ip === gateway.ip || tmpNodeInst.sid === gateway.sid)) { + tmpNodeInst.gateway = gateway; + } + } + }); + }); + }); + } + + static get gateways():LumiAqara.Gateway { + return this._gateways; + } +} \ No newline at end of file diff --git a/src/nodes/gateway/index.ejs b/src/nodes/gateway/index.ejs new file mode 100644 index 0000000..f925820 --- /dev/null +++ b/src/nodes/gateway/index.ejs @@ -0,0 +1,4 @@ +<%- include('./GatewayConfigurator', {}); %> +<%- include('./Gateway', {}); %> +<%- include('./GatewayIn', {}); %> +<%- include('./GatewayOut', {}); %> \ No newline at end of file diff --git a/src/nodes/gateway/index.ts b/src/nodes/gateway/index.ts new file mode 100644 index 0000000..3aaedb5 --- /dev/null +++ b/src/nodes/gateway/index.ts @@ -0,0 +1,17 @@ +import { Red, NodeProperties } from "node-red"; +import * as LumiAqara from 'lumi-aqara'; + +import { Searcher } from "./Searcher"; +import {default as GatewayConfigurator} from "./GatewayConfigurator"; +import {default as Gateway} from "./Gateway"; +import {default as GatewayIn} from "./GatewayIn"; +import {default as GatewayOut} from "./GatewayOut"; + +export = (RED:Red) => { + Searcher.discover(RED); + + GatewayConfigurator(RED); + Gateway(RED); + GatewayIn(RED); + GatewayOut(RED); +}; diff --git a/src/nodes/yeelight/Searcher.ts b/src/nodes/yeelight/Searcher.ts index b3d67a1..08e3cf1 100644 --- a/src/nodes/yeelight/Searcher.ts +++ b/src/nodes/yeelight/Searcher.ts @@ -2,6 +2,7 @@ import { Red } from "node-red"; import * as YeelightSearch from 'yeelight-wifi'; import { Constants } from "../constants"; +import { IYeelightConfiguratorNode } from "./YeelightConfigurator"; export class Searcher { static _bulbs:any[] = []; @@ -17,9 +18,9 @@ export class Searcher { }); RED.nodes.eachNode((tmpNode) => { if(tmpNode.type.indexOf(`${Constants.NODES_PREFIX}-yeelight configurator`) === 0) { - let tmpNodeInst = RED.nodes.getNode(tmpNode.id); - if(tmpNodeInst.ip === bulb.hostname || tmpNodeInst.sid === parseInt(bulb.id)) { - tmpNodeInst.setBulb(bulb); + let tmpNodeInst = RED.nodes.getNode(tmpNode.id); + if(tmpNodeInst.ip == bulb.hostname || tmpNodeInst.sid == parseInt(bulb.id)) { + tmpNodeInst.bulb = bulb; } } }); diff --git a/src/nodes/yeelight/YeelightConfigurator.ejs b/src/nodes/yeelight/YeelightConfigurator.ejs index 929972e..d395d3d 100644 --- a/src/nodes/yeelight/YeelightConfigurator.ejs +++ b/src/nodes/yeelight/YeelightConfigurator.ejs @@ -34,7 +34,7 @@
- +
diff --git a/src/nodes/yeelight/YeelightConfigurator.ts b/src/nodes/yeelight/YeelightConfigurator.ts index a2146de..dfdb6c7 100644 --- a/src/nodes/yeelight/YeelightConfigurator.ts +++ b/src/nodes/yeelight/YeelightConfigurator.ts @@ -1,8 +1,8 @@ -import { Red, NodeProperties, NodeStatus, ClearNodeStatus } from "node-red"; +import { Red, Node, NodeProperties, NodeStatus, ClearNodeStatus } from "node-red"; import { Constants } from "../constants"; import { Searcher } from "./Searcher"; -export interface IYeelightConfiguratorNode { +export interface IYeelightConfiguratorNode extends Node { ip:string; sid:number; bulb:any; diff --git a/src/nodes/yeelight/YeelightOut.ejs b/src/nodes/yeelight/YeelightOut.ejs index 4ee7404..4fef40c 100644 --- a/src/nodes/yeelight/YeelightOut.ejs +++ b/src/nodes/yeelight/YeelightOut.ejs @@ -1,10 +1,10 @@ - -