add MonitorSettingsProvider interface

This commit is contained in:
Alberto Iannaccone 2022-05-17 17:45:47 +02:00
parent 1982609c87
commit 7bf4ea0637
9 changed files with 233 additions and 201 deletions

View File

@ -5,8 +5,8 @@ import {
Monitor, Monitor,
MonitorManagerProxyClient, MonitorManagerProxyClient,
MonitorManagerProxyFactory, MonitorManagerProxyFactory,
MonitorSettings,
} from '../common/protocol/monitor-service'; } from '../common/protocol/monitor-service';
import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider';
@injectable() @injectable()
export class MonitorManagerProxyClientImpl export class MonitorManagerProxyClientImpl

View File

@ -1,134 +1,139 @@
import { Emitter, Event } from "@theia/core"; import { Emitter, Event } from '@theia/core';
import { FrontendApplicationContribution, LocalStorageService } from "@theia/core/lib/browser"; import {
import { inject, injectable } from "@theia/core/shared/inversify"; FrontendApplicationContribution,
LocalStorageService,
} from '@theia/core/lib/browser';
import { inject, injectable } from '@theia/core/shared/inversify';
@injectable() @injectable()
export class MonitorModel implements FrontendApplicationContribution { export class MonitorModel implements FrontendApplicationContribution {
protected static STORAGE_ID = 'arduino-monitor-model'; protected static STORAGE_ID = 'arduino-monitor-model';
@inject(LocalStorageService) @inject(LocalStorageService)
protected readonly localStorageService: LocalStorageService; protected readonly localStorageService: LocalStorageService;
protected readonly onChangeEmitter: Emitter<MonitorModel.State.Change<keyof MonitorModel.State>>; protected readonly onChangeEmitter: Emitter<
MonitorModel.State.Change<keyof MonitorModel.State>
>;
protected _autoscroll: boolean; protected _autoscroll: boolean;
protected _timestamp: boolean; protected _timestamp: boolean;
protected _lineEnding: MonitorModel.EOL; protected _lineEnding: MonitorModel.EOL;
protected _interpolate: boolean; protected _interpolate: boolean;
constructor() { constructor() {
this._autoscroll = true; this._autoscroll = true;
this._timestamp = false; this._timestamp = false;
this._interpolate = false; this._interpolate = false;
this._lineEnding = MonitorModel.EOL.DEFAULT; this._lineEnding = MonitorModel.EOL.DEFAULT;
this.onChangeEmitter = new Emitter< this.onChangeEmitter = new Emitter<
MonitorModel.State.Change<keyof MonitorModel.State> MonitorModel.State.Change<keyof MonitorModel.State>
>(); >();
}
onStart(): void {
this.localStorageService
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID)
.then(this.restoreState);
}
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
return this.onChangeEmitter.event;
}
protected restoreState(state: MonitorModel.State): void {
if (!state) {
return;
} }
this._autoscroll = state.autoscroll;
this._timestamp = state.timestamp;
this._lineEnding = state.lineEnding;
this._interpolate = state.interpolate;
}
onStart(): void { protected async storeState(): Promise<void> {
this.localStorageService return this.localStorageService.setData(MonitorModel.STORAGE_ID, {
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID) autoscroll: this._autoscroll,
.then(this.restoreState); timestamp: this._timestamp,
} lineEnding: this._lineEnding,
interpolate: this._interpolate,
});
}
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> { get autoscroll(): boolean {
return this.onChangeEmitter.event; return this._autoscroll;
} }
protected restoreState(state: MonitorModel.State): void { toggleAutoscroll(): void {
if (!state) { this._autoscroll = !this._autoscroll;
return; this.storeState().then(() => {
} this.onChangeEmitter.fire({
this._autoscroll = state.autoscroll; property: 'autoscroll',
this._timestamp = state.timestamp; value: this._timestamp,
this._lineEnding = state.lineEnding; });
this._interpolate = state.interpolate; });
} }
protected async storeState(): Promise<void> { get timestamp(): boolean {
return this.localStorageService.setData(MonitorModel.STORAGE_ID, { return this._timestamp;
autoscroll: this._autoscroll, }
timestamp: this._timestamp,
lineEnding: this._lineEnding,
interpolate: this._interpolate,
});
}
get autoscroll(): boolean { toggleTimestamp(): void {
return this._autoscroll; this._timestamp = !this._timestamp;
} this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'timestamp',
value: this._timestamp,
})
);
}
toggleAutoscroll(): void { get lineEnding(): MonitorModel.EOL {
this._autoscroll = !this._autoscroll; return this._lineEnding;
this.storeState().then(() => { }
this.onChangeEmitter.fire({
property: 'autoscroll',
value: this._timestamp
});
});
}
get timestamp(): boolean { set lineEnding(lineEnding: MonitorModel.EOL) {
return this._timestamp; this._lineEnding = lineEnding;
} this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'lineEnding',
value: this._lineEnding,
})
);
}
toggleTimestamp(): void { get interpolate(): boolean {
this._timestamp = !this._timestamp; return this._interpolate;
this.storeState().then(() => }
this.onChangeEmitter.fire({
property: 'timestamp',
value: this._timestamp,
})
);
}
get lineEnding(): MonitorModel.EOL { set interpolate(i: boolean) {
return this._lineEnding; this._interpolate = i;
} this.storeState().then(() =>
this.onChangeEmitter.fire({
set lineEnding(lineEnding: MonitorModel.EOL) { property: 'interpolate',
this._lineEnding = lineEnding; value: this._interpolate,
this.storeState().then(() => })
this.onChangeEmitter.fire({ );
property: 'lineEnding', }
value: this._lineEnding,
})
);
}
get interpolate(): boolean {
return this._interpolate;
}
set interpolate(i: boolean) {
this._interpolate = i;
this.storeState().then(() =>
this.onChangeEmitter.fire({
property: 'interpolate',
value: this._interpolate,
})
);
}
} }
export namespace MonitorModel { export namespace MonitorModel {
export interface State { export interface State {
autoscroll: boolean; autoscroll: boolean;
timestamp: boolean; timestamp: boolean;
lineEnding: EOL; lineEnding: EOL;
interpolate: boolean; interpolate: boolean;
} }
export namespace State { export namespace State {
export interface Change<K extends keyof State> { export interface Change<K extends keyof State> {
readonly property: K; readonly property: K;
readonly value: State[K]; readonly value: State[K];
}
} }
}
export type EOL = '' | '\n' | '\r' | '\r\n'; export type EOL = '' | '\n' | '\r' | '\r\n';
export namespace EOL { export namespace EOL {
export const DEFAULT: EOL = '\n'; export const DEFAULT: EOL = '\n';
} }
} }

