Skip to content

Commit 4c58d9b

Browse files
author
Alberto Iannaccone
committed
WIP connect serial plotter app with websocket
1 parent f2d1c4e commit 4c58d9b

File tree

6 files changed

+71
-33
lines changed

6 files changed

+71
-33
lines changed

arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ import {
258258
PlotterPath,
259259
PlotterService,
260260
} from '../common/protocol/plotter-service';
261+
import { PlotterConnection } from './plotter/plotter-connection';
261262

262263
const ElementQueries = require('css-element-queries/src/ElementQueries');
263264

@@ -409,6 +410,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
409410
})
410411
.inSingletonScope();
411412
bind(MonitorConnection).toSelf().inSingletonScope();
413+
bind(PlotterConnection).toSelf().inSingletonScope();
412414
// Serial monitor service client to receive and delegate notifications from the backend.
413415
bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope();
414416

arduino-ide-extension/src/browser/monitor/monitor-connection.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { BoardsConfig } from '../boards/boards-config';
2121
import { MonitorModel } from './monitor-model';
2222
import { NotificationCenter } from '../notification-center';
23+
import { Disposable } from '@theia/core';
2324

2425
@injectable()
2526
export class MonitorConnection {
@@ -47,7 +48,8 @@ export class MonitorConnection {
4748
@inject(FrontendApplicationStateService)
4849
protected readonly applicationState: FrontendApplicationStateService;
4950

50-
protected state: MonitorConnection.State | undefined;
51+
protected _state: MonitorConnection.State | undefined;
52+
5153
/**
5254
* Note: The idea is to toggle this property from the UI (`Monitor` view)
5355
* and the boards config and the boards attachment/detachment logic can be at on place, here.
@@ -61,6 +63,8 @@ export class MonitorConnection {
6163
*/
6264
protected readonly onReadEmitter = new Emitter<{ messages: string[] }>();
6365

66+
protected toDisposeOnDisconnect: Disposable[] = [];
67+
6468
/**
6569
* Array for storing previous monitor errors received from the server, and based on the number of elements in this array,
6670
* we adjust the reconnection delay.
@@ -71,15 +75,6 @@ export class MonitorConnection {
7175

7276
@postConstruct()
7377
protected init(): void {
74-
this.monitorServiceClient.onMessage(this.handleMessage.bind(this));
75-
this.monitorServiceClient.onError(this.handleError.bind(this));
76-
this.boardsServiceProvider.onBoardsConfigChanged(
77-
this.handleBoardConfigChange.bind(this)
78-
);
79-
this.notificationCenter.onAttachedBoardsChanged(
80-
this.handleAttachedBoardsChanged.bind(this)
81-
);
82-
8378
// Handles the `baudRate` changes by reconnecting if required.
8479
this.monitorModel.onChange(({ property }) => {
8580
if (property === 'baudRate' && this.autoConnect && this.connected) {
@@ -97,6 +92,12 @@ export class MonitorConnection {
9792
};
9893
}
9994

95+
protected set state(s: MonitorConnection.State | undefined) {
96+
this.onConnectionChangedEmitter.fire(this.state);
97+
this._state = s;
98+
if (!this.connected) this.toDisposeOnDisconnect.forEach((d) => d.dispose());
99+
}
100+
100101
get connected(): boolean {
101102
return !!this.state;
102103
}
@@ -175,7 +176,7 @@ export class MonitorConnection {
175176
}
176177
const oldState = this.state;
177178
this.state = undefined;
178-
this.onConnectionChangedEmitter.fire(this.state);
179+
179180
if (shouldReconnect) {
180181
if (this.monitorErrors.length >= 10) {
181182
this.messageService.warn(
@@ -232,6 +233,16 @@ export class MonitorConnection {
232233
}
233234

234235
async connect(config: MonitorConfig): Promise<Status> {
236+
this.toDisposeOnDisconnect.push(
237+
this.monitorServiceClient.onMessage(this.handleMessage.bind(this)),
238+
this.monitorServiceClient.onError(this.handleError.bind(this)),
239+
this.boardsServiceProvider.onBoardsConfigChanged(
240+
this.handleBoardConfigChange.bind(this)
241+
),
242+
this.notificationCenter.onAttachedBoardsChanged(
243+
this.handleAttachedBoardsChanged.bind(this)
244+
)
245+
);
235246
if (this.connected) {
236247
const disconnectStatus = await this.disconnect();
237248
if (!Status.isOK(disconnectStatus)) {
@@ -253,7 +264,7 @@ export class MonitorConnection {
253264
)} on port ${Port.toString(config.port)}.`
254265
);
255266
}
256-
this.onConnectionChangedEmitter.fire(this.state);
267+
257268
return Status.isOK(connectStatus);
258269
}
259270

@@ -281,7 +292,7 @@ export class MonitorConnection {
281292
);
282293
}
283294
this.state = undefined;
284-
this.onConnectionChangedEmitter.fire(this.state);
295+
285296
return status;
286297
}
287298

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { injectable } from 'inversify';
2+
import { Emitter, Event } from '@theia/core/lib/common/event';
3+
import { MonitorConnection } from '../monitor/monitor-connection';
4+
5+
@injectable()
6+
export class PlotterConnection extends MonitorConnection {
7+
protected readonly onWebSocketChangedEmitter = new Emitter<string>();
8+
9+
async handleMessage(port: string): Promise<void> {
10+
this.onWebSocketChangedEmitter.fire(port);
11+
}
12+
13+
get onWebSocketChanged(): Event<string> {
14+
return this.onWebSocketChangedEmitter.event;
15+
}
16+
}

arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@ import { injectable, inject } from 'inversify';
22
import {
33
Command,
44
CommandRegistry,
5+
DisposableCollection,
56
MaybePromise,
67
MenuModelRegistry,
78
} from '@theia/core';
89
import { MonitorModel } from '../monitor/monitor-model';
910
import { ArduinoMenus } from '../menu/arduino-menus';
1011
import { Contribution } from '../contributions/contribution';
11-
import { PlotterService } from '../../common/protocol/plotter-service';
1212
import { Endpoint, FrontendApplication } from '@theia/core/lib/browser';
1313
import { ipcRenderer } from '@theia/core/shared/electron';
14-
import { SerialPlotter } from './protocol';
1514
import { MonitorConfig } from '../../common/protocol';
15+
import { PlotterConnection } from './plotter-connection';
16+
import { SerialPlotter } from './protocol';
1617
const queryString = require('query-string');
1718

1819
export namespace SerialPlotterContribution {
@@ -29,38 +30,38 @@ export namespace SerialPlotterContribution {
2930
export class PlotterFrontendContribution extends Contribution {
3031
protected window: Window | null;
3132
protected url: string;
32-
protected initConfig: SerialPlotter.Config;
33+
protected wsPort: number;
34+
protected toDispose = new DisposableCollection();
3335

3436
@inject(MonitorModel)
3537
protected readonly model: MonitorModel;
3638

37-
@inject(PlotterService)
38-
protected readonly plotter: PlotterService;
39+
@inject(PlotterConnection)
40+
protected readonly plotterConnection: PlotterConnection;
3941

4042
onStart(app: FrontendApplication): MaybePromise<void> {
4143
this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString();
4244

43-
this.initConfig = {
44-
baudrates: MonitorConfig.BaudRates.map((b) => b),
45-
currentBaudrate: this.model.baudRate,
46-
darkTheme: true,
47-
wsPort: 0,
48-
generate: true,
49-
};
50-
5145
ipcRenderer.on('CLOSE_CHILD_WINDOW', () => {
5246
if (this.window) {
5347
if (!this.window.closed) this.window?.close();
5448
this.window = null;
49+
this.plotterConnection.autoConnect = false;
5550
}
5651
});
5752

53+
this.toDispose.pushAll([
54+
this.plotterConnection.onWebSocketChanged((wsPort) => {
55+
this.open(wsPort);
56+
}),
57+
]);
58+
5859
return super.onStart(app);
5960
}
6061

6162
registerCommands(registry: CommandRegistry): void {
6263
registry.registerCommand(SerialPlotterContribution.Commands.OPEN, {
63-
execute: async () => this.open(),
64+
execute: async () => (this.plotterConnection.autoConnect = true),
6465
});
6566
}
6667

@@ -72,14 +73,20 @@ export class PlotterFrontendContribution extends Contribution {
7273
});
7374
}
7475

75-
protected async open(): Promise<void> {
76+
protected async open(wsPort: string): Promise<void> {
77+
const initConfig: SerialPlotter.Config = {
78+
baudrates: MonitorConfig.BaudRates.map((b) => b),
79+
currentBaudrate: this.model.baudRate,
80+
darkTheme: true,
81+
wsPort: parseInt(wsPort, 10),
82+
};
7683
if (this.window) {
7784
this.window.focus();
7885
} else {
7986
const urlWithParams = queryString.stringifyUrl(
8087
{
8188
url: this.url,
82-
query: this.initConfig,
89+
query: initConfig,
8390
},
8491
{ arrayFormat: 'comma' }
8592
);

arduino-ide-extension/src/browser/plotter/protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export namespace SerialPlotter {
44
baudrates: number[];
55
darkTheme: boolean;
66
wsPort: number;
7-
generate: boolean;
7+
generate?: boolean;
88
};
99
export namespace Protocol {
1010
export enum Command {

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,16 @@ export class MonitorServiceImpl implements MonitorService {
126126
const ws = new WebSocket.Server({ port: 0 });
127127
const address: any = ws.address();
128128
this.client?.notifyMessage(address.port);
129-
let wsConn: WebSocket | null = null;
129+
const wsConn: WebSocket[] = [];
130130
ws.on('connection', (ws) => {
131-
wsConn = ws;
131+
wsConn.push(ws);
132132
});
133133

134134
const flushMessagesToFrontend = () => {
135135
if (this.messages.length) {
136-
wsConn?.send(JSON.stringify(this.messages));
136+
wsConn.forEach((w) => {
137+
w.send(JSON.stringify(this.messages));
138+
});
137139
this.messages = [];
138140
}
139141
};

0 commit comments

Comments
 (0)