From c5afb43b47dcc29dfd4fd24ce6c4f10aaa5de09b Mon Sep 17 00:00:00 2001 From: Pierre CLEMENT Date: Wed, 21 Mar 2018 22:52:10 +0100 Subject: [PATCH] feat(yeelight): handle yeelights --- .../xiaomi-actions.html | 104 ------------------ .../xiaomi-actions.js | 57 ---------- package.json | 3 +- src/devices/{ => gateway}/Gateway.ts | 2 +- src/devices/{ => gateway}/GatewayMessage.ts | 0 .../{ => gateway}/GatewayMessageData.ts | 0 src/devices/{ => gateway}/GatewayServer.ts | 32 +----- src/devices/{ => gateway}/GatewaySubdevice.ts | 0 src/devices/{ => gateway}/Magnet.ts | 0 src/devices/{ => gateway}/Motion.ts | 0 src/devices/{ => gateway}/Switch.ts | 0 src/devices/{ => gateway}/Weather.ts | 0 src/devices/{ => gateway}/index.ts | 1 + src/devices/yeelight/YeelightServer.ts | 47 ++++++++ src/devices/yeelight/index.ts | 0 src/nodes/actions/Light.ts | 12 +- src/nodes/actions/ToggleAction.ts | 20 ++++ src/nodes/actions/index.ejs | 26 ++++- src/nodes/actions/index.ts | 4 + src/nodes/gateway-subdevices/index.ts | 3 +- src/nodes/gateway/Gateway.ts | 1 - src/nodes/gateway/GatewayConfigurator.ts | 6 +- src/nodes/gateway/GatewayIn.ts | 2 +- src/nodes/gateway/GatewayOut.ts | 2 +- src/nodes/gateway/index.ts | 5 +- src/nodes/yeelight/Searcher.ts | 34 ------ src/nodes/yeelight/YeelightConfigurator.ejs | 14 +-- src/nodes/yeelight/YeelightConfigurator.ts | 57 +++++++--- src/nodes/yeelight/YeelightOut.ts | 87 ++++++++++----- src/nodes/yeelight/index.ts | 7 +- tsconfig.json | 3 +- 31 files changed, 219 insertions(+), 310 deletions(-) delete mode 100644 node-red-contrib-xiaomi-actions/xiaomi-actions.html delete mode 100644 node-red-contrib-xiaomi-actions/xiaomi-actions.js rename src/devices/{ => gateway}/Gateway.ts (98%) rename src/devices/{ => gateway}/GatewayMessage.ts (100%) rename src/devices/{ => gateway}/GatewayMessageData.ts (100%) rename src/devices/{ => gateway}/GatewayServer.ts (74%) rename src/devices/{ => gateway}/GatewaySubdevice.ts (100%) rename src/devices/{ => gateway}/Magnet.ts (100%) rename src/devices/{ => gateway}/Motion.ts (100%) rename src/devices/{ => gateway}/Switch.ts (100%) rename src/devices/{ => gateway}/Weather.ts (100%) rename src/devices/{ => gateway}/index.ts (92%) create mode 100644 src/devices/yeelight/YeelightServer.ts create mode 100644 src/devices/yeelight/index.ts create mode 100644 src/nodes/actions/ToggleAction.ts delete mode 100644 src/nodes/yeelight/Searcher.ts diff --git a/node-red-contrib-xiaomi-actions/xiaomi-actions.html b/node-red-contrib-xiaomi-actions/xiaomi-actions.html deleted file mode 100644 index fe523c3..0000000 --- a/node-red-contrib-xiaomi-actions/xiaomi-actions.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/node-red-contrib-xiaomi-actions/xiaomi-actions.js b/node-red-contrib-xiaomi-actions/xiaomi-actions.js deleted file mode 100644 index 38fd2f3..0000000 --- a/node-red-contrib-xiaomi-actions/xiaomi-actions.js +++ /dev/null @@ -1,57 +0,0 @@ -const miDevicesUtils = require('../src/utils'); -module.exports = (RED) => { - /********************************************* - Turn device on - *********************************************/ - function XiaomiActionPowerOn(config) { - RED.nodes.createNode(this, config); - - this.on('input', (msg) => { - if(msg.sid){ - msg.payload = { - cmd: "write", - data: { status: "on", sid: msg.sid } - }; - } - else { - msg.payload = "on"; - } - this.send(msg); - }); - } - RED.nodes.registerType("mi-devices-actions on", XiaomiActionPowerOn); - - /********************************************* - Turn device off - *********************************************/ - function XiaomiActionPowerOff(config) { - RED.nodes.createNode(this, config); - - this.on('input', (msg) => { - if(msg.sid){ - msg.payload = { - cmd: "write", - data: { status: "off", sid: msg.sid } - }; - } - else { - msg.payload = "off"; - } - this.send(msg); - }); - } - RED.nodes.registerType("mi-devices-actions off", XiaomiActionPowerOff); - - /********************************************* - Toggle device - *********************************************/ - function XiaomiActionToggle(config) { - RED.nodes.createNode(this, config); - - this.on('input', (msg) => { - msg.payload = "toggle"; - this.send(msg); - }); - } - RED.nodes.registerType("mi-devices-actions toggle", XiaomiActionToggle); -} diff --git a/package.json b/package.json index 76e1774..736c05a 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,7 @@ }, "dependencies": { "cryptojs": "^2.5.3", - "lumi-aqara": "^1.4.0", - "miio": "^0.15.2", + "miio": "^0.15.4", "yeelight-wifi": "^2.3.0" }, "engines": { diff --git a/src/devices/Gateway.ts b/src/devices/gateway/Gateway.ts similarity index 98% rename from src/devices/Gateway.ts rename to src/devices/gateway/Gateway.ts index 785276e..ce60384 100644 --- a/src/devices/Gateway.ts +++ b/src/devices/gateway/Gateway.ts @@ -4,7 +4,7 @@ import * as crypto from 'crypto'; import {GatewayServer} from "./GatewayServer"; import {GatewayMessage, GatewaySubdevice, Magnet, Motion, Switch, Weather} from "./"; import * as MessageData from "./GatewayMessageData"; -import {Color} from "../utils/Color"; +import {Color} from "../../utils/Color"; export class Gateway extends events.EventEmitter { static iv: Buffer = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]); diff --git a/src/devices/GatewayMessage.ts b/src/devices/gateway/GatewayMessage.ts similarity index 100% rename from src/devices/GatewayMessage.ts rename to src/devices/gateway/GatewayMessage.ts diff --git a/src/devices/GatewayMessageData.ts b/src/devices/gateway/GatewayMessageData.ts similarity index 100% rename from src/devices/GatewayMessageData.ts rename to src/devices/gateway/GatewayMessageData.ts diff --git a/src/devices/GatewayServer.ts b/src/devices/gateway/GatewayServer.ts similarity index 74% rename from src/devices/GatewayServer.ts rename to src/devices/gateway/GatewayServer.ts index baed06f..6ec5f19 100644 --- a/src/devices/GatewayServer.ts +++ b/src/devices/gateway/GatewayServer.ts @@ -1,6 +1,6 @@ import * as events from 'events'; import * as dgram from "dgram"; -import {Gateway} from "./Gateway"; +import {Gateway} from "./"; import Timer = NodeJS.Timer; import {GatewayMessage} from "./GatewayMessage"; @@ -92,36 +92,6 @@ export class GatewayServer extends events.EventEmitter { } gatewaySid && this._gateways[gatewaySid] && this._gateways[gatewaySid].handleMessage(msg); - - /*if(remote.address == this.addr) { - var msg = message.toString('utf8'); - var jsonMsg = JSON.parse(msg); - if(jsonMsg.data) { - jsonMsg.data = JSON.parse(jsonMsg.data) || jsonMsg.data; - if(jsonMsg.data.voltage) { - jsonMsg.data.batteryLevel = miDevicesUtils.computeBatteryLevel(jsonMsg.data.voltage); - } - } - msg = { payload: jsonMsg }; - if(this.gateway && jsonMsg.data.ip && jsonMsg.data.ip === this.gateway.ip) { - if(jsonMsg.token) { - this.gateway.lastToken = jsonMsg.token; - if(!this.gateway.sid) { - this.gateway.sid = jsonMsg.sid; - } - } - RED.nodes.eachNode((tmpNode) => { - if(tmpNode.type.indexOf("xiaomi-gateway") === 0 && tmpNode.gateway == this.gatewayNodeId) { - let tmpNodeInst = RED.nodes.getNode(tmpNode.id); - if(tmpNode.type === "xiaomi-gateway out" && !this.gateway.lastToken) { - tmpNodeInst.status({fill:"yellow", shape:"ring", text: "waiting input"}); - } - tmpNodeInst.status({fill:"blue", shape:"dot", text: "online"}); - } - }); - } - this.send(msg); - }*/ }); return new Promise((resolve, reject) => { diff --git a/src/devices/GatewaySubdevice.ts b/src/devices/gateway/GatewaySubdevice.ts similarity index 100% rename from src/devices/GatewaySubdevice.ts rename to src/devices/gateway/GatewaySubdevice.ts diff --git a/src/devices/Magnet.ts b/src/devices/gateway/Magnet.ts similarity index 100% rename from src/devices/Magnet.ts rename to src/devices/gateway/Magnet.ts diff --git a/src/devices/Motion.ts b/src/devices/gateway/Motion.ts similarity index 100% rename from src/devices/Motion.ts rename to src/devices/gateway/Motion.ts diff --git a/src/devices/Switch.ts b/src/devices/gateway/Switch.ts similarity index 100% rename from src/devices/Switch.ts rename to src/devices/gateway/Switch.ts diff --git a/src/devices/Weather.ts b/src/devices/gateway/Weather.ts similarity index 100% rename from src/devices/Weather.ts rename to src/devices/gateway/Weather.ts diff --git a/src/devices/index.ts b/src/devices/gateway/index.ts similarity index 92% rename from src/devices/index.ts rename to src/devices/gateway/index.ts index 4c93553..6d262f0 100644 --- a/src/devices/index.ts +++ b/src/devices/gateway/index.ts @@ -1,3 +1,4 @@ +export * from './'; export * from './Gateway'; export * from './GatewayMessage'; export * from './GatewayServer'; diff --git a/src/devices/yeelight/YeelightServer.ts b/src/devices/yeelight/YeelightServer.ts new file mode 100644 index 0000000..2eb7270 --- /dev/null +++ b/src/devices/yeelight/YeelightServer.ts @@ -0,0 +1,47 @@ +import * as events from 'events'; +import * as YeelightSearch from 'yeelight-wifi'; + +export class YeelightServer extends events.EventEmitter { + private static instance: YeelightServer; + + private _bulbs: { [sid: string]: any } = {}; + private _bulbsJson: { [sid: string]: any } = {}; + + static getInstance() { + if (!this.instance) { + this.instance = new YeelightServer(); + } + return this.instance; + } + + get bulbs(): { [sid: string]: any } { + return this._bulbsJson; + } + + getBulb(sid) { + return this._bulbs[sid]; + } + + discover() { + new Promise(() => { + (new YeelightSearch()).on('found', (bulb: any) => { + 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 + }; + } +} \ No newline at end of file diff --git a/src/devices/yeelight/index.ts b/src/devices/yeelight/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/nodes/actions/Light.ts b/src/nodes/actions/Light.ts index f5a86d9..733efd1 100644 --- a/src/nodes/actions/Light.ts +++ b/src/nodes/actions/Light.ts @@ -15,13 +15,11 @@ export default (RED: Red) => { protected setListeners() { ( this).on('input', (msg) => { - if (msg.sid) { - msg.payload = { - action: "setLight", - color: msg.color || this.color, - brightness: msg.brightness || this.brightness - }; - } + msg.payload = { + action: "setLight", + color: msg.color || this.color, + brightness: msg.brightness || this.brightness + }; ( this).send(msg); }); } diff --git a/src/nodes/actions/ToggleAction.ts b/src/nodes/actions/ToggleAction.ts new file mode 100644 index 0000000..b877468 --- /dev/null +++ b/src/nodes/actions/ToggleAction.ts @@ -0,0 +1,20 @@ +import {Red, NodeProperties} from "node-red"; +import {Constants} from "../constants"; + +export default (RED: Red, action: string) => { + class ToggleAction { + constructor(props: NodeProperties) { + RED.nodes.createNode( this, props); + ( this).setListeners(); + } + + protected setListeners() { + ( this).on('input', (msg) => { + msg.payload = { action }; + ( this).send(msg); + }); + } + } + + RED.nodes.registerType(`${Constants.NODES_PREFIX}-actions ${action}`, ToggleAction); +}; \ No newline at end of file diff --git a/src/nodes/actions/index.ejs b/src/nodes/actions/index.ejs index 1c7687e..db6b6d6 100644 --- a/src/nodes/actions/index.ejs +++ b/src/nodes/actions/index.ejs @@ -32,4 +32,28 @@ <%- include('./Light', {}) %> <%- include('./GatewayPlaySound', {}) %> -<%- include('./GatewayStopSound', {}) %> \ No newline at end of file +<%- include('./GatewayStopSound', {}) %> + +<%# ---------------------------------- turn_on ---------------------------------- %> +<%- include('./Action', { +type: "turn_on", +label: "turn on", +icon: "mi-on", +docTitle: "Turn device on." +}) %> + +<%# ---------------------------------- double_click ---------------------------------- %> +<%- include('./Action', { +type: "turn_off", +label: "turn off", +icon: "mi-off", +docTitle: "Turn device off." +}) %> + +<%# ---------------------------------- double_click ---------------------------------- %> +<%- include('./Action', { +type: "toggle", +label: "toggle", +icon: "mi-toggle", +docTitle: "Toggle device." +}) %> \ No newline at end of file diff --git a/src/nodes/actions/index.ts b/src/nodes/actions/index.ts index 6862c03..bd5e5df 100644 --- a/src/nodes/actions/index.ts +++ b/src/nodes/actions/index.ts @@ -5,6 +5,7 @@ import {default as WriteAction} from './WriteAction'; import {default as Light} from './Light'; import {default as GatewayPlaySound} from './GatewayPlaySound'; import {default as GatewayStopSound} from './GatewayStopSound'; +import {default as ToggleAction} from './ToggleAction'; export = (RED: Red) => { ["read", "get_id_list"].forEach((action) => { @@ -17,4 +18,7 @@ export = (RED: Red) => { Light(RED); GatewayPlaySound(RED); GatewayStopSound(RED); + ["turn_on", "turn_off", "toggle"].forEach(action => { + ToggleAction(RED, action); + }); }; \ No newline at end of file diff --git a/src/nodes/gateway-subdevices/index.ts b/src/nodes/gateway-subdevices/index.ts index 7d7441c..bd074b7 100644 --- a/src/nodes/gateway-subdevices/index.ts +++ b/src/nodes/gateway-subdevices/index.ts @@ -1,5 +1,4 @@ -import { Red, NodeProperties } from "node-red"; -import * as LumiAqara from 'lumi-aqara'; +import { Red } from "node-red"; import {default as All} from "./All"; import {default as Plug} from "./Plug"; diff --git a/src/nodes/gateway/Gateway.ts b/src/nodes/gateway/Gateway.ts index 814d320..34e6969 100644 --- a/src/nodes/gateway/Gateway.ts +++ b/src/nodes/gateway/Gateway.ts @@ -1,5 +1,4 @@ import { Red, Node, NodeProperties } from "node-red"; -import { LumiAqara } from "../../../typings/index"; import { Constants } from "../constants"; export interface IGatewayNode extends Node { diff --git a/src/nodes/gateway/GatewayConfigurator.ts b/src/nodes/gateway/GatewayConfigurator.ts index 71fe0cd..dcea5d0 100644 --- a/src/nodes/gateway/GatewayConfigurator.ts +++ b/src/nodes/gateway/GatewayConfigurator.ts @@ -1,8 +1,8 @@ import {Red, Node, NodeProperties} from "node-red"; import {Constants} from "../constants"; -import {GatewayServer} from "../../devices/GatewayServer"; -import {Gateway} from "../../devices/Gateway"; -import {GatewaySubdevice} from "../../devices/GatewaySubdevice"; +import {GatewayServer} from "../../devices/gateway/GatewayServer"; +import {Gateway} from "../../devices/gateway/Gateway"; +import {GatewaySubdevice} from "../../devices/gateway/GatewaySubdevice"; import {isString} from "util"; export interface IGatewayConfiguratorNode extends Node { diff --git a/src/nodes/gateway/GatewayIn.ts b/src/nodes/gateway/GatewayIn.ts index b438621..b99867a 100644 --- a/src/nodes/gateway/GatewayIn.ts +++ b/src/nodes/gateway/GatewayIn.ts @@ -1,6 +1,6 @@ import {Red, Node, NodeProperties} from 'node-red'; import {Constants} from '../constants'; -import {Gateway} from "../../devices/Gateway"; +import {Gateway} from "../../devices/gateway/Gateway"; export interface IGatewayInNode extends Node { gatewayConf: any; diff --git a/src/nodes/gateway/GatewayOut.ts b/src/nodes/gateway/GatewayOut.ts index 18dc848..24def08 100644 --- a/src/nodes/gateway/GatewayOut.ts +++ b/src/nodes/gateway/GatewayOut.ts @@ -1,6 +1,6 @@ import {Red, NodeProperties} from "node-red"; import {Constants} from "../constants"; -import {GatewayServer} from "../../devices/GatewayServer"; +import {GatewayServer} from "../../devices/gateway/GatewayServer"; export default (RED: Red) => { class GatewayOut { diff --git a/src/nodes/gateway/index.ts b/src/nodes/gateway/index.ts index 06000ef..923601c 100644 --- a/src/nodes/gateway/index.ts +++ b/src/nodes/gateway/index.ts @@ -1,7 +1,6 @@ -import { Red, NodeProperties } from "node-red"; -import * as LumiAqara from 'lumi-aqara'; +import { Red } from "node-red"; -import { GatewayServer } from "../../devices/GatewayServer"; +import { GatewayServer } from "../../devices/gateway/GatewayServer"; import {default as GatewayConfigurator} from "./GatewayConfigurator"; import {default as Gateway} from "./Gateway"; import {default as GatewayIn} from "./GatewayIn"; diff --git a/src/nodes/yeelight/Searcher.ts b/src/nodes/yeelight/Searcher.ts deleted file mode 100644 index 08e3cf1..0000000 --- a/src/nodes/yeelight/Searcher.ts +++ /dev/null @@ -1,34 +0,0 @@ -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[] = []; - - static discover(RED:Red) { - new Promise(() => { - (new YeelightSearch()).on('found', (bulb:any) => { - this._bulbs.push({ - name: bulb.name, - model: bulb.model, - sid: parseInt(bulb.id), - ip: bulb.hostname - }); - 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.bulb = bulb; - } - } - }); - }); - }); - } - - static get bulbs() { - return this._bulbs; - } -} \ No newline at end of file diff --git a/src/nodes/yeelight/YeelightConfigurator.ejs b/src/nodes/yeelight/YeelightConfigurator.ejs index 0b22dcb..8369171 100644 --- a/src/nodes/yeelight/YeelightConfigurator.ejs +++ b/src/nodes/yeelight/YeelightConfigurator.ejs @@ -3,23 +3,22 @@ category: 'config', defaults: { name: {value: ""}, - ip: {value: ""}, sid: {value: ""} }, label: function () { return this.name || "yeelight conf"; }, oneditprepare: function() { - RED.settings.miDevicesYeelightConfiguratorDiscoveredBulbs.forEach(function(bulb, index) { + var foundBulbs = RED.settings.miDevicesYeelightConfiguratorDiscoveredBulbs; + Object.keys(foundBulbs).forEach(function(sid) { + var bulb = foundBulbs[sid]; $('#discovered-bulbs').append(''); }); - var node = this; $('#discovered-bulbs').on('change', function() { var sid = $('#discovered-bulbs').val(); - var bulb = sid && RED.settings.miDevicesYeelightConfiguratorDiscoveredBulbs.filter(function(e) { return e.sid == sid })[0]; + var bulb = foundBulbs[sid]; $("#node-config-input-name").val(bulb && bulb.name); $("#node-config-input-sid").val(bulb && bulb.sid); - $("#node-config-input-ip").val(""); }); } }); @@ -37,15 +36,10 @@ -
- - -
-

Note: use ip or sid - sid is better.