Skip to content

Instantly share code, notes, and snippets.

@subzey
Last active March 30, 2023 17:59
Show Gist options
  • Save subzey/12407e91b13283bcb58b805df08d9a25 to your computer and use it in GitHub Desktop.
Save subzey/12407e91b13283bcb58b805df08d9a25 to your computer and use it in GitHub Desktop.
BlinkStick WebHID
<!doctype html>
<html>
<head></head>
<body>
<script>
async function getBlinkStick() {
const vendorId = 0x20a0;
const productId = 0x41e5;
const devices = await navigator.hid.getDevices();
let device = devices.find(d => d.vendorId === vendorId && d.productId === productId);
if (!device) {
device = (await navigator.hid.requestDevice({
filters: [{ vendorId, productId }],
}))[0];
}
if (!device.opened) {
await device.open();
}
return device;
}
async function start() {
let blinkStick = await getBlinkStick();
const abortController = new AbortController();
abortController.signal.addEventListener('abort', async () => {
if (!blinkStick || !blinkStick.opened) {
return;
}
// Turn off the lights
await blinkStick.sendFeatureReport(6, new Uint8ClampedArray(8 * 3 + 1));
// Close the device
await blinkStick.close();
});
// Abort on unload or error
window.addEventListener('unload', () => { abortController.abort() });
window.addEventListener('unhandledrejection', () => { abortController.abort() });
// Use a WebWorker as a clock pulse generator
// This is a workaround for the background timer throttling
const workerSource = `setInterval(() => postMessage(null), 1000)`;
const url = URL.createObjectURL(new Blob([workerSource], { type: 'application/javascript;charset=utf-8' }));
const worker = new Worker(url, { type: 'module', name: 'Clock' });
abortController.signal.addEventListener('abort', () => { worker.terminate(); });
const data = new Uint8ClampedArray(8 * 3 + 1);
const paint = () => {
frame(data);
blinkStick.sendFeatureReport(6, data);
}
paint();
worker.addEventListener('message', paint);
}
let frameId = 0;
function frame(uia) {
const brightness = /* 32 */ 16;
for (let i = 0; i < 8; i++) {
const localOffset = frameId % 8;
let ledPhase = frameId - localOffset + i;
if (localOffset > i) {
ledPhase += 8;
}
const hue = ledPhase / 100;
uia[i * 3 + 1] = Math.round((1 + Math.cos((hue + 1/3) * Math.PI * 2)) * brightness);
uia[i * 3 + 2] = Math.round((1 + Math.cos((hue) * Math.PI * 2)) * brightness);
uia[i * 3 + 3] = Math.round((1 + Math.cos((hue + 2/3) * Math.PI * 2)) * brightness);
}
frameId++;
}
async function main() {
try {
await start();
} catch (e) {
const playButton = document.createElement('button');
playButton.textContent = 'Start';
playButton.addEventListener('click', e => {
e.preventDefault();
playButton.remove();
start();
});
document.body.appendChild(playButton);
}
}
main();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment