Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 79eb9ff

Browse files
committedDec 3, 2021
Ttimeout on config change to prevent serial busy
1 parent dbffcd3 commit 79eb9ff

File tree

4 files changed

+121
-145
lines changed

4 files changed

+121
-145
lines changed
 

‎arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class MonitorWidget extends ReactWidget {
5757
this.scrollOptions = undefined;
5858
this.toDispose.push(this.clearOutputEmitter);
5959
this.toDispose.push(
60-
Disposable.create(() => this.serialConnection.closeSerial())
60+
Disposable.create(() => this.serialConnection.closeWStoBE())
6161
);
6262
}
6363

@@ -81,7 +81,7 @@ export class MonitorWidget extends ReactWidget {
8181

8282
protected onAfterAttach(msg: Message): void {
8383
super.onAfterAttach(msg);
84-
this.serialConnection.openSerial();
84+
this.serialConnection.openWSToBE();
8585
}
8686

8787
onCloseRequest(msg: Message): void {
@@ -169,7 +169,7 @@ export class MonitorWidget extends ReactWidget {
169169
<div className="head">
170170
<div className="send">
171171
<SerialMonitorSendInput
172-
serialConfig={this.serialConnection.serialConfig}
172+
serialConnection={this.serialConnection}
173173
resolveFocus={this.onFocusResolved}
174174
onSend={this.onSend}
175175
/>

‎arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx

+40-11
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,66 @@
11
import * as React from 'react';
22
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
33
import { Board, Port } from '../../../common/protocol/boards-service';
4-
import { SerialConfig } from '../../../common/protocol/serial-service';
54
import { isOSX } from '@theia/core/lib/common/os';
6-
import { nls } from '@theia/core/lib/common';
5+
import { DisposableCollection, nls } from '@theia/core/lib/common';
6+
import { SerialConnectionManager } from '../serial-connection-manager';
7+
import { SerialPlotter } from '../plotter/protocol';
78

89
export namespace SerialMonitorSendInput {
910
export interface Props {
10-
readonly serialConfig?: SerialConfig;
11+
readonly serialConnection: SerialConnectionManager;
1112
readonly onSend: (text: string) => void;
1213
readonly resolveFocus: (element: HTMLElement | undefined) => void;
1314
}
1415
export interface State {
1516
text: string;
17+
connected: boolean;
1618
}
1719
}
1820

1921
export class SerialMonitorSendInput extends React.Component<
2022
SerialMonitorSendInput.Props,
2123
SerialMonitorSendInput.State
2224
> {
25+
protected toDisposeBeforeUnmount = new DisposableCollection();
26+
2327
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
2428
super(props);
25-
this.state = { text: '' };
29+
this.state = { text: '', connected: false };
2630
this.onChange = this.onChange.bind(this);
2731
this.onSend = this.onSend.bind(this);
2832
this.onKeyDown = this.onKeyDown.bind(this);
2933
}
3034

35+
componentDidMount(): void {
36+
this.props.serialConnection.isBESerialConnected().then((connected) => {
37+
this.setState({ connected });
38+
});
39+
40+
this.toDisposeBeforeUnmount.pushAll([
41+
this.props.serialConnection.onRead(({ messages }) => {
42+
if (
43+
messages.command ===
44+
SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED &&
45+
'connected' in messages.data
46+
) {
47+
this.setState({ connected: messages.data.connected });
48+
}
49+
}),
50+
]);
51+
}
52+
53+
componentWillUnmount(): void {
54+
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
55+
this.toDisposeBeforeUnmount.dispose();
56+
}
57+
3158
render(): React.ReactNode {
3259
return (
3360
<input
3461
ref={this.setRef}
3562
type="text"
36-
className={`theia-input ${this.props.serialConfig ? '' : 'warning'}`}
63+
className={`theia-input ${this.state.connected ? '' : 'warning'}`}
3764
placeholder={this.placeholder}
3865
value={this.state.text}
3966
onChange={this.onChange}
@@ -43,8 +70,8 @@ export class SerialMonitorSendInput extends React.Component<
4370
}
4471

4572
protected get placeholder(): string {
46-
const { serialConfig } = this.props;
47-
if (!serialConfig) {
73+
const serialConfig = this.props.serialConnection.getConfig();
74+
if (!this.state.connected || !serialConfig) {
4875
return nls.localize(
4976
'arduino/serial/notConnected',
5077
'Not connected. Select a board and a port to connect automatically.'
@@ -55,10 +82,12 @@ export class SerialMonitorSendInput extends React.Component<
5582
'arduino/serial/message',
5683
"Message ({0} + Enter to send message to '{1}' on '{2}'",
5784
isOSX ? '⌘' : nls.localize('vscode/keybindingLabels/ctrlKey', 'Ctrl'),
58-
Board.toString(board, {
59-
useFqbn: false,
60-
}),
61-
Port.toString(port)
85+
board
86+
? Board.toString(board, {
87+
useFqbn: false,
88+
})
89+
: 'unknown',
90+
port ? Port.toString(port) : 'unknown'
6291
);
6392
}
6493

‎arduino-ide-extension/src/browser/serial/serial-connection-manager.ts

+5-68
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ export class SerialConnectionManager {
116116
}
117117

118118
/**
119-
* Set the config passing only the properties that has changed. If some has changed and the serial is open,
120-
* we try to reconnect
119+
* Updated the config in the BE passing only the properties that has changed.
120+
* BE will create a new connection if needed.
121121
*
122122
* @param newConfig the porperties of the config that has changed
123123
*/
@@ -150,10 +150,6 @@ export class SerialConnectionManager {
150150
return this.wsPort;
151151
}
152152

153-
isWebSocketConnected(): boolean {
154-
return !!this.webSocket?.url;
155-
}
156-
157153
protected handleWebSocketChanged(wsPort: number): void {
158154
this.wsPort = wsPort;
159155
}
@@ -168,7 +164,7 @@ export class SerialConnectionManager {
168164
return await this.serialService.isSerialPortOpen();
169165
}
170166

171-
openSerial(): void {
167+
openWSToBE(): void {
172168
if (!isSerialConfig(this.config)) {
173169
this.messageService.error(
174170
`Please select a board and a port to open the serial connection.`
@@ -189,7 +185,7 @@ export class SerialConnectionManager {
189185
}
190186
}
191187

192-
closeSerial(): void {
188+
closeWStoBE(): void {
193189
if (this.webSocket) {
194190
try {
195191
this.webSocket.close();
@@ -297,28 +293,6 @@ export class SerialConnectionManager {
297293
}
298294
}
299295

300-
// async connect(): Promise<Status> {
301-
// if (await this.serialService.isSerialPortOpen())
302-
// return Status.ALREADY_CONNECTED;
303-
// if (!isSerialConfig(this.config)) return Status.NOT_CONNECTED;
304-
305-
// console.info(
306-
// `>>> Creating serial connection for ${Board.toString(
307-
// this.config.board
308-
// )} on port ${Port.toString(this.config.port)}...`
309-
// );
310-
// const connectStatus = await this.serialService.connect(this.config);
311-
// if (Status.isOK(connectStatus)) {
312-
// console.info(
313-
// `<<< Serial connection created for ${Board.toString(this.config.board, {
314-
// useFqbn: false,
315-
// })} on port ${Port.toString(this.config.port)}.`
316-
// );
317-
// }
318-
319-
// return Status.isOK(connectStatus);
320-
// }
321-
322296
async reconnectAfterUpload(): Promise<void> {
323297
try {
324298
if (isSerialConfig(this.config)) {
@@ -339,31 +313,6 @@ export class SerialConnectionManager {
339313
}
340314
}
341315

342-
async disconnectSerialPort(): Promise<Status> {
343-
if (!(await this.serialService.isSerialPortOpen())) {
344-
return Status.OK;
345-
}
346-
347-
console.log('>>> Disposing existing serial connection...');
348-
const status = await this.serialService.disconnect();
349-
if (Status.isOK(status)) {
350-
console.log(
351-
`<<< Disposed serial connection. Was: ${Serial.Config.toString(
352-
this.config
353-
)}`
354-
);
355-
this.wsPort = undefined;
356-
} else {
357-
console.warn(
358-
`<<< Could not dispose serial connection. Activate connection: ${Serial.Config.toString(
359-
this.config
360-
)}`
361-
);
362-
}
363-
364-
return status;
365-
}
366-
367316
/**
368317
* Sends the data to the connected serial port.
369318
* The desired EOL is appended to `data`, you do not have to add it.
@@ -384,7 +333,7 @@ export class SerialConnectionManager {
384333
return this.onConnectionChangedEmitter.event;
385334
}
386335

387-
get onRead(): Event<{ messages: string[] }> {
336+
get onRead(): Event<{ messages: any }> {
388337
return this.onReadEmitter.event;
389338
}
390339

@@ -399,18 +348,6 @@ export class SerialConnectionManager {
399348
}
400349

401350
export namespace Serial {
402-
export enum Type {
403-
Monitor = 'Monitor',
404-
Plotter = 'Plotter',
405-
}
406-
407-
/**
408-
* The state represents which types of connections are needed by the client, and it should match whether the Serial Monitor
409-
* or the Serial Plotter are open or not in the GUI. It's an array cause it's possible to have both, none or only one of
410-
* them open
411-
*/
412-
export type State = Serial.Type[];
413-
414351
export namespace Config {
415352
export function toString(config: Partial<SerialConfig>): string {
416353
if (!isSerialConfig(config)) return '';

‎arduino-ide-extension/src/node/serial/serial-service-impl.ts

+73-63
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,18 @@ export class SerialServiceImpl implements SerialService {
176176
'error',
177177
((error: Error) => {
178178
const serialError = ErrorWithCode.toSerialError(error, serialConfig);
179-
this.disconnect(serialError).then(() => {
180-
if (this.theiaFEClient) {
181-
this.theiaFEClient.notifyError(serialError);
182-
}
183-
if (serialError.code === undefined) {
184-
// Log the original, unexpected error.
185-
this.logger.error(error);
186-
}
187-
});
179+
if (serialError.code !== SerialError.ErrorCodes.CLIENT_CANCEL) {
180+
this.disconnect(serialError).then(() => {
181+
if (this.theiaFEClient) {
182+
this.theiaFEClient.notifyError(serialError);
183+
}
184+
});
185+
}
186+
if (serialError.code === undefined) {
187+
// Log the original, unexpected error.
188+
this.logger.error(error);
189+
}
190+
// });
188191
}).bind(this)
189192
);
190193

@@ -231,27 +234,6 @@ export class SerialServiceImpl implements SerialService {
231234
// empty the queue every 32ms (~30fps)
232235
this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32);
233236

234-
// converts 'ab\nc\nd' => [ab\n,c\n,d]
235-
const stringToArray = (string: string, separator = '\n') => {
236-
const retArray: string[] = [];
237-
238-
let prevChar = separator;
239-
240-
for (let i = 0; i < string.length; i++) {
241-
const currChar = string[i];
242-
243-
if (prevChar === separator) {
244-
retArray.push(currChar);
245-
} else {
246-
const lastWord = retArray[retArray.length - 1];
247-
retArray[retArray.length - 1] = lastWord + currChar;
248-
}
249-
250-
prevChar = currChar;
251-
}
252-
return retArray;
253-
};
254-
255237
duplex.on(
256238
'data',
257239
((resp: StreamingOpenResponse) => {
@@ -301,41 +283,48 @@ export class SerialServiceImpl implements SerialService {
301283
}
302284

303285
public async disconnect(reason?: SerialError): Promise<Status> {
304-
try {
305-
if (this.onMessageReceived) {
306-
this.onMessageReceived.dispose();
307-
this.onMessageReceived = null;
308-
}
309-
if (this.flushMessagesInterval) {
310-
clearInterval(this.flushMessagesInterval);
311-
this.flushMessagesInterval = null;
312-
}
286+
return new Promise<Status>((resolve, reject) => {
287+
try {
288+
if (this.onMessageReceived) {
289+
this.onMessageReceived.dispose();
290+
this.onMessageReceived = null;
291+
}
292+
if (this.flushMessagesInterval) {
293+
clearInterval(this.flushMessagesInterval);
294+
this.flushMessagesInterval = null;
295+
}
313296

314-
if (
315-
!this.serialConnection &&
316-
reason &&
317-
reason.code === SerialError.ErrorCodes.CLIENT_CANCEL
318-
) {
319-
return Status.OK;
320-
}
321-
this.logger.info('>>> Disposing serial connection...');
322-
if (!this.serialConnection) {
323-
this.logger.warn('<<< Not connected. Nothing to dispose.');
324-
return Status.NOT_CONNECTED;
297+
if (
298+
!this.serialConnection &&
299+
reason &&
300+
reason.code === SerialError.ErrorCodes.CLIENT_CANCEL
301+
) {
302+
return Status.OK;
303+
}
304+
this.logger.info('>>> Disposing serial connection...');
305+
if (!this.serialConnection) {
306+
this.logger.warn('<<< Not connected. Nothing to dispose.');
307+
return Status.NOT_CONNECTED;
308+
}
309+
const { duplex, config } = this.serialConnection;
310+
311+
this.logger.info(
312+
`<<< Disposed serial connection for ${Board.toString(config.board, {
313+
useFqbn: false,
314+
})} on port ${Port.toString(config.port)}.`
315+
);
316+
317+
duplex.cancel();
318+
} finally {
319+
this.serialConnection = undefined;
320+
this.updateWsConfigParam({ connected: !!this.serialConnection });
321+
this.messages.length = 0;
322+
323+
setTimeout(() => {
324+
resolve(Status.OK);
325+
}, 200);
325326
}
326-
const { duplex, config } = this.serialConnection;
327-
duplex.cancel();
328-
this.logger.info(
329-
`<<< Disposed serial connection for ${Board.toString(config.board, {
330-
useFqbn: false,
331-
})} on port ${Port.toString(config.port)}.`
332-
);
333-
this.serialConnection = undefined;
334-
return Status.OK;
335-
} finally {
336-
this.updateWsConfigParam({ connected: !!this.serialConnection });
337-
this.messages.length = 0;
338-
}
327+
});
339328
}
340329

341330
async sendMessageToSerial(message: string): Promise<Status> {
@@ -366,3 +355,24 @@ export class SerialServiceImpl implements SerialService {
366355
}
367356
}
368357
}
358+
359+
// converts 'ab\nc\nd' => [ab\n,c\n,d]
360+
function stringToArray(string: string, separator = '\n') {
361+
const retArray: string[] = [];
362+
363+
let prevChar = separator;
364+
365+
for (let i = 0; i < string.length; i++) {
366+
const currChar = string[i];
367+
368+
if (prevChar === separator) {
369+
retArray.push(currChar);
370+
} else {
371+
const lastWord = retArray[retArray.length - 1];
372+
retArray[retArray.length - 1] = lastWord + currChar;
373+
}
374+
375+
prevChar = currChar;
376+
}
377+
return retArray;
378+
}

0 commit comments

Comments
 (0)
Please sign in to comment.