Skip to content
This repository has been archived by the owner on Dec 15, 2024. It is now read-only.

Commit

Permalink
feat: UHK80 recovery (#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
ert78gb authored Nov 10, 2024
1 parent f36b97e commit 2ddf8f1
Show file tree
Hide file tree
Showing 22 changed files with 189 additions and 90 deletions.
75 changes: 43 additions & 32 deletions packages/uhk-agent/src/services/device.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -39,6 +40,7 @@ import {
shouldUpgradeFirmware,
simulateInvalidUserConfigError,
UHK_80_DEVICE_LEFT,
UHK_DEVICE_IDS,
UHK_DONGLE,
UHK_MODULES,
UpdateFirmwareData,
Expand All @@ -52,7 +54,6 @@ import {
DevicePropertyIds,
EnumerationModes,
getCurrentUhkDeviceProduct,
getCurrentUhkDeviceProductByBootloaderId,
getCurrentUhkDongleHID,
getCurrenUhk80LeftHID,
getDeviceFirmwarePath,
Expand Down Expand Up @@ -364,7 +365,10 @@ export class DeviceService {
}

public async updateFirmware(event: Electron.IpcMainEvent, args?: Array<string>): Promise<void> {
const response = new FirmwareUpgradeIpcResponse();
const response: FirmwareUpgradeIpcResponse = {
success: false,
};

response.userConfigSaved = false;
response.firmwareDowngraded = false;
const data: UpdateFirmwareData = JSON.parse(args[0]);
Expand Down Expand Up @@ -517,15 +521,13 @@ export class DeviceService {
}
}

response.modules = await this.getHardwareModules(false);
await copySmartMacroDocToWebserver(firmwarePathData, this.logService);
await makeFolderWriteableToUserOnLinux(getSmartMacroDocRootPath());
response.success = true;
} catch (error) {
const err = { message: error.message, stack: error.stack };
this.logService.error('[DeviceService] updateFirmware error', err);

response.modules = await this.getHardwareModules(true);
response.error = err;
}

Expand All @@ -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<any>): Promise<void> {
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(
Expand All @@ -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<any>): Promise<void> {
const response = new FirmwareUpgradeIpcResponse();
const response: FirmwareUpgradeIpcResponse = {
success: false,
};
const moduleId: number = args[0];

try {
Expand All @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions packages/uhk-common/src/models/device-connection-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface DeviceConnectionState {
isPairedWithDongle?: boolean;
connectedDevice?: UhkDeviceProduct;
dongle: Dongle;
leftHalfBootloaderActive: boolean;
hasPermission: boolean;
bootloaderActive: boolean;
isMacroStatusDirty: boolean;
Expand Down
2 changes: 2 additions & 0 deletions packages/uhk-common/src/models/dongle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export interface Dongle {
bleAddress?: string;

bootloaderActive: boolean;

isPairedWithKeyboard?: boolean;
/**
* True if more than 1 UHK dongle connected.
Expand Down
3 changes: 0 additions & 3 deletions packages/uhk-common/src/models/ipc-response.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { HardwareModules } from './hardware-modules.js';

export class IpcResponse {
success: boolean;
error?: { message: string };
Expand All @@ -14,7 +12,6 @@ export enum FirmwareUpgradeFailReason {
}

export class FirmwareUpgradeIpcResponse extends IpcResponse {
modules?: HardwareModules;
failReason?: FirmwareUpgradeFailReason;
userConfigSaved?: boolean;
firmwareDowngraded?: boolean;
Expand Down
37 changes: 31 additions & 6 deletions packages/uhk-usb/src/uhk-hid-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
getNumberOfConnectedDevices,
getUhkDevices,
isDongleCommunicationDevice,
isSerialPortInVidPids,
snooze,
usbDeviceJsonFormatter,
validateConnectedDevices,
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}
}

Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion packages/uhk-usb/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down
9 changes: 9 additions & 0 deletions packages/uhk-usb/src/utils/is-serial-port-in-vid-pids.ts
Original file line number Diff line number Diff line change
@@ -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);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

<h1>
<fa-icon [icon]="faWrench"></fa-icon>
<span>Fix device</span>
<span>{{recoverPageState.title}}</span>
</h1>
<p>
Your device seems to be broken. No worries, Agent can fix it.
{{recoverPageState.description}}
</p>
<p>
<button class="btn btn-primary"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectorRef, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { faWrench } from '@fortawesome/free-solid-svg-icons';

import { RecoverPageState } from '../../../models/recover-page-state';
import { XtermLog } from '../../../models/xterm-log';
import { AppState, flashFirmwareButtonDisabled, updatingFirmware, xtermLog } from '../../../store';
import { AppState, flashFirmwareButtonDisabled, getRecoveryPageState, xtermLog } from '../../../store';
import { RecoveryDeviceAction } from '../../../store/actions/device';

@Component({
Expand All @@ -16,23 +17,35 @@ import { RecoveryDeviceAction } from '../../../store/actions/device';
'class': 'container-fluid full-screen-component'
}
})
export class RecoveryModeComponent implements OnInit {
export class RecoveryModeComponent implements OnDestroy, OnInit {
flashFirmwareButtonDisabled$: Observable<boolean>;
updatingFirmware$: Observable<boolean>;

recoverPageState: RecoverPageState;

xtermLog$: Observable<Array<XtermLog>>;
faWrench = faWrench;

constructor(private store: Store<AppState>) {
private recoverPageStateSubscription: Subscription;

constructor(private cdRef: ChangeDetectorRef,
private store: Store<AppState>) {
}

ngOnDestroy(): void {
this.recoverPageStateSubscription?.unsubscribe();
}

ngOnInit(): void {
this.flashFirmwareButtonDisabled$ = this.store.select(flashFirmwareButtonDisabled);
this.updatingFirmware$ = this.store.select(updatingFirmware);
this.recoverPageStateSubscription = this.store.select(getRecoveryPageState)
.subscribe((recoverPageState) => {
this.recoverPageState = recoverPageState;
this.cdRef.detectChanges();
});
this.xtermLog$ = this.store.select(xtermLog);
}

onRecoveryDevice(): void {
this.store.dispatch(new RecoveryDeviceAction());
this.store.dispatch(new RecoveryDeviceAction(this.recoverPageState.deviceId));
}
}
Loading

0 comments on commit 2ddf8f1

Please sign in to comment.