Skip to content

Commit d77cf46

Browse files
author
Akos Kitta
committed
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 <[email protected]>
1 parent afb02da commit d77cf46

File tree

5 files changed

+289
-49
lines changed

5 files changed

+289
-49
lines changed

Diff for: arduino-ide-extension/src/browser/boards/boards-config.tsx

+2-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DisposableCollection } from '@theia/core/lib/common/disposable';
66
import {
77
Board,
88
Port,
9+
BoardConfig as ProtocolBoardConfig,
910
BoardWithPackage,
1011
} from '../../common/protocol/boards-service';
1112
import { NotificationCenter } from '../notification-center';
@@ -18,10 +19,7 @@ import { nls } from '@theia/core/lib/common';
1819
import { FrontendApplicationState } from '@theia/core/lib/common/frontend-application-state';
1920

2021
export namespace BoardsConfig {
21-
export interface Config {
22-
selectedBoard?: Board;
23-
selectedPort?: Port;
24-
}
22+
export type Config = ProtocolBoardConfig;
2523

2624
export interface Props {
2725
readonly boardsServiceProvider: BoardsServiceProvider;

Diff for: arduino-ide-extension/src/browser/contributions/board-selection.ts

+20-43
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
InstalledBoardWithPackage,
2121
AvailablePorts,
2222
Port,
23+
getBoardInfo,
2324
} from '../../common/protocol';
2425
import { SketchContribution, Command, CommandRegistry } from './contribution';
2526
import { nls } from '@theia/core/lib/common';
@@ -49,52 +50,28 @@ export class BoardSelection extends SketchContribution {
4950
override registerCommands(registry: CommandRegistry): void {
5051
registry.registerCommand(BoardSelection.Commands.GET_BOARD_INFO, {
5152
execute: async () => {
52-
const { selectedBoard, selectedPort } =
53-
this.boardsServiceProvider.boardsConfig;
54-
if (!selectedBoard) {
55-
this.messageService.info(
56-
nls.localize(
57-
'arduino/board/selectBoardForInfo',
58-
'Please select a board to obtain board info.'
59-
)
60-
);
61-
return;
62-
}
63-
if (!selectedBoard.fqbn) {
64-
this.messageService.info(
65-
nls.localize(
66-
'arduino/board/platformMissing',
67-
"The platform for the selected '{0}' board is not installed.",
68-
selectedBoard.name
69-
)
70-
);
71-
return;
72-
}
73-
if (!selectedPort) {
74-
this.messageService.info(
75-
nls.localize(
76-
'arduino/board/selectPortForInfo',
77-
'Please select a port to obtain board info.'
78-
)
79-
);
53+
const boardInfo = await getBoardInfo(
54+
this.boardsServiceProvider.boardsConfig,
55+
this.boardsService.getState()
56+
);
57+
if (typeof boardInfo === 'string') {
58+
this.messageService.info(boardInfo);
8059
return;
8160
}
82-
const boardDetails = await this.boardsService.getBoardDetails({
83-
fqbn: selectedBoard.fqbn,
84-
});
85-
if (boardDetails) {
86-
const { VID, PID } = boardDetails;
87-
const detail = `BN: ${selectedBoard.name}
61+
const { BN, VID, PID, SN } = boardInfo;
62+
const detail = `
63+
BN: ${BN}
8864
VID: ${VID}
89-
PID: ${PID}`;
90-
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
91-
message: nls.localize('arduino/board/boardInfo', 'Board Info'),
92-
title: nls.localize('arduino/board/boardInfo', 'Board Info'),
93-
type: 'info',
94-
detail,
95-
buttons: [nls.localize('vscode/issueMainService/ok', 'OK')],
96-
});
97-
}
65+
PID: ${PID}
66+
SN: ${SN}
67+
`.trim();
68+
await remote.dialog.showMessageBox(remote.getCurrentWindow(), {
69+
message: nls.localize('arduino/board/boardInfo', 'Board Info'),
70+
title: nls.localize('arduino/board/boardInfo', 'Board Info'),
71+
type: 'info',
72+
detail,
73+
buttons: [nls.localize('vscode/issueMainService/ok', 'OK')],
74+
});
9875
},
9976
});
10077
}

Diff for: arduino-ide-extension/src/common/protocol/boards-service.ts

+125
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Updatable,
1212
} from '../nls';
1313
import URI from '@theia/core/lib/common/uri';
14+
import { MaybePromise } from '@theia/core/lib/common/types';
1415

1516
export type AvailablePorts = Record<string, [Port, Array<Board>]>;
1617
export namespace AvailablePorts {
@@ -647,3 +648,127 @@ export function sanitizeFqbn(fqbn: string | undefined): string | undefined {
647648
const [vendor, arch, id] = fqbn.split(':');
648649
return `${vendor}:${arch}:${id}`;
649650
}
651+
652+
export interface BoardConfig {
653+
selectedBoard?: Board;
654+
selectedPort?: Port;
655+
}
656+
657+
export interface BoardInfo {
658+
/**
659+
* Board name. Could be `'Unknown board`'.
660+
*/
661+
BN: string;
662+
/**
663+
* Vendor ID.
664+
*/
665+
VID: string;
666+
/**
667+
* Product ID.
668+
*/
669+
PID: string;
670+
/**
671+
* Serial number.
672+
*/
673+
SN: string;
674+
}
675+
676+
export const selectBoardForInfo = nls.localize(
677+
'arduino/board/selectBoardForInfo',
678+
'Please select a board to obtain board info.'
679+
);
680+
export function platformMissing(
681+
name: string
682+
): string | BoardInfo | PromiseLike<string | BoardInfo> {
683+
return nls.localize(
684+
'arduino/board/platformMissing',
685+
"The platform for the selected '{0}' board is not installed.",
686+
name
687+
);
688+
}
689+
export const selectPortForInfo = nls.localize(
690+
'arduino/board/selectPortForInfo',
691+
'Please select a port to obtain board info.'
692+
);
693+
export const nonSerialPort = nls.localize(
694+
'arduino/board/nonSerialPort',
695+
"Non-serial port, can't obtain info."
696+
);
697+
export const noNativeSerialPort = nls.localize(
698+
'arduino/board/noNativeSerialPort',
699+
"Native serial port, can't obtain info."
700+
);
701+
export const unknownBoard = nls.localize(
702+
'arduino/board/unknownBoard',
703+
'Unknown board'
704+
);
705+
706+
/**
707+
* 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.
708+
*/
709+
export async function getBoardInfo(
710+
boardsConfig: BoardConfig,
711+
availablePorts: MaybePromise<AvailablePorts>
712+
): Promise<BoardInfo | string> {
713+
const { selectedBoard, selectedPort } = boardsConfig;
714+
if (!selectedBoard) {
715+
return selectBoardForInfo;
716+
}
717+
if (!selectedBoard.fqbn) {
718+
return platformMissing(selectedBoard.name);
719+
}
720+
if (!selectedPort) {
721+
return selectPortForInfo;
722+
}
723+
// IDE2 must show the board info based on the selected port.
724+
// https://github.com/arduino/arduino-ide/issues/1489
725+
// IDE 1.x supports only serial port protocol
726+
if (selectedPort.protocol !== 'serial') {
727+
return nonSerialPort;
728+
}
729+
const selectedPortKey = Port.keyOf(selectedPort);
730+
const state = await availablePorts;
731+
const boardListOnSelectedPort = Object.entries(state).filter(
732+
([portKey, [port]]) =>
733+
portKey === selectedPortKey && isNonNativeSerial(port)
734+
);
735+
736+
if (!boardListOnSelectedPort.length) {
737+
return noNativeSerialPort;
738+
}
739+
740+
const [, [port, boards]] = boardListOnSelectedPort[0];
741+
if (boardListOnSelectedPort.length > 1 || boards.length > 1) {
742+
console.warn(
743+
`Detected more than one available boards on the selected port : ${JSON.stringify(
744+
selectedPort
745+
)}. Detected boards were: ${JSON.stringify(
746+
boardListOnSelectedPort
747+
)}. Using the first one: ${JSON.stringify([port, boards])}`
748+
);
749+
}
750+
751+
const board = boards[0];
752+
const BN = board?.name ?? unknownBoard;
753+
const VID = readProperty('vid', port);
754+
const PID = readProperty('pid', port);
755+
const SN = readProperty('serialNumber', port);
756+
return { VID, PID, SN, BN };
757+
}
758+
759+
// serial protocol with one or many detected boards or available VID+PID properties from the port
760+
function isNonNativeSerial(port: Port): boolean {
761+
return !!(
762+
port.protocol === 'serial' &&
763+
port.properties?.['vid'] &&
764+
port.properties?.['pid']
765+
);
766+
}
767+
768+
function readProperty(property: string, port: Port): string {
769+
return falsyToNullString(port.properties?.[property]);
770+
}
771+
772+
function falsyToNullString(s: string | undefined): string {
773+
return !!s ? s : '(null)';
774+
}

Diff for: arduino-ide-extension/src/test/common/boards-service.test.ts

+138-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1+
import { Deferred } from '@theia/core/lib/common/promise-util';
2+
import { Mutable } from '@theia/core/lib/common/types';
13
import { expect } from 'chai';
2-
import { AttachedBoardsChangeEvent } from '../../common/protocol';
4+
import {
5+
AttachedBoardsChangeEvent,
6+
BoardInfo,
7+
getBoardInfo,
8+
noNativeSerialPort,
9+
nonSerialPort,
10+
platformMissing,
11+
Port,
12+
selectBoardForInfo,
13+
selectPortForInfo,
14+
unknownBoard,
15+
} from '../../common/protocol';
16+
import { firstToUpperCase } from '../../common/utils';
317

418
describe('boards-service', () => {
519
describe('AttachedBoardsChangeEvent', () => {
@@ -80,4 +94,127 @@ describe('boards-service', () => {
8094
);
8195
});
8296
});
97+
98+
describe('getBoardInfo', () => {
99+
const vid = '0x0';
100+
const pid = '0x1';
101+
const serialNumber = '1730323';
102+
const name = 'The Board';
103+
const fqbn = 'alma:korte:szolo';
104+
const selectedBoard = { name, fqbn };
105+
const selectedPort = (
106+
properties: Record<string, string> = {},
107+
protocol = 'serial'
108+
): Mutable<Port> => ({
109+
address: 'address',
110+
addressLabel: 'addressLabel',
111+
protocol,
112+
protocolLabel: firstToUpperCase(protocol),
113+
properties,
114+
});
115+
116+
it('should handle when no board is selected', async () => {
117+
const info = await getBoardInfo({}, never());
118+
expect(info).to.be.equal(selectBoardForInfo);
119+
});
120+
121+
it('should handle when no board platform is not installed', async () => {
122+
const info = await getBoardInfo({ selectedBoard: { name } }, never());
123+
expect(info).to.be.equal(platformMissing(name));
124+
});
125+
126+
it('should handle when no port is selected', async () => {
127+
const info = await getBoardInfo({ selectedBoard }, never());
128+
expect(info).to.be.equal(selectPortForInfo);
129+
});
130+
131+
it("should handle when no port protocol is not 'serial'", async () => {
132+
await Promise.allSettled(
133+
['network', 'teensy'].map(async (protocol) => {
134+
const selectedPort: Port = {
135+
address: 'address',
136+
addressLabel: 'addressLabel',
137+
protocolLabel: firstToUpperCase(protocol),
138+
protocol,
139+
};
140+
const info = await getBoardInfo(
141+
{ selectedBoard, selectedPort },
142+
never()
143+
);
144+
expect(info).to.be.equal(nonSerialPort);
145+
})
146+
);
147+
});
148+
149+
it("should detect a port as non-native serial, if protocol is 'serial' and VID/PID available", async () => {
150+
const insufficientProperties: Record<string, string>[] = [
151+
{},
152+
{ vid },
153+
{ pid },
154+
{ VID: vid, pid: pid }, // is case sensitive
155+
];
156+
for (const properties of insufficientProperties) {
157+
const port = selectedPort(properties);
158+
const info = await getBoardInfo(
159+
{ selectedBoard, selectedPort: port },
160+
{
161+
[Port.keyOf(port)]: [port, []],
162+
}
163+
);
164+
expect(info).to.be.equal(noNativeSerialPort);
165+
}
166+
});
167+
168+
it("should detect a port as non-native serial, if protocol is 'serial' and VID/PID available", async () => {
169+
const port = selectedPort({ vid, pid });
170+
const info = await getBoardInfo(
171+
{ selectedBoard, selectedPort: port },
172+
{
173+
[Port.keyOf(port)]: [port, []],
174+
}
175+
);
176+
expect(typeof info).to.be.equal('object');
177+
const boardInfo = <BoardInfo>info;
178+
expect(boardInfo.VID).to.be.equal(vid);
179+
expect(boardInfo.PID).to.be.equal(pid);
180+
expect(boardInfo.SN).to.be.equal('(null)');
181+
expect(boardInfo.BN).to.be.equal(unknownBoard);
182+
});
183+
184+
it("should show the 'SN' even if no matching board was detected for the port", async () => {
185+
const port = selectedPort({ vid, pid, serialNumber });
186+
const info = await getBoardInfo(
187+
{ selectedBoard, selectedPort: port },
188+
{
189+
[Port.keyOf(port)]: [port, []],
190+
}
191+
);
192+
expect(typeof info).to.be.equal('object');
193+
const boardInfo = <BoardInfo>info;
194+
expect(boardInfo.VID).to.be.equal(vid);
195+
expect(boardInfo.PID).to.be.equal(pid);
196+
expect(boardInfo.SN).to.be.equal(serialNumber);
197+
expect(boardInfo.BN).to.be.equal(unknownBoard);
198+
});
199+
200+
it("should use the name of the matching board as 'BN'", async () => {
201+
const port = selectedPort({ vid, pid });
202+
const info = await getBoardInfo(
203+
{ selectedBoard, selectedPort: port },
204+
{
205+
[Port.keyOf(port)]: [port, [selectedBoard]],
206+
}
207+
);
208+
expect(typeof info).to.be.equal('object');
209+
const boardInfo = <BoardInfo>info;
210+
expect(boardInfo.VID).to.be.equal(vid);
211+
expect(boardInfo.PID).to.be.equal(pid);
212+
expect(boardInfo.SN).to.be.equal('(null)');
213+
expect(boardInfo.BN).to.be.equal(selectedBoard.name);
214+
});
215+
});
83216
});
217+
218+
function never<T>(): Promise<T> {
219+
return new Deferred<T>().promise;
220+
}

0 commit comments

Comments
 (0)