Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/kboot/src/kboot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ export class KBoot {
return this.peripheral.readMemory(startAddress, count);
}

async writeMemory(options: DataOption): Promise<void> {
async writeMemory(options: DataOption, onProgress?: (percent: number) => void): Promise<void> {
logger('Start write memory %o', { options });
return this.peripheral.writeMemory(options);
return this.peripheral.writeMemory(options, onProgress);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/kboot/src/peripheral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface Peripheral {

sendCommand(options: CommandOption): Promise<CommandResponse>;

writeMemory(data: DataOption): Promise<void>;
writeMemory(data: DataOption, onProgress?: (percent: number) => void): Promise<void>;

readMemory(startAddress: number, count: number): Promise<Buffer>;
}
4 changes: 3 additions & 1 deletion packages/kboot/src/usb-peripheral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,10 @@ export class UsbPeripheral implements Peripheral {
});
}

writeMemory(option: DataOption): Promise<void> {
writeMemory(option: DataOption, onProgress?: (percent: number) => void): Promise<void> {
return new Promise<void>(async (resolve, reject) => {
try {
onProgress?.(0);
const command: CommandOption = {
command: Commands.WriteMemory,
hasDataPhase: true,
Expand Down Expand Up @@ -113,6 +114,7 @@ export class UsbPeripheral implements Peripheral {
await this._device.write(writeData);
// workaround to prevent main thread blocking
await snooze(1);
onProgress?.(Math.min(100, Math.round((i + slice.length) / option.data.length * 100)));
}

const receivedData = await this._device.read(option.timeout || 2000);
Expand Down
3 changes: 2 additions & 1 deletion packages/kboot/test/test-peripheral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class TestPeripheral implements Peripheral {
return Promise.resolve(response);
}

writeMemory(data: DataOption): Promise<void> {
writeMemory(data: DataOption, onProgress?: (percent: number) => void): Promise<void> {
onProgress?.(100);
return Promise.resolve();
}

Expand Down
4 changes: 3 additions & 1 deletion packages/mcumgr/src/mcumgr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ export class McuManager {
/**
* Upload a firmware/bootloader image to the device
*/
async imageUpload(buffer: Buffer): Promise<void> {
async imageUpload(buffer: Buffer, onProgress?: (percent: number) => void): Promise<void> {
logger('Start send image upload command: %o', { bufferLength: buffer.byteLength });
let written = 0;
onProgress?.(0);

while (written < buffer.length) {
const message: ImageUploadRequest = {
Expand Down Expand Up @@ -104,6 +105,7 @@ export class McuManager {
}

logger('Image uploaded: %d', written / buffer.length * 100);
onProgress?.(Math.min(100, Math.round(written / buffer.length * 100)));
}
}

Expand Down
66 changes: 52 additions & 14 deletions packages/uhk-agent/src/services/device.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
escapeZephyrControlChars,
findUhkModuleById,
FIRMWARE_UPGRADE_METHODS,
FirmwareUpgradeConnectPrompt,
FirmwareUpgradeIpcResponse,
getHardwareConfigFromDeviceResponse,
getUserConfigFromDeviceResponse,
Expand All @@ -32,6 +33,7 @@ import {
LeftSlotModules,
LogService,
mapObjectToUserConfigBinaryBuffer,
ModuleFirmwareUpgradeProgress,
ModuleFirmwareUpgradeSkipInfo,
ModuleFirmwareUpgradeSkipReason,
ModuleInfo,
Expand Down Expand Up @@ -499,6 +501,24 @@ export class DeviceService {
let shouldEnableFirmwareVersionCheck = false;
let uhkDeviceProduct: UhkDeviceProduct;

const createFirmwareProgressReporter = (moduleName: string): ((percent: number) => void) => {
let lastProgress = -1;

return (percent: number) => {
const progress = Math.max(0, Math.min(100, Math.round(percent)));

if (progress === lastProgress) {
return;
}

lastProgress = progress;
event.sender.send(IpcEvents.device.moduleFirmwareUpgradeProgress, {
moduleName,
progress,
} as ModuleFirmwareUpgradeProgress);
};
};

try {
await this.stopPollUhkDevice();
await this.dongleZephyrLogService.disable();
Expand Down Expand Up @@ -544,7 +564,7 @@ export class DeviceService {
moduleName: UHK_DONGLE.name,
newFirmwareChecksum: deviceConfig.md5,
} as CurrentlyUpdatingModuleInfo);
await dongleOperations.updateDeviceFirmware(dongleFirmwarePath, UHK_DONGLE);
await dongleOperations.updateDeviceFirmware(dongleFirmwarePath, UHK_DONGLE, createFirmwareProgressReporter(UHK_DONGLE.name));
this.logService.misc('[DeviceService] Waiting for keyboard');
await waitForDevices(UHK_DONGLE.keyboard);
await dongleUhkDevice.close();
Expand Down Expand Up @@ -593,7 +613,7 @@ export class DeviceService {
newFirmwareChecksum: deviceConfig.md5,
moduleName: RIGHT_HALF_FIRMWARE_UPGRADE_MODULE_NAME,
} as CurrentlyUpdatingModuleInfo);
await this.operations.updateDeviceFirmware(deviceFirmwarePath, uhkDeviceProduct);
await this.operations.updateDeviceFirmware(deviceFirmwarePath, uhkDeviceProduct, createFirmwareProgressReporter(RIGHT_HALF_FIRMWARE_UPGRADE_MODULE_NAME));
this.logService.misc('[DeviceService] Waiting for keyboard');
await waitForDevices(uhkDeviceProduct.keyboard);

Expand Down Expand Up @@ -657,27 +677,29 @@ export class DeviceService {
} as CurrentlyUpdatingModuleInfo);

if(uhkDeviceProduct.firmwareUpgradeMethod === FIRMWARE_UPGRADE_METHODS.MCUBOOT) {
if (!(await isUhkDeviceConnected(UHK_80_DEVICE_LEFT))) {
this.logService.misc('[DeviceService] To continue the firmware upgrade, now connect the left half via USB. (You can disconnect the right half or use a second USB cable.)');
}
await this.waitForUhkDeviceWithConnectPrompt(
event,
UHK_80_DEVICE_LEFT,
'To continue the firmware upgrade, now connect the left half via USB. (You can disconnect the right half or use a second USB cable.)'
);

await waitForUhkDeviceConnected(UHK_80_DEVICE_LEFT);
await snooze(1000);
const firmwarePath = getDeviceFirmwarePath(UHK_80_DEVICE_LEFT, packageJson);
await this.operations.updateFirmwareWithMcuManager(firmwarePath, UHK_80_DEVICE_LEFT);

if (!(await isUhkDeviceConnected(uhkDeviceProduct))) {
this.logService.misc('[DeviceService] To finish the firmware upgrade, now connect the right half via USB. (You can disconnect the left half or use a second USB cable.)');
}
await this.operations.updateFirmwareWithMcuManager(firmwarePath, UHK_80_DEVICE_LEFT, createFirmwareProgressReporter(leftModuleInfo.module.name));

await waitForUhkDeviceConnected(uhkDeviceProduct);
await this.waitForUhkDeviceWithConnectPrompt(
event,
uhkDeviceProduct,
'To finish the firmware upgrade, now connect the right half via USB. (You can disconnect the left half or use a second USB cable.)'
);
}
else {
await this.operations
.updateModuleWithKboot(
getModuleFirmwarePath(leftModuleInfo.module, packageJson),
uhkDeviceProduct,
leftModuleInfo.module
leftModuleInfo.module,
createFirmwareProgressReporter(leftModuleInfo.module.name)
);
}
} else {
Expand Down Expand Up @@ -739,7 +761,8 @@ export class DeviceService {
.updateModuleWithKboot(
getModuleFirmwarePath(moduleInfo.module, packageJson),
uhkDeviceProduct,
moduleInfo.module
moduleInfo.module,
createFirmwareProgressReporter(moduleInfo.module.name)
);
this.logService.misc(`[DeviceService] "${moduleInfo.module.name}" firmware update done.`);
} else {
Expand Down Expand Up @@ -799,9 +822,24 @@ export class DeviceService {
await this.dongleZephyrLogService.enable();
await this.leftHalfZephyrLogService.enable();

event.sender.send(IpcEvents.device.firmwareUpgradeConnectPrompt, null);
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
}

private async waitForUhkDeviceWithConnectPrompt(
event: Electron.IpcMainEvent,
device: UhkDeviceProduct,
message: string
): Promise<void> {
if (!(await isUhkDeviceConnected(device))) {
this.logService.misc(`[DeviceService] ${message}`);
event.sender.send(IpcEvents.device.firmwareUpgradeConnectPrompt, { message } as FirmwareUpgradeConnectPrompt);
}

await waitForUhkDeviceConnected(device);
event.sender.send(IpcEvents.device.firmwareUpgradeConnectPrompt, null);
}

public async recoveryDevice(event: Electron.IpcMainEvent, args): Promise<void> {
const response: FirmwareUpgradeIpcResponse = {
success: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface FirmwareUpgradeConnectPrompt {
message: string;
}
2 changes: 2 additions & 0 deletions packages/uhk-common/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './device-version-information.js';
export * from './dongle.js';
export * from './firmware-json.js';
export * from './firmware-repo-info.js';
export * from './firmware-upgrade-connect-prompt.js';
export * from './firmware-upgrade-method.js';
export * from './firmware-version.js';
export * from './firmware-version-info.js';
Expand All @@ -23,6 +24,7 @@ export * from './app-start-info.js';
export * from './configuration-reply.js';
export * from './version-information.js';
export * from './device-connection-state.js';
export * from './module-firmware-upgrade-progress.js';
export * from './module-firmware-upgrade-skip-info.js';
export * from './module-slot-to-i2c-adress.js';
export * from './module-slot-id.js';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface ModuleFirmwareUpgradeProgress {
moduleName: string;
progress: number;
}
2 changes: 2 additions & 0 deletions packages/uhk-common/src/util/ipcEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export class Device {
public static readonly updateFirmware = 'device-update-firmware';
public static readonly updateFirmwareJson = 'device-update-firmware-json';
public static readonly updateFirmwareReply = 'device-update-firmware-reply';
public static readonly firmwareUpgradeConnectPrompt = 'device-firmware-upgrade-connect-prompt';
public static readonly moduleFirmwareUpgradeProgress = 'device-module-firmware-upgrade-progress';
public static readonly moduleFirmwareUpgradeSkip = 'device-module-firmware-upgrade-skip';
public static readonly moduleFirmwareUpgrading = 'device-module-firmware-upgrading';
public static readonly startConnectionPoller = 'device-start-connection-poller';
Expand Down
30 changes: 19 additions & 11 deletions packages/uhk-usb/src/uhk-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,22 +123,22 @@ export class UhkOperations {
await this.device.write(transfer);
}

public async updateDeviceFirmware(firmwarePath: string, device: UhkDeviceProduct): Promise<void> {
public async updateDeviceFirmware(firmwarePath: string, device: UhkDeviceProduct, onProgress?: (percent: number) => void): Promise<void> {
this.logService.misc(`[UhkOperations] Start flashing device firmware with ${device.firmwareUpgradeMethod}`);

switch (device.firmwareUpgradeMethod) {
case FIRMWARE_UPGRADE_METHODS.KBOOT:
return this.updateRightFirmwareWithKboot(firmwarePath, device);
return this.updateRightFirmwareWithKboot(firmwarePath, device, onProgress);

case FIRMWARE_UPGRADE_METHODS.MCUBOOT:
return this.updateFirmwareWithMcuManager(firmwarePath, device);
return this.updateFirmwareWithMcuManager(firmwarePath, device, onProgress);

default:
throw new Error(`Firmware upgrade method not implemented: ${device.firmwareUpgradeMethod}`);
}
}

public async updateRightFirmwareWithKboot(firmwarePath: string, device: UhkDeviceProduct): Promise<void> {
public async updateRightFirmwareWithKboot(firmwarePath: string, device: UhkDeviceProduct, onProgress?: (percent: number) => void): Promise<void> {
if (!(await existsAsync(firmwarePath))) {
throw new Error(`Firmware path not found: ${firmwarePath}`);
}
Expand All @@ -162,13 +162,20 @@ export class UhkOperations {
this.logService.misc('[UhkOperations] Read RIGHT firmware from file');
const bootloaderMemoryMap = await readBootloaderFirmwareFromHexFileAsync(firmwarePath);
this.logService.misc('[UhkOperations] Write memory');
const totalBytes = [...bootloaderMemoryMap.values()].reduce((sum, data) => sum + data.length, 0);
let writtenBytes = 0;
onProgress?.(0);
for (const [startAddress, data] of bootloaderMemoryMap.entries()) {
const dataOption: DataOption = {
startAddress,
data
};

await kboot.writeMemory(dataOption);
const bytesBeforeEntry = writtenBytes;
await kboot.writeMemory(dataOption, entryPercent => {
onProgress?.(Math.min(100, Math.round((bytesBeforeEntry + data.length * entryPercent / 100) / totalBytes * 100)));
});
writtenBytes += data.length;
}

this.logService.misc('[UhkOperations] Reset bootloader');
Expand All @@ -178,7 +185,7 @@ export class UhkOperations {
this.logService.misc('[UhkOperations] Right firmware successfully flashed');
}

public async updateFirmwareWithMcuManager(firmwarePath: string, device: UhkDeviceProduct) {
public async updateFirmwareWithMcuManager(firmwarePath: string, device: UhkDeviceProduct, onProgress?: (percent: number) => void) {
if (!(await existsAsync(firmwarePath))) {
throw new Error(`Firmware path not found: ${firmwarePath}`);
}
Expand All @@ -199,22 +206,23 @@ export class UhkOperations {
this.logService.misc(`[UhkOperations] Read ${device.logName} firmware from file`);
const configData = fs.readFileSync(firmwarePath);
this.logService.misc('[UhkOperations] Write memory with mcumgr');
await mcuManager.imageUpload(configData);
await mcuManager.imageUpload(configData, onProgress);
this.logService.misc('[UhkOperations] Reset mcu bootloader');
await mcuManager.reset();
this.logService.misc('[UhkOperations] Close mcu communication channels');
await mcuManager.close();
this.logService.misc(`[UhkOperations] ${device.logName} firmware successfully flashed`);
}

public async updateLeftModuleWithKboot(firmwarePath: string, device: UhkDeviceProduct): Promise<void> {
return this.updateModuleWithKboot(firmwarePath, device, LEFT_HALF_MODULE);
public async updateLeftModuleWithKboot(firmwarePath: string, device: UhkDeviceProduct, onProgress?: (percent: number) => void): Promise<void> {
return this.updateModuleWithKboot(firmwarePath, device, LEFT_HALF_MODULE, onProgress);
}

public async updateModuleWithKboot(
firmwarePath: string,
device: UhkDeviceProduct,
module: UhkModule
module: UhkModule,
onProgress?: (percent: number) => void
): Promise<void> {
this.logService.misc(`[UhkOperations] Start flashing "${module.name}" module firmware`);
await this.device.reenumerate({
Expand Down Expand Up @@ -279,7 +287,7 @@ export class UhkOperations {

this.logService.misc('[UhkOperations] Write memory');
await kboot.configureI2c(module.i2cAddress);
await kboot.writeMemory({ startAddress: 0, data: configData });
await kboot.writeMemory({ startAddress: 0, data: configData }, onProgress);

this.logService.misc(`[UhkOperations] Reset "${module.name}" keyboard`);
await kboot.reset();
Expand Down
Loading
Loading