Skip to content

Commit 20f7712

Browse files
author
Alberto Iannaccone
authored
Serial Plotter implementation (#597)
* spawn new window where to instantiate serial plotter app * initialize serial monito web app * connect serial plotter app with websocket * use npm serial-plotter package * refactor monitor connection and fix some connection issues * fix clearConsole + refactor monitor connection * add serial unit tests * refactoring and cleaning code
1 parent 9863dc2 commit 20f7712

40 files changed

+1670
-821
lines changed

arduino-ide-extension/README.md

+8-5
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,20 @@ The Core Service is responsible for building your sketches and uploading them to
3030
- compiling a sketch for a selected board type
3131
- uploading a sketch to a connected board
3232

33-
#### Monitor Service
33+
#### Serial Service
3434

35-
The Monitor Service allows getting information back from sketches running on your Arduino boards.
35+
The Serial Service allows getting information back from sketches running on your Arduino boards.
3636

37-
- [src/common/protocol/monitor-service.ts](./src/common/protocol/monitor-service.ts) implements the common classes and interfaces
38-
- [src/node/monitor/monitor-service-impl.ts](./src/node/monitor/monitor-service-impl.ts) implements the service backend:
37+
- [src/common/protocol/serial-service.ts](./src/common/protocol/serial-service.ts) implements the common classes and interfaces
38+
- [src/node/serial/serial-service-impl.ts](./src/node/serial/serial-service-impl.ts) implements the service backend:
3939
- connecting to / disconnecting from a board
4040
- receiving and sending data
41-
- [src/browser/monitor/monitor-widget.tsx](./src/browser/monitor/monitor-widget.tsx) implements the serial monitor front-end:
41+
- [src/browser/serial/serial-connection-manager.ts](./src/browser/serial/serial-connection-manager.ts) handles the serial connection in the frontend
42+
- [src/browser/serial/monitor/monitor-widget.tsx](./src/browser/serial/monitor/monitor-widget.tsx) implements the serial monitor front-end:
4243
- viewing the output from a connected board
4344
- entering data to send to the board
45+
- [src/browser/serial/plotter/plotter-frontend-contribution.ts](./src/browser/serial/plotter/plotter-frontend-contribution.ts) implements the serial plotter front-end:
46+
- opening a new window running the [Serial Plotter Web App](https://github.com/arduino/arduino-serial-plotter-webapp)
4447

4548
#### Config Service
4649

arduino-ide-extension/package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
"description": "An extension for Theia building the Arduino IDE",
55
"license": "AGPL-3.0-or-later",
66
"scripts": {
7-
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn clean && yarn download-examples && yarn build && yarn test",
7+
"prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn copy-serial-plotter && yarn clean && yarn download-examples && yarn build && yarn test",
88
"clean": "rimraf lib",
99
"download-cli": "node ./scripts/download-cli.js",
1010
"download-fwuploader": "node ./scripts/download-fwuploader.js",
11+
"copy-serial-plotter": "npx ncp ../node_modules/arduino-serial-plotter-webapp ./build/arduino-serial-plotter-webapp",
1112
"download-ls": "node ./scripts/download-ls.js",
1213
"download-examples": "node ./scripts/download-examples.js",
1314
"generate-protocol": "node ./scripts/generate-protocol.js",
@@ -18,11 +19,12 @@
1819
"test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\""
1920
},
2021
"dependencies": {
22+
"arduino-serial-plotter-webapp": "0.0.15",
2123
"@grpc/grpc-js": "^1.3.7",
2224
"@theia/application-package": "1.18.0",
2325
"@theia/core": "1.18.0",
2426
"@theia/editor": "1.18.0",
25-
"@theia/editor-preview": "1.18.0",
27+
"@theia/editor-preview": "1.18.0",
2628
"@theia/filesystem": "1.18.0",
2729
"@theia/git": "1.18.0",
2830
"@theia/keymaps": "1.18.0",
@@ -77,6 +79,7 @@
7779
"open": "^8.0.6",
7880
"p-queue": "^5.0.0",
7981
"ps-tree": "^1.2.0",
82+
"query-string": "^7.0.1",
8083
"react-disable": "^0.1.0",
8184
"react-select": "^3.0.4",
8285
"react-tabs": "^3.1.2",

arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx

+1-53
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,13 @@ import {
33
MAIN_MENU_BAR,
44
MenuContribution,
55
MenuModelRegistry,
6-
SelectionService,
76
ILogger,
87
DisposableCollection,
98
} from '@theia/core';
109
import {
11-
ContextMenuRenderer,
1210
FrontendApplication,
1311
FrontendApplicationContribution,
1412
LocalStorageService,
15-
OpenerService,
1613
StatusBar,
1714
StatusBarAlignment,
1815
} from '@theia/core/lib/browser';
@@ -35,7 +32,6 @@ import {
3532
EditorManager,
3633
EditorOpenerOptions,
3734
} from '@theia/editor/lib/browser';
38-
import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog';
3935
import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution';
4036
import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu';
4137
import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution';
@@ -47,33 +43,25 @@ import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-con
4743
import { inject, injectable, postConstruct } from 'inversify';
4844
import * as React from 'react';
4945
import { remote } from 'electron';
50-
import { MainMenuManager } from '../common/main-menu-manager';
5146
import {
5247
BoardsService,
53-
CoreService,
5448
Port,
5549
SketchesService,
5650
ExecutableService,
5751
Sketch,
5852
} from '../common/protocol';
59-
import { ArduinoDaemon } from '../common/protocol/arduino-daemon';
6053
import { ConfigService } from '../common/protocol/config-service';
61-
import { FileSystemExt } from '../common/protocol/filesystem-ext';
6254
import { ArduinoCommands } from './arduino-commands';
6355
import { BoardsConfig } from './boards/boards-config';
6456
import { BoardsConfigDialog } from './boards/boards-config-dialog';
65-
import { BoardsDataStore } from './boards/boards-data-store';
6657
import { BoardsServiceProvider } from './boards/boards-service-provider';
6758
import { BoardsToolBarItem } from './boards/boards-toolbar-item';
6859
import { EditorMode } from './editor-mode';
6960
import { ArduinoMenus } from './menu/arduino-menus';
70-
import { MonitorConnection } from './monitor/monitor-connection';
71-
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
72-
import { WorkspaceService } from './theia/workspace/workspace-service';
61+
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
7362
import { ArduinoToolbar } from './toolbar/arduino-toolbar';
7463
import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin';
7564
import { FileService } from '@theia/filesystem/lib/browser/file-service';
76-
import { ResponseService } from '../common/protocol/response-service';
7765
import { ArduinoPreferences } from './arduino-preferences';
7866
import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl';
7967
import { SaveAsSketch } from './contributions/save-as-sketch';
@@ -101,24 +89,12 @@ export class ArduinoFrontendContribution
10189
@inject(BoardsService)
10290
protected readonly boardsService: BoardsService;
10391

104-
@inject(CoreService)
105-
protected readonly coreService: CoreService;
106-
10792
@inject(BoardsServiceProvider)
10893
protected readonly boardsServiceClientImpl: BoardsServiceProvider;
10994

110-
@inject(SelectionService)
111-
protected readonly selectionService: SelectionService;
112-
11395
@inject(EditorManager)
11496
protected readonly editorManager: EditorManager;
11597

116-
@inject(ContextMenuRenderer)
117-
protected readonly contextMenuRenderer: ContextMenuRenderer;
118-
119-
@inject(FileDialogService)
120-
protected readonly fileDialogService: FileDialogService;
121-
12298
@inject(FileService)
12399
protected readonly fileService: FileService;
124100

@@ -128,21 +104,12 @@ export class ArduinoFrontendContribution
128104
@inject(BoardsConfigDialog)
129105
protected readonly boardsConfigDialog: BoardsConfigDialog;
130106

131-
@inject(MenuModelRegistry)
132-
protected readonly menuRegistry: MenuModelRegistry;
133-
134107
@inject(CommandRegistry)
135108
protected readonly commandRegistry: CommandRegistry;
136109

137110
@inject(StatusBar)
138111
protected readonly statusBar: StatusBar;
139112

140-
@inject(WorkspaceService)
141-
protected readonly workspaceService: WorkspaceService;
142-
143-
@inject(MonitorConnection)
144-
protected readonly monitorConnection: MonitorConnection;
145-
146113
@inject(FileNavigatorContribution)
147114
protected readonly fileNavigatorContributions: FileNavigatorContribution;
148115

@@ -167,40 +134,21 @@ export class ArduinoFrontendContribution
167134
@inject(EditorMode)
168135
protected readonly editorMode: EditorMode;
169136

170-
@inject(ArduinoDaemon)
171-
protected readonly daemon: ArduinoDaemon;
172-
173-
@inject(OpenerService)
174-
protected readonly openerService: OpenerService;
175-
176137
@inject(ConfigService)
177138
protected readonly configService: ConfigService;
178139

179-
@inject(BoardsDataStore)
180-
protected readonly boardsDataStore: BoardsDataStore;
181-
182-
@inject(MainMenuManager)
183-
protected readonly mainMenuManager: MainMenuManager;
184-
185-
@inject(FileSystemExt)
186-
protected readonly fileSystemExt: FileSystemExt;
187-
188140
@inject(HostedPluginSupport)
189141
protected hostedPluginSupport: HostedPluginSupport;
190142

191143
@inject(ExecutableService)
192144
protected executableService: ExecutableService;
193145

194-
@inject(ResponseService)
195-
protected readonly responseService: ResponseService;
196-
197146
@inject(ArduinoPreferences)
198147
protected readonly arduinoPreferences: ArduinoPreferences;
199148

200149
@inject(SketchesServiceClientImpl)
201150
protected readonly sketchServiceClient: SketchesServiceClientImpl;
202151

203-
@inject(FrontendApplicationStateService)
204152
protected readonly appStateService: FrontendApplicationStateService;
205153

206154
@inject(LocalStorageService)

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

+21-18
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,20 @@ import { ScmContribution } from './theia/scm/scm-contribution';
6969
import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution';
7070
import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution';
7171
import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution';
72-
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
72+
import { SerialServiceClientImpl } from './serial/serial-service-client-impl';
7373
import {
74-
MonitorServicePath,
75-
MonitorService,
76-
MonitorServiceClient,
77-
} from '../common/protocol/monitor-service';
74+
SerialServicePath,
75+
SerialService,
76+
SerialServiceClient,
77+
} from '../common/protocol/serial-service';
7878
import {
7979
ConfigService,
8080
ConfigServicePath,
8181
} from '../common/protocol/config-service';
82-
import { MonitorWidget } from './monitor/monitor-widget';
83-
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
84-
import { MonitorConnection } from './monitor/monitor-connection';
85-
import { MonitorModel } from './monitor/monitor-model';
82+
import { MonitorWidget } from './serial/monitor/monitor-widget';
83+
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
84+
import { SerialConnectionManager } from './serial/serial-connection-manager';
85+
import { SerialModel } from './serial/serial-model';
8686
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
8787
import { TabBarDecoratorService } from './theia/core/tab-bar-decorator';
8888
import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser';
@@ -253,6 +253,7 @@ import {
253253
UploadCertificateDialogProps,
254254
UploadCertificateDialogWidget,
255255
} from './dialogs/certificate-uploader/certificate-uploader-dialog';
256+
import { PlotterFrontendContribution } from './serial/plotter/plotter-frontend-contribution';
256257
import { nls } from '@theia/core/lib/browser/nls';
257258

258259
const ElementQueries = require('css-element-queries/src/ElementQueries');
@@ -386,27 +387,28 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
386387
.inSingletonScope();
387388

388389
// Serial monitor
389-
bind(MonitorModel).toSelf().inSingletonScope();
390-
bind(FrontendApplicationContribution).toService(MonitorModel);
390+
bind(SerialModel).toSelf().inSingletonScope();
391+
bind(FrontendApplicationContribution).toService(SerialModel);
391392
bind(MonitorWidget).toSelf();
392393
bindViewContribution(bind, MonitorViewContribution);
393394
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
394395
bind(WidgetFactory).toDynamicValue((context) => ({
395396
id: MonitorWidget.ID,
396397
createWidget: () => context.container.get(MonitorWidget),
397398
}));
398-
// Frontend binding for the serial monitor service
399-
bind(MonitorService)
399+
// Frontend binding for the serial service
400+
bind(SerialService)
400401
.toDynamicValue((context) => {
401402
const connection = context.container.get(WebSocketConnectionProvider);
402403
const client =
403-
context.container.get<MonitorServiceClient>(MonitorServiceClient);
404-
return connection.createProxy(MonitorServicePath, client);
404+
context.container.get<SerialServiceClient>(SerialServiceClient);
405+
return connection.createProxy(SerialServicePath, client);
405406
})
406407
.inSingletonScope();
407-
bind(MonitorConnection).toSelf().inSingletonScope();
408-
// Serial monitor service client to receive and delegate notifications from the backend.
409-
bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope();
408+
bind(SerialConnectionManager).toSelf().inSingletonScope();
409+
410+
// Serial service client to receive and delegate notifications from the backend.
411+
bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope();
410412

411413
bind(WorkspaceService).toSelf().inSingletonScope();
412414
rebind(TheiaWorkspaceService).toService(WorkspaceService);
@@ -597,6 +599,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
597599
Contribution.configure(bind, AddFile);
598600
Contribution.configure(bind, ArchiveSketch);
599601
Contribution.configure(bind, AddZipLibrary);
602+
Contribution.configure(bind, PlotterFrontendContribution);
600603

601604
bind(ResponseServiceImpl)
602605
.toSelf()

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution {
6464
* This even also fires, when the boards package was not available for the currently selected board,
6565
* and the user installs the board package. Note: installing a board package will set the `fqbn` of the
6666
* currently selected board.\
67-
* This even also emitted when the board package for the currently selected board was uninstalled.
67+
* This event is also emitted when the board package for the currently selected board was uninstalled.
6868
*/
6969
readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event;
7070
readonly onAvailableBoardsChanged =

arduino-ide-extension/src/browser/contributions/board-selection.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,11 @@ PID: ${PID}`;
138138
// The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order.
139139
this.menuModelRegistry.registerSubmenu(
140140
boardsSubmenuPath,
141-
nls.localize('arduino/board/board', 'Board{0}', !!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''),
141+
nls.localize(
142+
'arduino/board/board',
143+
'Board{0}',
144+
!!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''
145+
),
142146
{ order: '100' }
143147
);
144148
this.toDisposeBeforeMenuRebuild.push(
@@ -155,7 +159,11 @@ PID: ${PID}`;
155159
const portsSubmenuLabel = config.selectedPort?.address;
156160
this.menuModelRegistry.registerSubmenu(
157161
portsSubmenuPath,
158-
nls.localize('arduino/board/port', 'Port{0}', portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''),
162+
nls.localize(
163+
'arduino/board/port',
164+
'Port{0}',
165+
portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''
166+
),
159167
{ order: '101' }
160168
);
161169
this.toDisposeBeforeMenuRebuild.push(
@@ -193,9 +201,10 @@ PID: ${PID}`;
193201

194202
const packageLabel =
195203
packageName +
196-
`${manuallyInstalled
197-
? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)')
198-
: ''
204+
`${
205+
manuallyInstalled
206+
? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)')
207+
: ''
199208
}`;
200209
// Platform submenu
201210
const platformMenuPath = [...boardsPackagesGroup, packageId];
@@ -268,8 +277,9 @@ PID: ${PID}`;
268277
});
269278
}
270279
for (const { name, fqbn } of boards) {
271-
const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : ''
272-
}`;
280+
const id = `arduino-select-port--${address}${
281+
fqbn ? `--${fqbn}` : ''
282+
}`;
273283
const command = { id };
274284
const handler = {
275285
execute: () => {

arduino-ide-extension/src/browser/contributions/burn-bootloader.ts

+6-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
33
import { CoreService } from '../../common/protocol';
44
import { ArduinoMenus } from '../menu/arduino-menus';
55
import { BoardsDataStore } from '../boards/boards-data-store';
6-
import { MonitorConnection } from '../monitor/monitor-connection';
6+
import { SerialConnectionManager } from '../serial/serial-connection-manager';
77
import { BoardsServiceProvider } from '../boards/boards-service-provider';
88
import {
99
SketchContribution,
@@ -18,8 +18,8 @@ export class BurnBootloader extends SketchContribution {
1818
@inject(CoreService)
1919
protected readonly coreService: CoreService;
2020

21-
@inject(MonitorConnection)
22-
protected readonly monitorConnection: MonitorConnection;
21+
@inject(SerialConnectionManager)
22+
protected readonly serialConnection: SerialConnectionManager;
2323

2424
@inject(BoardsDataStore)
2525
protected readonly boardsDataStore: BoardsDataStore;
@@ -48,10 +48,7 @@ export class BurnBootloader extends SketchContribution {
4848
}
4949

5050
async burnBootloader(): Promise<void> {
51-
const monitorConfig = this.monitorConnection.monitorConfig;
52-
if (monitorConfig) {
53-
await this.monitorConnection.disconnect();
54-
}
51+
await this.serialConnection.disconnect();
5552
try {
5653
const { boardsConfig } = this.boardsServiceClientImpl;
5754
const port = boardsConfig.selectedPort;
@@ -84,8 +81,8 @@ export class BurnBootloader extends SketchContribution {
8481
} catch (e) {
8582
this.messageService.error(e.toString());
8683
} finally {
87-
if (monitorConfig) {
88-
await this.monitorConnection.connect(monitorConfig);
84+
if (this.serialConnection.isSerialOpen()) {
85+
await this.serialConnection.connect();
8986
}
9087
}
9188
}

0 commit comments

Comments
 (0)