A Node.js module to interact with the X-keys panels.
Licence: MIT
The project is based on the documentation available here: http://xkeys.com/PISupport/DeveloperHIDDataReports.php
If you are using a browser that supports WebHID, you can try out the library right away, in the browser: Demo.
$ npm install --save xkeys or $ yarn add xkeysUses WebHID, see list of supported browsers: caniuse.com/webhid.
$ npm install --save xkeys-webhid or $ yarn add xkeys-webhidOn linux, the udev subsystem blocks access for non-root users to the X-keys without some special configuration. Save the following to /etc/udev/rules.d/50-xkeys.rules and reload the rules with sudo udevadm control --reload-rules
SUBSYSTEM=="input", GROUP="input", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="05f3", MODE:="666", GROUP="plugdev" KERNEL=="hidraw*", ATTRS{idVendor}=="05f3", MODE="0666", GROUP="plugdev"Note: If you need more than 4 panels connected simultaneously, you might also have to set your env-var UV_THREADPOOL_SIZE:
var { env } = require('process') env.UV_THREADPOOL_SIZE = 8 // Allow up to 8 panelsPlease note that version 2.0.0 is a BREAKING CHANGE, as most of the API have changed. If you're upgrading from <2.0.0, please read the Migrations section below.
See examples folder for more examples.
This is the recommended way to use this library, to automatically be connected or reconnected to the panel.
Note: The watcher depends on the node-usb library, which might be unsupported on some platforms.
const { XKeysWatcher } = require('xkeys') // or 'xkeys-webhid' in a browser /* This example connects to any connected x-keys panels and logs whenever a button is pressed or analog thing is moved */ // Set up the watcher for xkeys: const watcher = new XKeysWatcher({ // automaticUnitIdMode: false // usePolling: false // pollingInterval= 1000 }) watcher.on('error', (e) => { console.log('Error in XKeysWatcher', e) }) watcher.on('connected', (xkeysPanel) => { console.log(`X-keys panel of type ${xkeysPanel.info.name} connected`) xkeysPanel.on('disconnected', () => { console.log(`X-keys panel of type ${xkeysPanel.info.name} was disconnected`) // Clean up stuff xkeysPanel.removeAllListeners() }) xkeysPanel.on('error', (...errs) => { console.log('X-keys error:', ...errs) }) // Listen to pressed buttons: xkeysPanel.on('down', (keyIndex, metadata) => { console.log('Button pressed ', keyIndex, metadata) // Light up a button when pressed: xkeysPanel.setBacklight(keyIndex, 'red') }) // Listen to released buttons: xkeysPanel.on('up', (keyIndex, metadata) => { console.log('Button released', keyIndex, metadata) // Turn off button light when released: xkeysPanel.setBacklight(keyIndex, false) }) // Listen to jog wheel changes: xkeysPanel.on('jog', (index, deltaPos, metadata) => { console.log(`Jog ${index} position has changed`, deltaPos, metadata) }) // Listen to shuttle changes: xkeysPanel.on('shuttle', (index, shuttlePos, metadata) => { console.log(`Shuttle ${index} position has changed`, shuttlePos, metadata) }) // Listen to joystick changes: xkeysPanel.on('joystick', (index, position, metadata) => { console.log(`Joystick ${index} position has changed`, position, metadata) // {x, y, z} }) // Listen to t-bar changes: xkeysPanel.on('tbar', (index, position, metadata) => { console.log(`T-bar ${index} position has changed`, position, metadata) }) }) // To stop watching, call // watcher.stop().catch(console.error)const { setupXkeysPanel } = require('xkeys') /* This example shows how to use XKeys.setupXkeysPanel() directly, instead of going via XKeysWatcher() */ // Connect to an xkeys-panel: setupXkeysPanel() .then((xkeysPanel) => { xkeysPanel.on('disconnected', () => { console.log(`X-keys panel of type ${xkeysPanel.info.name} was disconnected`) // Clean up stuff xkeysPanel.removeAllListeners() }) xkeysPanel.on('error', (...errs) => { console.log('X-keys error:', ...errs) }) xkeysPanel.on('down', (keyIndex, metadata) => { console.log('Button pressed', keyIndex, metadata) }) // ... }) .catch(console.log) // Handle erroror
const { listAllConnectedPanels, setupXkeysPanel } = require('xkeys') // List and connect to all xkeys-panels: listAllConnectedPanels().forEach((connectedPanel) => { setupXkeysPanel(connectedPanel) .then((xkeysPanel) => { console.log(`Connected to ${xkeysPanel.info.name}`) // ... }) .catch(console.log) // Handle errorSee the example implementation at packages/webhid-demo.
const { XKeysWatcher, requestXkeysPanels } = require('xkeys-webhid') const watcher = new XKeysWatcher({}) watcher.on('error', (e) => { console.log('Error in XKeysWatcher', e) }) watcher.on('connected', (xkeysPanel) => { // This will be triggered whenever a panel is connected, or permissions is granted. // >> See the example above for setting up the xkeysPanel << }) myHTMLButton.addEventListener('click', async () => { // Open the Request device permissions dialog: requestXkeysPanels().catch((error) => console.error(error)) // Notes: // When the user has granted permissions, the browser will remember this between sessions. // However, if the panel is disconnected and reconnected, the user will have to grant permissions again. })If you are using a Chromium v89+ based browser, you can try out the webhid demo.
The XKeysWatcher has a few different options that can be set upon initialization:
const { XKeysWatcher } = require('xkeys') const watcher = new XKeysWatcher({ // automaticUnitIdMode: false // usePolling: false // pollingInterval= 1000 }) watcher.on('error', (e) => { console.log('Error in XKeysWatcher', e) }) watcher.on('connected', (xkeysPanel) => { // xkeysPanel connected... }) // Note: In a browser, user must first grant permissions to access the X-keys, using requestXkeysPanels().When this is set to true, the XKeysWatcher will enable the "reconnected" event for the xkeysPanels.
By default, there is no unique identifier stored on the X-keys panel that can be used to differ between "reconnecting a previously known panel" or "connecting a new panel". The automaticUnitIdMode fixes this by writing a pseudo-unique id to the unitId of the panel, if none has been set previously.
When this is set, the XKeysWatcher will not use the usb library for detecting connected panels, but instead resort to polling at an interval (pollingInterval). This is compatible with more systems and OS:es, but might result in slower detection of new panels.
// Example: xkeysPanel.on('down', (keyIndex, metadata) => { console.log('Button pressed', keyIndex, metadata) })| Event | Description |
|---|---|
"error" | Triggered on error. Emitted with (error). |
"down", "up" | Triggered when a button is pressed/released. Emitted with (keyIndex, metadata). |
"jog" | Triggered when the jog wheel is moved. Emitted with (index, jogValue, metadata) |
"shuttle" | Triggered when the shuttle is moved. Emitted with (index, shuttleValue, metadata) |
"joystick" | Triggered when the joystick is moved. Emitted with (index, {x, y, z, deltaZ}) |
"tbar" | Triggered when the T-bar is moved. Emitted with (index, tbarPosition, metadata) |
"disconnected" | Triggered when panel is disconnected. |
"reconnected" | Triggered when panel is reconnection. Only emitted when automaticUnitIdMode is enabled. |
Setting the backlight of a button
xkeysPanel.setBacklight(keyIndex, color) // Examples: // Set blue light xkeysPanel.setBacklight(keyIndex, '0000ff') // Set any available default light xkeysPanel.setBacklight(keyIndex, true) // Turn off light xkeysPanel.setBacklight(keyIndex, false) // Set flashing light xkeysPanel.setBacklight(keyIndex, 'red', true) // Set color (for RGB-supported devices) xkeysPanel.setBacklight(keyIndex, 'ff3300')Set the indicator LEDs (the red/green status LED's)
xkeysPanel.setIndicatorLED(ledIndex, on, flashing) // Examples: // Light up the green LED xkeysPanel.setIndicatorLED(1, true) // Flash the red LED xkeysPanel.setIndicatorLED(2, true, true)Set backlight intensity
xkeysPanel.setBacklightIntensity(intensity) // Example: // Set max intensity xkeysPanel.setBacklightIntensity(255)Set all backlights on or off
xkeysPanel.setAllBacklights(color) // Example: // Light up all buttons xkeysPanel.setAllBacklights(true) // Light up all buttons in a nice color xkeysPanel.setAllBacklights('ff33ff') // Turn of all buttons xkeysPanel.setAllBacklights(false)Set flashing frequency
// The frequency can be set to 1-255, where 1 is fastest and 255 is the slowest. // 255 is approximately 4 seconds between flashes. xkeysPanel.setFrequency(frequency) // Example: // Set the frequency to a pretty fast flash xkeysPanel.setFrequency(8)** Set unit ID **
// Sets the UID (unit Id) value in the X-keys hardware // Note: This writes to the EEPROM, don't call this function too often, or you'll kill the EEPROM! (An EEPROM only support a few thousands of write operations.) xkeysPanel.setUnitId(unitId)** Save backlights **
// Save the backlights (so they are restored to this after a power cycle). // Note: This writes to the EEPROM, don't call this function too often, or you'll kill the EEPROM! (An EEPROM only support a few thousands of write operations.) xkeysPanel.saveBackLights()See the XKeys-class for more functionality.
Thanks to official support from P.I Enginneering, the X-keys manufacturer, there is support for all official (and some experimental) devices.
See the full list in products.ts.
Version 2.0.0 is a breaking changes, which requires several changes in how to use the library.
The most notable changes are:
Before, <2.0.0 | Changes in >=2.0.0 |
|---|---|
let myXkeys = new XKeys() | let myXkeys = await XKeys.setupXkeysPanel() |
myXkeys.on('down', (keyIndex) => {} ) | The numbering of keyIndexes has changed:_ The PS-button is on index 0. _ Other buttons start on index 1. * Numbering of buttons have changed for some models. |
myXkeys.on('downKey', (keyIndex) => {} ) | Use .on('down') instead |
myXkeys.on('upKey', (keyIndex) => {} ) | Use .on('up') instead |
myXkeys.on('downAlt', (keyIndex) => {} ) | Use .on('down') instead (PS-button is on index 0) |
myXkeys.on('upAlt', (keyIndex) => {} ) | Use .on('up') instead (PS-button is on index 0) |
myXkeys.on('jog', (position) => {} ) | myXkeys.on('jog', (index, position) => {} ) |
myXkeys.on('shuttle', (position) => {} ) | myXkeys.on('shuttle', (index, position) => {} ) |
myXkeys.on('tbar', (position, rawPosition) => {} ) | myXkeys.on('tbar', (index, position) => {} ) |
myXkeys.on('joystick', (position) => {} ) | myXkeys.on('joystick', (index, position) => {} ) |
myXkeys.setBacklight(...) | Arguments have changed, see docs |
myXkeys.setAllBacklights(...) | Arguments have changed, see docs |
myXkeys.setLED(index, ...) | myXkeys.setIndicatorLED(index, ...) (index 1 = the red, 2 = the green one) |
Version 2.1.1 has a minor change for when stopping the XKeysWatcher instance:
const watcher = new XKeysWatcher() await watcher.stop() // Now returns a promiseThis is a mono-repo, using Lerna and Yarn.
This repo is using Yarn. If you don't want to use it, replace yarn xyz with npm run xyz below.
To install Yarn, just run npm install -g yarn.
- Clone the repo and
cdinto it. - Install all dependencies:
yarn. - Do an initial build:
yarn build
If you'd like to run and test your local changes, yarn link is a useful tool to symlink your local xkeys dependency into your test repo.
# To set up the xkeys-repo for linking: cd your/xkeys/repo yarn lerna exec yarn link # This runs "yarn link" in all of the mono-repo packages yarn build # Every time after you've made any changes to the xkeys-repo you need to rebuild cd your/xkeys/repo yarn build # Set up your local test repo to used the linked xkeys libraries: cd your/test/repo yarn add xkeys yarn link xkeys yarn link @xkeys-lib/core # To unlink the xkeys-lib from your local test repo: cd your/test/repo yarn unlink xkeys yarn unlink @xkeys-lib/core yarn --force # So that it reinstalls the ordinary dependenciesIf you have any questions or want to report a bug, please open an issue at Github.
If you want to contribute a bug fix or improvement, we'd happily accept Pull Requests. (If you're planning something big, please open an issue to announce it first, and spark discussions.
Please follow the same coding style as the rest of the repository as you type. :)
Before committing your code to git, be sure to run these commands:
yarn # To ensure the right dependencies are installed yarn build # To ensure that there are no syntax or build errors yarn lint # To ensure that the formatting follows the right rules yarn test # To ensure that your code passes the unit tests.If you're adding a new functionality, adding unit tests for it is much appreciated.
- Push your changes to any branch
- Trigger a run of CI: publish-nightly
- Update the branch (preferably the master branch)
yarn release:bump-prereleaseand push the changes (including the tag)- Trigger a run of CI: publish-prerelease
- Update the the master branch
yarn release:bump-releaseand push the changes (including the tag)- Trigger a run of Publish Release-version to NPM to publish to NPM.
- Trigger a run of Publish the WebHID demo to pages to update the docs.
By contributing, you agree that your contributions will be licensed under the MIT License.