yeelight-awesome
Version:
The node.js client api to control yeelight device over WIFI
314 lines • 14 kB
JavaScript
;
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