From 4e2e5df1811427d78f4515b3e313a39e4625bbb7 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 14 Nov 2024 13:30:26 +0000 Subject: [PATCH] feat: additional studio firmware versions (usb) --- packages/core/src/models/studio.ts | 9 ++++- .../src/services/properties/all-firmware.ts | 36 ++++++++++++++++++ packages/core/src/services/properties/gen2.ts | 12 +++--- .../core/src/services/properties/studio.ts | 14 ++++++- packages/node/examples/streamdeck-studio.js | 1 + packages/tcp/src/connectionManager.ts | 37 ++++--------------- 6 files changed, 70 insertions(+), 39 deletions(-) create mode 100644 packages/core/src/services/properties/all-firmware.ts diff --git a/packages/core/src/models/studio.ts b/packages/core/src/models/studio.ts index ac8f128..2b60974 100644 --- a/packages/core/src/models/studio.ts +++ b/packages/core/src/models/studio.ts @@ -5,6 +5,7 @@ import { DeviceModelId, MODEL_NAMES } from '../id.js' import { freezeDefinitions, generateButtonsGrid } from '../controlsGenerator.js' import type { StreamDeckControlDefinition } from '../controlDefinition.js' import type { PropertiesService } from '../services/properties/interface.js' +import { StudioPropertiesService } from '../services/properties/studio.js' const studioControls: StreamDeckControlDefinition[] = [ { @@ -52,6 +53,12 @@ export function StreamDeckStudioFactory( options: Required, propertiesService?: PropertiesService, ): StreamDeckBase { - const services = createBaseGen2Properties(device, options, studioProperties, propertiesService ?? null, true) + const services = createBaseGen2Properties( + device, + options, + studioProperties, + propertiesService ?? new StudioPropertiesService(device), + true, + ) return new StreamDeckBase(device, options, services) } diff --git a/packages/core/src/services/properties/all-firmware.ts b/packages/core/src/services/properties/all-firmware.ts new file mode 100644 index 0000000..08325b8 --- /dev/null +++ b/packages/core/src/services/properties/all-firmware.ts @@ -0,0 +1,36 @@ +import { uint8ArrayToDataView } from '../../util' + +export async function parseAllFirmwareVersionsHelper(reportData: { + // Future: LD, AP1? + ap2: Uint8Array | null + encoderAp2: Uint8Array | null + encoderLd: Uint8Array | null +}): Promise> { + const decoder = new TextDecoder('ascii') + + const versions: Record = {} + + if (reportData.ap2) { + const ap2DataDataView = uint8ArrayToDataView(reportData.ap2) + versions.AP2 = decoder.decode(reportData.ap2.subarray(6, 6 + 8)) + versions.AP2_CHECKSUM = ap2DataDataView.getUint32(2, false).toString(16) + } + + if (reportData.encoderLd && (reportData.encoderLd[0] === 0x18 || reportData.encoderLd[1] === 0x18)) { + const encoderLdDataView = uint8ArrayToDataView(reportData.encoderLd) + versions.ENCODER_LD_1 = decoder.decode(reportData.encoderLd.subarray(2, 2 + 8)) + versions.ENCODER_LD_1_CHECKSUM = encoderLdDataView.getUint32(10, false).toString(16) + versions.ENCODER_LD_2 = decoder.decode(reportData.encoderLd.subarray(14, 14 + 8)) + versions.ENCODER_LD_2_CHECKSUM = encoderLdDataView.getUint32(22, false).toString(16) + } + + if (reportData.encoderAp2 && (reportData.encoderAp2[0] === 0x18 || reportData.encoderAp2[1] === 0x18)) { + const encoderAp2DataView = uint8ArrayToDataView(reportData.encoderAp2) + versions.ENCODER_AP2_1 = decoder.decode(reportData.encoderAp2.subarray(2, 2 + 8)) + versions.ENCODER_AP2_1_CHECKSUM = encoderAp2DataView.getUint32(10, false).toString(16) + versions.ENCODER_AP2_2 = decoder.decode(reportData.encoderAp2.subarray(14, 14 + 8)) + versions.ENCODER_AP2_2_CHECKSUM = encoderAp2DataView.getUint32(22, false).toString(16) + } + + return versions +} diff --git a/packages/core/src/services/properties/gen2.ts b/packages/core/src/services/properties/gen2.ts index 2305d0c..2df8423 100644 --- a/packages/core/src/services/properties/gen2.ts +++ b/packages/core/src/services/properties/gen2.ts @@ -2,10 +2,10 @@ import type { HIDDevice } from '../../hid-device.js' import type { PropertiesService } from './interface.js' export class Gen2PropertiesService implements PropertiesService { - readonly #device: HIDDevice + protected readonly device: HIDDevice constructor(device: HIDDevice) { - this.#device = device + this.device = device } public async setBrightness(percentage: number): Promise { @@ -21,7 +21,7 @@ export class Gen2PropertiesService implements PropertiesService { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); - await this.#device.sendFeatureReport(brightnessCommandBuffer) + await this.device.sendFeatureReport(brightnessCommandBuffer) } public async resetToLogo(): Promise { @@ -33,11 +33,11 @@ export class Gen2PropertiesService implements PropertiesService { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]); - await this.#device.sendFeatureReport(resetCommandBuffer) + await this.device.sendFeatureReport(resetCommandBuffer) } public async getFirmwareVersion(): Promise { - const val = await this.#device.getFeatureReport(5, 32) + const val = await this.device.getFeatureReport(5, 32) const end = val[1] + 2 return new TextDecoder('ascii').decode(val.subarray(6, end)) } @@ -50,7 +50,7 @@ export class Gen2PropertiesService implements PropertiesService { } public async getSerialNumber(): Promise { - const val = await this.#device.getFeatureReport(6, 32) + const val = await this.device.getFeatureReport(6, 32) const end = val[1] + 2 return new TextDecoder('ascii').decode(val.subarray(2, end)) } diff --git a/packages/core/src/services/properties/studio.ts b/packages/core/src/services/properties/studio.ts index 2762664..7218f56 100644 --- a/packages/core/src/services/properties/studio.ts +++ b/packages/core/src/services/properties/studio.ts @@ -1,8 +1,18 @@ +import { parseAllFirmwareVersionsHelper } from './all-firmware' import { Gen2PropertiesService } from './gen2' export class StudioPropertiesService extends Gen2PropertiesService { public async getAllFirmwareVersions(): Promise> { - // Not supported for most models - return {} + const [ap2Data, encoderAp2Data, encoderLdData] = await Promise.all([ + this.device.getFeatureReport(0x05, 32), + this.device.getFeatureReport(0x11, 32), + this.device.getFeatureReport(0x13, 32), + ]) + + return parseAllFirmwareVersionsHelper({ + ap2: ap2Data, + encoderAp2: encoderAp2Data, + encoderLd: encoderLdData, + }) } } diff --git a/packages/node/examples/streamdeck-studio.js b/packages/node/examples/streamdeck-studio.js index 245740a..fa66bd4 100644 --- a/packages/node/examples/streamdeck-studio.js +++ b/packages/node/examples/streamdeck-studio.js @@ -28,6 +28,7 @@ function generateEncoderColor(value, max) { console.log('firmware', await streamDeck.getFirmwareVersion()) console.log('serial number', await streamDeck.getSerialNumber()) + console.log('all-firmware', await streamDeck.getAllFirmwareVersions()) /** @type {import('@elgato-stream-deck/core').StreamDeckEncoderControlDefinition[]} */ const encoders = streamDeck.CONTROLS.filter((control) => control.type === 'encoder') diff --git a/packages/tcp/src/connectionManager.ts b/packages/tcp/src/connectionManager.ts index d8f88e4..36f0ad5 100644 --- a/packages/tcp/src/connectionManager.ts +++ b/packages/tcp/src/connectionManager.ts @@ -4,9 +4,10 @@ import { DEFAULT_TCP_PORT } from './constants.js' import { SocketWrapper } from './socketWrapper.js' import { type JPEGEncodeOptions, encodeJPEG } from '@elgato-stream-deck/node-lib' import type { HIDDevice, OpenStreamDeckOptions, ChildHIDDeviceInfo, PropertiesService } from '@elgato-stream-deck/core' -import { DEVICE_MODELS, uint8ArrayToDataView } from '@elgato-stream-deck/core' +import { DEVICE_MODELS } from '@elgato-stream-deck/core' import { StreamDeckTcpWrapper } from './tcpWrapper.js' import { TcpHidDevice } from './hid-device.js' +import { parseAllFirmwareVersionsHelper } from '@elgato-stream-deck/core/dist/services/properties/all-firmware.js' export interface StreamDeckTcpConnectionManagerEvents { connected: [streamdeck: StreamDeckTcp] @@ -284,41 +285,17 @@ class TcpPropertiesService implements PropertiesService { } public async getAllFirmwareVersions(): Promise> { - // Future: LD, AP1? const [ap2Data, encoderAp2Data, encoderLdData] = await Promise.all([ this.#device.getFeatureReport(0x83, -1), this.#device.getFeatureReport(0x86, -1), this.#device.getFeatureReport(0x8a, -1), ]) - const decoder = new TextDecoder('ascii') - - const ap2DataDataView = uint8ArrayToDataView(ap2Data) - - const versions: Record = { - AP2: decoder.decode(ap2Data.subarray(8, 16)), - AP2_CHECKSUM: ap2DataDataView.getUint32(4, false).toString(16), - } - - const encoderLdDataLen = encoderLdData[2] - if (encoderLdDataLen === 0x18) { - const encoderLdDataView = uint8ArrayToDataView(encoderLdData) - versions.ENCODER_LD_1 = decoder.decode(encoderLdData.subarray(4, 4 + 8)) - versions.ENCODER_LD_1_CHECKSUM = encoderLdDataView.getUint32(12, false).toString(16) - versions.ENCODER_LD_2 = decoder.decode(encoderLdData.subarray(16, 16 + 8)) - versions.ENCODER_LD_2_CHECKSUM = encoderLdDataView.getUint32(24, false).toString(16) - } - - const encoderAp2DataLen = encoderAp2Data[2] - if (encoderAp2DataLen === 0x18) { - const encoderAp2DataView = uint8ArrayToDataView(encoderAp2Data) - versions.ENCODER_AP2_1 = decoder.decode(encoderAp2Data.subarray(4, 4 + 8)) - versions.ENCODER_AP2_1_CHECKSUM = encoderAp2DataView.getUint32(12, false).toString(16) - versions.ENCODER_AP2_2 = decoder.decode(encoderAp2Data.subarray(16, 16 + 8)) - versions.ENCODER_AP2_2_CHECKSUM = encoderAp2DataView.getUint32(24, false).toString(16) - } - - return versions + return parseAllFirmwareVersionsHelper({ + ap2: ap2Data.slice(2), + encoderAp2: encoderAp2Data.slice(2), + encoderLd: encoderLdData.slice(2), + }) } public async getSerialNumber(): Promise {