Skip to content

Pluggable monitor #982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 74 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
2b2ea72
backend structure WIP
fstasi Mar 2, 2022
ebab0b2
Scaffold interfaces and classes for pluggable monitors
silvanocerza Mar 3, 2022
3133b01
Implement MonitorService to handle pluggable monitor lifetime
silvanocerza Mar 4, 2022
750796d
Rename WebSocketService to WebSocketProvider and uninjected it
silvanocerza Mar 4, 2022
116b3d5
Moved some interfaces
silvanocerza Mar 4, 2022
2c95e7f
Changed upload settings
silvanocerza Mar 4, 2022
480492a
Enhance MonitorManager APIs
silvanocerza Mar 4, 2022
c5695d3
Fixed WebSocketChange event signature
silvanocerza Mar 7, 2022
61b8bde
Add monitor proxy functions for the frontend
silvanocerza Mar 7, 2022
31b704c
Moved settings to MonitorService
silvanocerza Mar 8, 2022
9058abb
Remove several unnecessary serial monitor classes
silvanocerza Mar 10, 2022
ee265ae
Changed how connection is handled on upload
silvanocerza Mar 10, 2022
bf958fd
Proxied more monitor methods to frontend
silvanocerza Mar 10, 2022
cbd5b4d
WebSocketProvider is not injectable anymore
silvanocerza Mar 10, 2022
50239c5
Add generic monitor settings storaging
silvanocerza Mar 10, 2022
6cf61c4
More serial classes removal
silvanocerza Mar 10, 2022
ad781f0
Remove unused file
silvanocerza Mar 10, 2022
6b7b333
Changed plotter contribution to use new manager proxy
silvanocerza Mar 10, 2022
7889f40
Changed MonitorWidget and children to use new monitor proxy
silvanocerza Mar 10, 2022
ce2f1c2
Updated MonitorWidget to use new monitor proxy
silvanocerza Mar 11, 2022
b97af32
Fix backend logger bindings
silvanocerza Mar 14, 2022
f9da9fc
Delete unnecessary Symbol
silvanocerza Mar 15, 2022
397ca56
coreClientProvider is now set when constructing MonitorService
silvanocerza Mar 15, 2022
a8d803e
Add missing binding
silvanocerza Mar 15, 2022
fbe8fb4
Fix `MonitorManagerProxy` DI issue
msujew Mar 22, 2022
eff960b
fix monitor connection
Apr 8, 2022
9b58c9d
delete duplex when connection is closed
May 10, 2022
62eaeb1
update arduino-cli to 0.22.0
May 10, 2022
1982609
fix upload when monitor is open
May 12, 2022
7bf4ea0
add MonitorSettingsProvider interface
May 17, 2022
355dec8
monitor settings provider stub
fstasi May 19, 2022
80ade4c
updated pseudo code
fstasi May 19, 2022
0427759
refactor monitor settings interfaces
May 19, 2022
a4ff05a
monitor service provider singleton
fstasi May 19, 2022
c0a9bbb
add unit tests
fstasi May 20, 2022
9a16cf9
change MonitorService providers to injectable deps
davegarthsimpson May 20, 2022
077dff8
Merge branch 'pluggable-monitor-class-refactoring' into pluggable-mon…
davegarthsimpson May 23, 2022
9ea51b2
fix monitor settings client communication
May 23, 2022
5035b0b
refactor monitor commands protocol
May 23, 2022
a7433ee
use monitor settings provider properly
May 23, 2022
f92f875
add settings to monitor model
May 24, 2022
7daad3b
add settings to monitor model
May 24, 2022
26077e2
Merge branch 'pluggable-monitor' of https://github.com/arduino/arduin…
davegarthsimpson May 26, 2022
29a4ed4
reset serial monitor when port changes
May 26, 2022
97312fc
fix serial plotter opening
May 26, 2022
5c05240
Merge branch 'pluggable-monitor' of github.com:arduino/arduino-ide in…
May 26, 2022
7fbcf33
refine monitor connection settings
May 26, 2022
e4c7f05
fix hanging web socket connections
May 27, 2022
5519a82
add serial plotter reset command
May 27, 2022
9c1aba5
send port to web socket clients
May 27, 2022
df992d1
Merge branch 'main' of github.com:arduino/arduino-ide into pluggable-…
May 27, 2022
16e2423
monitor service wait for success serial port open
davegarthsimpson Jun 1, 2022
959e257
fix reset loop
Jun 1, 2022
1baefdf
update serial plotter version
Jun 3, 2022
4e4ed97
update arduino-cli version to 0.23.0-rc1 and regenerate grpc protocol
Jun 3, 2022
900a5f9
remove useless plotter protocol file
Jun 3, 2022
dac12aa
localize web socket errors
Jun 3, 2022
038114f
clean-up code
Jun 3, 2022
750c534
update translation file
Jun 3, 2022
3f472f0
Fix duplicated editor tabs (#1012)
msujew May 31, 2022
bee7830
Save dialog for closing temporary sketch and unsaved files (#893)
msujew Jun 1, 2022
f019664
fix serial monitor send line ending
Jun 3, 2022
c43136f
Merge branch 'main' of github.com:arduino/arduino-ide into pluggable-…
Jun 3, 2022
761efd0
refactor monitor-service poll for test/readability
davegarthsimpson Jun 3, 2022
f169e22
Merge branch 'pluggable-monitor' of https://github.com/arduino/arduin…
davegarthsimpson Jun 3, 2022
ee41685
localize web socket errors
Jun 3, 2022
efdc86b
update translation file
Jun 3, 2022
980673a
Fix duplicated editor tabs (#1012)
msujew May 31, 2022
7b15a44
i18n:check rerun
davegarthsimpson Jun 6, 2022
5b60cb4
Speed up IDE startup time.
May 20, 2022
c65ca78
Merge branch 'startup-time-signed' of github.com:arduino/arduino-ide …
Jun 7, 2022
2c330ad
override coreClientProvider in monitor-service
Jun 7, 2022
1cf18c8
Merge branch 'main' of github.com:arduino/arduino-ide into pluggable-…
Jun 7, 2022
cb8df1c
cleanup merged code
Jun 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix reset loop
  • Loading branch information
Alberto Iannaccone committed Jun 1, 2022
commit 959e2572a2241b1e0f41ca62a8c322f5b3eb7704
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { CommandRegistry, Emitter, MessageService } from '@theia/core';
import {
CommandRegistry,
Disposable,
Emitter,
MessageService,
} from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Board, Port } from '../common/protocol';
import {
Expand All @@ -11,8 +16,7 @@ import {
MonitorSettings,
} from '../node/monitor-settings/monitor-settings-provider';
import { BoardsConfig } from './boards/boards-config';
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
import { SerialPlotterContribution } from './serial/plotter/plotter-frontend-contribution';
import { BoardsServiceProvider } from './boards/boards-service-provider';

@injectable()
export class MonitorManagerProxyClientImpl
Expand All @@ -32,11 +36,15 @@ export class MonitorManagerProxyClientImpl
readonly onMonitorSettingsDidChange =
this.onMonitorSettingsDidChangeEmitter.event;

protected readonly onMonitorShouldResetEmitter = new Emitter();
readonly onMonitorShouldReset = this.onMonitorShouldResetEmitter.event;

// WebSocket used to handle pluggable monitor communication between
// frontend and backend.
private webSocket?: WebSocket;
private wsPort?: number;
private lastConnectedBoard: BoardsConfig.Config;
private onBoardsConfigChanged: Disposable | undefined;

getWebSocketPort(): number | undefined {
return this.wsPort;
Expand All @@ -51,7 +59,10 @@ export class MonitorManagerProxyClientImpl
protected server: MonitorManagerProxyFactory,

@inject(CommandRegistry)
protected readonly commandRegistry: CommandRegistry
protected readonly commandRegistry: CommandRegistry,

@inject(BoardsServiceProvider)
protected readonly boardsServiceProvider: BoardsServiceProvider
) {}

/**
Expand Down Expand Up @@ -89,6 +100,8 @@ export class MonitorManagerProxyClientImpl
*/
disconnect(): void {
if (!this.webSocket) return;
this.onBoardsConfigChanged?.dispose();
this.onBoardsConfigChanged = undefined;
try {
this.webSocket?.close();
this.webSocket = undefined;
Expand All @@ -101,27 +114,46 @@ export class MonitorManagerProxyClientImpl
return !!this.webSocket;
}

async startMonitor(
board: Board,
port: Port,
settings?: PluggableMonitorSettings
): Promise<void> {
await this.server().startMonitor(board, port, settings);
if (
board.fqbn !== this.lastConnectedBoard?.selectedBoard?.fqbn ||
port.id !== this.lastConnectedBoard?.selectedPort?.id
) {
await this.commandRegistry.executeCommand(
MonitorViewContribution.RESET_SERIAL_MONITOR
);
await this.commandRegistry.executeCommand(
SerialPlotterContribution.Commands.RESET.id
);
}
async startMonitor(settings?: PluggableMonitorSettings): Promise<void> {
this.lastConnectedBoard = {
selectedBoard: board,
selectedPort: port,
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort,
};

if (!this.onBoardsConfigChanged) {
this.onBoardsConfigChanged =
this.boardsServiceProvider.onBoardsConfigChanged(
async ({ selectedBoard, selectedPort }) => {
if (
typeof selectedBoard === 'undefined' ||
typeof selectedPort === 'undefined'
)
return;

// a board is plugged and it's different from the old connected board
if (
selectedBoard?.fqbn !==
this.lastConnectedBoard?.selectedBoard?.fqbn ||
selectedPort?.id !== this.lastConnectedBoard?.selectedPort?.id
) {
this.onMonitorShouldResetEmitter.fire(null);
this.lastConnectedBoard = {
selectedBoard: selectedBoard,
selectedPort: selectedPort,
};
} else {
// a board is plugged and it's the same as prev, rerun "this.startMonitor" to
// recreate the listener callback
this.startMonitor();
}
}
);
}

const { selectedBoard, selectedPort } =
this.boardsServiceProvider.boardsConfig;
if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return;
await this.server().startMonitor(selectedBoard, selectedPort, settings);
}

getCurrentSettings(board: Board, port: Port): Promise<MonitorSettings> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
import { ArduinoMenus } from '../../menu/arduino-menus';
import { nls } from '@theia/core/lib/common';
import { MonitorModel } from '../../monitor-model';
import { MonitorManagerProxyClient } from '../../../common/protocol';

export namespace SerialMonitor {
export namespace Commands {
Expand Down Expand Up @@ -49,10 +50,13 @@ export class MonitorViewContribution
MonitorWidget.ID + ':toggle-toolbar';
static readonly RESET_SERIAL_MONITOR = MonitorWidget.ID + ':reset';

@inject(MonitorModel)
protected readonly model: MonitorModel;
constructor(
@inject(MonitorModel)
protected readonly model: MonitorModel,

constructor() {
@inject(MonitorManagerProxyClient)
protected readonly monitorManagerProxy: MonitorManagerProxyClient
) {
super({
widgetId: MonitorWidget.ID,
widgetName: MonitorWidget.LABEL,
Expand All @@ -62,6 +66,7 @@ export class MonitorViewContribution
toggleCommandId: MonitorViewContribution.TOGGLE_SERIAL_MONITOR,
toggleKeybinding: 'CtrlCmd+Shift+M',
});
this.monitorManagerProxy.onMonitorShouldReset(() => this.reset());
}

registerMenus(menus: MenuModelRegistry): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,6 @@ export class MonitorWidget extends ReactWidget {
this.toDispose.push(
Disposable.create(() => this.monitorManagerProxy.disconnect())
);

// Start monitor right away if there is already a board/port combination selected
const { selectedBoard, selectedPort } =
this.boardsServiceProvider.boardsConfig;
if (selectedBoard && selectedBoard.fqbn && selectedPort) {
this.monitorManagerProxy.startMonitor(selectedBoard, selectedPort);
}

this.toDispose.push(
this.boardsServiceProvider.onBoardsConfigChanged(
async ({ selectedBoard, selectedPort }) => {
if (selectedBoard && selectedBoard.fqbn && selectedPort) {
await this.monitorManagerProxy.startMonitor(
selectedBoard,
selectedPort
);

this.update();
}
}
)
);
}

protected onBeforeAttach(msg: Message): void {
Expand All @@ -92,6 +70,8 @@ export class MonitorWidget extends ReactWidget {
this.monitorManagerProxy.onMonitorSettingsDidChange(
this.onMonitorSettingsDidChange.bind(this)
);

this.monitorManagerProxy.startMonitor();
}

onMonitorSettingsDidChange(settings: MonitorSettings): void {
Expand Down Expand Up @@ -207,7 +187,6 @@ export class MonitorWidget extends ReactWidget {
<div className="send">
<SerialMonitorSendInput
boardsServiceProvider={this.boardsServiceProvider}
monitorManagerProxy={this.monitorManagerProxy}
monitorModel={this.monitorModel}
resolveFocus={this.onFocusResolved}
onSend={this.onSend}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { Key, KeyCode } from '@theia/core/lib/browser/keys';
import { Board } from '../../../common/protocol/boards-service';
import { isOSX } from '@theia/core/lib/common/os';
import { DisposableCollection, nls } from '@theia/core/lib/common';
import { MonitorManagerProxyClient } from '../../../common/protocol';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { MonitorModel } from '../../monitor-model';

export namespace SerialMonitorSendInput {
export interface Props {
readonly boardsServiceProvider: BoardsServiceProvider;
readonly monitorManagerProxy: MonitorManagerProxyClient;
readonly monitorModel: MonitorModel;
readonly onSend: (text: string) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MonitorManagerProxyClient } from '../../../common/protocol';
import { SerialPlotter } from './protocol';
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { MonitorModel } from '../../monitor-model';

const queryString = require('query-string');

export namespace SerialPlotterContribution {
Expand Down Expand Up @@ -57,6 +58,8 @@ export class PlotterFrontendContribution extends Contribution {
this.window = null;
}
});
this.monitorManagerProxy.onMonitorShouldReset(() => this.reset());

return super.onStart(app);
}

Expand All @@ -78,19 +81,7 @@ export class PlotterFrontendContribution extends Contribution {
}

async startPlotter(): Promise<void> {
if (
!this.boardsServiceProvider.boardsConfig.selectedBoard ||
!this.boardsServiceProvider.boardsConfig.selectedPort
) {
this.messageService.error(
`You need to select a connected board to start the serial plotter`
);
return;
}
await this.monitorManagerProxy.startMonitor(
this.boardsServiceProvider.boardsConfig.selectedBoard,
this.boardsServiceProvider.boardsConfig.selectedPort
);
await this.monitorManagerProxy.startMonitor();
if (!!this.window) {
this.window.focus();
return;
Expand Down
7 changes: 2 additions & 5 deletions arduino-ide-extension/src/common/protocol/monitor-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,12 @@ export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient');
export interface MonitorManagerProxyClient {
onMessagesReceived: Event<{ messages: string[] }>;
onMonitorSettingsDidChange: Event<MonitorSettings>;
onMonitorShouldReset: Event<void>;
connect(addressPort: number): void;
disconnect(): void;
getWebSocketPort(): number | undefined;
isWSConnected(): Promise<boolean>;
startMonitor(
board: Board,
port: Port,
settings?: PluggableMonitorSettings
): Promise<void>;
startMonitor(settings?: PluggableMonitorSettings): Promise<void>;
getCurrentSettings(board: Board, port: Port): Promise<MonitorSettings>;
send(message: string): void;
changeSettings(settings: MonitorSettings): void;
Expand Down
44 changes: 27 additions & 17 deletions arduino-ide-extension/src/node/monitor-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class MonitorService extends CoreClientAware implements Disposable {

protected uploadInProgress = false;
protected _initialized = new Deferred<void>();
protected creating: Deferred<Status>;

constructor(
@inject(ILogger)
Expand Down Expand Up @@ -121,6 +122,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
dispose(): void {
this.stop();
this.onDisposeEmitter.fire();
this.onWSClientsNumberChanged?.dispose();
}

/**
Expand Down Expand Up @@ -148,24 +150,30 @@ export class MonitorService extends CoreClientAware implements Disposable {
* @returns a status to verify connection has been established.
*/
async start(): Promise<Status> {
if (this.creating?.state === 'unresolved') return this.creating.promise;
this.creating = new Deferred();
if (this.duplex) {
this.updateClientsSettings({
monitorUISettings: { connected: true, serialPort: this.port.address },
});
return Status.ALREADY_CONNECTED;
this.creating.resolve(Status.ALREADY_CONNECTED);
return this.creating.promise;
}

if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) {
this.updateClientsSettings({ monitorUISettings: { connected: false } });

return Status.CONFIG_MISSING;
this.creating.resolve(Status.CONFIG_MISSING);
return this.creating.promise;
}

if (this.uploadInProgress) {
this.updateClientsSettings({
monitorUISettings: { connected: false, serialPort: this.port.address },
});
return Status.UPLOAD_IN_PROGRESS;

this.creating.resolve(Status.UPLOAD_IN_PROGRESS);
return this.creating.promise;
}

this.logger.info('starting monitor');
Expand Down Expand Up @@ -213,7 +221,7 @@ export class MonitorService extends CoreClientAware implements Disposable {

// Promise executor
const writeToStream = (resolve: (value: boolean) => void) => {
this.duplex = this.duplex || coreClient.client.monitor();
this.duplex = coreClient.client.monitor();

const duplexHandlers: DuplexHandler[] = [
{
Expand Down Expand Up @@ -257,17 +265,16 @@ export class MonitorService extends CoreClientAware implements Disposable {
this.logger.error(res.getError());
return;
}
if (res.getSuccess()) {
resolve(true);
return;
}
const data = res.getRxData();
const message =
typeof data === 'string'
? data
: new TextDecoder('utf8').decode(data);
this.messages.push(...splitLines(message));

// if (res.getSuccess()) {
// resolve(true);
// return;
// }
},
},
];
Expand All @@ -277,27 +284,30 @@ export class MonitorService extends CoreClientAware implements Disposable {
};

let attemptsRemaining = 10;
let wroteToStreamWithoutError = false;
do {
await new Promise((r) => setTimeout(r, 10000));
wroteToStreamWithoutError = await new Promise(writeToStream);
let wroteToStreamSuccessfully = false;
while (attemptsRemaining > 0) {
wroteToStreamSuccessfully = await new Promise(writeToStream);
if (wroteToStreamSuccessfully) break;
attemptsRemaining -= 1;
} while (!wroteToStreamWithoutError && attemptsRemaining > 0);
await new Promise((r) => setTimeout(r, 2000));
}

if (wroteToStreamWithoutError) {
if (wroteToStreamSuccessfully) {
this.startMessagesHandlers();
this.logger.info(
`started monitor to ${this.port?.address} using ${this.port?.protocol}`
);
this.updateClientsSettings({
monitorUISettings: { connected: true, serialPort: this.port.address },
});
return Status.OK;
this.creating.resolve(Status.OK);
return this.creating.promise;
} else {
this.logger.warn(
`failed starting monitor to ${this.port?.address} using ${this.port?.protocol}`
);
return Status.NOT_CONNECTED;
this.creating.resolve(Status.NOT_CONNECTED);
return this.creating.promise;
}
}

Expand Down