diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index a533be8a..d660d7af 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -117,6 +117,7 @@ import { EditorWidgetFactory as TheiaEditorWidgetFactory } from '@theia/editor/l import { EditorWidgetFactory } from './theia/editor/editor-widget-factory'; import { OutputWidget as TheiaOutputWidget } from '@theia/output/lib/browser/output-widget'; import { OutputWidget } from './theia/output/output-widget'; +import { BurnBootloader } from './contributions/burn-bootloader'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -358,4 +359,5 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, QuitApp); Contribution.configure(bind, SketchControl); Contribution.configure(bind, Settings); + Contribution.configure(bind, BurnBootloader); }); diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts new file mode 100644 index 00000000..e2ff53d7 --- /dev/null +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -0,0 +1,94 @@ +import { inject, injectable } from 'inversify'; +import { OutputChannelManager } from '@theia/output/lib/common/output-channel'; +import { CoreService } from '../../common/protocol'; +import { ArduinoMenus } from '../menu/arduino-menus'; +import { BoardsDataStore } from '../boards/boards-data-store'; +import { MonitorConnection } from '../monitor/monitor-connection'; +import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl'; +import { SketchContribution, Command, CommandRegistry, MenuModelRegistry } from './contribution'; + +@injectable() +export class BurnBootloader extends SketchContribution { + + @inject(CoreService) + protected readonly coreService: CoreService; + + @inject(MonitorConnection) + protected readonly monitorConnection: MonitorConnection; + + @inject(BoardsDataStore) + protected readonly boardsDataStore: BoardsDataStore; + + @inject(BoardsServiceClientImpl) + protected readonly boardsServiceClientImpl: BoardsServiceClientImpl; + + @inject(OutputChannelManager) + protected readonly outputChannelManager: OutputChannelManager; + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(BurnBootloader.Commands.BURN_BOOTLOADER, { + execute: () => this.burnBootloader() + }); + } + + registerMenus(registry: MenuModelRegistry): void { + registry.registerMenuAction(ArduinoMenus.TOOLS__BOARD_SETTINGS_GROUP, { + commandId: BurnBootloader.Commands.BURN_BOOTLOADER.id, + label: 'Burn Bootloader', + order: 'z99' + }); + } + + async burnBootloader(): Promise { + const monitorConfig = this.monitorConnection.monitorConfig; + if (monitorConfig) { + await this.monitorConnection.disconnect(); + } + try { + const { boardsConfig } = this.boardsServiceClientImpl; + if (!boardsConfig || !boardsConfig.selectedBoard) { + throw new Error('No boards selected. Please select a board.'); + } + if (!boardsConfig.selectedBoard.fqbn) { + throw new Error(`No core is installed for the '${boardsConfig.selectedBoard.name}' board. Please install the core.`); + } + const { selectedPort } = boardsConfig; + if (!selectedPort) { + throw new Error('No ports selected. Please select a port.'); + } + + const port = selectedPort.address; + const [fqbn, { selectedProgrammer: programmer }] = await Promise.all([ + this.boardsDataStore.appendConfigToFqbn(boardsConfig.selectedBoard.fqbn), + this.boardsDataStore.getData(boardsConfig.selectedBoard.fqbn) + ]); + + if (!programmer) { + throw new Error('Programmer is not selected. Please select a programmer from the `Tools` > `Programmer` menu.'); + } + + this.outputChannelManager.getChannel('Arduino: bootloader').clear(); + await this.coreService.burnBootloader({ + fqbn, + programmer, + port + }); + this.messageService.info('Done burning bootloader.', { timeout: 1000 }); + } catch (e) { + this.messageService.error(e.toString()); + } finally { + if (monitorConfig) { + await this.monitorConnection.connect(monitorConfig); + } + } + } + +} + +export namespace BurnBootloader { + export namespace Commands { + export const BURN_BOOTLOADER: Command = { + id: 'arduino-burn-bootloader' + }; + } +} diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 59091031..027eca7e 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -103,7 +103,7 @@ export class UploadSketch extends SketchContribution { if (usingProgrammer) { const programmer = selectedProgrammer; if (!programmer) { - throw new Error('Programmer is not selected. Please select a programmer.'); + throw new Error('Programmer is not selected. Please select a programmer from the `Tools` > `Programmer` menu.'); } let port: undefined | string = undefined; // If the port is set by the user, we pass it to the CLI as it might be required. diff --git a/arduino-ide-extension/src/browser/menu/arduino-menus.ts b/arduino-ide-extension/src/browser/menu/arduino-menus.ts index 471de834..7008b48d 100644 --- a/arduino-ide-extension/src/browser/menu/arduino-menus.ts +++ b/arduino-ide-extension/src/browser/menu/arduino-menus.ts @@ -30,7 +30,7 @@ export namespace ArduinoMenus { export const TOOLS = [...MAIN_MENU_BAR, '4_tools']; // `Auto Format`, `Library Manager...`, `Boards Manager...` export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main']; - // Core settings, such as `Processor` and `Programmers` for the board. + // Core settings, such as `Processor` and `Programmers` for the board and `Burn Bootloader` export const TOOLS__BOARD_SETTINGS_GROUP = [...TOOLS, '1_board_settings']; // Context menu diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index cc51b738..d244a77a 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -11,6 +11,7 @@ export const CoreService = Symbol('CoreService'); export interface CoreService extends JsonRpcServer { compile(options: CoreService.Compile.Options): Promise; upload(options: CoreService.Upload.Options): Promise; + burnBootloader(options: CoreService.Bootloader.Options): Promise; } export namespace CoreService { @@ -29,4 +30,12 @@ export namespace CoreService { Compile.Options & Readonly<{ programmer: Programmer, port?: string }>; } + export namespace Bootloader { + export interface Options { + readonly fqbn: string; + readonly programmer: Programmer; + readonly port: string; + } + } + } diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index 67163544..bd0064d5 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -6,7 +6,7 @@ import { BoardsService } from '../common/protocol/boards-service'; import { CoreClientProvider } from './core-client-provider'; import * as path from 'path'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service'; -import { UploadReq, UploadResp } from './cli-protocol/commands/upload_pb'; +import { UploadReq, UploadResp, BurnBootloaderReq, BurnBootloaderResp } from './cli-protocol/commands/upload_pb'; @injectable() export class CoreServiceImpl implements CoreService { @@ -113,9 +113,9 @@ export class CoreServiceImpl implements CoreService { try { await new Promise((resolve, reject) => { - result.on('data', (cr: UploadResp) => { - this.toolOutputService.append({ tool: 'upload', chunk: Buffer.from(cr.getOutStream_asU8()).toString() }); - this.toolOutputService.append({ tool: 'upload', chunk: Buffer.from(cr.getErrStream_asU8()).toString() }); + result.on('data', (resp: UploadResp) => { + this.toolOutputService.append({ tool: 'upload', chunk: Buffer.from(resp.getOutStream_asU8()).toString() }); + this.toolOutputService.append({ tool: 'upload', chunk: Buffer.from(resp.getErrStream_asU8()).toString() }); }); result.on('error', error => reject(error)); result.on('end', () => resolve()); @@ -127,6 +127,40 @@ export class CoreServiceImpl implements CoreService { } } + async burnBootloader(options: CoreService.Bootloader.Options): Promise { + const coreClient = await this.coreClientProvider.client(); + if (!coreClient) { + return; + } + const { fqbn, port, programmer } = options; + if (!fqbn) { + throw new Error('The selected board has no FQBN.'); + } + if (!port) { + throw new Error('Port must be specified.'); + } + const { client, instance } = coreClient; + const req = new BurnBootloaderReq(); + req.setFqbn(fqbn); + req.setPort(port); + req.setProgrammer(programmer.id); + req.setInstance(instance); + const result = client.burnBootloader(req); + try { + await new Promise((resolve, reject) => { + result.on('data', (resp: BurnBootloaderResp) => { + this.toolOutputService.append({ tool: 'bootloader', chunk: Buffer.from(resp.getOutStream_asU8()).toString() }); + this.toolOutputService.append({ tool: 'bootloader', chunk: Buffer.from(resp.getErrStream_asU8()).toString() }); + }); + result.on('error', error => reject(error)); + result.on('end', () => resolve()); + }); + } catch (e) { + this.toolOutputService.append({ tool: 'bootloader', chunk: `Error while burning the bootloader: ${e}\n`, severity: 'error' }); + throw e; + } + } + setClient(client: CoreServiceClient | undefined): void { this.client = client; }