From 868fa07299cd7c08fd2f86df4dfa59981bf2741c Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Fri, 6 Jan 2023 13:21:51 +0100 Subject: [PATCH] fix: show board info based on the selected port include serial number of board if available Closes #1489 Closes #1435 Signed-off-by: Akos Kitta --- .../src/browser/boards/boards-config.tsx | 6 +- .../browser/contributions/board-selection.ts | 63 ++++------ .../src/common/protocol/boards-service.ts | 105 ++++++++++++++++ .../src/test/common/boards-service.test.ts | 112 +++++++++++++++++- i18n/en.json | 7 +- 5 files changed, 242 insertions(+), 51 deletions(-) diff --git a/arduino-ide-extension/src/browser/boards/boards-config.tsx b/arduino-ide-extension/src/browser/boards/boards-config.tsx index 91cae2e29..8781981e6 100644 --- a/arduino-ide-extension/src/browser/boards/boards-config.tsx +++ b/arduino-ide-extension/src/browser/boards/boards-config.tsx @@ -6,6 +6,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { Board, Port, + BoardConfig as ProtocolBoardConfig, BoardWithPackage, } from '../../common/protocol/boards-service'; import { NotificationCenter } from '../notification-center'; @@ -18,10 +19,7 @@ import { nls } from '@theia/core/lib/common'; import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state'; export namespace BoardsConfig { - export interface Config { - selectedBoard?: Board; - selectedPort?: Port; - } + export type Config = ProtocolBoardConfig; export interface Props { readonly boardsServiceProvider: BoardsServiceProvider; diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts index 010f18164..5b7c11209 100644 --- a/arduino-ide-extension/src/browser/contributions/board-selection.ts +++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts @@ -20,6 +20,7 @@ import { InstalledBoardWithPackage, AvailablePorts, Port, + getBoardInfo, } from '../../common/protocol'; import { SketchContribution, Command, CommandRegistry } from './contribution'; import { nls } from '@theia/core/lib/common'; @@ -49,52 +50,28 @@ export class BoardSelection extends SketchContribution { override registerCommands(registry: CommandRegistry): void { registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, { execute: async () => { - const { selectedBoard, selectedPort } = - this.boardsServiceProvider.boardsConfig; - if (!selectedBoard) { - this.messageService.info( - nls.localize( - 'arduino/board/selectBoardForInfo', - 'Please select a board to obtain board info.' - ) - ); - return; - } - if (!selectedBoard.fqbn) { - this.messageService.info( - nls.localize( - 'arduino/board/platformMissing', - "The platform for the selected '{0}' board is not installed.", - selectedBoard.name - ) - ); - return; - } - if (!selectedPort) { - this.messageService.info( - nls.localize( - 'arduino/board/selectPortForInfo', - 'Please select a port to obtain board info.' - ) - ); + const boardInfo = await getBoardInfo( + this.boardsServiceProvider.boardsConfig.selectedPort, + this.boardsService.getState() + ); + if (typeof boardInfo === 'string') { + this.messageService.info(boardInfo); return; } - const boardDetails = await this.boardsService.getBoardDetails({ - fqbn: selectedBoard.fqbn, - }); - if (boardDetails) { - const { VID, PID } = boardDetails; - const detail = `BN: ${selectedBoard.name} + const { BN, VID, PID, SN } = boardInfo; + const detail = ` +BN: ${BN} VID: ${VID} -PID: ${PID}`; - await remote.dialog.showMessageBox(remote.getCurrentWindow(), { - message: nls.localize('arduino/board/boardInfo', 'Board Info'), - title: nls.localize('arduino/board/boardInfo', 'Board Info'), - type: 'info', - detail, - buttons: [nls.localize('vscode/issueMainService/ok', 'OK')], - }); - } +PID: ${PID} +SN: ${SN} +`.trim(); + await remote.dialog.showMessageBox(remote.getCurrentWindow(), { + message: nls.localize('arduino/board/boardInfo', 'Board Info'), + title: nls.localize('arduino/board/boardInfo', 'Board Info'), + type: 'info', + detail, + buttons: [nls.localize('vscode/issueMainService/ok', 'OK')], + }); }, }); } diff --git a/arduino-ide-extension/src/common/protocol/boards-service.ts b/arduino-ide-extension/src/common/protocol/boards-service.ts index a929d72ed..cbb057028 100644 --- a/arduino-ide-extension/src/common/protocol/boards-service.ts +++ b/arduino-ide-extension/src/common/protocol/boards-service.ts @@ -11,6 +11,7 @@ import { Updatable, } from '../nls'; import URI from '@theia/core/lib/common/uri'; +import { MaybePromise } from '@theia/core/lib/common/types'; export type AvailablePorts = Record]>; export namespace AvailablePorts { @@ -657,3 +658,107 @@ export function sanitizeFqbn(fqbn: string | undefined): string | undefined { const [vendor, arch, id] = fqbn.split(':'); return `${vendor}:${arch}:${id}`; } + +export interface BoardConfig { + selectedBoard?: Board; + selectedPort?: Port; +} + +export interface BoardInfo { + /** + * Board name. Could be `'Unknown board`'. + */ + BN: string; + /** + * Vendor ID. + */ + VID: string; + /** + * Product ID. + */ + PID: string; + /** + * Serial number. + */ + SN: string; +} + +export const selectPortForInfo = nls.localize( + 'arduino/board/selectPortForInfo', + 'Please select a port to obtain board info.' +); +export const nonSerialPort = nls.localize( + 'arduino/board/nonSerialPort', + "Non-serial port, can't obtain info." +); +export const noNativeSerialPort = nls.localize( + 'arduino/board/noNativeSerialPort', + "Native serial port, can't obtain info." +); +export const unknownBoard = nls.localize( + 'arduino/board/unknownBoard', + 'Unknown board' +); + +/** + * The returned promise resolves to a `BoardInfo` if available to show in the UI or an info message explaining why showing the board info is not possible. + */ +export async function getBoardInfo( + selectedPort: Port | undefined, + availablePorts: MaybePromise +): Promise { + if (!selectedPort) { + return selectPortForInfo; + } + // IDE2 must show the board info based on the selected port. + // https://github.com/arduino/arduino-ide/issues/1489 + // IDE 1.x supports only serial port protocol + if (selectedPort.protocol !== 'serial') { + return nonSerialPort; + } + const selectedPortKey = Port.keyOf(selectedPort); + const state = await availablePorts; + const boardListOnSelectedPort = Object.entries(state).filter( + ([portKey, [port]]) => + portKey === selectedPortKey && isNonNativeSerial(port) + ); + + if (!boardListOnSelectedPort.length) { + return noNativeSerialPort; + } + + const [, [port, boards]] = boardListOnSelectedPort[0]; + if (boardListOnSelectedPort.length > 1 || boards.length > 1) { + console.warn( + `Detected more than one available boards on the selected port : ${JSON.stringify( + selectedPort + )}. Detected boards were: ${JSON.stringify( + boardListOnSelectedPort + )}. Using the first one: ${JSON.stringify([port, boards])}` + ); + } + + const board = boards[0]; + const BN = board?.name ?? unknownBoard; + const VID = readProperty('vid', port); + const PID = readProperty('pid', port); + const SN = readProperty('serialNumber', port); + return { VID, PID, SN, BN }; +} + +// serial protocol with one or many detected boards or available VID+PID properties from the port +function isNonNativeSerial(port: Port): boolean { + return !!( + port.protocol === 'serial' && + port.properties?.['vid'] && + port.properties?.['pid'] + ); +} + +function readProperty(property: string, port: Port): string { + return falsyToNullString(port.properties?.[property]); +} + +function falsyToNullString(s: string | undefined): string { + return !!s ? s : '(null)'; +} diff --git a/arduino-ide-extension/src/test/common/boards-service.test.ts b/arduino-ide-extension/src/test/common/boards-service.test.ts index 85ffa3be9..d2cae5a53 100644 --- a/arduino-ide-extension/src/test/common/boards-service.test.ts +++ b/arduino-ide-extension/src/test/common/boards-service.test.ts @@ -1,5 +1,17 @@ +import { Deferred } from '@theia/core/lib/common/promise-util'; +import { Mutable } from '@theia/core/lib/common/types'; import { expect } from 'chai'; -import { AttachedBoardsChangeEvent } from '../../common/protocol'; +import { + AttachedBoardsChangeEvent, + BoardInfo, + getBoardInfo, + noNativeSerialPort, + nonSerialPort, + Port, + selectPortForInfo, + unknownBoard, +} from '../../common/protocol'; +import { firstToUpperCase } from '../../common/utils'; describe('boards-service', () => { describe('AttachedBoardsChangeEvent', () => { @@ -80,4 +92,102 @@ describe('boards-service', () => { ); }); }); + + describe('getBoardInfo', () => { + const vid = '0x0'; + const pid = '0x1'; + const serialNumber = '1730323'; + const name = 'The Board'; + const fqbn = 'alma:korte:szolo'; + const selectedBoard = { name, fqbn }; + const selectedPort = ( + properties: Record = {}, + protocol = 'serial' + ): Mutable => ({ + address: 'address', + addressLabel: 'addressLabel', + protocol, + protocolLabel: firstToUpperCase(protocol), + properties, + }); + + it('should handle when no port is selected', async () => { + const info = await getBoardInfo(undefined, never()); + expect(info).to.be.equal(selectPortForInfo); + }); + + it("should handle when port protocol is not 'serial'", async () => { + await Promise.allSettled( + ['network', 'teensy'].map(async (protocol) => { + const selectedPort: Port = { + address: 'address', + addressLabel: 'addressLabel', + protocolLabel: firstToUpperCase(protocol), + protocol, + }; + const info = await getBoardInfo(selectedPort, never()); + expect(info).to.be.equal(nonSerialPort); + }) + ); + }); + + it("should not detect a port as non-native serial, if protocol is 'serial' but VID or PID is missing", async () => { + const insufficientProperties: Record[] = [ + {}, + { vid }, + { pid }, + { VID: vid, pid: pid }, // case sensitive + ]; + for (const properties of insufficientProperties) { + const port = selectedPort(properties); + const info = await getBoardInfo(port, { + [Port.keyOf(port)]: [port, []], + }); + expect(info).to.be.equal(noNativeSerialPort); + } + }); + + it("should detect a port as non-native serial, if protocol is 'serial' and VID/PID are available", async () => { + const port = selectedPort({ vid, pid }); + const info = await getBoardInfo(port, { + [Port.keyOf(port)]: [port, []], + }); + expect(typeof info).to.be.equal('object'); + const boardInfo = info; + expect(boardInfo.VID).to.be.equal(vid); + expect(boardInfo.PID).to.be.equal(pid); + expect(boardInfo.SN).to.be.equal('(null)'); + expect(boardInfo.BN).to.be.equal(unknownBoard); + }); + + it("should show the 'SN' even if no matching board was detected for the port", async () => { + const port = selectedPort({ vid, pid, serialNumber }); + const info = await getBoardInfo(port, { + [Port.keyOf(port)]: [port, []], + }); + expect(typeof info).to.be.equal('object'); + const boardInfo = info; + expect(boardInfo.VID).to.be.equal(vid); + expect(boardInfo.PID).to.be.equal(pid); + expect(boardInfo.SN).to.be.equal(serialNumber); + expect(boardInfo.BN).to.be.equal(unknownBoard); + }); + + it("should use the name of the matching board as 'BN' if available", async () => { + const port = selectedPort({ vid, pid }); + const info = await getBoardInfo(port, { + [Port.keyOf(port)]: [port, [selectedBoard]], + }); + expect(typeof info).to.be.equal('object'); + const boardInfo = info; + expect(boardInfo.VID).to.be.equal(vid); + expect(boardInfo.PID).to.be.equal(pid); + expect(boardInfo.SN).to.be.equal('(null)'); + expect(boardInfo.BN).to.be.equal(selectedBoard.name); + }); + }); }); + +function never(): Promise { + return new Deferred().promise; +} diff --git a/i18n/en.json b/i18n/en.json index e6cb59679..f5339efa4 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -18,11 +18,12 @@ "installNow": "The \"{0} {1}\" core has to be installed for the currently selected \"{2}\" board. Do you want to install it now?", "noBoardsFound": "No boards found for \"{0}\"", "noFQBN": "The FQBN is not available for the selected board \"{0}\". Do you have the corresponding core installed?", + "noNativeSerialPort": "Native serial port, can't obtain info.", "noPortsDiscovered": "No ports discovered", "noPortsSelected": "No ports selected for board: '{0}'.", + "nonSerialPort": "Non-serial port, can't obtain info.", "noneSelected": "No boards selected.", "openBoardsConfig": "Select other board and port…", - "platformMissing": "The platform for the selected '{0}' board is not installed.", "pleasePickBoard": "Please pick a board connected to the port you have selected.", "port": "Port{0}", "portLabel": "Port: {0}", @@ -31,13 +32,13 @@ "reselectLater": "Reselect later", "searchBoard": "Search board", "selectBoard": "Select Board", - "selectBoardForInfo": "Please select a board to obtain board info.", "selectPortForInfo": "Please select a port to obtain board info.", "showAllAvailablePorts": "Shows all available ports when enabled", "showAllPorts": "Show all ports", "succesfullyInstalledPlatform": "Successfully installed platform {0}:{1}", "succesfullyUninstalledPlatform": "Successfully uninstalled platform {0}:{1}", - "typeOfPorts": "{0} ports" + "typeOfPorts": "{0} ports", + "unknownBoard": "Unknown board" }, "boardsManager": "Boards Manager", "boardsType": {