View File

@ -14,11 +14,9 @@ import { SerialMonitorSendInput } from './serial-monitor-send-input';
import { SerialMonitorOutput } from './serial-monitor-send-output'; import { SerialMonitorOutput } from './serial-monitor-send-output';
import { BoardsServiceProvider } from '../../boards/boards-service-provider'; import { BoardsServiceProvider } from '../../boards/boards-service-provider';
import { nls } from '@theia/core/lib/common'; import { nls } from '@theia/core/lib/common';
import { import { MonitorManagerProxyClient } from '../../../common/protocol';
MonitorManagerProxyClient,
MonitorSettings,
} from '../../../common/protocol';
import { MonitorModel } from '../../monitor-model'; import { MonitorModel } from '../../monitor-model';
import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider';
@injectable() @injectable()
export class MonitorWidget extends ReactWidget { export class MonitorWidget extends ReactWidget {

View File

@ -91,7 +91,7 @@ export class PlotterFrontendContribution extends Contribution {
const settings = this.monitorManagerProxy.getCurrentSettings(board, port); const settings = this.monitorManagerProxy.getCurrentSettings(board, port);
if ('baudrate' in settings) { if ('baudrate' in settings) {
// Convert from string to numbers // Convert from string to numbers
baudrates = settings['baudrate'].values.map(b => +b); baudrates = settings['baudrate'].values.map((b) => +b);
currentBaudrate = +settings['baudrate'].selectedValue; currentBaudrate = +settings['baudrate'].selectedValue;
} }
} }

View File

@ -1,4 +1,5 @@
import { Event, JsonRpcServer } from '@theia/core'; import { Event, JsonRpcServer } from '@theia/core';
import { MonitorSettings } from '../../node/monitor-settings/monitor-settings-provider';
import { Board, Port } from './boards-service'; import { Board, Port } from './boards-service';
export const MonitorManagerProxyFactory = Symbol('MonitorManagerProxyFactory'); export const MonitorManagerProxyFactory = Symbol('MonitorManagerProxyFactory');
@ -53,8 +54,6 @@ export interface MonitorSetting {
selectedValue: string; selectedValue: string;
} }
export type MonitorSettings = Record<string, MonitorSetting>;
export namespace Monitor { export namespace Monitor {
export enum Command { export enum Command {
SEND_MESSAGE = 'MONITOR_SEND_MESSAGE', SEND_MESSAGE = 'MONITOR_SEND_MESSAGE',

View File

@ -1,80 +1,92 @@
import { inject, injectable } from "@theia/core/shared/inversify"; import { inject, injectable } from '@theia/core/shared/inversify';
import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings, Status } from "../common/protocol"; import {
import { Board, Port } from "../common/protocol"; MonitorManagerProxy,
import { MonitorManager } from "./monitor-manager"; MonitorManagerProxyClient,
Status,
} from '../common/protocol';
import { Board, Port } from '../common/protocol';
import { MonitorManager } from './monitor-manager';
import { MonitorSettings } from './monitor-settings/monitor-settings-provider';
@injectable() @injectable()
export class MonitorManagerProxyImpl implements MonitorManagerProxy { export class MonitorManagerProxyImpl implements MonitorManagerProxy {
protected client: MonitorManagerProxyClient; protected client: MonitorManagerProxyClient;
constructor( constructor(
@inject(MonitorManager) @inject(MonitorManager)
protected readonly manager: MonitorManager, protected readonly manager: MonitorManager
) { ) {}
}
dispose(): void { dispose(): void {
this.client?.disconnect(); this.client?.disconnect();
} }
/** /**
* Start a pluggable monitor and/or change its settings. * Start a pluggable monitor and/or change its settings.
* If settings are defined they'll be set before starting the monitor, * If settings are defined they'll be set before starting the monitor,
* otherwise default ones will be used by the monitor. * otherwise default ones will be used by the monitor.
* @param board board connected to port * @param board board connected to port
* @param port port to monitor * @param port port to monitor
* @param settings map of supported configuration by the monitor * @param settings map of supported configuration by the monitor
*/ */
async startMonitor(board: Board, port: Port, settings?: MonitorSettings): Promise<void> { async startMonitor(
if (settings) { board: Board,
await this.changeMonitorSettings(board, port, settings); port: Port,
} settings?: MonitorSettings
const status = await this.manager.startMonitor(board, port); ): Promise<void> {
if (status === Status.ALREADY_CONNECTED || status === Status.OK) { if (settings) {
// Monitor started correctly, connect it with the frontend await this.changeMonitorSettings(board, port, settings);
this.client.connect(this.manager.getWebsocketAddressPort(board, port));
}
} }
const status = await this.manager.startMonitor(board, port);
if (status === Status.ALREADY_CONNECTED || status === Status.OK) {
// Monitor started correctly, connect it with the frontend
this.client.connect(this.manager.getWebsocketAddressPort(board, port));
}
}
/** /**
* Changes the settings of a running pluggable monitor, if that monitor is not * Changes the settings of a running pluggable monitor, if that monitor is not
* started this function is a noop. * started this function is a noop.
* @param board board connected to port * @param board board connected to port
* @param port port monitored * @param port port monitored
* @param settings map of supported configuration by the monitor * @param settings map of supported configuration by the monitor
*/ */
async changeMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise<void> { async changeMonitorSettings(
if (!this.manager.isStarted(board, port)) { board: Board,
// Monitor is not running, no need to change settings port: Port,
return; settings: MonitorSettings
} ): Promise<void> {
return this.manager.changeMonitorSettings(board, port, settings); if (!this.manager.isStarted(board, port)) {
// Monitor is not running, no need to change settings
return;
} }
return this.manager.changeMonitorSettings(board, port, settings);
}
/** /**
* Stops a running pluggable monitor. * Stops a running pluggable monitor.
* @param board board connected to port * @param board board connected to port
* @param port port monitored * @param port port monitored
*/ */
async stopMonitor(board: Board, port: Port): Promise<void> { async stopMonitor(board: Board, port: Port): Promise<void> {
return this.manager.stopMonitor(board, port); return this.manager.stopMonitor(board, port);
} }
/** /**
* Returns the current settings by the pluggable monitor connected to specified * Returns the current settings by the pluggable monitor connected to specified
* by board/port combination. * by board/port combination.
* @param board board connected to port * @param board board connected to port
* @param port port monitored * @param port port monitored
* @returns a map of MonitorSetting * @returns a map of MonitorSetting
*/ */
getCurrentSettings(board: Board, port: Port): MonitorSettings { getCurrentSettings(board: Board, port: Port): MonitorSettings {
return this.manager.currentMonitorSettings(board, port); return this.manager.currentMonitorSettings(board, port);
} }
setClient(client: MonitorManagerProxyClient | undefined): void { setClient(client: MonitorManagerProxyClient | undefined): void {
if (!client) { if (!client) {
return; return;
}
this.client = client;
} }
} this.client = client;
}
}

View File

@ -1,8 +1,9 @@
import { ILogger } from '@theia/core'; import { ILogger } from '@theia/core';
import { inject, injectable, named } from '@theia/core/shared/inversify'; import { inject, injectable, named } from '@theia/core/shared/inversify';
import { Board, Port, Status, MonitorSettings } from '../common/protocol'; import { Board, Port, Status } from '../common/protocol';
import { CoreClientAware } from './core-client-provider'; import { CoreClientAware } from './core-client-provider';
import { MonitorService } from './monitor-service'; import { MonitorService } from './monitor-service';
import { MonitorSettings } from './monitor-settings/monitor-settings-provider';
type MonitorID = string; type MonitorID = string;
@ -54,7 +55,7 @@ export class MonitorManager extends CoreClientAware {
if (!monitor) { if (!monitor) {
monitor = this.createMonitor(board, port); monitor = this.createMonitor(board, port);
} }
return await monitor.start(); return await monitor.start(monitorID);
} }
/** /**
@ -134,7 +135,7 @@ export class MonitorManager extends CoreClientAware {
return Status.NOT_CONNECTED; return Status.NOT_CONNECTED;
} }
monitor.setUploadInProgress(false); monitor.setUploadInProgress(false);
return await monitor.start(); return await monitor.start(monitorID);
} }
/** /**

View File

@ -1,13 +1,7 @@
import { ClientDuplexStream } from '@grpc/grpc-js'; import { ClientDuplexStream } from '@grpc/grpc-js';
import { Disposable, Emitter, ILogger } from '@theia/core'; import { Disposable, Emitter, ILogger } from '@theia/core';
import { inject, named } from '@theia/core/shared/inversify'; import { inject, named } from '@theia/core/shared/inversify';
import { import { Board, Port, Status, Monitor } from '../common/protocol';
Board,
Port,
Status,
MonitorSettings,
Monitor,
} from '../common/protocol';
import { import {
EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsRequest,
EnumerateMonitorPortSettingsResponse, EnumerateMonitorPortSettingsResponse,
@ -20,6 +14,10 @@ import { CoreClientAware, CoreClientProvider } from './core-client-provider';
import { WebSocketProvider } from './web-socket/web-socket-provider'; import { WebSocketProvider } from './web-socket/web-socket-provider';
import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb'; import { Port as gRPCPort } from 'arduino-ide-extension/src/node/cli-protocol/cc/arduino/cli/commands/v1/port_pb';
import WebSocketProviderImpl from './web-socket/web-socket-provider-impl'; import WebSocketProviderImpl from './web-socket/web-socket-provider-impl';
import {
MonitorSettings,
MonitorSettingsProvider,
} from './monitor-settings/monitor-settings-provider';
export const MonitorServiceName = 'monitor-service'; export const MonitorServiceName = 'monitor-service';
@ -50,6 +48,10 @@ export class MonitorService extends CoreClientAware implements Disposable {
protected readonly onDisposeEmitter = new Emitter<void>(); protected readonly onDisposeEmitter = new Emitter<void>();
readonly onDispose = this.onDisposeEmitter.event; readonly onDispose = this.onDisposeEmitter.event;
@inject(MonitorSettingsProvider)
protected readonly monitorSettingsProvider: MonitorSettingsProvider;
// TODO: use dependency injection
protected readonly webSocketProvider: WebSocketProvider = protected readonly webSocketProvider: WebSocketProvider =
new WebSocketProviderImpl(); new WebSocketProviderImpl();
@ -75,11 +77,6 @@ export class MonitorService extends CoreClientAware implements Disposable {
this.dispose(); this.dispose();
} }
}); });
// Sets default settings for this monitor
this.portMonitorSettings(port.protocol, board.fqbn!).then(
(settings) => (this.settings = settings)
);
} }
setUploadInProgress(status: boolean): void { setUploadInProgress(status: boolean): void {
@ -108,9 +105,10 @@ export class MonitorService extends CoreClientAware implements Disposable {
* Start and connects a monitor using currently set board and port. * Start and connects a monitor using currently set board and port.
* If a monitor is already started or board fqbn, port address and/or protocol * If a monitor is already started or board fqbn, port address and/or protocol
* are missing nothing happens. * are missing nothing happens.
* @param id
* @returns a status to verify connection has been established. * @returns a status to verify connection has been established.
*/ */
async start(): Promise<Status> { async start(monitorID: string): Promise<Status> {
if (this.duplex) { if (this.duplex) {
return Status.ALREADY_CONNECTED; return Status.ALREADY_CONNECTED;
} }
@ -124,6 +122,10 @@ export class MonitorService extends CoreClientAware implements Disposable {
} }
this.logger.info('starting monitor'); this.logger.info('starting monitor');
this.settings = await this.monitorSettingsProvider.init(
monitorID,
this.coreClientProvider
);
await this.coreClientProvider.initialized; await this.coreClientProvider.initialized;
const coreClient = await this.coreClient(); const coreClient = await this.coreClient();
const { client, instance } = coreClient; const { client, instance } = coreClient;
@ -281,6 +283,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
return this.settings; return this.settings;
} }
// TODO: move this into MonitoSettingsProvider
/** /**
* Returns the possible configurations used to connect a monitor * Returns the possible configurations used to connect a monitor
* to the board specified by fqbn using the specified protocol * to the board specified by fqbn using the specified protocol

View File

@ -0,0 +1,14 @@
import { MonitorSetting } from '../../common/protocol';
import { CoreClientProvider } from '../core-client-provider';
export type MonitorSettings = Record<string, MonitorSetting>;
export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider');
export interface MonitorSettingsProvider {
init(
id: string,
coreClientProvider: CoreClientProvider
): Promise<MonitorSettings>;
get(): Promise<MonitorSettings>;
set(settings: MonitorSettings): Promise<MonitorSettings>;
}