Created
March 10, 2025 07:02
-
-
Save maietta/0c7729c7ee640bba2bdb616ba2462b9f to your computer and use it in GitHub Desktop.
Stream Deck Plus LCD testing in TYpeScript & Bun
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
import { openStreamDeck, listStreamDecks, StreamDeck } from '@elgato-stream-deck/node'; | |
import sharp from 'sharp'; | |
import HID from 'node-hid'; | |
import usb, { LibUSBException } from 'usb'; | |
async function main() { | |
try { | |
console.log('Looking for Stream Deck Plus devices...'); | |
// Find the Stream Deck Plus device | |
const device = usb.findByIds(0x0fd9, 0x0084); | |
if (!device) { | |
throw new Error('No Stream Deck Plus devices found'); | |
} | |
console.log('Found device:', device); | |
try { | |
device.open(); | |
console.log('Opened device'); | |
// Claim interface 0 | |
const iface = device.interface(0); | |
try { | |
if (iface.isKernelDriverActive()) { | |
console.log('Detaching kernel driver...'); | |
iface.detachKernelDriver(); | |
} | |
} catch (err) { | |
console.log('No kernel driver active'); | |
} | |
iface.claim(); | |
console.log('Claimed interface'); | |
// Get endpoints | |
const outEndpoint = iface.endpoints.find(ep => ep.direction === 'out'); | |
if (!outEndpoint) { | |
throw new Error('Could not find OUT endpoint'); | |
} | |
console.log('Found OUT endpoint:', outEndpoint); | |
// Set maximum brightness using control transfer | |
const brightnessReport = Buffer.alloc(32); | |
brightnessReport[0] = 0x03; // Feature report ID | |
brightnessReport[1] = 0x08; // Set brightness command | |
brightnessReport[2] = 100; // Brightness value (0-100) | |
try { | |
await new Promise((resolve, reject) => { | |
device.controlTransfer( | |
0x21, // bmRequestType (Host-to-device, Class, Interface) | |
0x09, // bRequest (SET_REPORT) | |
0x0300, // wValue (Feature report 0x03) | |
0x0000, // wIndex | |
brightnessReport, | |
(error?: LibUSBException, data?: Buffer | number) => { | |
if (error) reject(error); | |
else resolve(true); | |
} | |
); | |
}); | |
console.log('Set brightness to maximum'); | |
} catch (err) { | |
console.error('Error setting brightness:', err); | |
} | |
// Create a red raw image for the LCD display | |
const width = 200; | |
const height = 100; | |
const redImage = await sharp({ | |
create: { | |
width, | |
height, | |
channels: 3, | |
background: { r: 255, g: 0, b: 0 } | |
} | |
}) | |
.raw() | |
.toBuffer(); | |
// Try each LCD display (0-3 for Stream Deck Plus) | |
for (let i = 0; i < 4; i++) { | |
try { | |
console.log(`\nSending red image to LCD ${i}...`); | |
// Reset key stream | |
const resetData = Buffer.alloc(32); | |
resetData[0] = 0x02; // Report ID | |
resetData[1] = 0x0B; // Reset command | |
try { | |
await new Promise((resolve, reject) => { | |
(outEndpoint as any).transferOut(resetData, (error?: LibUSBException) => { | |
if (error) reject(error); | |
else resolve(true); | |
}); | |
}); | |
console.log('Reset command sent successfully'); | |
} catch (err) { | |
console.error('Error sending reset command:', err); | |
continue; | |
} | |
await new Promise(resolve => setTimeout(resolve, 50)); | |
// LCD header | |
const header = Buffer.alloc(32); | |
header[0] = 0x02; // Report ID | |
header[1] = 0x07; // Set LCD image | |
header[2] = i & 0xff; // LCD index | |
header[3] = 0x00; // Padding | |
header[4] = 0x00; // Padding | |
header[5] = 0x00; // Padding | |
header[6] = 0x00; // Padding | |
header[7] = 0x00; // Padding | |
header[8] = redImage.length & 0xff; // Image size (little endian) | |
header[9] = (redImage.length >> 8) & 0xff; | |
header[10] = (redImage.length >> 16) & 0xff; | |
header[11] = (redImage.length >> 24) & 0xff; | |
header[12] = 0x00; // Raw image format | |
header[13] = 0x00; | |
header[14] = 0x00; | |
header[15] = 0x00; | |
// Send header | |
try { | |
await new Promise((resolve, reject) => { | |
(outEndpoint as any).transferOut(header, (error?: LibUSBException) => { | |
if (error) reject(error); | |
else resolve(true); | |
}); | |
}); | |
console.log('Header sent successfully'); | |
} catch (err) { | |
console.error('Error sending header:', err); | |
continue; | |
} | |
await new Promise(resolve => setTimeout(resolve, 50)); | |
// Send image data in chunks | |
const CHUNK_SIZE = 1024; | |
const numChunks = Math.ceil(redImage.length / CHUNK_SIZE); | |
let chunksSent = 0; | |
for (let chunk = 0; chunk < numChunks; chunk++) { | |
const start = chunk * CHUNK_SIZE; | |
const end = Math.min(start + CHUNK_SIZE, redImage.length); | |
const chunkData = redImage.slice(start, end); | |
// Create chunk packet | |
const packet = Buffer.alloc(CHUNK_SIZE); | |
chunkData.copy(packet); | |
// Send chunk | |
try { | |
await new Promise((resolve, reject) => { | |
(outEndpoint as any).transferOut(packet, (error?: LibUSBException) => { | |
if (error) reject(error); | |
else resolve(true); | |
}); | |
}); | |
chunksSent++; | |
if (chunksSent % 10 === 0) { | |
console.log(`Sent ${chunksSent}/${numChunks} chunks...`); | |
} | |
} catch (err) { | |
console.error(`Error sending chunk ${chunk}:`, err); | |
break; | |
} | |
await new Promise(resolve => setTimeout(resolve, 25)); | |
} | |
console.log(`Successfully sent ${chunksSent}/${numChunks} chunks to LCD ${i}`); | |
} catch (err) { | |
console.error(`Error setting image on LCD ${i}:`, err); | |
} | |
} | |
console.log('\nTest complete. Press Ctrl+C to exit.'); | |
// Keep the process running | |
process.on('SIGINT', () => { | |
try { | |
iface.release(() => { | |
device.close(); | |
console.log('Device closed'); | |
process.exit(); | |
}); | |
} catch (err) { | |
console.error('Error closing device:', err); | |
process.exit(1); | |
} | |
}); | |
} catch (err) { | |
console.error('Error:', err); | |
if (device) { | |
try { | |
device.close(); | |
} catch (closeErr) { | |
console.error('Error closing device:', closeErr); | |
} | |
} | |
} | |
} catch (err) { | |
console.error('Error:', err); | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment