diff --git a/dist/commonjs/cli/litra-backlight-off.d.ts b/dist/commonjs/cli/litra-backlight-off.d.ts new file mode 100644 index 0000000..b798801 --- /dev/null +++ b/dist/commonjs/cli/litra-backlight-off.d.ts @@ -0,0 +1,2 @@ +#!/usr/bin/env node +export {}; diff --git a/dist/commonjs/cli/litra-backlight-off.js b/dist/commonjs/cli/litra-backlight-off.js new file mode 100644 index 0000000..bd62bec --- /dev/null +++ b/dist/commonjs/cli/litra-backlight-off.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const commander_1 = require("commander"); +const driver_1 = require("./../driver"); +const utils_1 = require("./utils"); +commander_1.program + .name('litra-backlight-off') + .description('Deactivate the backlight on a Litra Beam LX device') + .option('-s, --serial-number ', 'serial number of the Litra Beam LX device to control'); +commander_1.program.parse(); +const { serialNumber } = commander_1.program.opts(); +try { + const device = (0, utils_1.getDeviceForCLI)(serialNumber); + (0, driver_1.backlightOff)(device); + process.exit(0); +} +catch (e) { + console.log(e); + process.exit(1); +} diff --git a/dist/commonjs/cli/litra-backlight-on.d.ts b/dist/commonjs/cli/litra-backlight-on.d.ts new file mode 100644 index 0000000..b798801 --- /dev/null +++ b/dist/commonjs/cli/litra-backlight-on.d.ts @@ -0,0 +1,2 @@ +#!/usr/bin/env node +export {}; diff --git a/dist/commonjs/cli/litra-backlight-on.js b/dist/commonjs/cli/litra-backlight-on.js new file mode 100644 index 0000000..2a3f701 --- /dev/null +++ b/dist/commonjs/cli/litra-backlight-on.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const commander_1 = require("commander"); +const driver_1 = require("./../driver"); +const utils_1 = require("./utils"); +commander_1.program + .name('litra-backlight-on') + .description('Activate the backlight on a Litra Beam LX device') + .option('-s, --serial-number ', 'serial number of the Litra Beam LX device to control'); +commander_1.program.parse(); +const { serialNumber } = commander_1.program.opts(); +try { + const device = (0, utils_1.getDeviceForCLI)(serialNumber); + (0, driver_1.backlightOn)(device); + process.exit(0); +} +catch (e) { + console.log(e); + process.exit(1); +} diff --git a/dist/commonjs/driver.d.ts b/dist/commonjs/driver.d.ts index 973cf27..751ac9e 100644 --- a/dist/commonjs/driver.d.ts +++ b/dist/commonjs/driver.d.ts @@ -139,3 +139,28 @@ export declare const getAllowedTemperaturesInKelvinForDevice: (device: Device) = * @returns {string} The name of the device, e.g. "Logitech Litra Glow" */ export declare const getNameForDevice: (device: Device) => string; +/** + * Turns the backlight on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight on for + * @throws {string} If the device is not a Litra Beam LX + */ +export declare const backlightOn: (device: Device) => void; +/** + * Turns the backlight off for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight off for + * @throws {string} If the device is not a Litra Beam LX + */ +export declare const backlightOff: (device: Device) => void; +/** + * Checks whether the backlight is currently on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to check the backlight status for + * @returns {boolean} true if the backlight is on, false if it is off + * @throws {string} If the device is not a Litra Beam LX + */ +export declare const isBacklightOn: (device: Device) => boolean; diff --git a/dist/commonjs/driver.js b/dist/commonjs/driver.js index 75fe385..4bc4c2e 100644 --- a/dist/commonjs/driver.js +++ b/dist/commonjs/driver.js @@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getNameForDevice = exports.getAllowedTemperaturesInKelvinForDevice = exports.getMaximumTemperatureInKelvinForDevice = exports.getMinimumTemperatureInKelvinForDevice = exports.getMaximumBrightnessInLumenForDevice = exports.getMinimumBrightnessInLumenForDevice = exports.setBrightnessPercentage = exports.getBrightnessInLumen = exports.setBrightnessInLumen = exports.getTemperatureInKelvin = exports.setTemperatureInKelvin = exports.isOn = exports.toggle = exports.turnOff = exports.turnOn = exports.findDevices = exports.findDevice = exports.DeviceType = void 0; +exports.isBacklightOn = exports.backlightOff = exports.backlightOn = exports.getNameForDevice = exports.getAllowedTemperaturesInKelvinForDevice = exports.getMaximumTemperatureInKelvinForDevice = exports.getMinimumTemperatureInKelvinForDevice = exports.getMaximumBrightnessInLumenForDevice = exports.getMinimumBrightnessInLumenForDevice = exports.setBrightnessPercentage = exports.getBrightnessInLumen = exports.setBrightnessInLumen = exports.getTemperatureInKelvin = exports.setTemperatureInKelvin = exports.isOn = exports.toggle = exports.turnOff = exports.turnOn = exports.findDevices = exports.findDevice = exports.DeviceType = void 0; const node_hid_1 = __importDefault(require("node-hid")); const utils_1 = require("./utils"); var DeviceType; @@ -358,3 +358,60 @@ const getNameForDevice = (device) => { return NAME_BY_DEVICE_TYPE[device.type]; }; exports.getNameForDevice = getNameForDevice; +const buildBacklightOnBytes = () => { + return (0, utils_1.padRight)([0x11, 0xff, 0x0a, 0x4b, 0x01], 20, 0x00); +}; +/** + * Turns the backlight on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight on for + * @throws {string} If the device is not a Litra Beam LX + */ +const backlightOn = (device) => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + const message = buildBacklightOnBytes(); + device.hid.write(message); +}; +exports.backlightOn = backlightOn; +const buildBacklightOffBytes = () => { + return (0, utils_1.padRight)([0x11, 0xff, 0x0a, 0x4b, 0x00], 20, 0x00); +}; +/** + * Turns the backlight off for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight off for + * @throws {string} If the device is not a Litra Beam LX + */ +const backlightOff = (device) => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + const message = buildBacklightOffBytes(); + device.hid.write(message); +}; +exports.backlightOff = backlightOff; +const buildBacklightStatusQueryBytes = () => { + return (0, utils_1.padRight)([0x11, 0xff, 0x0a, 0x3b], 20, 0x00); +}; +/** + * Checks whether the backlight is currently on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to check the backlight status for + * @returns {boolean} true if the backlight is on, false if it is off + * @throws {string} If the device is not a Litra Beam LX + */ +const isBacklightOn = (device) => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + const query = buildBacklightStatusQueryBytes(); + device.hid.write(query); + const response = device.hid.readSync(); + return response[4] === 1; +}; +exports.isBacklightOn = isBacklightOn; diff --git a/dist/esm/cli/litra-backlight-off.d.ts b/dist/esm/cli/litra-backlight-off.d.ts new file mode 100644 index 0000000..b798801 --- /dev/null +++ b/dist/esm/cli/litra-backlight-off.d.ts @@ -0,0 +1,2 @@ +#!/usr/bin/env node +export {}; diff --git a/dist/esm/cli/litra-backlight-off.js b/dist/esm/cli/litra-backlight-off.js new file mode 100644 index 0000000..00199fe --- /dev/null +++ b/dist/esm/cli/litra-backlight-off.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +import { program } from 'commander'; +import { backlightOff } from './../driver'; +import { getDeviceForCLI } from './utils'; +program + .name('litra-backlight-off') + .description('Deactivate the backlight on a Litra Beam LX device') + .option('-s, --serial-number ', 'serial number of the Litra Beam LX device to control'); +program.parse(); +const { serialNumber } = program.opts(); +try { + const device = getDeviceForCLI(serialNumber); + backlightOff(device); + process.exit(0); +} +catch (e) { + console.log(e); + process.exit(1); +} diff --git a/dist/esm/cli/litra-backlight-on.d.ts b/dist/esm/cli/litra-backlight-on.d.ts new file mode 100644 index 0000000..b798801 --- /dev/null +++ b/dist/esm/cli/litra-backlight-on.d.ts @@ -0,0 +1,2 @@ +#!/usr/bin/env node +export {}; diff --git a/dist/esm/cli/litra-backlight-on.js b/dist/esm/cli/litra-backlight-on.js new file mode 100644 index 0000000..7eea01d --- /dev/null +++ b/dist/esm/cli/litra-backlight-on.js @@ -0,0 +1,19 @@ +#!/usr/bin/env node +import { program } from 'commander'; +import { backlightOn } from './../driver'; +import { getDeviceForCLI } from './utils'; +program + .name('litra-backlight-on') + .description('Activate the backlight on a Litra Beam LX device') + .option('-s, --serial-number ', 'serial number of the Litra Beam LX device to control'); +program.parse(); +const { serialNumber } = program.opts(); +try { + const device = getDeviceForCLI(serialNumber); + backlightOn(device); + process.exit(0); +} +catch (e) { + console.log(e); + process.exit(1); +} diff --git a/dist/esm/driver.d.ts b/dist/esm/driver.d.ts index 973cf27..751ac9e 100644 --- a/dist/esm/driver.d.ts +++ b/dist/esm/driver.d.ts @@ -139,3 +139,28 @@ export declare const getAllowedTemperaturesInKelvinForDevice: (device: Device) = * @returns {string} The name of the device, e.g. "Logitech Litra Glow" */ export declare const getNameForDevice: (device: Device) => string; +/** + * Turns the backlight on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight on for + * @throws {string} If the device is not a Litra Beam LX + */ +export declare const backlightOn: (device: Device) => void; +/** + * Turns the backlight off for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight off for + * @throws {string} If the device is not a Litra Beam LX + */ +export declare const backlightOff: (device: Device) => void; +/** + * Checks whether the backlight is currently on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to check the backlight status for + * @returns {boolean} true if the backlight is on, false if it is off + * @throws {string} If the device is not a Litra Beam LX + */ +export declare const isBacklightOn: (device: Device) => boolean; diff --git a/dist/esm/driver.js b/dist/esm/driver.js index 4a27621..f7f57e9 100644 --- a/dist/esm/driver.js +++ b/dist/esm/driver.js @@ -335,3 +335,57 @@ export const getAllowedTemperaturesInKelvinForDevice = (device) => { export const getNameForDevice = (device) => { return NAME_BY_DEVICE_TYPE[device.type]; }; +const buildBacklightOnBytes = () => { + return padRight([0x11, 0xff, 0x0a, 0x4b, 0x01], 20, 0x00); +}; +/** + * Turns the backlight on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight on for + * @throws {string} If the device is not a Litra Beam LX + */ +export const backlightOn = (device) => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + const message = buildBacklightOnBytes(); + device.hid.write(message); +}; +const buildBacklightOffBytes = () => { + return padRight([0x11, 0xff, 0x0a, 0x4b, 0x00], 20, 0x00); +}; +/** + * Turns the backlight off for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight off for + * @throws {string} If the device is not a Litra Beam LX + */ +export const backlightOff = (device) => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + const message = buildBacklightOffBytes(); + device.hid.write(message); +}; +const buildBacklightStatusQueryBytes = () => { + return padRight([0x11, 0xff, 0x0a, 0x3b], 20, 0x00); +}; +/** + * Checks whether the backlight is currently on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to check the backlight status for + * @returns {boolean} true if the backlight is on, false if it is off + * @throws {string} If the device is not a Litra Beam LX + */ +export const isBacklightOn = (device) => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + const query = buildBacklightStatusQueryBytes(); + device.hid.write(query); + const response = device.hid.readSync(); + return response[4] === 1; +}; diff --git a/package-lock.json b/package-lock.json index ada8d5d..ff1ca1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,6 +161,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz", "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -1442,6 +1443,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", "dev": true, + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.0.0", @@ -2055,7 +2057,8 @@ "version": "18.7.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/node-hid": { "version": "1.3.4", @@ -2211,6 +2214,7 @@ "integrity": "sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.27.0", "@typescript-eslint/types": "8.27.0", @@ -2652,6 +2656,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2965,6 +2970,7 @@ "url": "https://tidelift.com/funding/github/npm/browserslist" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -3769,6 +3775,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3828,6 +3835,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4971,6 +4979,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -5804,6 +5813,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz", "integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==", "dev": true, + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -9311,6 +9321,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9708,6 +9719,7 @@ "integrity": "sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -10520,6 +10532,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -10591,6 +10604,7 @@ "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10993,6 +11007,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz", "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", @@ -11968,6 +11983,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", "dev": true, + "peer": true, "requires": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.0.0", @@ -12435,7 +12451,8 @@ "version": "18.7.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "dev": true + "dev": true, + "peer": true }, "@types/node-hid": { "version": "1.3.4", @@ -12542,6 +12559,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.27.0.tgz", "integrity": "sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.27.0", "@typescript-eslint/types": "8.27.0", @@ -12793,7 +12811,8 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -13027,6 +13046,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001400", "electron-to-chromium": "^1.4.251", @@ -13585,6 +13605,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -13635,6 +13656,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "requires": {} }, "eslint-plugin-prettier": { @@ -14442,6 +14464,7 @@ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, + "peer": true, "requires": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -15096,7 +15119,8 @@ "version": "12.0.0", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz", "integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==", - "dev": true + "dev": true, + "peer": true }, "marked-terminal": { "version": "7.0.0", @@ -17478,7 +17502,8 @@ "version": "3.4.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", - "dev": true + "dev": true, + "peer": true }, "prettier-linter-helpers": { "version": "1.0.0", @@ -17740,6 +17765,7 @@ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.3.tgz", "integrity": "sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A==", "dev": true, + "peer": true, "requires": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -18323,6 +18349,7 @@ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, + "peer": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -18364,7 +18391,8 @@ "version": "5.8.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true + "dev": true, + "peer": true }, "uglify-js": { "version": "3.17.4", diff --git a/package.json b/package.json index 4e42ab4..555bf11 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,8 @@ "litra-brightness-lm": "./dist/commonjs/cli/litra-brightness-lm.js", "litra-temperature-k": "./dist/commonjs/cli/litra-temperature-k.js", "litra-identify": "./dist/commonjs/cli/litra-identify.js", - "litra-devices": "./dist/commonjs/cli/litra-devices.js" + "litra-devices": "./dist/commonjs/cli/litra-devices.js", + "litra-backlight-on": "./dist/commonjs/cli/litra-backlight-on.js", + "litra-backlight-off": "./dist/commonjs/cli/litra-backlight-off.js" } } diff --git a/src/cli/litra-backlight-off.ts b/src/cli/litra-backlight-off.ts new file mode 100644 index 0000000..b32717d --- /dev/null +++ b/src/cli/litra-backlight-off.ts @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +import { program } from 'commander'; +import { backlightOff } from './../driver'; +import { getDeviceForCLI } from './utils'; + +program + .name('litra-backlight-off') + .description('Deactivate the backlight on a Litra Beam LX device') + .option( + '-s, --serial-number ', + 'serial number of the Litra Beam LX device to control', + ); + +program.parse(); +const { serialNumber } = program.opts(); + +try { + const device = getDeviceForCLI(serialNumber); + backlightOff(device); + process.exit(0); +} catch (e) { + console.log(e); + process.exit(1); +} diff --git a/src/cli/litra-backlight-on.ts b/src/cli/litra-backlight-on.ts new file mode 100644 index 0000000..241e5f3 --- /dev/null +++ b/src/cli/litra-backlight-on.ts @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +import { program } from 'commander'; +import { backlightOn } from './../driver'; +import { getDeviceForCLI } from './utils'; + +program + .name('litra-backlight-on') + .description('Activate the backlight on a Litra Beam LX device') + .option( + '-s, --serial-number ', + 'serial number of the Litra Beam LX device to control', + ); + +program.parse(); +const { serialNumber } = program.opts(); + +try { + const device = getDeviceForCLI(serialNumber); + backlightOn(device); + process.exit(0); +} catch (e) { + console.log(e); + process.exit(1); +} diff --git a/src/driver.ts b/src/driver.ts index 077d2c8..451d287 100644 --- a/src/driver.ts +++ b/src/driver.ts @@ -425,3 +425,68 @@ export const getAllowedTemperaturesInKelvinForDevice = (device: Device): number[ export const getNameForDevice = (device: Device): string => { return NAME_BY_DEVICE_TYPE[device.type]; }; + +const buildBacklightOnBytes = (): number[] => { + return padRight([0x11, 0xff, 0x0a, 0x4b, 0x01], 20, 0x00); +}; + +/** + * Turns the backlight on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight on for + * @throws {string} If the device is not a Litra Beam LX + */ +export const backlightOn = (device: Device): void => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + + const message = buildBacklightOnBytes(); + device.hid.write(message); +}; + +const buildBacklightOffBytes = (): number[] => { + return padRight([0x11, 0xff, 0x0a, 0x4b, 0x00], 20, 0x00); +}; + +/** + * Turns the backlight off for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to turn the backlight off for + * @throws {string} If the device is not a Litra Beam LX + */ +export const backlightOff = (device: Device): void => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + + const message = buildBacklightOffBytes(); + device.hid.write(message); +}; + +const buildBacklightStatusQueryBytes = (): number[] => { + return padRight([0x11, 0xff, 0x0a, 0x3b], 20, 0x00); +}; + +/** + * Checks whether the backlight is currently on for your Logitech Litra Beam LX device. + * Note: This feature is only available on Litra Beam LX devices. + * + * @param {Device} device The device to check the backlight status for + * @returns {boolean} true if the backlight is on, false if it is off + * @throws {string} If the device is not a Litra Beam LX + */ +export const isBacklightOn = (device: Device): boolean => { + if (device.type !== DeviceType.LitraBeamLX) { + throw 'Backlight control is only supported on Litra Beam LX devices'; + } + + const query = buildBacklightStatusQueryBytes(); + device.hid.write(query); + + const response = device.hid.readSync(); + + return response[4] === 1; +}; diff --git a/tests/driver.test.ts b/tests/driver.test.ts index 95147ef..dfd9408 100644 --- a/tests/driver.test.ts +++ b/tests/driver.test.ts @@ -15,6 +15,9 @@ import { turnOn, toggle, isOn, + backlightOn, + backlightOff, + isBacklightOn, Device, } from '../src/driver'; @@ -608,3 +611,85 @@ describe('getNameForDevice', () => { expect(getNameForDevice(fakeLitraBeamLx)).toEqual('Logitech Litra Beam LX'); }); }); + +describe('backlightOn', () => { + it('writes correct command bytes to activate Litra Beam LX backlight', () => { + backlightOn(fakeLitraBeamLx); + + expect(fakeLitraBeamLx.hid.write).toBeCalledWith([ + 17, 255, 10, 75, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + }); + + it('throws error when attempting to enable backlight on Litra Glow', () => { + expect(() => backlightOn(fakeLitraGlow)).toThrow( + 'Backlight control is only supported on Litra Beam LX devices', + ); + }); + + it('throws error when attempting to enable backlight on Litra Beam', () => { + expect(() => backlightOn(fakeLitraBeam)).toThrow( + 'Backlight control is only supported on Litra Beam LX devices', + ); + }); +}); + +describe('backlightOff', () => { + it('writes correct command bytes to deactivate Litra Beam LX backlight', () => { + backlightOff(fakeLitraBeamLx); + + expect(fakeLitraBeamLx.hid.write).toBeCalledWith([ + 17, 255, 10, 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + }); + + it('throws error when attempting to disable backlight on Litra Glow', () => { + expect(() => backlightOff(fakeLitraGlow)).toThrow( + 'Backlight control is only supported on Litra Beam LX devices', + ); + }); + + it('throws error when attempting to disable backlight on Litra Beam', () => { + expect(() => backlightOff(fakeLitraBeam)).toThrow( + 'Backlight control is only supported on Litra Beam LX devices', + ); + }); +}); + +describe('isBacklightOn', () => { + it('writes correct query bytes and returns true when Litra Beam LX backlight is active', () => { + fakeLitraBeamLx.hid.readSync = jest + .fn() + .mockReturnValue([17, 255, 10, 59, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + expect(isBacklightOn(fakeLitraBeamLx)).toEqual(true); + + expect(fakeLitraBeamLx.hid.write).toBeCalledWith([ + 17, 255, 10, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + }); + + it('writes correct query bytes and returns false when Litra Beam LX backlight is inactive', () => { + fakeLitraBeamLx.hid.readSync = jest + .fn() + .mockReturnValue([17, 255, 10, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + + expect(isBacklightOn(fakeLitraBeamLx)).toEqual(false); + + expect(fakeLitraBeamLx.hid.write).toBeCalledWith([ + 17, 255, 10, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]); + }); + + it('throws error when checking backlight status on Litra Glow', () => { + expect(() => isBacklightOn(fakeLitraGlow)).toThrow( + 'Backlight control is only supported on Litra Beam LX devices', + ); + }); + + it('throws error when checking backlight status on Litra Beam', () => { + expect(() => isBacklightOn(fakeLitraBeam)).toThrow( + 'Backlight control is only supported on Litra Beam LX devices', + ); + }); +});