Skip to content

Commit 7d790a6

Browse files
author
Alberto Iannaccone
committed
WIP connect serial plotter app with websocket
1 parent 0f72c72 commit 7d790a6

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
import { nls } from '@theia/core/lib/browser/nls';
262263

263264
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -410,6 +411,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
410411
})
411412
.inSingletonScope();
412413
bind(MonitorConnection).toSelf().inSingletonScope();
414+
bind(PlotterConnection).toSelf().inSingletonScope();
413415
// Serial monitor service client to receive and delegate notifications from the backend.
414416
bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope();
415417

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
import { nls } from '@theia/core/lib/browser/nls';
2425

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

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

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

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

96+
protected set state(s: MonitorConnection.State | undefined) {
97+
this.onConnectionChangedEmitter.fire(this.state);
98+
this._state = s;
99+
if (!this.connected) this.toDisposeOnDisconnect.forEach((d) => d.dispose());
100+
}
101+
101102
get connected(): boolean {
102103
return !!this.state;
103104
}
@@ -188,7 +189,7 @@ export class MonitorConnection {
188189
}
189190
const oldState = this.state;
190191
this.state = undefined;
191-
this.onConnectionChangedEmitter.fire(this.state);
192+
192193
if (shouldReconnect) {
193194
if (this.monitorErrors.length >= 10) {
194195
this.messageService.warn(
@@ -254,6 +255,16 @@ export class MonitorConnection {
254255
}
255256

256257
async connect(config: MonitorConfig): Promise<Status> {
258+
this.toDisposeOnDisconnect.push(
259+
this.monitorServiceClient.onMessage(this.handleMessage.bind(this)),
260+
this.monitorServiceClient.onError(this.handleError.bind(this)),
261+
this.boardsServiceProvider.onBoardsConfigChanged(
262+
this.handleBoardConfigChange.bind(this)
263+
),
264+
this.notificationCenter.onAttachedBoardsChanged(
265+
this.handleAttachedBoardsChanged.bind(this)
266+
)
267+
);
257268
if (this.connected) {
258269
const disconnectStatus = await this.disconnect();
259270
if (!Status.isOK(disconnectStatus)) {
@@ -275,7 +286,7 @@ export class MonitorConnection {
275286
)} on port ${Port.toString(config.port)}.`
276287
);
277288
}
278-
this.onConnectionChangedEmitter.fire(this.state);
289+
279290
return Status.isOK(connectStatus);
280291
}
281292

@@ -303,7 +314,7 @@ export class MonitorConnection {
303314
);
304315
}
305316
this.state = undefined;
306-
this.onConnectionChangedEmitter.fire(this.state);
317+
307318
return status;
308319
}
309320

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)