Last active
March 30, 2023 17:59
-
-
Save subzey/12407e91b13283bcb58b805df08d9a25 to your computer and use it in GitHub Desktop.
BlinkStick WebHID
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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