UNPKG

xled2

Version:

Library to control Twinkly LED lights

878 lines (877 loc) 32.5 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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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 (g && (g = 0, op[0] && (_ = 0)), _) 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 }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Led = exports.OneColourFrame = exports.Frame = exports.deviceMode = exports.AuthenticationToken = exports.Movie = exports.Light = void 0; // imports var node_crypto_1 = require("node:crypto"); var axios_1 = __importDefault(require("axios")); var delay_1 = __importDefault(require("delay")); var udp = __importStar(require("node:dgram")); // create error var errNoToken = Error("No valid token"); /** * Represents a Twinkly device * @public * */ var Light = /** @class */ (function () { /** * Creates an instance of Light. * * @constructor * @param {string} ipaddr IP Address of the Twinkly device */ function Light(ipaddr, timeout) { if (timeout === void 0) { timeout = 1000; } this.ipaddr = ipaddr; this.challenge = (0, node_crypto_1.randomBytes)(256).toString("hex"); this.net = axios_1.default.create({ baseURL: "http://".concat(this.ipaddr, "/xled/v1/"), timeout: timeout, }); this.activeLoginCall = false; this.udpClient = udp.createSocket("udp4"); } Light.prototype.autoEndLoginCall = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, (0, delay_1.default)(1000)]; case 1: _a.sent(); this.activeLoginCall = false; return [2 /*return*/]; } }); }); }; /** * Sends a login request * * @returns {*} */ Light.prototype.login = function () { return __awaiter(this, void 0, void 0, function () { var res, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: this.activeLoginCall = true; this.autoEndLoginCall(); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.net.post("/login", { challenge: this.challenge, })]; case 2: res = _a.sent(); return [3 /*break*/, 4]; case 3: err_1 = _a.sent(); throw err_1; case 4: this.token = new AuthenticationToken(res); this.net.defaults.headers["X-Auth-Token"] = this.token.getToken(); if (res.data.code != 1000) { throw Error("Login request failed"); } console.log("Login request successful"); try { this.verify(); } catch (err) { throw err; } this.activeLoginCall = false; return [2 /*return*/]; } }); }); }; /** * Check that we are logged in to the device */ Light.prototype.verify = function () { return __awaiter(this, void 0, void 0, function () { var res, err_2; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.token === undefined) throw errNoToken; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.net.post("/verify", { "challenge-response": this.token.getChallengeResponse(), })]; case 2: res = _a.sent(); return [3 /*break*/, 4]; case 3: err_2 = _a.sent(); throw err_2; case 4: if (res.data.code != 1000) { throw errNoToken; } return [2 /*return*/]; } }); }); }; /** * Ensure that we are logged into to the device, and if not initiate a login request */ Light.prototype.ensureLoggedIn = function () { return __awaiter(this, void 0, void 0, function () { var err_3, i; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 7]); return [4 /*yield*/, this.verify()]; case 1: _a.sent(); return [3 /*break*/, 7]; case 2: err_3 = _a.sent(); if (err_3 != errNoToken) { throw err_3; } i = 0; _a.label = 3; case 3: if (!(this.activeLoginCall && i < 5)) return [3 /*break*/, 5]; return [4 /*yield*/, (0, delay_1.default)(1200)]; case 4: _a.sent(); i++; return [3 /*break*/, 3]; case 5: return [4 /*yield*/, this.login()]; case 6: _a.sent(); return [3 /*break*/, 7]; case 7: return [2 /*return*/]; } }); }); }; /** * Gets details about the device * * @returns {Promise<object>} Results vary, see https://xled-docs.readthedocs.io/en/latest/rest_api.html#device-details */ Light.prototype.getDeviceDetails = function () { return __awaiter(this, void 0, void 0, function () { var data; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/gestalt", undefined, false)]; case 1: data = _a.sent(); return [2 /*return*/, data]; } }); }); }; /** * Turns the device off * * @returns {unknown} */ Light.prototype.setOff = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.setMode(deviceMode.off)]; }); }); }; /** * Sets the state * @experimental * @param {boolean} state - Set on/off */ Light.prototype.setState = function (state) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.setMode(state ? deviceMode.color : deviceMode.off)]; }); }); }; /** * Get the name of the device * * @returns {Promise<string>} Name of device */ Light.prototype.getName = function () { return __awaiter(this, void 0, void 0, function () { var data, res; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/device_name")]; case 1: data = _a.sent(); res = data.name; return [2 /*return*/, res]; } }); }); }; /** * Sets the name of the device * * @param {string} name Desired device name, max 32 charachters * @returns {Promise<void>} */ Light.prototype.setName = function (name) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (name.length > 32) throw new Error("Name is too long - must be 32 char or less"); return [4 /*yield*/, this.sendPostRequest("/led/out/brightness", { name: name, })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Gets time when lights will turn on and off * * @returns {Promise<timer>} */ Light.prototype.getTimer = function () { return __awaiter(this, void 0, void 0, function () { var data; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/timer")]; case 1: data = _a.sent(); return [2 /*return*/, data]; } }); }); }; /** * Sets the brightness level * * @param {number} value * @param {string} [mode="enabled"] * @param {string} [type="A"] * @returns {} */ Light.prototype.setBrightness = function (value, mode, type) { if (mode === void 0) { mode = "enabled"; } if (type === void 0) { type = "A"; } return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendPostRequest("/led/out/brightness", { mode: mode, type: type, value: value, })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Gets the current brightness level * * @returns {number} Current brightness in range 0..100 */ Light.prototype.getBrightness = function () { return __awaiter(this, void 0, void 0, function () { var data; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/led/out/brightness", {})]; case 1: data = _a.sent(); return [2 /*return*/, data.value]; } }); }); }; /** * Gets the current colour in HSV */ Light.prototype.getHSVColour = function () { return __awaiter(this, void 0, void 0, function () { var data, res; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/led/color", {})]; case 1: data = _a.sent(); res = { hue: data.hue, saturation: data.saturation, value: data.value, }; return [2 /*return*/, res]; } }); }); }; /** * Gets the current colour in RGB */ Light.prototype.getRGBColour = function () { return __awaiter(this, void 0, void 0, function () { var data, res; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/led/color", {})]; case 1: data = _a.sent(); res = { red: data.red, green: data.green, blue: data.blue }; return [2 /*return*/, res]; } }); }); }; /** * Sets the colour in RGB when in colour mode * * @param {rgbColour} colour A RGB colour */ Light.prototype.setRGBColour = function (colour) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendPostRequest("/led/color", { red: colour.red, green: colour.green, blue: colour.blue, })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; Light.prototype.setRGBColourRealTime = function (colour) { return __awaiter(this, void 0, void 0, function () { var frame, _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: _a = OneColourFrame.bind; _b = [void 0, colour]; return [4 /*yield*/, this.getNLeds()]; case 1: frame = new (_a.apply(OneColourFrame, _b.concat([_c.sent()])))(); return [4 /*yield*/, this.sendRealTimeFrame(frame)]; case 2: _c.sent(); return [2 /*return*/]; } }); }); }; /** * Sets the colour in HSV when in colour mode * * @param {hsvColour} colour A HSV colour */ Light.prototype.setHSVColour = function (colour) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendPostRequest("/led/color", { hue: Math.round(colour.hue), saturation: Math.round(colour.saturation), value: Math.round(colour.value), })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Gets the LED operation mode * * @returns {deviceMode} mode */ Light.prototype.getMode = function () { return __awaiter(this, void 0, void 0, function () { var res, mode; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/led/mode", {})]; case 1: res = _a.sent(); mode = deviceMode[res.mode]; return [2 /*return*/, mode]; } }); }); }; /** * Sets the LED operation mode * * @param {deviceMode} mode */ Light.prototype.setMode = function (mode) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendPostRequest("/led/mode", { mode: mode })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Sends a POST request to the device, appending the required tokens * * @param {string} url * @param {object} params */ Light.prototype.sendPostRequest = function (url, params) { return __awaiter(this, void 0, void 0, function () { var res, err_4; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.token) throw errNoToken; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.net.post(url, params)]; case 2: res = _a.sent(); return [3 /*break*/, 4]; case 3: err_4 = _a.sent(); throw err_4; case 4: if (res.data.code != 1000) { throw Error("Mode set failed"); } return [2 /*return*/, res.data]; } }); }); }; /** * Sends a GET request to the device, appending the required tokens * * @param {string} url * @param {object} params */ Light.prototype.sendGetRequest = function (url, params, requiresToken) { if (requiresToken === void 0) { requiresToken = true; } return __awaiter(this, void 0, void 0, function () { var res, err_5; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.token && requiresToken) throw errNoToken; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.net.get(url, params || {})]; case 2: res = _a.sent(); return [3 /*break*/, 4]; case 3: err_5 = _a.sent(); throw err_5; case 4: if (res.data.code != 1000) { throw Error("Get Request failed"); } return [2 /*return*/, res.data]; } }); }); }; Light.prototype.sendRealTimeFrame = function (frame) { return __awaiter(this, void 0, void 0, function () { var res, err_6; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.token) throw errNoToken; _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.net.post("/led/rt/frame", frame.toOctet(), { headers: { "Content-Type": "application/octet-stream", }, })]; case 2: res = _a.sent(); return [3 /*break*/, 4]; case 3: err_6 = _a.sent(); throw err_6; case 4: if (res.data.code != 1000) { throw Error("Failed to send RT frame"); } return [2 /*return*/, res.data]; } }); }); }; Light.prototype.sendRealTimeFrameUDP = function (frame) { return __awaiter(this, void 0, void 0, function () { var tokenArray, udpHeader, data; return __generator(this, function (_a) { if (!this.token) throw errNoToken; tokenArray = this.token.getTokenDecoded(); udpHeader = Buffer.alloc(tokenArray.length + 4); udpHeader.writeUInt8(0x03); // the version number udpHeader.fill(tokenArray, 1); // the actual token, 8 bytes udpHeader.writeUInt8(0x00, tokenArray.length + 1); // zero blanking udpHeader.writeUInt8(0x00, tokenArray.length + 2); // zero blanking udpHeader.writeUInt8(0x00, tokenArray.length + 3); // number of packets (currently only 1 as i only hav 250 leds) data = Buffer.alloc(udpHeader.length + frame.getNLeds() * 3); data.fill(udpHeader); data.fill(frame.toOctet(), udpHeader.length); this.udpClient.send(data, 7777, this.ipaddr, function (error) { if (error) { console.warn(error); } }); return [2 /*return*/]; }); }); }; Light.prototype.getListOfMovies = function () { return __awaiter(this, void 0, void 0, function () { var res, movies; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/movies", {})]; case 1: res = _a.sent(); movies = res.movies.map(function (data) { return new Movie(data); }); return [2 /*return*/, movies]; } }); }); }; Light.prototype.addMovie = function (movie) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendPostRequest("/movies/new", movie.export())]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }; Light.prototype.getLayout = function () { return __awaiter(this, void 0, void 0, function () { var res; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.sendGetRequest("/led/layout/full", {})]; case 1: res = _a.sent(); return [2 /*return*/, res]; } }); }); }; Light.prototype.getNLeds = function () { return __awaiter(this, void 0, void 0, function () { var res, nleds; return __generator(this, function (_a) { switch (_a.label) { case 0: if (this.nleds) return [2 /*return*/, this.nleds]; return [4 /*yield*/, this.getDeviceDetails()]; case 1: res = _a.sent(); nleds = res.number_of_led; this.nleds = nleds; return [2 /*return*/, nleds]; } }); }); }; return Light; }()); exports.Light = Light; var Movie = /** @class */ (function () { function Movie(data) { this.id = data.id; this.name = data.name; this.unique_id = data.unique_id; this.descriptor_type = data.descriptor_type; this.leds_per_frame = data.leds_per_frame; this.frames_number = data.frames_number; this.fps = data.fps; } Movie.prototype.export = function () { return { id: this.id, name: this.name, unique_id: this.unique_id, descriptor_type: this.descriptor_type, leds_per_frame: this.leds_per_frame, frames: this.frames_number, fps: this.fps, }; }; return Movie; }()); exports.Movie = Movie; /** * Represents an authentication token used to login to an xled instance * @internal */ var AuthenticationToken = /** @class */ (function () { /** * Creates an instance of AuthenticationToken. * * @constructor * @param {AxiosResponse} res Response from POST request */ function AuthenticationToken(res) { this.token = res.data.authentication_token; this.expiry = new Date(Date.now() + res.data.authentication_token_expires_in * 1000); this.challengeResponse = res.data.challenge_response; } /** * * @returns Token as string */ AuthenticationToken.prototype.getToken = function () { return this.token; }; /** * * @returns Token as buffer, for UDP use */ AuthenticationToken.prototype.getTokenDecoded = function () { return Buffer.from(this.getToken(), "base64"); }; /** * * @returns Challenge response generated by the XLED instance */ AuthenticationToken.prototype.getChallengeResponse = function () { return this.challengeResponse; }; return AuthenticationToken; }()); exports.AuthenticationToken = AuthenticationToken; var deviceMode; (function (deviceMode) { deviceMode["demo"] = "demo"; deviceMode["color"] = "color"; deviceMode["off"] = "off"; deviceMode["effect"] = "effect"; deviceMode["movie"] = "movie"; deviceMode["playlist"] = "playlist"; deviceMode["rt"] = "rt"; })(deviceMode = exports.deviceMode || (exports.deviceMode = {})); /** * A frame of LEDs, used when you wish to set colour pixel by pixel * * @export * @class Frame * @typedef {Frame} */ var Frame = /** @class */ (function () { /** * Creates an instance of Frame. * * @constructor * @param {Led[]} leds Array of Led, of same length as nleds */ function Frame(leds) { this.leds = leds; } /** * Output the frame as a Uint8Array of bytes * * @returns {Uint8Array} */ Frame.prototype.toOctet = function () { var bytes = this.leds.map(function (led) { return led.toOctet(); }); var output = new Uint8Array(this.leds.length * 3); var offset = 0; bytes.forEach(function (item) { output.set(item, offset); offset += item.length; }); return output; }; /** * Get the number of LEDs in this frame * * @returns {number} */ Frame.prototype.getNLeds = function () { return this.leds.length; }; return Frame; }()); exports.Frame = Frame; /** * Easy way to create an entire frame of one colour * * @export * @class OneColourFrame * @typedef {OneColourFrame} * @extends {Frame} */ var OneColourFrame = /** @class */ (function (_super) { __extends(OneColourFrame, _super); /** * Creates an instance of OneColourFrame. * * @constructor * @param {rgbColour} rgb * @param {number} nleds Number of LEDs to include in this frame (probably the number of LEDs in the string) */ function OneColourFrame(rgb, nleds) { var leds = Array(nleds).fill(new Led(rgb.red, rgb.green, rgb.blue)); return _super.call(this, leds) || this; } return OneColourFrame; }(Frame)); exports.OneColourFrame = OneColourFrame; /** * A RGB led * * @export * @class Led * @typedef {Led} */ var Led = /** @class */ (function () { /** * Creates an instance of Led. * * @constructor * @param {number} red * @param {number} green * @param {number} blue */ function Led(red, green, blue) { this.red = red; this.green = green; this.blue = blue; } /** * Returns the LED in octet form * * @returns {Uint8Array} */ Led.prototype.toOctet = function () { return new Uint8Array([this.red, this.green, this.blue]); }; return Led; }()); exports.Led = Led;