diff --git a/packages/uhk-usb/src/uhk-operations.ts b/packages/uhk-usb/src/uhk-operations.ts index 76315f2f894..a06d544db0e 100644 --- a/packages/uhk-usb/src/uhk-operations.ts +++ b/packages/uhk-usb/src/uhk-operations.ts @@ -812,29 +812,53 @@ export class UhkOperations { return convertSlaveI2cErrorBuffer(responseBuffer, slaveId); } - public async getVariable(variableId: UsbVariables, iteration: number = 0): Promise { - this.logService.usbOps(`[DeviceOperation] USB[T]: get variable: ${UsbVariables[variableId]}. Iteration: ${iteration}`); + public async getVariable(variableId: UsbVariables): Promise { + if (variableId === UsbVariables.statusBuffer || variableId === UsbVariables.ShellBuffer) { + return this.getVariableWithIteration(variableId); + } + + this.logService.usbOps(`[DeviceOperation] USB[T]: get variable: ${UsbVariables[variableId]}`); const buffer = Buffer.from([UsbCommand.GetVariable, variableId]); const responseBuffer = await this.device.write(buffer); - if (variableId === UsbVariables.statusBuffer || variableId === UsbVariables.ShellBuffer) { - let message = readUhkResponseAs0EndString(UhkBuffer.fromArray(convertBufferToIntArray(responseBuffer))); - this.logService.misc(`[DeviceOperation] status buffer segment: ${message}`); - if (message.length === responseBuffer.length - 1 && iteration < 20) { - message += await this.getVariable(variableId, iteration + 1); + return responseBuffer[1]; + } + + private async getVariableWithIteration(variableId: UsbVariables): Promise { + // Firmware status buffer is STATUS_BUFFER_MAX_LENGTH (3000); shell buffer is 2048. + // Each USB transfer returns at most (report length - 1) payload bytes (~62). + // The buffers are NUL terminated strings. + // The maxIterations is a safeguard against infinite loops. + const maxIterations = 100; + let message = ''; + + for (let iteration = 0; iteration < maxIterations; iteration++) { + this.logService.usbOps(`[DeviceOperation] USB[T]: get variable: ${UsbVariables[variableId]}. Iteration: ${iteration}`); + const buffer = Buffer.from([UsbCommand.GetVariable, variableId]); + const responseBuffer = await this.device.write(buffer); + const segment = readUhkResponseAs0EndString(UhkBuffer.fromArray(convertBufferToIntArray(responseBuffer))); + this.logService.misc(`[DeviceOperation] status buffer segment: ${segment}`); + message += segment; + + // The content of the variable is a NUL terminated string. + // When the segment length is not equal to the buffer length - 1, the buffer is complete. + if (segment.length !== responseBuffer.length - 1) { + break; } - // The shell buffer carries a raw VT100 stream (colors, cursor control) that must be - // forwarded verbatim to the terminal emulator. Only the macro status buffer gets the - // dedup/reorder normalization. - if (iteration === 0 && variableId === UsbVariables.statusBuffer) { - message = normalizeStatusBuffer(message); + if (iteration === maxIterations) { + this.logService.error(`[DeviceOperation] ${UsbVariables[variableId]} truncated after ${maxIterations} USB transfers`); } + } - return message; + // The shell buffer carries a raw VT100 stream (colors, cursor control) that must be + // forwarded verbatim to the terminal emulator. Only the macro status buffer gets the + // dedup/reorder normalization. + if (variableId === UsbVariables.statusBuffer) { + message = normalizeStatusBuffer(message); } - return responseBuffer[1]; + return message; } public async pairToDongle(dongle: UhkHidDevice) : Promise { diff --git a/packages/uhk-web/src/app/util/status-buffer-parser.ts b/packages/uhk-web/src/app/util/status-buffer-parser.ts index 58f4edd10f1..9b0dc4d3cca 100644 --- a/packages/uhk-web/src/app/util/status-buffer-parser.ts +++ b/packages/uhk-web/src/app/util/status-buffer-parser.ts @@ -68,6 +68,11 @@ function transformToErrorBlock(macros: Macro[], block: string): string { const url = `#/macro/${macro.id}?actionIndex=${macroActionIndex}&lineNr=${lineNr}&columnNr=${columnNr}&inlineEdit=true`; const newLine2 = `${escapeHtml(line1Result[1])}${escapeHtml(line1Result[2])}`; + // Keep binding-site lines and nested location-stack lines (firmware may emit more than 3). + const extraLines = lines + .slice(3) + .map(line => escapeHtml(line)) + .join('\n'); - return `${escapeHtml(lines[0])}\n${newLine2}\n${escapeHtml(lines[2])}\n`; + return `${escapeHtml(lines[0])}\n${newLine2}\n${escapeHtml(lines[2])}\n${extraLines}`; }