Skip to content

fix: show board info based on the selected port #1803

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 6, 2023
Merged
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
6 changes: 2 additions & 4 deletions arduino-ide-extension/src/browser/boards/boards-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down
63 changes: 20 additions & 43 deletions arduino-ide-extension/src/browser/contributions/board-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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')],
});
},
});
}
Expand Down
105 changes: 105 additions & 0 deletions arduino-ide-extension/src/common/protocol/boards-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, [Port, Array<Board>]>;
export namespace AvailablePorts {
Expand Down Expand Up @@ -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<AvailablePorts>
): Promise<BoardInfo | string> {
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)';
}
112 changes: 111 additions & 1 deletion arduino-ide-extension/src/test/common/boards-service.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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<string, string> = {},
protocol = 'serial'
): Mutable<Port> => ({
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<string, string>[] = [
{},
{ 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 = <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 = <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 = <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<T>(): Promise<T> {
return new Deferred<T>().promise;
}
7 changes: 4 additions & 3 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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}",
Expand All @@ -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": {
Expand Down