Skip to content

Commit 54a67fc

Browse files
fstasiAlberto Iannaccone
and
Alberto Iannaccone
committed
Improve Serial Monitor Performances (#524)
Co-authored-by: Alberto Iannaccone <[email protected]>
1 parent 7f8b227 commit 54a67fc

13 files changed

+868
-461
lines changed

Diff for: arduino-ide-extension/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
1919
},
2020
"dependencies": {
21-
"@grpc/grpc-js": "^1.1.1",
21+
"@grpc/grpc-js": "^1.3.7",
2222
"@theia/application-package": "next",
2323
"@theia/core": "next",
2424
"@theia/editor": "next",
@@ -64,6 +64,7 @@
6464
"fuzzy": "^0.1.3",
6565
"glob": "^7.1.6",
6666
"google-protobuf": "^3.11.4",
67+
"grpc": "^1.24.11",
6768
"hash.js": "^1.1.7",
6869
"is-valid-path": "^0.1.1",
6970
"js-yaml": "^3.13.1",
@@ -78,6 +79,7 @@
7879
"react-disable": "^0.1.0",
7980
"react-select": "^3.0.4",
8081
"react-tabs": "^3.1.2",
82+
"react-window": "^1.8.6",
8183
"semver": "^7.3.2",
8284
"string-natural-compare": "^2.0.3",
8385
"temp": "^0.9.1",
@@ -89,6 +91,7 @@
8991
"@types/chai": "^4.2.7",
9092
"@types/chai-string": "^1.4.2",
9193
"@types/mocha": "^5.2.7",
94+
"@types/react-window": "^1.8.5",
9295
"chai": "^4.2.0",
9396
"chai-string": "^1.5.0",
9497
"decompress": "^4.2.0",
@@ -97,6 +100,7 @@
97100
"download": "^7.1.0",
98101
"grpc_tools_node_protoc_ts": "^4.1.0",
99102
"mocha": "^7.0.0",
103+
"mockdate": "^3.0.5",
100104
"moment": "^2.24.0",
101105
"protoc": "^1.0.4",
102106
"shelljs": "^0.8.3",

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+3-13
Original file line numberDiff line numberDiff line change
@@ -400,24 +400,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
400400
bind(MonitorService)
401401
.toDynamicValue((context) => {
402402
const connection = context.container.get(WebSocketConnectionProvider);
403-
const client = context.container.get(MonitorServiceClientImpl);
403+
const client =
404+
context.container.get<MonitorServiceClient>(MonitorServiceClient);
404405
return connection.createProxy(MonitorServicePath, client);
405406
})
406407
.inSingletonScope();
407408
bind(MonitorConnection).toSelf().inSingletonScope();
408409
// Serial monitor service client to receive and delegate notifications from the backend.
409-
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
410-
bind(MonitorServiceClient)
411-
.toDynamicValue((context) => {
412-
const client = context.container.get(MonitorServiceClientImpl);
413-
WebSocketConnectionProvider.createProxy(
414-
context.container,
415-
MonitorServicePath,
416-
client
417-
);
418-
return client;
419-
})
420-
.inSingletonScope();
410+
bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope();
421411

422412
bind(WorkspaceService).toSelf().inSingletonScope();
423413
rebind(TheiaWorkspaceService).toService(WorkspaceService);

Diff for: arduino-ide-extension/src/browser/monitor/monitor-connection.ts

+122-117
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
MonitorConfig,
99
MonitorError,
1010
Status,
11+
MonitorServiceClient,
1112
} from '../../common/protocol/monitor-service';
1213
import { BoardsServiceProvider } from '../boards/boards-service-provider';
1314
import {
@@ -16,7 +17,6 @@ import {
1617
BoardsService,
1718
AttachedBoardsChangeEvent,
1819
} from '../../common/protocol/boards-service';
19-
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
2020
import { BoardsConfig } from '../boards/boards-config';
2121
import { MonitorModel } from './monitor-model';
2222
import { NotificationCenter } from '../notification-center';
@@ -29,8 +29,8 @@ export class MonitorConnection {
2929
@inject(MonitorService)
3030
protected readonly monitorService: MonitorService;
3131

32-
@inject(MonitorServiceClientImpl)
33-
protected readonly monitorServiceClient: MonitorServiceClientImpl;
32+
@inject(MonitorServiceClient)
33+
protected readonly monitorServiceClient: MonitorServiceClient;
3434

3535
@inject(BoardsService)
3636
protected readonly boardsService: BoardsService;
@@ -59,7 +59,7 @@ export class MonitorConnection {
5959
/**
6060
* This emitter forwards all read events **iff** the connection is established.
6161
*/
62-
protected readonly onReadEmitter = new Emitter<{ message: string }>();
62+
protected readonly onReadEmitter = new Emitter<{ messages: string[] }>();
6363

6464
/**
6565
* Array for storing previous monitor errors received from the server, and based on the number of elements in this array,
@@ -71,112 +71,15 @@ export class MonitorConnection {
7171

7272
@postConstruct()
7373
protected init(): void {
74-
this.monitorServiceClient.onError(async (error) => {
75-
let shouldReconnect = false;
76-
if (this.state) {
77-
const { code, config } = error;
78-
const { board, port } = config;
79-
const options = { timeout: 3000 };
80-
switch (code) {
81-
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
82-
console.debug(
83-
`Connection was canceled by client: ${MonitorConnection.State.toString(
84-
this.state
85-
)}.`
86-
);
87-
break;
88-
}
89-
case MonitorError.ErrorCodes.DEVICE_BUSY: {
90-
this.messageService.warn(
91-
`Connection failed. Serial port is busy: ${Port.toString(port)}.`,
92-
options
93-
);
94-
shouldReconnect = this.autoConnect;
95-
this.monitorErrors.push(error);
96-
break;
97-
}
98-
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
99-
this.messageService.info(
100-
`Disconnected ${Board.toString(board, {
101-
useFqbn: false,
102-
})} from ${Port.toString(port)}.`,
103-
options
104-
);
105-
break;
106-
}
107-
case undefined: {
108-
this.messageService.error(
109-
`Unexpected error. Reconnecting ${Board.toString(
110-
board
111-
)} on port ${Port.toString(port)}.`,
112-
options
113-
);
114-
console.error(JSON.stringify(error));
115-
shouldReconnect = this.connected && this.autoConnect;
116-
break;
117-
}
118-
}
119-
const oldState = this.state;
120-
this.state = undefined;
121-
this.onConnectionChangedEmitter.fire(this.state);
122-
if (shouldReconnect) {
123-
if (this.monitorErrors.length >= 10) {
124-
this.messageService.warn(
125-
`Failed to reconnect ${Board.toString(board, {
126-
useFqbn: false,
127-
})} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(
128-
port
129-
)} serial port is busy. after 10 consecutive attempts.`
130-
);
131-
this.monitorErrors.length = 0;
132-
} else {
133-
const attempts = this.monitorErrors.length || 1;
134-
if (this.reconnectTimeout !== undefined) {
135-
// Clear the previous timer.
136-
window.clearTimeout(this.reconnectTimeout);
137-
}
138-
const timeout = attempts * 1000;
139-
this.messageService.warn(
140-
`Reconnecting ${Board.toString(board, {
141-
useFqbn: false,
142-
})} to ${Port.toString(port)} in ${attempts} seconds...`,
143-
{ timeout }
144-
);
145-
this.reconnectTimeout = window.setTimeout(
146-
() => this.connect(oldState.config),
147-
timeout
148-
);
149-
}
150-
}
151-
}
152-
});
74+
this.monitorServiceClient.onMessage(this.handleMessage.bind(this));
75+
this.monitorServiceClient.onError(this.handleError.bind(this));
15376
this.boardsServiceProvider.onBoardsConfigChanged(
15477
this.handleBoardConfigChange.bind(this)
15578
);
156-
this.notificationCenter.onAttachedBoardsChanged((event) => {
157-
if (this.autoConnect && this.connected) {
158-
const { boardsConfig } = this.boardsServiceProvider;
159-
if (
160-
this.boardsServiceProvider.canUploadTo(boardsConfig, {
161-
silent: false,
162-
})
163-
) {
164-
const { attached } = AttachedBoardsChangeEvent.diff(event);
165-
if (
166-
attached.boards.some(
167-
(board) =>
168-
!!board.port && BoardsConfig.Config.sameAs(boardsConfig, board)
169-
)
170-
) {
171-
const { selectedBoard: board, selectedPort: port } = boardsConfig;
172-
const { baudRate } = this.monitorModel;
173-
this.disconnect().then(() =>
174-
this.connect({ board, port, baudRate })
175-
);
176-
}
177-
}
178-
}
179-
});
79+
this.notificationCenter.onAttachedBoardsChanged(
80+
this.handleAttachedBoardsChanged.bind(this)
81+
);
82+
18083
// Handles the `baudRate` changes by reconnecting if required.
18184
this.monitorModel.onChange(({ property }) => {
18285
if (property === 'baudRate' && this.autoConnect && this.connected) {
@@ -186,6 +89,14 @@ export class MonitorConnection {
18689
});
18790
}
18891

92+
async handleMessage(port: string): Promise<void> {
93+
const w = new WebSocket(`ws://localhost:${port}`);
94+
w.onmessage = (res) => {
95+
const messages = JSON.parse(res.data);
96+
this.onReadEmitter.fire({ messages });
97+
};
98+
}
99+
189100
get connected(): boolean {
190101
return !!this.state;
191102
}
@@ -217,6 +128,109 @@ export class MonitorConnection {
217128
}
218129
}
219130

131+
handleError(error: MonitorError): void {
132+
let shouldReconnect = false;
133+
if (this.state) {
134+
const { code, config } = error;
135+
const { board, port } = config;
136+
const options = { timeout: 3000 };
137+
switch (code) {
138+
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
139+
console.debug(
140+
`Connection was canceled by client: ${MonitorConnection.State.toString(
141+
this.state
142+
)}.`
143+
);
144+
break;
145+
}
146+
case MonitorError.ErrorCodes.DEVICE_BUSY: {
147+
this.messageService.warn(
148+
`Connection failed. Serial port is busy: ${Port.toString(port)}.`,
149+
options
150+
);
151+
shouldReconnect = this.autoConnect;
152+
this.monitorErrors.push(error);
153+
break;
154+
}
155+
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
156+
this.messageService.info(
157+
`Disconnected ${Board.toString(board, {
158+
useFqbn: false,
159+
})} from ${Port.toString(port)}.`,
160+
options
161+
);
162+
break;
163+
}
164+
case undefined: {
165+
this.messageService.error(
166+
`Unexpected error. Reconnecting ${Board.toString(
167+
board
168+
)} on port ${Port.toString(port)}.`,
169+
options
170+
);
171+
console.error(JSON.stringify(error));
172+
shouldReconnect = this.connected && this.autoConnect;
173+
break;
174+
}
175+
}
176+
const oldState = this.state;
177+
this.state = undefined;
178+
this.onConnectionChangedEmitter.fire(this.state);
179+
if (shouldReconnect) {
180+
if (this.monitorErrors.length >= 10) {
181+
this.messageService.warn(
182+
`Failed to reconnect ${Board.toString(board, {
183+
useFqbn: false,
184+
})} to the the serial-monitor after 10 consecutive attempts. The ${Port.toString(
185+
port
186+
)} serial port is busy. after 10 consecutive attempts.`
187+
);
188+
this.monitorErrors.length = 0;
189+
} else {
190+
const attempts = this.monitorErrors.length || 1;
191+
if (this.reconnectTimeout !== undefined) {
192+
// Clear the previous timer.
193+
window.clearTimeout(this.reconnectTimeout);
194+
}
195+
const timeout = attempts * 1000;
196+
this.messageService.warn(
197+
`Reconnecting ${Board.toString(board, {
198+
useFqbn: false,
199+
})} to ${Port.toString(port)} in ${attempts} seconds...`,
200+
{ timeout }
201+
);
202+
this.reconnectTimeout = window.setTimeout(
203+
() => this.connect(oldState.config),
204+
timeout
205+
);
206+
}
207+
}
208+
}
209+
}
210+
211+
handleAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void {
212+
if (this.autoConnect && this.connected) {
213+
const { boardsConfig } = this.boardsServiceProvider;
214+
if (
215+
this.boardsServiceProvider.canUploadTo(boardsConfig, {
216+
silent: false,
217+
})
218+
) {
219+
const { attached } = AttachedBoardsChangeEvent.diff(event);
220+
if (
221+
attached.boards.some(
222+
(board) =>
223+
!!board.port && BoardsConfig.Config.sameAs(boardsConfig, board)
224+
)
225+
) {
226+
const { selectedBoard: board, selectedPort: port } = boardsConfig;
227+
const { baudRate } = this.monitorModel;
228+
this.disconnect().then(() => this.connect({ board, port, baudRate }));
229+
}
230+
}
231+
}
232+
}
233+
220234
async connect(config: MonitorConfig): Promise<Status> {
221235
if (this.connected) {
222236
const disconnectStatus = await this.disconnect();
@@ -231,15 +245,6 @@ export class MonitorConnection {
231245
);
232246
const connectStatus = await this.monitorService.connect(config);
233247
if (Status.isOK(connectStatus)) {
234-
const requestMessage = () => {
235-
this.monitorService.request().then(({ message }) => {
236-
if (this.connected) {
237-
this.onReadEmitter.fire({ message });
238-
requestMessage();
239-
}
240-
});
241-
};
242-
requestMessage();
243248
this.state = { config };
244249
console.info(
245250
`<<< Serial monitor connection created for ${Board.toString(
@@ -300,7 +305,7 @@ export class MonitorConnection {
300305
return this.onConnectionChangedEmitter.event;
301306
}
302307

303-
get onRead(): Event<{ message: string }> {
308+
get onRead(): Event<{ messages: string[] }> {
304309
return this.onReadEmitter.event;
305310
}
306311

Diff for: arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts

+7
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ export class MonitorServiceClientImpl implements MonitorServiceClient {
1010
protected readonly onErrorEmitter = new Emitter<MonitorError>();
1111
readonly onError = this.onErrorEmitter.event;
1212

13+
protected readonly onMessageEmitter = new Emitter<string>();
14+
readonly onMessage = this.onMessageEmitter.event;
15+
1316
notifyError(error: MonitorError): void {
1417
this.onErrorEmitter.fire(error);
1518
}
19+
20+
notifyMessage(message: string): void {
21+
this.onMessageEmitter.fire(message);
22+
}
1623
}

0 commit comments

Comments
 (0)