Skip to content

Commit 3e0842e

Browse files
authored
Merge pull request #54 from bcmi-labs/monitor
Serial Monitor
2 parents 90add23 + 7973130 commit 3e0842e

18 files changed

+1089
-1738
lines changed

arduino-ide-extension/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
"@theia/search-in-workspace": "next",
2424
"@types/ps-tree": "^1.1.0",
2525
"@types/which": "^1.3.1",
26+
"@types/react-select": "^3.0.0",
27+
"@types/google-protobuf": "^3.7.1",
2628
"css-element-queries": "^1.2.0",
29+
"react-select": "^3.0.4",
2730
"p-queue": "^5.0.0",
2831
"ps-tree": "^1.2.0",
2932
"tree-kill": "^1.2.1",

arduino-ide-extension/src/browser/arduino-commands.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,4 @@ export namespace ArduinoCommands {
4242
export const TOGGLE_PRO_MODE: Command = {
4343
id: "arduino-toggle-pro-mode"
4444
}
45-
46-
export const CONNECT_TODO: Command = {
47-
id: 'connect-to-attached-board',
48-
label: 'Connect to Attached Board'
49-
}
50-
51-
export const SEND: Command = {
52-
id: 'send',
53-
label: 'Send a Message to the Connected Board'
54-
}
55-
5645
}

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

Lines changed: 25 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
55
import { MessageService } from '@theia/core/lib/common/message-service';
66
import { CommandContribution, CommandRegistry, Command } from '@theia/core/lib/common/command';
77
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
8-
import { BoardsService, AttachedSerialBoard } from '../common/protocol/boards-service';
8+
import { BoardsService } from '../common/protocol/boards-service';
99
import { ArduinoCommands } from './arduino-commands';
1010
import { CoreService } from '../common/protocol/core-service';
1111
import { WorkspaceServiceExt } from './workspace-service-ext';
@@ -26,8 +26,6 @@ import {
2626
StatusBar,
2727
ShellLayoutRestorer,
2828
StatusBarAlignment,
29-
QuickOpenItem,
30-
QuickOpenMode,
3129
QuickOpenService,
3230
LabelProvider
3331
} from '@theia/core/lib/browser';
@@ -47,6 +45,8 @@ import { BoardsToolBarItem } from './boards/boards-toolbar-item';
4745
import { BoardsConfig } from './boards/boards-config';
4846
import { MonitorService } from '../common/protocol/monitor-service';
4947
import { ConfigService } from '../common/protocol/config-service';
48+
import { MonitorConnection } from './monitor/monitor-connection';
49+
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
5050

5151
export namespace ArduinoMenus {
5252
export const SKETCH = [...MAIN_MENU_BAR, '3_sketch'];
@@ -72,9 +72,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
7272
@inject(MonitorService)
7373
protected readonly monitorService: MonitorService;
7474

75-
// TODO: make this better!
76-
protected connectionId: string | undefined;
77-
7875
@inject(WorkspaceServiceExt)
7976
protected readonly workspaceServiceExt: WorkspaceServiceExt;
8077

@@ -143,6 +140,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
143140

144141
@inject(ConfigService)
145142
protected readonly configService: ConfigService;
143+
@inject(MonitorConnection)
144+
protected readonly monitorConnection: MonitorConnection;
146145

147146
protected boardsToolbarItem: BoardsToolBarItem | null;
148147
protected wsSketchCount: number = 0;
@@ -197,13 +196,19 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
197196
commands={this.commands}
198197
boardsServiceClient={this.boardsServiceClient}
199198
boardService={this.boardsService} />,
200-
isVisible: widget => this.isArduinoToolbar(widget)
199+
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left'
200+
});
201+
registry.registerItem({
202+
id: 'toggle-serial-monitor',
203+
command: MonitorViewContribution.OPEN_SERIAL_MONITOR,
204+
tooltip: 'Toggle Serial Monitor',
205+
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'right'
201206
})
202207
}
203208

