Skip to content

Commit cc5cf3b

Browse files
fix 180: prevent erroneous "auto-reconnect"(s) in board selector (#1328)
* ensure auto-select doesn't select wrong port prevent auto-select of unattached boards Co-authored-by: Francesco Spissu <[email protected]> * clean up * add "uploadInProgress" prop to boards change event * remove unused methods and deps * [WIP]: leverage upload event to derived new port * remove timeout * refine port selection logic * clean up naming * refine port selection logic & add delayed clean up * renaming & refactoring * method syntax & remove unnecessary methods
1 parent 125bd64 commit cc5cf3b

File tree

5 files changed

+122
-21
lines changed

5 files changed

+122
-21
lines changed

arduino-ide-extension/src/browser/boards/boards-service-provider.ts

+106-21
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
6565
protected _availablePorts: Port[] = [];
6666
protected _availableBoards: AvailableBoard[] = [];
6767

68+
private lastBoardsConfigOnUpload: BoardsConfig.Config | undefined;
69+
private lastAvailablePortsOnUpload: Port[] | undefined;
70+
private boardConfigToAutoSelect: BoardsConfig.Config | undefined;
71+
6872
/**
6973
* Unlike `onAttachedBoardsChanged` this event fires when the user modifies the selected board in the IDE.\
7074
* This event also fires, when the boards package was not available for the currently selected board,
@@ -112,6 +116,84 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
112116
return this._reconciled.promise;
113117
}
114118

119+
snapshotBoardDiscoveryOnUpload(): void {
120+
this.lastBoardsConfigOnUpload = this._boardsConfig;
121+
this.lastAvailablePortsOnUpload = this._availablePorts;
122+
}
123+
124+
clearBoardDiscoverySnapshot(): void {
125+
this.lastBoardsConfigOnUpload = undefined;
126+
this.lastAvailablePortsOnUpload = undefined;
127+
}
128+
129+
private portToAutoSelectCanBeDerived(): boolean {
130+
return Boolean(
131+
this.lastBoardsConfigOnUpload && this.lastAvailablePortsOnUpload
132+
);
133+
}
134+
135+
attemptPostUploadAutoSelect(): void {
136+
setTimeout(() => {
137+
if (this.portToAutoSelectCanBeDerived()) {
138+
this.attemptAutoSelect({
139+
ports: this._availablePorts,
140+
boards: this._availableBoards,
141+
});
142+
}
143+
}, 2000); // 2 second delay same as IDE 1.8
144+
}
145+
146+
private attemptAutoSelect(
147+
newState: AttachedBoardsChangeEvent['newState']
148+
): void {
149+
this.deriveBoardConfigToAutoSelect(newState);
150+
this.tryReconnect();
151+
}
152+
153+
private deriveBoardConfigToAutoSelect(
154+
newState: AttachedBoardsChangeEvent['newState']
155+
): void {
156+
if (!this.portToAutoSelectCanBeDerived()) {
157+
this.boardConfigToAutoSelect = undefined;
158+
return;
159+
}
160+
161+
const oldPorts = this.lastAvailablePortsOnUpload!;
162+
const { ports: newPorts, boards: newBoards } = newState;
163+
164+
const appearedPorts =
165+
oldPorts.length > 0
166+
? newPorts.filter((newPort: Port) =>
167+
oldPorts.every((oldPort: Port) => !Port.sameAs(newPort, oldPort))
168+
)
169+
: newPorts;
170+
171+
for (const port of appearedPorts) {
172+
const boardOnAppearedPort = newBoards.find((board: Board) =>
173+
Port.sameAs(board.port, port)
174+
);
175+
176+
const lastBoardsConfigOnUpload = this.lastBoardsConfigOnUpload!;
177+
178+
if (
179+
boardOnAppearedPort &&
180+
lastBoardsConfigOnUpload.selectedBoard &&
181+
Board.sameAs(
182+
boardOnAppearedPort,
183+
lastBoardsConfigOnUpload.selectedBoard
184+
)
185+
) {
186+
this.clearBoardDiscoverySnapshot();
187+
188+
this.boardConfigToAutoSelect = {
189+
selectedBoard: boardOnAppearedPort,
190+
selectedPort: port,
191+
};
192+
return;
193+
}
194+
}
195+
}
196+
115197
protected notifyAttachedBoardsChanged(
116198
event: AttachedBoardsChangeEvent
117199
): void {
@@ -120,10 +202,18 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
120202
this.logger.info(AttachedBoardsChangeEvent.toString(event));
121203
this.logger.info('------------------------------------------');
122204
}
205+
123206
this._attachedBoards = event.newState.boards;
124207
this._availablePorts = event.newState.ports;
125208
this.onAvailablePortsChangedEmitter.fire(this._availablePorts);
126-
this.reconcileAvailableBoards().then(() => this.tryReconnect());
209+
this.reconcileAvailableBoards().then(() => {
210+
const { uploadInProgress } = event;
211+
// avoid attempting "auto-selection" while an
212+
// upload is in progress
213+
if (!uploadInProgress) {
214+
this.attemptAutoSelect(event.newState);
215+
}
216+
});
127217
}
128218

129219
protected notifyPlatformInstalled(event: { item: BoardsPackage }): void {
@@ -239,24 +329,12 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
239329
return true;
240330
}
241331
}
242-
// If we could not find an exact match, we compare the board FQBN-name pairs and ignore the port, as it might have changed.
243-
// See documentation on `latestValidBoardsConfig`.
244-
for (const board of this.availableBoards.filter(
245-
({ state }) => state !== AvailableBoard.State.incomplete
246-
)) {
247-
if (
248-
this.latestValidBoardsConfig.selectedBoard.fqbn === board.fqbn &&
249-
this.latestValidBoardsConfig.selectedBoard.name === board.name &&
250-
this.latestValidBoardsConfig.selectedPort.protocol ===
251-
board.port?.protocol
252-
) {
253-
this.boardsConfig = {
254-
...this.latestValidBoardsConfig,
255-
selectedPort: board.port,
256-
};
257-
return true;
258-
}
259-
}
332+
333+
if (!this.boardConfigToAutoSelect) return false;
334+
335+
this.boardsConfig = this.boardConfigToAutoSelect;
336+
this.boardConfigToAutoSelect = undefined;
337+
return true;
260338
}
261339
return false;
262340
}
@@ -450,6 +528,11 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
450528
const board = attachedBoards.find(({ port }) =>
451529
Port.sameAs(boardPort, port)
452530
);
531+
// "board" will always be falsey for
532+
// port that was originally mapped
533+
// to unknown board and then selected
534+
// manually by user
535+
453536
const lastSelectedBoard = await this.getLastSelectedBoardOnPort(
454537
boardPort
455538
);
@@ -468,7 +551,9 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
468551
availableBoard = {
469552
...lastSelectedBoard,
470553
state: AvailableBoard.State.guessed,
471-
selected: BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard),
554+
selected:
555+
BoardsConfig.Config.sameAs(boardsConfig, lastSelectedBoard) &&
556+
Port.sameAs(boardPort, boardsConfig.selectedPort), // to avoid double selection
472557
port: boardPort,
473558
};
474559
} else {
@@ -483,7 +568,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
483568

484569
if (
485570
boardsConfig.selectedBoard &&
486-
!availableBoards.some(({ selected }) => selected)
571+
availableBoards.every(({ selected }) => !selected)
487572
) {
488573
// If the selected board has the same port of an unknown board
489574
// that is already in availableBoards we might get a duplicate port.

arduino-ide-extension/src/browser/contributions/upload-sketch.ts

+2
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ export class UploadSketch extends CoreServiceContribution {
190190
// toggle the toolbar button and menu item state.
191191
// uploadInProgress will be set to false whether the upload fails or not
192192
this.uploadInProgress = true;
193+
this.boardsServiceProvider.snapshotBoardDiscoveryOnUpload();
193194
this.onDidChangeEmitter.fire();
194195
this.clearVisibleNotification();
195196

@@ -243,6 +244,7 @@ export class UploadSketch extends CoreServiceContribution {
243244
this.handleError(e);
244245
} finally {
245246
this.uploadInProgress = false;
247+
this.boardsServiceProvider.attemptPostUploadAutoSelect();
246248
this.onDidChangeEmitter.fire();
247249
}
248250
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export namespace AvailablePorts {
2525
export interface AttachedBoardsChangeEvent {
2626
readonly oldState: Readonly<{ boards: Board[]; ports: Port[] }>;
2727
readonly newState: Readonly<{ boards: Board[]; ports: Port[] }>;
28+
readonly uploadInProgress: boolean;
2829
}
2930
export namespace AttachedBoardsChangeEvent {
3031
export function isEmpty(event: AttachedBoardsChangeEvent): boolean {

arduino-ide-extension/src/node/board-discovery.ts

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export class BoardDiscovery
5454
private readonly onStreamDidCancelEmitter = new Emitter<void>(); // when the watcher is canceled by the IDE2
5555
private readonly toDisposeOnStopWatch = new DisposableCollection();
5656

57+
private uploadInProgress = false;
58+
5759
/**
5860
* Keys are the `address` of the ports.
5961
*
@@ -123,6 +125,10 @@ export class BoardDiscovery
123125
});
124126
}
125127

128+
setUploadInProgress(uploadAttemptInProgress: boolean): void {
129+
this.uploadInProgress = uploadAttemptInProgress;
130+
}
131+
126132
private createTimeout(
127133
after: number,
128134
onTimeout: (error: Error) => void
@@ -318,6 +324,7 @@ export class BoardDiscovery
318324
ports: newAvailablePorts,
319325
boards: newAttachedBoards,
320326
},
327+
uploadInProgress: this.uploadInProgress,
321328
};
322329

323330
this._availablePorts = newState;

arduino-ide-extension/src/node/core-service-impl.ts

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { Instance } from './cli-protocol/cc/arduino/cli/commands/v1/common_pb';
3434
import { firstToUpperCase, notEmpty } from '../common/utils';
3535
import { ServiceError } from './service-error';
3636
import { ExecuteWithProgress, ProgressResponse } from './grpc-progressible';
37+
import { BoardDiscovery } from './board-discovery';
3738

3839
namespace Uploadable {
3940
export type Request = UploadRequest | UploadUsingProgrammerRequest;
@@ -51,6 +52,9 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
5152
@inject(CommandService)
5253
private readonly commandService: CommandService;
5354

55+
@inject(BoardDiscovery)
56+
private readonly boardDiscovery: BoardDiscovery;
57+
5458
async compile(options: CoreService.Options.Compile): Promise<void> {
5559
const coreClient = await this.coreClient;
5660
const { client, instance } = coreClient;
@@ -378,6 +382,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
378382
fqbn?: string | undefined;
379383
port?: Port | undefined;
380384
}): Promise<void> {
385+
this.boardDiscovery.setUploadInProgress(true);
381386
return this.monitorManager.notifyUploadStarted(fqbn, port);
382387
}
383388

@@ -388,6 +393,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
388393
fqbn?: string | undefined;
389394
port?: Port | undefined;
390395
}): Promise<Status> {
396+
this.boardDiscovery.setUploadInProgress(false);
391397
return this.monitorManager.notifyUploadFinished(fqbn, port);
392398
}
393399

0 commit comments

Comments
 (0)