"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const tradfri = require("node-tradfri-client"); module.exports = function (RED) { function lightFromAccessory(accessory) { return Object.assign({}, { id: accessory.instanceId, name: accessory.name, model: accessory.deviceInfo.modelNumber, firmware: accessory.deviceInfo.firmwareVersion, alive: accessory.alive, on: accessory.lightList[0].onOff, onTime: accessory.lightList[0].onTime, brightness: accessory.lightList[0].dimmer, colorTemperature: accessory.lightList[0].colorTemperature, color: accessory.lightList[0].color, hue: accessory.lightList[0].hue, saturation: accessory.lightList[0].saturation, colorX: accessory.lightList[0].colorX, colorY: accessory.lightList[0].colorY, transition: accessory.lightList[0].transitionTime, created: accessory.createdAt, seen: accessory.lastSeen, type: accessory.type, power: accessory.deviceInfo.power }); } function toLightOperation(op) { let props = { on: "onOff", brightness: "dimmer", transition: "transitionTime" }; for (let k in props) { if (op.hasOwnProperty(k)) { op[props[k]] = op[k]; delete op[k]; } } return op; } RED.httpAdmin.get("/tradfri/lights", RED.auth.needsPermission('tradfri.read'), function (req, res) { let nodeId = req.query.nodeId; if (nodeId != null) { let node = RED.nodes.getNode(nodeId); let lights = node.getLights(); let ret = []; for (let k in lights) { ret.push({ name: lights[k].name, id: k }); } res.json(JSON.stringify(ret)); } }); function TradfriConnectionNode(config) { var node = this; RED.nodes.createNode(node, config); node.name = config.name; node.address = config.address; node.securityCode = node.credentials.securityCode; node.identity = node.credentials.identity; node.psk = node.credentials.psk; if ((node.identity == null && node.psk != null) || (node.identity != null && node.psk == null)) { RED.log.error("Must provide both identity and PSK or leave both blank to generate new credentials from security code."); } if (node.identity == null && node.psk == null && node.securityCode == null) { RED.log.error("Must provide either identity and PSK or a security code to connect to the Tradfri hub"); } var _lights = {}; var _listeners = {}; var _client = null; var _deviceUpdatedCallback = (accessory) => { if (accessory.type === tradfri.AccessoryTypes.lightbulb) { _lights[accessory.instanceId] = accessory; } if (_listeners[accessory.instanceId]) { for (let nodeId in _listeners[accessory.instanceId]) { _listeners[accessory.instanceId][nodeId](accessory); } } }; let _setupClient = () => __awaiter(this, void 0, void 0, function* () { let loggerFunction = (message, severity) => { RED.log.info(severity + ", " + message); }; let client = new tradfri.TradfriClient(node.address); if (node.identity == null && node.psk == null) { const { identity, psk } = yield client.authenticate(node.securityCode); node.identity = identity; node.psk = psk; } if (yield client.connect(node.identity, node.psk)) { client.on("device updated", _deviceUpdatedCallback); client.observeDevices(); _client = client; } else { throw new Error(`Client not available`); } }); let _reconnect = () => __awaiter(this, void 0, void 0, function* () { let timeout = 5000; if (_client != null) { _client.destroy(); _client = null; } while (_client == null) { try { yield _setupClient(); } catch (e) { RED.log.trace(`[Tradfri: ${node.id}] ${e.toString()}, reconnecting...`); } yield new Promise(resolve => setTimeout(resolve, timeout)); } }); let pingInterval = 30; let _ping = setInterval(() => __awaiter(this, void 0, void 0, function* () { try { let client = yield node.getClient(); let res = yield client.ping(); RED.log.trace(`[Tradfri: ${node.id}] ping returned '${res}'`); } catch (e) { RED.log.trace(`[Tradfri: ${node.id}] ping returned '${e.toString()}'`); } }), pingInterval * 1000); _reconnect(); node.getClient = () => __awaiter(this, void 0, void 0, function* () { let maxRetries = 5; let timeout = 2; for (let i = 0; i < maxRetries; i++) { if (_client == null) { yield new Promise(resolve => setTimeout(resolve, timeout * 1000)); } else { return _client; } } throw new Error('Client not available'); }); node.getLight = (instanceId) => __awaiter(this, void 0, void 0, function* () { let maxRetries = 5; let timeout = 2; for (let i = 0; i < maxRetries; i++) { if (_lights[instanceId] == null) { yield new Promise(resolve => setTimeout(resolve, timeout * 1000)); } else { return _lights[instanceId]; } } throw new Error('Light not available'); }); node.getLights = () => { return _lights; }; node.register = (nodeId, instanceId, callback) => { if (!_listeners[instanceId]) { _listeners[instanceId] = {}; } _listeners[instanceId][nodeId] = callback; RED.log.info(`[Tradfri: ${nodeId}] registered event listener for ${instanceId}`); }; node.unregister = (nodeId) => { for (let instanceId in _listeners) { if (_listeners[instanceId].hasOwnProperty(nodeId)) { delete _listeners[instanceId][nodeId]; RED.log.info(`[Tradfri: ${nodeId}] unregistered event listeners`); } } }; node.on('close', () => { clearInterval(_ping); _client.destroy(); RED.log.debug(`[Tradfri: ${node.id}] Config was closed`); }); } RED.nodes.registerType("tradfri-connection", TradfriConnectionNode, { credentials: { securityCode: { type: "text" }, identity: { type: "text" }, psk: { type: "text" } } }); function TradfriNode(config) { var node = this; RED.nodes.createNode(node, config); node.name = config.name; node.deviceId = config.deviceId; node.deviceName = config.deviceName; node.observe = config.observe; var _config = RED.nodes.getNode(config.connection); var _prev = {}; var _send = (payload) => { node.send({ topic: "tradfri", payload: payload }); }; var _getPayload = (accessory) => { let light = lightFromAccessory(accessory); light['prev'] = Object.assign({}, _prev); return light; }; var _deviceUpdated = (accessory) => { let ret = _getPayload(accessory); _prev = lightFromAccessory(accessory); _send(ret); RED.log.trace(`[Tradfri: ${node.id}] recieved update for '${accessory.name}' (${accessory.instanceId})`); }; var _getTargetId = (msg) => { let payload = msg.payload; if (payload.hasOwnProperty('id') && Array.isArray(payload.id)) { return payload.id; } else if (payload.hasOwnProperty('id')) { return [payload.id]; } else if (node.deviceId > 0) { return [node.deviceId]; } else { throw new Error('No valid target device'); } }; var _handleDirectStatus = () => __awaiter(this, void 0, void 0, function* () { try { let client = yield _config.getClient(); let res = yield client.request('15001/' + node.deviceId, 'get'); _send(res); } catch (e) { _send(e); } }); var _handleStatus = () => __awaiter(this, void 0, void 0, function* () { try { let accessory = yield _config.getLight(node.deviceId); _send(_getPayload(accessory)); RED.log.trace(`[Tradfri: ${node.id}] Status request successful`); } catch (e) { RED.log.info(`[Tradfri: ${node.id}] Status request unsuccessful, '${e.toString()}'`); } }); var _handleDirectLightOp = (light) => __awaiter(this, void 0, void 0, function* () { let clamp = (num, min, max) => { return num <= min ? min : num >= max ? max : num; }; let cmd = { 3311: [Object.assign({}, { 5851: clamp(light.brightness, 0, 254), 5711: clamp(light.colorTemperature, 200, 454), 5712: light.transition, 5850: light.on, 5707: light.hue, 5708: light.saturation, 5706: light.color // f5faf6, f1e0b5, efd275 are valid for WS bulbs })] }; try { let client = yield _config.getClient(); let res = yield client.request('15001/' + node.deviceId, 'put', Object.assign({}, cmd)); RED.log.trace(`[Tradfri: ${node.id}] DirectLightOp '${JSON.stringify(cmd)}' returned '${res}'`); } catch (e) { RED.log.info(`[Tradfri: ${node.id}] DirectLightOp '${JSON.stringify(cmd)}' unsuccessful, '${e.toString()}'`); } }); var _handleLightOp = (light) => __awaiter(this, void 0, void 0, function* () { let lightOp = toLightOperation(light); if (!lightOp.hasOwnProperty('transitionTime')) { lightOp['transitionTime'] = 0; } try { let light = yield _config.getLight(node.deviceId); if (Object.keys(lightOp).length > 0) { let client = yield _config.getClient(); let res = yield client.operateLight(light, lightOp); RED.log.trace(`[Tradfri: ${node.id}] LightOp '${JSON.stringify(lightOp)}' returned '${res}'`); } } catch (e) { RED.log.info(`[Tradfri: ${node.id}] LightOp '${JSON.stringify(lightOp)}' unsuccessful, '${e.toString()}'`); } }); if (node.observe) { _config.register(node.id, node.deviceId, _deviceUpdated); } node.on('input', function (msg) { (() => __awaiter(this, void 0, void 0, function* () { if (msg.hasOwnProperty('payload')) { if (msg.payload === "status") { msg.payload = { status: true }; } let isDirect = msg.payload.hasOwnProperty('direct'); let isStatus = msg.payload.hasOwnProperty('status'); if (isDirect && isStatus) { _handleDirectStatus(); } else if (isStatus) { _handleStatus(); } else if (isDirect) { _handleDirectLightOp(msg.payload); } else { _handleLightOp(msg.payload); } } }))(); }); node.on('close', function () { RED.log.debug(`[Tradfri: ${node.id}] Node was closed`); _config.unregister(node.id); }); } RED.nodes.registerType("tradfri", TradfriNode); };