204209
registerCommands(registry: CommandRegistry): void {
205210
registry.registerCommand(ArduinoCommands.VERIFY, {
206-
isVisible: widget => this.isArduinoToolbar(widget),
211+
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
207212
isEnabled: widget => true,
208213
execute: async () => {
209214
const widget = this.getCurrentWidget();
@@ -231,7 +236,7 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
231236
}
232237
});
233238
registry.registerCommand(ArduinoCommands.UPLOAD, {
234-
isVisible: widget => this.isArduinoToolbar(widget),
239+
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
235240
isEnabled: widget => true,
236241
execute: async () => {
237242
const widget = this.getCurrentWidget();
@@ -244,6 +249,9 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
244249
return;
245250
}
246251

252+
const connectionConfig = this.monitorConnection.connectionConfig;
253+
await this.monitorConnection.disconnect();
254+
247255
try {
248256
const { boardsConfig } = this.boardsServiceClient;
249257
if (!boardsConfig || !boardsConfig.selectedBoard) {
@@ -256,12 +264,16 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
256264
await this.coreService.upload({ uri: uri.toString(), board: boardsConfig.selectedBoard, port: selectedPort });
257265
} catch (e) {
258266
await this.messageService.error(e.toString());
267+
} finally {
268+
if (connectionConfig) {
269+
await this.monitorConnection.connect(connectionConfig);
270+
}
259271
}
260272
}
261273
});
262274
registry.registerCommand(ArduinoCommands.SHOW_OPEN_CONTEXT_MENU, {
263-
isVisible: widget => this.isArduinoToolbar(widget),
264-
isEnabled: widget => this.isArduinoToolbar(widget),
275+
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
276+
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
265277
execute: async (widget: Widget, target: EventTarget) => {
266278
if (this.wsSketchCount) {
267279
const el = (target as HTMLElement).parentElement;
@@ -287,8 +299,8 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
287299
}
288300
})
289301
registry.registerCommand(ArduinoCommands.SAVE_SKETCH, {
290-
isEnabled: widget => this.isArduinoToolbar(widget),
291-
isVisible: widget => this.isArduinoToolbar(widget),
302+
isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
303+
isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left',
292304
execute: async (sketch: Sketch) => {
293305
registry.executeCommand(CommonCommands.SAVE_ALL.id);
294306
}
@@ -324,65 +336,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
324336
},
325337
isToggled: () => ARDUINO_PRO_MODE
326338
});
327-
registry.registerCommand(ArduinoCommands.CONNECT_TODO, {
328-
execute: async () => {
329-
const { boardsConfig } = this.boardsServiceClient;
330-
const { selectedBoard, selectedPort } = boardsConfig;
331-
if (!selectedBoard) {
332-
this.messageService.warn('No boards selected.');
333-
return;
334-
}
335-
const { name } = selectedBoard;
336-
if (!selectedPort) {
337-
this.messageService.warn(`No ports selected for board: '${name}'.`);
338-
return;
339-
}
340-
const attachedBoards = await this.boardsService.getAttachedBoards();
341-
const connectedBoard = attachedBoards.boards.filter(AttachedSerialBoard.is).find(board => BoardsConfig.Config.sameAs(boardsConfig, board));
342-
if (!connectedBoard) {
343-
this.messageService.warn(`The selected '${name}' board is not connected on ${selectedPort}.`);
344-
return;
345-
}
346-
if (this.connectionId) {
347-
console.log('>>> Disposing existing monitor connection before establishing a new one...');
348-
const result = await this.monitorService.disconnect(this.connectionId);
349-
if (!result) {
350-
// TODO: better!!!
351-
console.error(`Could not close connection: ${this.connectionId}. Check the backend logs.`);
352-
} else {
353-
console.log(`<<< Disposed ${this.connectionId} connection.`)
354-
}
355-
}
356-
const { connectionId } = await this.monitorService.connect({ board: selectedBoard, port: selectedPort });
357-
this.connectionId = connectionId;
358-
}
359-
});
360-
registry.registerCommand(ArduinoCommands.SEND, {
361-
isEnabled: () => !!this.connectionId,
362-
execute: async () => {
363-
const { monitorService, connectionId } = this;
364-
const model = {
365-
onType(lookFor: string, acceptor: (items: QuickOpenItem[]) => void): void {
366-
acceptor([
367-
new QuickOpenItem({
368-
label: "Type your message and press 'Enter' to send it to the board. Escape to cancel.",
369-
run: (mode: QuickOpenMode): boolean => {
370-
if (mode !== QuickOpenMode.OPEN) {
371-
return false;
372-
}
373-
monitorService.send(connectionId!, lookFor + '\n');
374-
return true;
375-
}
376-
})
377-
]);
378-
}
379-
};
380-
const options = {
381-
placeholder: "Your message. The message will be suffixed with a LF ['\\n'].",
382-
};
383-
this.quickOpenService.open(model, options);
384-
}
385-
})
386339
}
387340

388341
registerMenus(registry: MenuModelRegistry) {
@@ -555,13 +508,6 @@ export class ArduinoFrontendContribution implements TabBarToolbarContribution, C
555508
return undefined;
556509
}
557510

558-
private isArduinoToolbar(maybeToolbarWidget: any): boolean {
559-
if (maybeToolbarWidget instanceof ArduinoToolbar) {
560-
return true;
561-
}
562-
return false;
563-
}
564-
565511
private toUri(arg: any): URI | undefined {
566512
if (arg instanceof URI) {
567513
return arg;

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

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ import { BoardItemRenderer } from './boards/boards-item-renderer';
5757
import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl';
5858
import { MonitorServicePath, MonitorService, MonitorServiceClient } from '../common/protocol/monitor-service';
5959
import { ConfigService, ConfigServicePath } from '../common/protocol/config-service';
60+
import { MonitorWidget } from './monitor/monitor-widget';
61+
import { MonitorViewContribution } from './monitor/monitor-view-contribution';
62+
import { MonitorConnection } from './monitor/monitor-connection';
63+
import { MonitorModel } from './monitor/monitor-model';
6064
const ElementQueries = require('css-element-queries/src/ElementQueries');
6165

62-
if (!ARDUINO_PRO_MODE) {
63-
require('../../src/browser/style/silent-bottom-panel-tabs.css');
64-
}
65-
6666
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
6767
ElementQueries.listen();
6868
ElementQueries.init();
@@ -155,12 +155,23 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
155155
return workspaceServiceExt;
156156
});
157157

