From 480492a7c8ad150d7be10be757d69741b913a240 Mon Sep 17 00:00:00 2001 From: Silvano Cerza Date: Fri, 4 Mar 2022 18:03:28 +0100 Subject: [PATCH] Enhance MonitorManager APIs --- .../monitor-manager-proxy-client-impl.ts | 7 + .../src/common/monitor-manager-proxy.ts | 12 -- .../src/node/monitor-manager-proxy-impl.ts | 45 ++++- .../src/node/monitor-manager.ts | 189 +++++++++++++++++- 4 files changed, 235 insertions(+), 18 deletions(-) delete mode 100644 arduino-ide-extension/src/common/monitor-manager-proxy.ts diff --git a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts index 70602783..45956ce5 100644 --- a/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor-manager-proxy-client-impl.ts @@ -1,6 +1,13 @@ +import { Emitter } from "@theia/core"; import { injectable } from "@theia/core/shared/inversify"; import { MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; @injectable() export class MonitorManagerProxyClientImpl implements MonitorManagerProxyClient { + protected readonly onWebSocketChangedEmitter = new Emitter(); + readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; + + notifyWebSocketChanged(message: number): void { + this.onWebSocketChangedEmitter.fire(message); + } } diff --git a/arduino-ide-extension/src/common/monitor-manager-proxy.ts b/arduino-ide-extension/src/common/monitor-manager-proxy.ts deleted file mode 100644 index ba08b361..00000000 --- a/arduino-ide-extension/src/common/monitor-manager-proxy.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { JsonRpcServer } from "@theia/core"; - -export const MonitorManagerProxyPath = '/services/monitor-manager-proxy'; -export const MonitorManagerProxy = Symbol('MonitorManagerProxy'); -export interface MonitorManagerProxy extends JsonRpcServer { - -} - -export const MonitorManagerProxyClient = Symbol('MonitorManagerProxyClient'); -export interface MonitorManagerProxyClient { - -} diff --git a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts index 814c7bc3..d582ea09 100644 --- a/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts +++ b/arduino-ide-extension/src/node/monitor-manager-proxy-impl.ts @@ -1,20 +1,57 @@ -import { inject, injectable } from "@theia/core/shared/inversify"; -import { MonitorManagerProxy, MonitorManagerProxyClient } from "../common/monitor-manager-proxy"; +import { Emitter, ILogger } from "@theia/core"; +import { inject, injectable, named } from "@theia/core/shared/inversify"; +import { Disposable } from "@theia/core/shared/vscode-languageserver-protocol"; +import { MonitorManagerProxy, MonitorManagerProxyClient, MonitorSettings, Status } from "../common/protocol"; +import { Board, Port } from "../common/protocol"; import { MonitorManager } from "./monitor-manager"; @injectable() export class MonitorManagerProxyImpl implements MonitorManagerProxy { + protected client: MonitorManagerProxyClient; + + protected selectedBoard: Board | null; + protected selectedPort: Port | null; + constructor( + @inject(ILogger) + @named("monitor-manager-proxy") + protected readonly logger: ILogger, + @inject(MonitorManager) protected readonly manager: MonitorManager, ) { } dispose(): void { - // TODO + // NOOP + } + + + // setMonitorConfig is called by the FE when trying to establish a monitor connection to a board or when changing some + // settings (such as the baudrate, when available) + async setMonitorSettings(board: Board, port: Port, settings: MonitorSettings): Promise { + + // check if it's a different connection or a change in the settings + if (board === this.selectedBoard && port === this.selectedPort) { + + // TODO: update the settings + return; + } + + const startStatus: Status = await this.manager.startMonitor(board, port); + + if (startStatus === Status.ALREADY_CONNECTED || startStatus === Status.OK) { + this.client.notifyWebSocketChanged(this.manager.getWebsocketAddress(board, port)); + } + } setClient(client: MonitorManagerProxyClient | undefined): void { - // TODO + if (!client) { + return; + } + this.client = client; + } + } \ No newline at end of file diff --git a/arduino-ide-extension/src/node/monitor-manager.ts b/arduino-ide-extension/src/node/monitor-manager.ts index 1814c94a..62434bdc 100644 --- a/arduino-ide-extension/src/node/monitor-manager.ts +++ b/arduino-ide-extension/src/node/monitor-manager.ts @@ -1,6 +1,191 @@ -import { injectable } from "@theia/core/shared/inversify"; +import { Emitter, ILogger } from "@theia/core"; +import { inject, injectable, named } from "@theia/core/shared/inversify"; +import { Board, Port, Status, MonitorSetting, MonitorSettings } from "../common/protocol"; +import { EnumerateMonitorPortSettingsRequest, EnumerateMonitorPortSettingsResponse } from "./cli-protocol/cc/arduino/cli/commands/v1/monitor_pb"; +import { CoreClientAware } from "./core-client-provider"; +import { MonitorService } from "./monitor-service"; + +type MonitorID = string; @injectable() -export class MonitorManager { +export class MonitorManager extends CoreClientAware { + // Map of monitor services that manage the running pluggable monitors. + // Each service handles the lifetime of one, and only one, monitor. + // If either the board or port managed changes a new service must + // be started. + private monitorServices = new Map(); + // Used to notify a monitor service that an upload process started + // to the board/port combination it manages + protected readonly onUploadStartedEmitter = new Emitter<{ board: Board, port: Port }>(); + readonly onUploadStarted = this.onUploadStartedEmitter.event; + + // Used to notify a monitor service that an upload process finished + // to the board/port combination it manages + + + + constructor( + @inject(ILogger) + @named('monitor-manager') + protected readonly logger: ILogger, + ) { + super(); + } + + /** + * Returns the possible configurations used to connect a monitor + * to the board specified by fqbn using the specified protocol + * @param protocol the protocol of the monitor we want get settings for + * @param fqbn the fqbn of the board we want to monitor + * @returns a map of all the settings supported by the monitor + */ + async portMonitorSettings(protocol: string, fqbn: string): Promise { + const coreClient = await this.coreClient(); + const { client, instance } = coreClient; + const req = new EnumerateMonitorPortSettingsRequest(); + req.setInstance(instance); + req.setPortProtocol(protocol); + req.setFqbn(fqbn); + + const res = await new Promise((resolve, reject) => { + client.enumerateMonitorPortSettings(req, (err, resp) => { + if (!!err) { + reject(err) + } + resolve(resp) + }) + }) + + let settings: MonitorSettings = {}; + for (const iterator of res.getSettingsList()) { + settings[iterator.getSettingId()] = { + 'id': iterator.getSettingId(), + 'label': iterator.getLabel(), + 'type': iterator.getType(), + 'values': iterator.getEnumValuesList(), + 'selectedValue': iterator.getValue(), + } + } + return settings; + } + + /** + * + * @param board + * @param port + */ + async startMonitor(board: Board, port: Port): Promise { + const monitorID = this.monitorID(board, port); + let monitor = this.monitorServices.get(monitorID); + if (!monitor) { + monitor = this.createMonitor(board, port) + } + return await monitor.start(); + // TODO: I need to return the address here right? + } + + async stopMonitor(board: Board, port: Port): Promise { + const monitorID = this.monitorID(board, port); + + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor to stop, bail + return; + } + return await monitor.stop(); + } + + getWebsocketAddress(board: Board, port: Port): number { + const monitorID = this.monitorID(board, port); + + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + return -1; + } + return monitor.getWebsocketAddress(); + } + + /** + * Notifies the monitor service of that board/port combination + * that an upload process started on that exact board/port combination. + * This must be done so that we can stop the monitor for the time being + * until the upload process finished. + * @param board + * @param port + */ + async notifyUploadStarted(board?: Board, port?: Port): Promise { + if (!board || !port) { + // We have no way of knowing which monitor + // to retrieve if we don't have this information. + return; + } + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor running there, bail + return; + } + return await monitor.pause(); + } + + /** + * Notifies the monitor service of that board/port combination + * that an upload process started on that exact board/port combination. + * @param board + * @param port + * @returns + */ + async notifyUploadFinished(board?: Board, port?: Port): Promise { + if (!board || !port) { + // We have no way of knowing which monitor + // to retrieve if we don't have this information. + return Status.NOT_CONNECTED; + } + const monitorID = this.monitorID(board, port); + const monitor = this.monitorServices.get(monitorID); + if (!monitor) { + // There's no monitor running there, bail + return Status.NOT_CONNECTED; + } + return await monitor.start(); + } + + /** + * + * @param board + * @param port + * @param settings map of monitor settings to change + */ + changeMonitorSettings(board: Board, port: Port, settings: Record) { + const monitorID = this.monitorID(board, port); + let monitor = this.monitorServices.get(monitorID); + if (!monitor) { + monitor = this.createMonitor(board, port) + monitor.changeSettings(settings); + } + } + + private createMonitor(board: Board, port: Port): MonitorService { + const monitorID = this.monitorID(board, port); + const monitor = new MonitorService( + this.logger, + board, + port + ); + monitor.onDispose((() => { + this.monitorServices.delete(monitorID); + }).bind(this)); + return monitor + } + + /** + * Utility function to create a unique ID for a monitor service. + * @param board + * @param port + * @returns a unique monitor ID + */ + private monitorID(board: Board, port: Port): MonitorID { + return `${board.fqbn}-${port.address}-${port.protocol}`; + } } \ No newline at end of file