From 2ddf8f118f766e6e27f132ae45149ecbd980d9c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Kiss?= Date: Sun, 10 Nov 2024 15:22:42 +0100 Subject: [PATCH] feat: UHK80 recovery (#136) --- .../uhk-agent/src/services/device.service.ts | 75 ++++++++++-------- .../src/models/device-connection-state.ts | 1 + packages/uhk-common/src/models/dongle.ts | 2 + .../uhk-common/src/models/ipc-response.ts | 3 - packages/uhk-usb/src/uhk-hid-device.ts | 37 +++++++-- ...ent-uhk-device-product-by-bootloader-id.ts | 18 ----- packages/uhk-usb/src/utils/index.ts | 2 +- .../src/utils/is-serial-port-in-vid-pids.ts | 9 +++ .../recovery-mode.component.html | 4 +- .../recovery-mode/recovery-mode.component.ts | 29 +++++-- .../app/components/xterm/xterm.component.ts | 4 + packages/uhk-web/src/app/models/index.ts | 1 + .../src/app/models/recover-page-state.ts | 7 ++ .../models/update-firmware-success-payload.ts | 3 - .../app/services/device-renderer.service.ts | 8 +- .../uhk-web/src/app/store/actions/device.ts | 5 +- .../uhk-web/src/app/store/effects/device.ts | 8 +- packages/uhk-web/src/app/store/index.ts | 1 + .../uhk-web/src/app/store/reducers/device.ts | 50 +++++++++++- .../reducers/firmware-upgrade.reducer.ts | 2 - scripts/download-firmware.js | 10 ++- scripts/uhk-firmware-80-11.2.0-28b254f.tar.gz | Bin 0 -> 4667759 bytes 22 files changed, 189 insertions(+), 90 deletions(-) delete mode 100644 packages/uhk-usb/src/utils/get-current-uhk-device-product-by-bootloader-id.ts create mode 100644 packages/uhk-usb/src/utils/is-serial-port-in-vid-pids.ts create mode 100644 packages/uhk-web/src/app/models/recover-page-state.ts create mode 100644 scripts/uhk-firmware-80-11.2.0-28b254f.tar.gz diff --git a/packages/uhk-agent/src/services/device.service.ts b/packages/uhk-agent/src/services/device.service.ts index 08c1fbc37..4b5e5fc5d 100644 --- a/packages/uhk-agent/src/services/device.service.ts +++ b/packages/uhk-agent/src/services/device.service.ts @@ -3,6 +3,7 @@ import { emptyDir } from 'fs-extra'; import { cloneDeep, isEqual } from 'lodash'; import os from 'os'; import { + ALL_UHK_DEVICES, BackupUserConfigurationInfo, ChangeKeyboardLayoutIpcResponse, CommandLineArgs, @@ -39,6 +40,7 @@ import { shouldUpgradeFirmware, simulateInvalidUserConfigError, UHK_80_DEVICE_LEFT, + UHK_DEVICE_IDS, UHK_DONGLE, UHK_MODULES, UpdateFirmwareData, @@ -52,7 +54,6 @@ import { DevicePropertyIds, EnumerationModes, getCurrentUhkDeviceProduct, - getCurrentUhkDeviceProductByBootloaderId, getCurrentUhkDongleHID, getCurrenUhk80LeftHID, getDeviceFirmwarePath, @@ -364,7 +365,10 @@ export class DeviceService { } public async updateFirmware(event: Electron.IpcMainEvent, args?: Array): Promise { - const response = new FirmwareUpgradeIpcResponse(); + const response: FirmwareUpgradeIpcResponse = { + success: false, + }; + response.userConfigSaved = false; response.firmwareDowngraded = false; const data: UpdateFirmwareData = JSON.parse(args[0]); @@ -517,7 +521,6 @@ export class DeviceService { } } - response.modules = await this.getHardwareModules(false); await copySmartMacroDocToWebserver(firmwarePathData, this.logService); await makeFolderWriteableToUserOnLinux(getSmartMacroDocRootPath()); response.success = true; @@ -525,7 +528,6 @@ export class DeviceService { const err = { message: error.message, stack: error.stack }; this.logService.error('[DeviceService] updateFirmware error', err); - response.modules = await this.getHardwareModules(true); response.error = err; } @@ -535,22 +537,26 @@ export class DeviceService { await snooze(500); + this.savedState = undefined; this.startPollUhkDevice(); event.sender.send(IpcEvents.device.updateFirmwareReply, response); } public async recoveryDevice(event: Electron.IpcMainEvent, args: Array): Promise { - const response = new FirmwareUpgradeIpcResponse(); + const response: FirmwareUpgradeIpcResponse = { + success: false, + }; try { await this.stopPollUhkDevice(); - - const userConfig = args[0]; + const arg = args[0]; + const userConfig = arg.userConfig; + const deviceId = arg.deviceId; const firmwarePathData: TmpFirmware = getDefaultFirmwarePath(this.rootDir); const packageJson = await getFirmwarePackageJson(firmwarePathData); - const uhkDeviceProduct = await getCurrentUhkDeviceProductByBootloaderId(); + const uhkDeviceProduct = ALL_UHK_DEVICES.find(uhkProduct => uhkProduct.id === deviceId); checkFirmwareAndDeviceCompatibility(packageJson, uhkDeviceProduct); this.logService.misc( @@ -563,39 +569,40 @@ export class DeviceService { this.logService.misc('[DeviceService] Waiting for keyboard'); await waitForDevices(uhkDeviceProduct.keyboard); - this.logService.config( - '[DeviceService] User configuration will be saved after right module recovery', - userConfig); - const buffer = mapObjectToUserConfigBinaryBuffer(userConfig); - await this.operations.saveUserConfiguration(buffer); - this._checkStatusBuffer = true; - - response.modules = await this.getHardwareModules(false); - await copySmartMacroDocToWebserver(firmwarePathData, this.logService); - await makeFolderWriteableToUserOnLinux(getSmartMacroDocRootPath()); + if (deviceId === UHK_DEVICE_IDS.UHK_DONGLE || deviceId === UHK_DEVICE_IDS.UHK80_LEFT) { + this.logService.misc('[DeviceService] skip save user configuration'); + } + else { + this.logService.config( + '[DeviceService] User configuration will be saved after right module recovery', + userConfig); + const buffer = mapObjectToUserConfigBinaryBuffer(userConfig); + await this.operations.saveUserConfiguration(buffer); + this._checkStatusBuffer = true; + response.userConfigSaved = true; + + await copySmartMacroDocToWebserver(firmwarePathData, this.logService); + await makeFolderWriteableToUserOnLinux(getSmartMacroDocRootPath()); + } response.success = true; - response.userConfigSaved = true; response.firmwareDowngraded = false; } catch (error) { const err = { message: error.message, stack: error.stack }; this.logService.error('[DeviceService] updateFirmware error', err); - - response.modules = { - moduleInfos: [], - rightModuleInfo: { - modules: {}, - } - }; response.error = err; } - this.startPollUhkDevice(); - await snooze(500); event.sender.send(IpcEvents.device.recoveryDeviceReply, response); + await snooze(500); + + this.savedState = undefined; + this.startPollUhkDevice(); } public async recoveryModule(event: Electron.IpcMainEvent, args: Array): Promise { - const response = new FirmwareUpgradeIpcResponse(); + const response: FirmwareUpgradeIpcResponse = { + success: false, + }; const moduleId: number = args[0]; try { @@ -621,16 +628,15 @@ export class DeviceService { uhkModule ); - response.modules = await this.getHardwareModules(false); response.success = true; } catch (error) { const err = { message: error.message, stack: error.stack }; this.logService.error('[DeviceService] Module recovery error', err); - response.modules = await this.getHardwareModules(true); response.error = err; } + this.savedState = undefined; this.startPollUhkDevice(); await snooze(500); event.sender.send(IpcEvents.device.recoveryModuleReply, response); @@ -864,7 +870,12 @@ export class DeviceService { state.bleAddress = convertBleAddressArrayToString(deviceBleAddress); } - if (isDeviceSupportWirelessUSBCommands && !state.dongle.multiDevice && state.dongle.serialNumber && state.dongle.serialNumber !== this.savedState?.dongle?.serialNumber) { + if (isDeviceSupportWirelessUSBCommands + && !state.dongle.multiDevice + && !state.dongle.bootloaderActive + && state.dongle.serialNumber + && state.dongle.serialNumber !== this.savedState?.dongle?.serialNumber) { + const dongle = await getCurrentUhkDongleHID(); let dongleUhkDevice: UhkHidDevice; try { diff --git a/packages/uhk-common/src/models/device-connection-state.ts b/packages/uhk-common/src/models/device-connection-state.ts index 68372d8b4..3310dfedb 100644 --- a/packages/uhk-common/src/models/device-connection-state.ts +++ b/packages/uhk-common/src/models/device-connection-state.ts @@ -9,6 +9,7 @@ export interface DeviceConnectionState { isPairedWithDongle?: boolean; connectedDevice?: UhkDeviceProduct; dongle: Dongle; + leftHalfBootloaderActive: boolean; hasPermission: boolean; bootloaderActive: boolean; isMacroStatusDirty: boolean; diff --git a/packages/uhk-common/src/models/dongle.ts b/packages/uhk-common/src/models/dongle.ts index efb317ffb..87247b534 100644 --- a/packages/uhk-common/src/models/dongle.ts +++ b/packages/uhk-common/src/models/dongle.ts @@ -1,6 +1,8 @@ export interface Dongle { bleAddress?: string; + bootloaderActive: boolean; + isPairedWithKeyboard?: boolean; /** * True if more than 1 UHK dongle connected. diff --git a/packages/uhk-common/src/models/ipc-response.ts b/packages/uhk-common/src/models/ipc-response.ts index 4ce516ca7..9e615d1ba 100644 --- a/packages/uhk-common/src/models/ipc-response.ts +++ b/packages/uhk-common/src/models/ipc-response.ts @@ -1,5 +1,3 @@ -import { HardwareModules } from './hardware-modules.js'; - export class IpcResponse { success: boolean; error?: { message: string }; @@ -14,7 +12,6 @@ export enum FirmwareUpgradeFailReason { } export class FirmwareUpgradeIpcResponse extends IpcResponse { - modules?: HardwareModules; failReason?: FirmwareUpgradeFailReason; userConfigSaved?: boolean; firmwareDowngraded?: boolean; diff --git a/packages/uhk-usb/src/uhk-hid-device.ts b/packages/uhk-usb/src/uhk-hid-device.ts index 9a3274132..0a8192702 100644 --- a/packages/uhk-usb/src/uhk-hid-device.ts +++ b/packages/uhk-usb/src/uhk-hid-device.ts @@ -58,6 +58,7 @@ import { getNumberOfConnectedDevices, getUhkDevices, isDongleCommunicationDevice, + isSerialPortInVidPids, snooze, usbDeviceJsonFormatter, validateConnectedDevices, @@ -317,9 +318,11 @@ export class UhkHidDevice { bootloaderActive: false, communicationInterfaceAvailable: false, dongle: { + bootloaderActive: false, multiDevice: false, serialNumber: '', }, + leftHalfBootloaderActive: false, hasPermission: await this.hasPermission(), halvesInfo: { areHalvesMerged: true, @@ -337,6 +340,17 @@ export class UhkHidDevice { return result; } + function setDongleSerialNumber(serialNumber: string): void { + if (result.dongle.serialNumber) { + if (result.dongle.serialNumber !== serialNumber) { + result.dongle.multiDevice = true; + } + } + else { + result.dongle.serialNumber = serialNumber; + } + } + for (const dev of devs) { if (this.options.vid || this.options['serial-number']) { const isUhkDevice = findDeviceByDeviceIdentifier(this.options); @@ -365,12 +379,23 @@ export class UhkHidDevice { } if (isDongleCommunicationDevice(dev)) { - if (result.dongle.serialNumber) { - result.dongle.multiDevice = true; - } - else { - result.dongle.serialNumber = dev.serialNumber; - } + setDongleSerialNumber(dev.serialNumber); + } + } + + const serialDevices = await SerialPort.list(); + + for (const serialDevice of serialDevices) { + if (isSerialPortInVidPids(serialDevice, UHK_DONGLE.bootloader)) { + result.dongle.bootloaderActive = true; + setDongleSerialNumber(serialDevice.serialNumber); + } + else if (isSerialPortInVidPids(serialDevice, UHK_80_DEVICE.bootloader)) { + result.connectedDevice = UHK_80_DEVICE; + result.bootloaderActive = true; + } + else if (isSerialPortInVidPids(serialDevice, UHK_80_DEVICE_LEFT.bootloader)) { + result.leftHalfBootloaderActive = true; } } diff --git a/packages/uhk-usb/src/utils/get-current-uhk-device-product-by-bootloader-id.ts b/packages/uhk-usb/src/utils/get-current-uhk-device-product-by-bootloader-id.ts deleted file mode 100644 index 8d4f78d23..000000000 --- a/packages/uhk-usb/src/utils/get-current-uhk-device-product-by-bootloader-id.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { devicesAsync } from 'node-hid'; -import { UHK_DEVICES, UhkDeviceProduct } from 'uhk-common'; - -import { validateConnectedDevices } from './validate-connected-devices.js'; - -export async function getCurrentUhkDeviceProductByBootloaderId(): Promise { - await validateConnectedDevices(); - - const hidDevices = await devicesAsync(); - - for (const hidDevice of hidDevices) { - for (const uhkDevice of UHK_DEVICES) { - if (uhkDevice.bootloader.some(vidPid => vidPid.vid === hidDevice.vendorId && vidPid.pid === hidDevice.productId)) { - return uhkDevice; - } - } - } -} diff --git a/packages/uhk-usb/src/utils/index.ts b/packages/uhk-usb/src/utils/index.ts index ca70d82dd..b134d2757 100644 --- a/packages/uhk-usb/src/utils/index.ts +++ b/packages/uhk-usb/src/utils/index.ts @@ -6,7 +6,6 @@ export * from './convert-slave-i2c-error-buffer.js'; export * from './device-vid-pid-interface-filter.js'; export * from './find-device-by-device-identifier.js'; export * from './get-current-uhk-device-product.js'; -export * from './get-current-uhk-device-product-by-bootloader-id.js'; export * from './get-current-uhk-dongle-HID.js'; export * from './get-current-uhk-80-left-HID.js'; export * from './get-device-enumerate-vid-pid-pairs.js'; @@ -20,6 +19,7 @@ export * from './get-uhk-devices.js'; export * from './get-uhk-dongles.js'; export * from './is-dongle-communication-device.js'; export * from './is-left-half-communication-device.js'; +export * from './is-serial-port-in-vid-pids.js'; export * from './is-uhk-device-connected.js'; export * from './is-uhk-keyboard-connected.js'; export * from './snooze.js'; diff --git a/packages/uhk-usb/src/utils/is-serial-port-in-vid-pids.ts b/packages/uhk-usb/src/utils/is-serial-port-in-vid-pids.ts new file mode 100644 index 000000000..62ed9eb85 --- /dev/null +++ b/packages/uhk-usb/src/utils/is-serial-port-in-vid-pids.ts @@ -0,0 +1,9 @@ +import { PortInfo } from '@serialport/bindings-interface'; +import { VidPidPair } from 'uhk-common'; + +export function isSerialPortInVidPids(serialDevice: PortInfo, vidPids: VidPidPair[]): boolean { + return vidPids.some(vidPid => { + return vidPid.vid === Number.parseInt(serialDevice.vendorId, 16) + && vidPid.pid == Number.parseInt(serialDevice.productId, 16); + }); +} diff --git a/packages/uhk-web/src/app/components/device/recovery-mode/recovery-mode.component.html b/packages/uhk-web/src/app/components/device/recovery-mode/recovery-mode.component.html index cb3036a31..2ecafcb73 100644 --- a/packages/uhk-web/src/app/components/device/recovery-mode/recovery-mode.component.html +++ b/packages/uhk-web/src/app/components/device/recovery-mode/recovery-mode.component.html @@ -4,10 +4,10 @@

- Fix device + {{recoverPageState.title}}

- Your device seems to be broken. No worries, Agent can fix it. + {{recoverPageState.description}}