UNPKG

@xkeys-lib/core

Version:

NPM package to interact with the X-keys panels

792 lines 35.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XKeys = void 0; const events_1 = require("events"); const products_1 = require("./products"); const lib_1 = require("./lib"); class XKeys extends events_1.EventEmitter { constructor(device, deviceInfo, _devicePath) { super(); this.device = device; this.deviceInfo = deviceInfo; this._devicePath = _devicePath; /** All button states */ this._buttonStates = new Map(); /** Analogue states, such as jog-wheels, shuttle etc */ this._analogStates = { jog: [], joystick: [], shuttle: [], tbar: [], rotary: [], trackball: [], }; this._initialized = false; this._firmwareVersion = 0; // is set after init() this._firmwareVersionIsSet = false; this._unitId = 0; // is set after init() this._unitIdIsSet = false; this._disconnected = false; this.product = this._setupDevice(deviceInfo); } /** Vendor id for the X-keys panels */ static get vendorId() { return products_1.XKEYS_VENDOR_ID; } /** * Takes a HID device as input. If the HID device is NOT an X-Keys returns null, otherwise some info about it. */ static filterDevice(deviceInfo) { if (deviceInfo.vendorId !== products_1.XKEYS_VENDOR_ID) return null; for (const product of Object.values(products_1.PRODUCTS)) { for (const hidDevice of product.hidDevices) { if (hidDevice[0] === deviceInfo.productId && (deviceInfo.interface === null || hidDevice[1] === deviceInfo.interface)) { return { product, productId: hidDevice[0], interface: hidDevice[1], }; // Return & break out of the loops } } } return null; } _setupDevice(deviceInfo) { const found = XKeys.filterDevice(deviceInfo); if (!found) throw new Error(`Unknown/Unsupported X-keys: "${deviceInfo.product}" (productId: "${deviceInfo.productId}", interface: "${deviceInfo.interface}").\nPlease report this as an issue on the xkeys github page! (https://github.com/SuperFlyTV/xkeys)`); this.device.on('data', (data) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; if (deviceInfo.productId === 210) { // Note: The RailDriver is an older device, which doesn't follow the rest of xkeys data structure. // To make it easy for us, we'll just remap the data to work for us. const rdData = new Uint8Array(32); rdData[0] = 0; // this sets the Unit ID to 0 always if (!this._firmwareVersionIsSet) { rdData[1] = 214; // Fake initial message to set _firmwareVersion } else if (!this._unitIdIsSet) { rdData[1] = 3; // Fake initial message to set _unitId } else { rdData[1] = 0; // no pg switch, byte is always 0 } rdData[2] = data.readUInt8(7); // remap button bits rdData[3] = data.readUInt8(8); // remap button bits rdData[4] = data.readUInt8(9); // remap button bits rdData[5] = data.readUInt8(10); // remap button bits rdData[6] = data.readUInt8(11); // remap button bits rdData[7] = data.readUInt8(12); // remap button bits // Add Bailoff to button byte, if (data.readUInt8(4) >= 160) { // set bit 5 to 1 rdData[7] = rdData[7] | 16; } rdData[8] = data.readUInt8(0); // remap analog bytes rdData[9] = data.readUInt8(1); // remap analog bytes rdData[10] = data.readUInt8(2); // remap analog bytes rdData[11] = data.readUInt8(3); // remap analog bytes rdData[12] = data.readUInt8(5); // remap analog bytes rdData[13] = data.readUInt8(6); // remap analog bytes for (let i = 0; i < 15; i++) { data[i] = rdData[i]; } } //------------------------ if (data.readUInt8(1) === 214) { // this is a special report that does not correlate to the regular data report, it is created by sending getVersion() const firmVersion = data.readUInt8(10); // data.readUInt8(0) the unit ID is the first byte, index 0, used to tell between 2 identical X-keys, UID is set by user // data.readUInt16LE(11) // PID is also in this report as a double check. this._firmwareVersion = firmVersion || 1; // Firmware version this._firmwareVersionIsSet = true; (_a = this.receivedVersionResolve) === null || _a === void 0 ? void 0 : _a.call(this); return; // quit here because this data would be interpreted as button data and give bad results. } // TODO: Add other special reports here. // A standard data report will be sent when something physical happens on the keys, button press, or lever moved for example // other special reports may be sent in response to a request or some data input on the device. // if (data.readUInt8(1) > 3) return; // Protect against all special data reports now and into the future. const newButtonStates = new Map(); const newAnalogStates = { jog: [], joystick: [], shuttle: [], tbar: [], rotary: [], trackball: [], }; // UID, unit id, is used to uniquely identify a certain panel, from factory it's set to 0, it can be set by a user with this.setUID() const UID = data.readUInt8(0); // the unit ID is the first byte, index 0, used to tell between 2 identical X-keys, UID is set by user // const PID = deviceInfo.productId // from USB hardware ID let timestamp = undefined; if (this.product.timestampByte !== undefined) { timestamp = data.readUInt32BE(this.product.timestampByte); // Time stamp is 4 bytes, use UInt32BE } const dd = data.readUInt8(1); // The genData bit is set when the message is a reply to the Generate Data message const genData = dd & (1 << 1) ? true : false; if (genData) { // Note, the generateData is used to get the full state this._unitId = UID; this._unitIdIsSet = true; (_b = this.receivedGenerateDataResolve) === null || _b === void 0 ? void 0 : _b.call(this); } // Note: first button data (bByte) is on byte index 2 for (let x = 0; x < this.product.bBytes; x++) { for (let y = 0; y < this.product.bBits; y++) { const index = x * this.product.bBits + y + 1; // add 1 so PS is at index 0, more accurately displays the total button number, but confuses the index for other use, such as LED addressing. const d = data.readUInt8(2 + x); const bit = d & (1 << y) ? true : false; newButtonStates.set(index, bit); } } if (this.product.hasPS) { // program switch/button is on byte index 1 , bit 1 const d = data.readUInt8(1); const bit = d & (1 << 0) ? true : false; // get first bit only newButtonStates.set(0, bit); // always keyIndex of PS to 0 } (_c = this.product.hasExtraButtons) === null || _c === void 0 ? void 0 : _c.forEach((exButton, index) => { //const d = data[jog.jogByte] // Jog //newAnalogStates.jog[index] = d < 128 ? d : d - 256 const d = data.readUInt8(exButton.ebByte); const bit = d & (1 << exButton.ebBit) ? true : false; const startIndex = this.product.bBytes * this.product.bBits + 1; // find the end of the button array newButtonStates.set(startIndex + index, bit); // start the extra buttons after that. }); (_d = this.product.hasJog) === null || _d === void 0 ? void 0 : _d.forEach((jog, index) => { const d = data[jog.jogByte]; // Jog newAnalogStates.jog[index] = d < 128 ? d : d - 256; }); (_e = this.product.hasShuttle) === null || _e === void 0 ? void 0 : _e.forEach((shuttle, index) => { const d = data[shuttle.shuttleByte]; // Shuttle newAnalogStates.shuttle[index] = d < 128 ? d : d - 256; }); (_f = this.product.hasJoystick) === null || _f === void 0 ? void 0 : _f.forEach((joystick, index) => { const x = data.readUInt8(joystick.joyXbyte); // Joystick X let y = data.readUInt8(joystick.joyYbyte); // Joystick Y const z = data.readUInt8(joystick.joyZbyte); // Joystick Z (twist of joystick) y = -y; // "Up" on the joystick should be positive if (y === 0) y = 0; // To deal with negative signed zero newAnalogStates.joystick[index] = { x: x < 128 ? x : x - 256, y: y < -128 ? y + 256 : y, z: z, // joystick z is a continuous value that rolls over to 0 after 255 }; }); (_g = this.product.hasTrackball) === null || _g === void 0 ? void 0 : _g.forEach((trackball, index) => { const x = 256 * data.readUInt8(trackball.trackXbyte_H) + data.readUInt8(trackball.trackXbyte_L); // Trackball X //Delta X motion, X ball motion = 256*DELTA_X_H + DELTA_X_L. const y = 256 * data.readUInt8(trackball.trackYbyte_H) + data.readUInt8(trackball.trackYbyte_L); // Trackball Y newAnalogStates.trackball[index] = { x: x < 32768 ? x : x - 65536, y: y < 32768 ? y : y - 65536, // -32768 to 32768// Trackball Y }; }); (_h = this.product.hasTbar) === null || _h === void 0 ? void 0 : _h.forEach((tBar, index) => { const d = data.readUInt8(tBar.tbarByte); // T-bar (calibrated) newAnalogStates.tbar[index] = d; }); (_j = this.product.hasRotary) === null || _j === void 0 ? void 0 : _j.forEach((rotary, index) => { const d = data.readUInt8(rotary.rotaryByte); newAnalogStates.rotary[index] = d; }); // Disabled/nonexisting buttons: important as some "buttons" in the jog & shuttle devices are used for shuttle events in hardware. if (this.product.disableButtons) { this.product.disableButtons.forEach((keyIndex) => { newButtonStates.set(keyIndex, false); }); } // Compare with previous button states: newButtonStates.forEach((buttonState, index) => { if ((this._buttonStates.get(index) || false) !== newButtonStates.get(index)) { const btnLocation = this._findBtnLocation(index); const metadata = { row: btnLocation.row, col: btnLocation.col, timestamp: timestamp, }; if (buttonState) { // Button is pressed this.emit('down', index, metadata); } else { this.emit('up', index, metadata); } } }); const eventMetadata = { timestamp: timestamp, }; // Compare with previous analogue states: newAnalogStates.jog.forEach((newValue, index) => { const oldValue = this._analogStates.jog[index]; // Special case for jog: // The jog emits the delta value followed by it being reset to 0 // Ignore the 0, since that won't be useful if (newValue === 0) return; if (newValue !== oldValue) this.emit('jog', index, newValue, eventMetadata); }); newAnalogStates.shuttle.forEach((newValue, index) => { const oldValue = this._analogStates.shuttle[index]; if (newValue !== oldValue) this.emit('shuttle', index, newValue, eventMetadata); }); newAnalogStates.joystick.forEach((newValue, index) => { const oldValue = this._analogStates.joystick[index]; if (!oldValue) { const emitValue = { ...newValue, // Calculate deltaZ, since that is not trivial to do: deltaZ: 0, }; this.emit('joystick', index, emitValue, eventMetadata); } else if (oldValue.x !== newValue.x || oldValue.y !== newValue.y || oldValue.z !== newValue.z) { const emitValue = { ...newValue, // Calculate deltaZ, since that is not trivial to do: deltaZ: XKeys.calculateDelta(newValue.z, oldValue.z), }; this.emit('joystick', index, emitValue, eventMetadata); } }); newAnalogStates.tbar.forEach((newValue, index) => { const oldValue = this._analogStates.tbar[index]; if (newValue !== oldValue) this.emit('tbar', index, newValue, eventMetadata); }); newAnalogStates.rotary.forEach((newValue, index) => { const oldValue = this._analogStates.rotary[index]; if (newValue !== oldValue) this.emit('rotary', index, newValue, eventMetadata); }); newAnalogStates.trackball.forEach((newValue, index) => { // We only need to emit the value when not zero, since the trackball motion are relative values. if (newValue.x !== 0 || newValue.y !== 0) this.emit('trackball', index, newValue, eventMetadata); }); // Store the new states: this._buttonStates = newButtonStates; this._analogStates = newAnalogStates; }); this.device.on('error', (err) => { if ((err + '').match(/could not read from/)) { // The device has been disconnected this._handleDeviceDisconnected().catch((error) => { this.emit('error', error); }); } else { this.emit('error', err); } }); return { ...found.product, productId: found.productId, interface: found.interface, }; } /** Initialize the device. This ensures that the essential information from the device about its state has been received. */ async init() { const pReceivedVersion = new Promise((resolve) => { this.receivedVersionResolve = resolve; }); const pReceivedGenerateData = new Promise((resolve) => { this.receivedGenerateDataResolve = resolve; }); this._getVersion(); this._generateData(); await pReceivedVersion; await pReceivedGenerateData; this._initialized = true; } /** Closes the device. Subsequent commands will raise errors. */ async close() { await this._handleDeviceDisconnected(); } /** Firmware version of the device */ get firmwareVersion() { return this._firmwareVersion; } /** Unit id ("UID") of the device, is used to uniquely identify a certain panel, or panel type. * From factory it's set to 0, but it can be changed using this.setUnitId() */ get unitId() { return this._unitId; } /** Various information about the device and its capabilities */ get info() { var _a, _b, _c, _d, _e, _f, _g, _h; this.ensureInitialized(); return (0, lib_1.literal)({ name: this.product.name, vendorId: products_1.XKEYS_VENDOR_ID, productId: this.product.productId, interface: this.product.interface, unitId: this.unitId, firmwareVersion: this._firmwareVersion, colCount: this.product.colCount, rowCount: this.product.rowCount, layout: ((_a = this.product.layouts) === null || _a === void 0 ? void 0 : _a.map((region) => { return (0, lib_1.literal)({ name: region[0], index: region[1], startRow: region[2], startCol: region[3], endRow: region[4], endCol: region[5], }); })) || [], emitsTimestamp: this.product.timestampByte !== undefined, hasPS: this.product.hasPS, hasJoystick: ((_b = this.product.hasJoystick) === null || _b === void 0 ? void 0 : _b.length) || 0, hasTrackball: ((_c = this.product.hasTrackball) === null || _c === void 0 ? void 0 : _c.length) || 0, hasExtraButtons: ((_d = this.product.hasExtraButtons) === null || _d === void 0 ? void 0 : _d.length) || 0, hasJog: ((_e = this.product.hasJog) === null || _e === void 0 ? void 0 : _e.length) || 0, hasShuttle: ((_f = this.product.hasShuttle) === null || _f === void 0 ? void 0 : _f.length) || 0, hasTbar: ((_g = this.product.hasTbar) === null || _g === void 0 ? void 0 : _g.length) || 0, hasRotary: ((_h = this.product.hasRotary) === null || _h === void 0 ? void 0 : _h.length) || 0, hasLCD: this.product.hasLCD || false, hasGPIO: this.product.hasGPIO || false, hasSerialData: this.product.hasSerialData || false, hasDMX: this.product.hasDMX || false, }); } /** * Returns an object with current Button states */ getButtons() { return new Map(this._buttonStates); // Make a copy } /** * Sets the indicator-LED on the device, usually a red and green LED at the top of many X-keys * @param ledIndex the LED to set (1 = green (top), 2 = red (bottom)) * @param on boolean: on or off * @param flashing boolean: flashing or not (if on) * @returns undefined */ setIndicatorLED(ledIndex, on, flashing) { this.ensureInitialized(); //force to 6 or 7 if (ledIndex === 1) ledIndex = 6; else if (ledIndex === 2) ledIndex = 7; this._write([0, 179, ledIndex, on ? (flashing ? 2 : 1) : 0]); } /** * Sets the backlight of a button * @param keyIndex The button of which to set the backlight color * @param color r,g,b or string (RGB, RRGGBB, #RRGGBB) * @param bankIndex number: Which LED bank (top or bottom) to set the color of. (Only applicable to RGB-based panels. ) * @param flashing boolean: flashing or not (if on) * @returns undefined */ setBacklight(keyIndex, /** RGB, RRGGBB, #RRGGBB */ color, flashing, bankIndex) { this.ensureInitialized(); if (keyIndex === 0) return; // PS-button has no backlight this._verifyButtonIndex(keyIndex); color = this._interpretColor(color, this.product.backLightType); const location = this._findBtnLocation(keyIndex); if (this.product.backLightType === products_1.BackLightType.REMAP_24) { // obsolete, Consider removing MHH const ledIndex = (location.col - 1) * 8 + location.row - 1; // backlight LED type 5 is the RGB 24 buttons this._write([0, 181, ledIndex, color.g, color.r, color.b, flashing ? 1 : 0]); // Byte order is actually G,R,B,F) } else if (this.product.backLightType === products_1.BackLightType.RGBx2) { // backlight LED type 6, 2 banks of full RGB LEDs const ledIndex = keyIndex - 1; // 0 based linear numbering sort of... if (bankIndex !== undefined) { this._write([0, 165, ledIndex, bankIndex, color.r, color.g, color.b, flashing ? 1 : 0]); } else { // There are 2 LEDs in under a key, 0 for top and 1 for bottom. this._write([0, 165, ledIndex, 0, color.r, color.g, color.b, flashing ? 1 : 0]); this._write([0, 165, ledIndex, 1, color.r, color.g, color.b, flashing ? 1 : 0]); } } else if (this.product.backLightType === products_1.BackLightType.STICK_BUTTONS) { // The stick buttons, that requires special mapping. let ledIndex = location.col - 1; // 0 based linear numbering sort of... if (ledIndex > 11) ledIndex = ledIndex + 4; else if (ledIndex > 5) ledIndex = ledIndex + 2; const on = color.r > 0 || color.g > 0 || color.b > 0; this._write([0, 181, ledIndex, on ? (flashing ? 2 : 1) : 0, 1]); } else if (this.product.backLightType === products_1.BackLightType.LINEAR) { // The 40 buttons, that requires special mapping. const ledIndexBlue = keyIndex - 1; // 0 based linear numbering sort of... const ledIndexRed = ledIndexBlue + this.product.backLight2offset; this._write([0, 181, ledIndexBlue, color.b > 0 ? (flashing ? 2 : 1) : 0, 0]); this._write([0, 181, ledIndexRed, color.r > 0 || color.g > 0 ? (flashing ? 2 : 1) : 0, 0]); } else if (this.product.backLightType === products_1.BackLightType.LEGACY) { const ledIndexBlue = (location.col - 1) * 8 + location.row - 1; const ledIndexRed = ledIndexBlue + (this.product.backLight2offset || 0); // Blue LED: this._write([0, 181, ledIndexBlue, color.b > 0 ? (flashing ? 2 : 1) : 0, 1]); // Red LED: this._write([0, 181, ledIndexRed, color.r > 0 || color.g > 0 ? (flashing ? 2 : 1) : 0, 1]); } else if (this.product.backLightType === products_1.BackLightType.NONE) { // No backlight, do nothing } } /** * Sets the backlight of all buttons * @param color r,g,b or string (RGB, RRGGBB, #RRGGBB) * @param bankIndex number: Which LED bank (top or bottom) to control. */ setAllBacklights(color, bankIndex) { this.ensureInitialized(); color = this._interpretColor(color, this.product.backLightType); if (this.product.backLightType === products_1.BackLightType.RGBx2) { // backlight LED type 6 is the RGB devices if (bankIndex !== undefined) { this._write([0, 166, bankIndex, color.r, color.g, color.b]); } else { // There are 2 LEDs in under a key, 0 for top and 1 for bottom. this._write([0, 166, 0, color.r, color.g, color.b]); this._write([0, 166, 1, color.r, color.g, color.b]); } } else { // Blue LEDs: this._write([0, 182, 0, color.b]); // Red LEDs: this._write([0, 182, 1, color.r || color.g]); } } /** * On first call: Turn all backlights off * On second call: Return all backlights to their previous states */ toggleAllBacklights() { this.ensureInitialized(); this._write([0, 184]); } /** * Sets the backlight intensity of the device * @param blueIntensity 0-255 * @param redIntensity 0-255 */ setBacklightIntensity(blueIntensity, redIntensity) { this.ensureInitialized(); if (redIntensity === undefined) redIntensity = blueIntensity; blueIntensity = Math.max(Math.min(blueIntensity, 255), 0); redIntensity = Math.max(Math.min(redIntensity, 255), 0); if (this.product.backLightType === products_1.BackLightType.LEGACY || this.product.backLightType === products_1.BackLightType.LINEAR) { this._write([0, 187, blueIntensity, redIntensity]); } else { this._write([0, 187, blueIntensity]); } } /** * Save the current backlights. This will restore the backlights after a power cycle. * Note: EEPROM command, don't call this function too often, or you'll kill the EEPROM! * (An EEPROM only support a few thousands of write operations.) */ saveBackLights() { this.ensureInitialized(); this._write([0, 199, 1]); } /** * Sets the flash frequency of LEDs for the entire X-keys. Flashing will always be synchronized * @param frequency 1-255, where 1 is fastest and 255 is the slowest. 255 is approximately 4 seconds between flashes. * @returns undefined */ setFrequency(frequency) { this.ensureInitialized(); if (!(frequency >= 1 && frequency <= 255)) { throw new Error(`Invalid frequency: ${frequency}`); } this._write([0, 180, frequency]); } /** * Sets the UID (unit Id) value in the X-keys hardware * Note: EEPROM command, don't call this function too often, or you'll kill the EEPROM! * (An EEPROM only supports a few thousands of write operations.) * @param unitId Unit id ("UID"). Allowed values: 0-255. 0 is factory default * @returns undefined */ setUnitId(unitId) { this.ensureInitialized(); if (!(unitId >= 0 && unitId <= 255)) { throw new Error(`Invalid UID: ${unitId} (needs to be between 0 - 255)`); } this._write([0, 189, unitId]); this._unitId = unitId; } /** * Reboots the device * @returns undefined */ rebootDevice() { this.ensureInitialized(); this._write([0, 238]); } /** * Sets the 2x16 LCD display * @param line 1 for top line, 2 for bottom line. * @param displayChar // string to display, empty string to clear * @param backlight 0 for off, 1 for on. * @returns undefined */ writeLcdDisplay(line, displayChar, backlight) { this.ensureInitialized(); if (!this.product.hasLCD) return; // only used for LCD display devices. const byteValues = [0, 206, 0, 1, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32]; // load the array with 206 op code and spaces // change line number to 0 or 1 and set line # byte if (line < 2) line = 0; if (line > 1) line = 1; byteValues[2] = line; // change backlight to 0 or 1 and set backlight byte let liteByte; if (backlight) { liteByte = 1; } else { liteByte = 0; } byteValues[3] = liteByte; // set the LCD backlight on or off. // loop through the string and load array with ascii byte values let i; for (i = 0; i < displayChar.length; i++) { byteValues[i + 4] = displayChar.charCodeAt(i); if (i > 15) break; // quit at 16 chars } this._write(byteValues); } /** * Writes a Buffer of data bytes to the X-keys device * Used to send custom messages to X-keys for testing and development, see documentation for valid messages * @param buffer The buffer written to the device * @returns undefined */ writeData(message) { this._write(message); } /** * Returns a Promise that settles when all writes have been completed */ async flush() { await this.device.flush(); } /** (Internal function) Called when there has been detected that the device has been disconnected */ async _handleDeviceDisconnected() { if (!this._disconnected) { this._disconnected = true; await this.device.close(); this.emit('disconnected'); } } /** (Internal function) Called when there has been detected that a device has been reconnected */ async _handleDeviceReconnected(device, deviceInfo) { if (this._disconnected) { this._disconnected = false; // Re-vitalize: this.device = device; this.product = this._setupDevice(deviceInfo); await this.init(); this.emit('reconnected'); } } _getHIDDevice() { return this.device; } _getDeviceInfo() { return this.deviceInfo; } get devicePath() { return this._devicePath; } /** The unique id of the xkeys-panel. Note: This is only available if options.automaticUnitIdMode is set for the Watcher */ get uniqueId() { return `${this.info.productId}_${this.unitId}`; } /** * Writes a Buffer to the X-keys device * * @param buffer The buffer written to the device * @returns undefined */ _write(message) { if (this._disconnected) throw new Error('X-keys panel has been disconnected'); message = this._padMessage(message); const intArray = []; for (let index = 0; index < message.length; index++) { const value = message[index]; intArray[index] = typeof value === 'string' ? parseInt(value, 10) : value; } try { this.device.write(intArray); } catch (e) { this.emit('error', e); } } _padMessage(message) { const messageLength = 36; while (message.length < messageLength) { message.push(0); } return message; } _verifyButtonIndex(keyIndex) { if (!(keyIndex >= 0 && keyIndex < 8 * this.product.bBytes + 1)) { throw new Error(`Invalid keyIndex: ${keyIndex}`); } } _findBtnLocation(keyIndex) { let location = { row: 0, col: 0 }; // derive the Row and Column from the button index for many products if (keyIndex !== 0) { // program switch is always on index 0 and always R:0, C:0 unless remapped by btnLocation array location.row = keyIndex - this.product.bBits * (Math.ceil(keyIndex / this.product.bBits) - 1); location.col = Math.ceil(keyIndex / this.product.bBits); } // if the product has a btnLocation array, then look up the Row and Column if (this.product.btnLocation !== undefined) { location = { row: this.product.btnLocation[keyIndex][0], col: this.product.btnLocation[keyIndex][1], }; } return location; } /** * Generate data: forces the unit to send a data report with current states. Important to get the Unit ID. * @param none * @returns undefined //an input report will be generated by the X-keys with bit 2 of PS set. This is useful in determining the initial state of the device before any data has changed. */ _generateData() { this._write([0, 177]); } /** * Gets the firmware version and UID : forces the unit to send a special data report with firmware version and Unit ID. * @param none * @returns undefined //an input report will be generated by the X-keys with byte 2 set to 214. This has the firmware version and UID. */ _getVersion() { this._write([0, 214]); } /** Makes best effort to interpret a color */ _interpretColor(color, _backLightType) { if (typeof color === 'boolean' || color === null) { // todo: Should we use _backLightType in some way to determine different default colors? if (color) return { r: 0, g: 0, b: 255 }; else return { r: 0, g: 0, b: 0 }; } else if (typeof color === 'string') { // Note: Handle a few "worded" colors, these colors are tweaked to look nice with the X-keys LEDs: if (color === 'red') color = 'ff0000'; else if (color === 'blue') color = '0000ff'; else if (color === 'violet') color = '600096'; else if (color === 'aquamarine') color = '00ff45'; else if (color === 'turquoise') color = '00ff81'; else if (color === 'purple') color = '960096'; else if (color === 'redblue') color = 'ff00ff'; else if (color === 'pink') color = 'ff0828'; else if (color === 'orange') color = 'ff1400'; else if (color === 'yellow') color = 'ff8000'; else if (color === 'green') color = '00ff00'; else if (color === 'black') color = '000000'; else if (color === 'white') color = 'ffffff'; else if (color === 'on') color = 'ffffff'; else if (color === 'off') color = '000000'; let m; if ((m = color.match(/([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})/))) { // 'RRGGBB' return { r: parseInt(m[1], 16), g: parseInt(m[2], 16), b: parseInt(m[3], 16), }; } else if ((m = color.match(/([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})/))) { // 'RGB' return { r: parseInt(m[1] + m[1], 16), g: parseInt(m[2] + m[2], 16), b: parseInt(m[3] + m[3], 16), }; } else if ((m = color.match(/([0-9]{1,3}),([0-9]{1,3}),([0-9]{1,3})/))) { // '255,127,0' // comma separated integers return { r: parseInt(m[1], 10), g: parseInt(m[2], 10), b: parseInt(m[3], 10), }; } else { // Fallback: this.emit('error', new Error(`Unable to interpret color "${color}"`)); return { r: 127, g: 127, b: 127, }; } } else { return color; } } /** Check that the .init() function has run, throw otherwise */ ensureInitialized() { if (!this._initialized) throw new Error('XKeys.init() must be run first!'); } /** Calculate delta value */ static calculateDelta(newValue, oldValue, overflow = 256) { let delta = newValue - oldValue; if (delta < -overflow * 0.5) delta += overflow; // Deal with when the new value overflows if (delta > overflow * 0.5) delta -= overflow; // Deal with when the new value underflows return delta; } } exports.XKeys = XKeys; //# sourceMappingURL=xkeys.js.map