xled2
Version:
Library to control Twinkly LED lights
878 lines (877 loc) • 32.5 kB
JavaScript
"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;