UNPKG

yeelight-awesome

Version:

The node.js client api to control yeelight device over WIFI

314 lines 14 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 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) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; exports.__esModule = true; exports.Discover = void 0; var dgram_1 = require("dgram"); var events_1 = require("events"); var ip_1 = require("ip"); var portscanner_1 = require("portscanner"); var logger_1 = require("./logger"); var utils_1 = require("./utils"); /** * The class to discover yeelight device on wifi network using UDP package * @constructor * @param {string} title - Yeelight Discover * @param {string} author - samuraitruong@hotmail.com */ var Discover = /** @class */ (function (_super) { __extends(Discover, _super); /** * @constructor * @param {IDiscoverConfig } options discover object include the port and multicast host. * see {@link IDiscoverConfig} for more detail * @param {ILogger} logger the application logger which implement of log, info, debug and error function */ function Discover(options, logger) { var _this = _super.call(this) || this; _this.logger = logger; _this.options = { debug: true, fallback: true, host: null, limit: 1, multicastHost: "239.255.255.250", port: 1982, timeout: 10000 }; _this.clientBound = false; _this.isDestroyed = false; _this.devices = []; _this.options = __assign(__assign({}, _this.options), options); _this.client = dgram_1.createSocket("udp4"); _this.client.on("message", _this.onSocketMessage.bind(_this)); _this.client.on("error", _this.onError.bind(_this)); _this.clientBound = false; _this.logger = logger || logger_1.defaultLogger; return _this; } /** * Try to verify if the light on and listening on the know ip address * @param ipAddress : know IP Address of the light. */ Discover.prototype.detectLightIP = function (ipAddress) { return __awaiter(this, void 0, void 0, function () { var device; var _this = this; return __generator(this, function (_a) { device = { host: ipAddress, port: 55443 }; return [2 /*return*/, new Promise(function (resolve, reject) { portscanner_1.checkPortStatus(55443, ipAddress, function (err, status) { if (err || status === "closed") { return resolve(null); } else { _this.addDevice(device); return resolve(device); } }); })]; }); }); }; /** * Perfrom IP port scan to find an IP with port 55443 open rather than using SSDP discovery method * @requires {Promise<IDevice[]>} promise of list of device found */ Discover.prototype.scanByIp = function () { return __awaiter(this, void 0, void 0, function () { var localIp, count, availabledIps, promises; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: localIp = ip_1.address(); count = 0; availabledIps = utils_1.Utils.getListIpAddress(localIp); promises = availabledIps.map(function (x) { return _this.detectLightIP(x); }); return [4 /*yield*/, Promise.all(promises)]; case 1: _a.sent(); return [2 /*return*/, this.devices]; } }); }); }; /** * The class to discover yeelight device on wifi network using UDP package * You need to turn on "LAN Control" on phone app to get SSDP discover function work * @returns {Promise<IDevice[]>} a promise that could resolve to 1 or many devices on the network */ Discover.prototype.start = function () { var _this = this; return new Promise(function (resolve, reject) { try { if (!_this.clientBound) { _this.clientBound = true; _this.client.bind(_this.options.port, null, resolve); } else { // Already bound to a port resolve(null); } } catch (e) { reject(e); } }) .then(function () { return new Promise(function (resolve, reject) { _this.logger.debug("discover options: ", _this.options); _this.client.setBroadcast(true); _this.client.send(_this.getMessage(), _this.options.port, _this.options.multicastHost, function (err) { if (err) { _this.logger.log("ERROR", err); reject(err); } else { var ts_1 = 0; var interval_1 = _this.options.scanInterval || 200; var timer_1 = null; var callback_1 = function (error, result) { if (timer_1) { clearInterval(timer_1); timer_1 = null; } if (error) { reject(error); } else { resolve(result); } }; timer_1 = setInterval(function () { if (_this.isDestroyed) { callback_1("Discover got destroyed"); return; } ts_1 += interval_1; if (_this.options.limit && _this.devices.length >= _this.options.limit) { clearInterval(_this.timer); callback_1(null, _this.devices); return; } if (_this.options.timeout > 0 && _this.options.timeout < ts_1) { if (_this.devices.length > 0) { clearInterval(_this.timer); callback_1(null, _this.devices); return; } else { clearInterval(_this.timer); if (!_this.options.fallback) { callback_1("No device found after timeout exceeded : " + ts_1); return; } } } _this.client.send(_this.getMessage(), _this.options.port, _this.options.multicastHost); if (ts_1 > _this.options.timeout && _this.options.fallback) { _this.scanByIp()["catch"](function (error) { callback_1(error); }); } }, interval_1); } }); }); }); }; /** * Clean up resource and close all open connection, * call this function after you finish your action to avoid memory leak * @returns {Promise} return a promise, fullfil will call after internal socket connection dropped */ Discover.prototype.destroy = function () { var _this = this; this.isDestroyed = true; if (!this.client) { return Promise.resolve(); } return new Promise(function (resolve) { _this.removeAllListeners(); if (_this.client) { _this.client.close(resolve); } }); }; /** * Internal function to handle socket error * @param error Error details */ Discover.prototype.onError = function (error) { this.logger.error("Internal Error ", error); this.emit("error", error); }; /** * Generate the UDP message to discover device on local network. */ Discover.prototype.getMessage = function () { // tslint:disable-next-line:max-line-length var message = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1982\r\nMAN: \"ssdp:discover\"\r\nST: wifi_bulb\r\n"; return Buffer.from(message); }; /** * The event run when recieved the message devices * @param {Buffer} buffer the buffer revieved from the socket * @param {AddressInfo} address the TCP info of the devices who send the message */ Discover.prototype.onSocketMessage = function (buffer, addressInfo) { var message = buffer.toString(); if (this.options.debug && this.logger) { this.logger.info(message); } var device = utils_1.Utils.parseDeviceInfo(message); if (device) { this.addDevice(device); } }; /** * Add the new discovered device into the internal list * @param {IDevice} device - the new device found from network * @returns {0 |1 } return 0 if device already existing, 1 if new device added to the list */ Discover.prototype.addDevice = function (device) { if (!this.options.filter || this.options.filter(device)) { var existDevice = this.devices.findIndex(function (x) { return (x.host && device.host && x.host === device.host && x.port && device.port && x.port === device.port); }); if (existDevice === -1) { this.devices.push(device); this.emit("deviceAdded", device); } this.devices[existDevice] = device; } }; return Discover; }(events_1.EventEmitter)); exports.Discover = Discover; //# sourceMappingURL=discover.js.map