158+
// Serial Monitor
159+
bind(MonitorModel).toSelf().inSingletonScope();
160+
bind(MonitorWidget).toSelf();
161+
bindViewContribution(bind, MonitorViewContribution);
162+
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
163+
bind(WidgetFactory).toDynamicValue(context => ({
164+
id: MonitorWidget.ID,
165+
createWidget: () => context.container.get(MonitorWidget)
166+
}));
158167
// Frontend binding for the monitor service.
159168
bind(MonitorService).toDynamicValue(context => {
160169
const connection = context.container.get(WebSocketConnectionProvider);
161170
const client = context.container.get(MonitorServiceClientImpl);
162171
return connection.createProxy(MonitorServicePath, client);
163172
}).inSingletonScope();
173+
// MonitorConnection
174+
bind(MonitorConnection).toSelf().inSingletonScope();
164175
// Monitor service client to receive and delegate notifications from the backend.
165176
bind(MonitorServiceClientImpl).toSelf().inSingletonScope();
166177
bind(MonitorServiceClient).toDynamicValue(context => {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { injectable, inject } from "inversify";
2+
import { MonitorService, ConnectionConfig } from "../../common/protocol/monitor-service";
3+
import { Emitter, Event } from "@theia/core";
4+
5+
@injectable()
6+
export class MonitorConnection {
7+
8+
@inject(MonitorService)
9+
protected readonly monitorService: MonitorService;
10+
11+
connectionId: string | undefined;
12+
13+
protected _connectionConfig: ConnectionConfig | undefined;
14+
15+
protected readonly onConnectionChangedEmitter = new Emitter<string | undefined>();
16+
readonly onConnectionChanged: Event<string | undefined> = this.onConnectionChangedEmitter.event;
17+
18+
get connectionConfig(): ConnectionConfig | undefined {
19+
return this._connectionConfig;
20+
}
21+
22+
async connect(config: ConnectionConfig): Promise<string | undefined> {
23+
if (this.connectionId) {
24+
await this.disconnect();
25+
}
26+
const { connectionId } = await this.monitorService.connect(config);
27+
this.connectionId = connectionId;
28+
this._connectionConfig = config;
29+
30+
this.onConnectionChangedEmitter.fire(this.connectionId);
31+
32+
return connectionId;
33+
}
34+
35+
async disconnect(): Promise<boolean> {
36+
let result = true;
37+
const connections = await this.monitorService.getConnectionIds();
38+
if (this.connectionId && connections.findIndex(id => id === this.connectionId) >= 0) {
39+
console.log('>>> Disposing existing monitor connection before establishing a new one...');
40+
result = await this.monitorService.disconnect(this.connectionId);
41+
if (!result) {
42+
// TODO: better!!!
43+
console.error(`Could not close connection: ${this.connectionId}. Check the backend logs.`);
44+
} else {
45+
console.log(`<<< Disposed ${this.connectionId} connection.`);
46+
this.connectionId = undefined;
47+
this._connectionConfig = undefined;
48+
this.onConnectionChangedEmitter.fire(this.connectionId);
49+
}
50+
}
51+
return result;
52+
}
53+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { injectable } from "inversify";
2+
import { Emitter } from "@theia/core";
3+
4+
export namespace MonitorModel {
5+
export interface Data {
6+
autoscroll: boolean,
7+
timestamp: boolean,
8+
baudRate: number,
9+
lineEnding: string
10+
}
11+
}
12+
13+
@injectable()
14+
export class MonitorModel {
15+
16+
protected readonly onChangeEmitter = new Emitter<void>();
17+
18+
readonly onChange = this.onChangeEmitter.event;
19+
20+
protected _autoscroll: boolean = true;
21+
protected _timestamp: boolean = false;
22+
baudRate: number;
23+
lineEnding: string = '\n';
24+
25+
get autoscroll(): boolean {
26+
return this._autoscroll;
27+
}
28+
29+
get timestamp(): boolean {
30+
return this._timestamp;
31+
}
32+
33+
toggleAutoscroll(): void {
34+
this._autoscroll = !this._autoscroll;
35+
this.onChangeEmitter.fire(undefined);
36+
}
37+
38+
toggleTimestamp(): void {
39+
this._timestamp = !this._timestamp;
40+
this.onChangeEmitter.fire(undefined);
41+
}
42+
43+
restore(model: MonitorModel.Data) {
44+
this._autoscroll = model.autoscroll;
45+
this._timestamp = model.timestamp;
46+
this.baudRate = model.baudRate;
47+
this.lineEnding = model.lineEnding;
48+
}
49+
50+
store(): MonitorModel.Data {
51+
return {
52+
autoscroll: this._autoscroll,
53+
timestamp: this._timestamp,
54+
baudRate: this.baudRate,
55+
lineEnding: this.lineEnding
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)