From 401bcf57e5816674e53ffc5568cc5d9f0a489a2b Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 14 Oct 2021 12:04:48 +0200 Subject: [PATCH 01/27] spawn new window where to instantiate serial plotter app --- .../browser/arduino-ide-frontend-module.ts | 2 + .../src/browser/menu/arduino-menus.ts | 2 +- .../browser/plotter/plotter-contribution.ts | 69 +++++++++++++++++++ .../theia/electron-main-application.ts | 21 ++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 arduino-ide-extension/src/browser/plotter/plotter-contribution.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index ec572776f..1e505c507 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -253,6 +253,7 @@ import { UploadCertificateDialogProps, UploadCertificateDialogWidget, } from './dialogs/certificate-uploader/certificate-uploader-dialog'; +import { PlotterContribution } from './plotter/plotter-contribution'; import { nls } from '@theia/core/lib/browser/nls'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -597,6 +598,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, AddFile); Contribution.configure(bind, ArchiveSketch); Contribution.configure(bind, AddZipLibrary); + Contribution.configure(bind, PlotterContribution); bind(ResponseServiceImpl) .toSelf() diff --git a/arduino-ide-extension/src/browser/menu/arduino-menus.ts b/arduino-ide-extension/src/browser/menu/arduino-menus.ts index 2055cbb61..500bdf124 100644 --- a/arduino-ide-extension/src/browser/menu/arduino-menus.ts +++ b/arduino-ide-extension/src/browser/menu/arduino-menus.ts @@ -86,7 +86,7 @@ export namespace ArduinoMenus { // -- Tools export const TOOLS = [...MAIN_MENU_BAR, '4_tools']; - // `Auto Format`, `Archive Sketch`, `Manage Libraries...`, `Serial Monitor` + // `Auto Format`, `Archive Sketch`, `Manage Libraries...`, `Serial Monitor`, Serial Plotter export const TOOLS__MAIN_GROUP = [...TOOLS, '0_main']; // `WiFi101 / WiFiNINA Firmware Updater` export const TOOLS__FIRMWARE_UPLOADER_GROUP = [ diff --git a/arduino-ide-extension/src/browser/plotter/plotter-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-contribution.ts new file mode 100644 index 000000000..e6ca5f50f --- /dev/null +++ b/arduino-ide-extension/src/browser/plotter/plotter-contribution.ts @@ -0,0 +1,69 @@ +import { injectable, inject } from 'inversify'; +import { Command, CommandRegistry, MenuModelRegistry } from '@theia/core'; +import { MonitorModel } from '../monitor/monitor-model'; +import { ArduinoMenus } from '../menu/arduino-menus'; +import { Contribution } from '../contributions/contribution'; +import { MonitorConfig } from '../../common/protocol'; + +export namespace SerialPlotter { + export namespace Commands { + export const OPEN: Command = { + id: 'serial-plotter-open', + label: 'Serial Plotter', + category: 'Arduino', + }; + } + export type InitMessage = { + baudrate: MonitorConfig.BaudRate; + darkTheme: boolean; + wsPort: number; + }; +} + +@injectable() +export class PlotterContribution extends Contribution { + protected window: Window | null; + + @inject(MonitorModel) + protected readonly model: MonitorModel; + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(SerialPlotter.Commands.OPEN, { + execute: async () => this.open(), + }); + } + + registerMenus(menus: MenuModelRegistry): void { + menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { + commandId: SerialPlotter.Commands.OPEN.id, + label: SerialPlotter.Commands.OPEN.label, + order: '7', + }); + } + + protected async open(): Promise { + const url = 'http://localhost:8080/index.html'; + if (this.window) { + this.window.focus(); + } else { + this.window = window.open(url, 'serialPlotter'); + window.addEventListener('message', handlePostMessage); + const initMessage: SerialPlotter.InitMessage = { + baudrate: this.model.baudRate, + darkTheme: false, + wsPort: 0, + }; + if (this.window) { + this.window.postMessage(initMessage, url); + this.window.onclose = () => { + window.removeEventListener('message', handlePostMessage); + this.window = null; + }; + } + } + } +} + +const handlePostMessage = (event: MessageEvent) => { + console.log(event.data); +}; diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 81e21b03b..76d9e6f68 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -18,6 +18,8 @@ import { } from '@theia/core/lib/electron-main/electron-main-application'; import { SplashServiceImpl } from '../splash/splash-service-impl'; +app.commandLine.appendSwitch('disable-http-cache'); + @injectable() export class ElectronMainApplication extends TheiaElectronMainApplication { protected _windows: BrowserWindow[] = []; @@ -88,6 +90,25 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { this.splashService.onCloseRequested ); } + + electronWindow.webContents.on( + 'new-window', + (event, url, frameName, disposition, options, additionalFeatures) => { + if (frameName === 'serialPlotter') { + event.preventDefault(); + Object.assign(options, { + webPreferences: { + devTools: true, + nativeWindowOpen: true, + openerId: electronWindow?.webContents.id, + }, + }); + event.newGuest = new BrowserWindow(options); + event.newGuest?.loadURL(url); + } + } + ); + this._windows.push(electronWindow); electronWindow.on('closed', () => { if (electronWindow) { From a834e8663ce60d8b7afb1ddbed4dc4a7b7375e36 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Wed, 20 Oct 2021 12:26:57 +0200 Subject: [PATCH 02/27] initialize serial monito web app --- arduino-ide-extension/package.json | 3 +- .../browser/arduino-frontend-contribution.tsx | 52 ----------- .../browser/arduino-ide-frontend-module.ts | 13 ++- .../browser/plotter/plotter-contribution.ts | 69 -------------- .../plotter/plotter-frontend-contribution.ts | 89 +++++++++++++++++++ .../src/browser/plotter/protocol.ts | 23 +++++ .../src/common/protocol/monitor-service.ts | 14 +-- .../src/common/protocol/plotter-service.ts | 16 ++++ .../theia/electron-main-application.ts | 3 + .../src/node/arduino-ide-backend-module.ts | 4 + .../plotter/plotter-backend-contribution.ts | 30 +++++++ yarn.lock | 10 +++ 12 files changed, 192 insertions(+), 134 deletions(-) delete mode 100644 arduino-ide-extension/src/browser/plotter/plotter-contribution.ts create mode 100644 arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts create mode 100644 arduino-ide-extension/src/browser/plotter/protocol.ts create mode 100644 arduino-ide-extension/src/common/protocol/plotter-service.ts create mode 100644 arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 6bb9166b8..04912fae8 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -22,7 +22,7 @@ "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", "@theia/editor": "1.18.0", - "@theia/editor-preview": "1.18.0", + "@theia/editor-preview": "1.18.0", "@theia/filesystem": "1.18.0", "@theia/git": "1.18.0", "@theia/keymaps": "1.18.0", @@ -77,6 +77,7 @@ "open": "^8.0.6", "p-queue": "^5.0.0", "ps-tree": "^1.2.0", + "query-string": "^7.0.1", "react-disable": "^0.1.0", "react-select": "^3.0.4", "react-tabs": "^3.1.2", diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 8352f6dc1..34b7a31fd 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -3,16 +3,13 @@ import { MAIN_MENU_BAR, MenuContribution, MenuModelRegistry, - SelectionService, ILogger, DisposableCollection, } from '@theia/core'; import { - ContextMenuRenderer, FrontendApplication, FrontendApplicationContribution, LocalStorageService, - OpenerService, StatusBar, StatusBarAlignment, } from '@theia/core/lib/browser'; @@ -35,7 +32,6 @@ import { EditorManager, EditorOpenerOptions, } from '@theia/editor/lib/browser'; -import { FileDialogService } from '@theia/filesystem/lib/browser/file-dialog'; import { ProblemContribution } from '@theia/markers/lib/browser/problem/problem-contribution'; import { MonacoMenus } from '@theia/monaco/lib/browser/monaco-menu'; import { FileNavigatorContribution } from '@theia/navigator/lib/browser/navigator-contribution'; @@ -47,33 +43,25 @@ import { TerminalMenus } from '@theia/terminal/lib/browser/terminal-frontend-con import { inject, injectable, postConstruct } from 'inversify'; import * as React from 'react'; import { remote } from 'electron'; -import { MainMenuManager } from '../common/main-menu-manager'; import { BoardsService, - CoreService, Port, SketchesService, ExecutableService, Sketch, } from '../common/protocol'; -import { ArduinoDaemon } from '../common/protocol/arduino-daemon'; import { ConfigService } from '../common/protocol/config-service'; -import { FileSystemExt } from '../common/protocol/filesystem-ext'; import { ArduinoCommands } from './arduino-commands'; import { BoardsConfig } from './boards/boards-config'; import { BoardsConfigDialog } from './boards/boards-config-dialog'; -import { BoardsDataStore } from './boards/boards-data-store'; import { BoardsServiceProvider } from './boards/boards-service-provider'; import { BoardsToolBarItem } from './boards/boards-toolbar-item'; import { EditorMode } from './editor-mode'; import { ArduinoMenus } from './menu/arduino-menus'; -import { MonitorConnection } from './monitor/monitor-connection'; import { MonitorViewContribution } from './monitor/monitor-view-contribution'; -import { WorkspaceService } from './theia/workspace/workspace-service'; import { ArduinoToolbar } from './toolbar/arduino-toolbar'; import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; -import { ResponseService } from '../common/protocol/response-service'; import { ArduinoPreferences } from './arduino-preferences'; import { SketchesServiceClientImpl } from '../common/protocol/sketches-service-client-impl'; import { SaveAsSketch } from './contributions/save-as-sketch'; @@ -101,24 +89,12 @@ export class ArduinoFrontendContribution @inject(BoardsService) protected readonly boardsService: BoardsService; - @inject(CoreService) - protected readonly coreService: CoreService; - @inject(BoardsServiceProvider) protected readonly boardsServiceClientImpl: BoardsServiceProvider; - @inject(SelectionService) - protected readonly selectionService: SelectionService; - @inject(EditorManager) protected readonly editorManager: EditorManager; - @inject(ContextMenuRenderer) - protected readonly contextMenuRenderer: ContextMenuRenderer; - - @inject(FileDialogService) - protected readonly fileDialogService: FileDialogService; - @inject(FileService) protected readonly fileService: FileService; @@ -128,21 +104,12 @@ export class ArduinoFrontendContribution @inject(BoardsConfigDialog) protected readonly boardsConfigDialog: BoardsConfigDialog; - @inject(MenuModelRegistry) - protected readonly menuRegistry: MenuModelRegistry; - @inject(CommandRegistry) protected readonly commandRegistry: CommandRegistry; @inject(StatusBar) protected readonly statusBar: StatusBar; - @inject(WorkspaceService) - protected readonly workspaceService: WorkspaceService; - - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; - @inject(FileNavigatorContribution) protected readonly fileNavigatorContributions: FileNavigatorContribution; @@ -167,40 +134,21 @@ export class ArduinoFrontendContribution @inject(EditorMode) protected readonly editorMode: EditorMode; - @inject(ArduinoDaemon) - protected readonly daemon: ArduinoDaemon; - - @inject(OpenerService) - protected readonly openerService: OpenerService; - @inject(ConfigService) protected readonly configService: ConfigService; - @inject(BoardsDataStore) - protected readonly boardsDataStore: BoardsDataStore; - - @inject(MainMenuManager) - protected readonly mainMenuManager: MainMenuManager; - - @inject(FileSystemExt) - protected readonly fileSystemExt: FileSystemExt; - @inject(HostedPluginSupport) protected hostedPluginSupport: HostedPluginSupport; @inject(ExecutableService) protected executableService: ExecutableService; - @inject(ResponseService) - protected readonly responseService: ResponseService; - @inject(ArduinoPreferences) protected readonly arduinoPreferences: ArduinoPreferences; @inject(SketchesServiceClientImpl) protected readonly sketchServiceClient: SketchesServiceClientImpl; - @inject(FrontendApplicationStateService) protected readonly appStateService: FrontendApplicationStateService; @inject(LocalStorageService) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 1e505c507..e6d22cb2d 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -253,7 +253,11 @@ import { UploadCertificateDialogProps, UploadCertificateDialogWidget, } from './dialogs/certificate-uploader/certificate-uploader-dialog'; -import { PlotterContribution } from './plotter/plotter-contribution'; +import { PlotterFrontendContribution } from './plotter/plotter-frontend-contribution'; +import { + PlotterPath, + PlotterService, +} from '../common/protocol/plotter-service'; import { nls } from '@theia/core/lib/browser/nls'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -598,7 +602,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { Contribution.configure(bind, AddFile); Contribution.configure(bind, ArchiveSketch); Contribution.configure(bind, AddZipLibrary); - Contribution.configure(bind, PlotterContribution); + Contribution.configure(bind, PlotterFrontendContribution); bind(ResponseServiceImpl) .toSelf() @@ -738,4 +742,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(UploadCertificateDialogProps).toConstantValue({ title: 'UploadCertificate', }); + bind(PlotterService) + .toDynamicValue((ctx) => + ctx.container.get(WebSocketConnectionProvider).createProxy(PlotterPath) + ) + .inSingletonScope(); }); diff --git a/arduino-ide-extension/src/browser/plotter/plotter-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-contribution.ts deleted file mode 100644 index e6ca5f50f..000000000 --- a/arduino-ide-extension/src/browser/plotter/plotter-contribution.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { injectable, inject } from 'inversify'; -import { Command, CommandRegistry, MenuModelRegistry } from '@theia/core'; -import { MonitorModel } from '../monitor/monitor-model'; -import { ArduinoMenus } from '../menu/arduino-menus'; -import { Contribution } from '../contributions/contribution'; -import { MonitorConfig } from '../../common/protocol'; - -export namespace SerialPlotter { - export namespace Commands { - export const OPEN: Command = { - id: 'serial-plotter-open', - label: 'Serial Plotter', - category: 'Arduino', - }; - } - export type InitMessage = { - baudrate: MonitorConfig.BaudRate; - darkTheme: boolean; - wsPort: number; - }; -} - -@injectable() -export class PlotterContribution extends Contribution { - protected window: Window | null; - - @inject(MonitorModel) - protected readonly model: MonitorModel; - - registerCommands(registry: CommandRegistry): void { - registry.registerCommand(SerialPlotter.Commands.OPEN, { - execute: async () => this.open(), - }); - } - - registerMenus(menus: MenuModelRegistry): void { - menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { - commandId: SerialPlotter.Commands.OPEN.id, - label: SerialPlotter.Commands.OPEN.label, - order: '7', - }); - } - - protected async open(): Promise { - const url = 'http://localhost:8080/index.html'; - if (this.window) { - this.window.focus(); - } else { - this.window = window.open(url, 'serialPlotter'); - window.addEventListener('message', handlePostMessage); - const initMessage: SerialPlotter.InitMessage = { - baudrate: this.model.baudRate, - darkTheme: false, - wsPort: 0, - }; - if (this.window) { - this.window.postMessage(initMessage, url); - this.window.onclose = () => { - window.removeEventListener('message', handlePostMessage); - this.window = null; - }; - } - } - } -} - -const handlePostMessage = (event: MessageEvent) => { - console.log(event.data); -}; diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts new file mode 100644 index 000000000..933d92903 --- /dev/null +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -0,0 +1,89 @@ +import { injectable, inject } from 'inversify'; +import { + Command, + CommandRegistry, + MaybePromise, + MenuModelRegistry, +} from '@theia/core'; +import { MonitorModel } from '../monitor/monitor-model'; +import { ArduinoMenus } from '../menu/arduino-menus'; +import { Contribution } from '../contributions/contribution'; +import { PlotterService } from '../../common/protocol/plotter-service'; +import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; +import { ipcRenderer } from '@theia/core/shared/electron'; +import { SerialPlotter } from './protocol'; +import { MonitorConfig } from '../../common/protocol'; +const queryString = require('query-string'); + +export namespace SerialPlotterContribution { + export namespace Commands { + export const OPEN: Command = { + id: 'serial-plotter-open', + label: 'Serial Plotter', + category: 'Arduino', + }; + } +} + +@injectable() +export class PlotterFrontendContribution extends Contribution { + protected window: Window | null; + protected url: string; + protected initConfig: SerialPlotter.Config; + + @inject(MonitorModel) + protected readonly model: MonitorModel; + + @inject(PlotterService) + protected readonly plotter: PlotterService; + + onStart(app: FrontendApplication): MaybePromise { + this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); + + this.initConfig = { + baudrates: MonitorConfig.BaudRates.map((b) => b), + currentBaudrate: this.model.baudRate, + darkTheme: true, + wsPort: 0, + generate: true, + }; + + ipcRenderer.on('CLOSE_CHILD_WINDOW', () => { + if (this.window) { + if (!this.window.closed) this.window?.close(); + this.window = null; + } + }); + + return super.onStart(app); + } + + registerCommands(registry: CommandRegistry): void { + registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { + execute: async () => this.open(), + }); + } + + registerMenus(menus: MenuModelRegistry): void { + menus.registerMenuAction(ArduinoMenus.TOOLS__MAIN_GROUP, { + commandId: SerialPlotterContribution.Commands.OPEN.id, + label: SerialPlotterContribution.Commands.OPEN.label, + order: '7', + }); + } + + protected async open(): Promise { + if (this.window) { + this.window.focus(); + } else { + const urlWithParams = queryString.stringifyUrl( + { + url: this.url, + query: this.initConfig, + }, + { arrayFormat: 'comma' } + ); + this.window = window.open(urlWithParams, 'serialPlotter'); + } + } +} diff --git a/arduino-ide-extension/src/browser/plotter/protocol.ts b/arduino-ide-extension/src/browser/plotter/protocol.ts new file mode 100644 index 000000000..c2d0dfde6 --- /dev/null +++ b/arduino-ide-extension/src/browser/plotter/protocol.ts @@ -0,0 +1,23 @@ +export namespace SerialPlotter { + export type Config = { + currentBaudrate: number; + baudrates: number[]; + darkTheme: boolean; + wsPort: number; + generate: boolean; + }; + export namespace Protocol { + export enum Command { + PLOTTER_REQUEST_CONFIG = 'PLOTTER_REQUEST_CONFIG', + PLOTTER_READY = 'PLOTTER_READY', + PLOTTER_SET_BAUDRATE = 'PLOTTER_SET_BAUDRATE', + PLOTTER_SET_LINE_ENDING = 'PLOTTER_SET_LINE_ENDING', + PLOTTER_SEND_MESSAGE = 'PLOTTER_SEND_MESSAGE', + PARENT_SET_CONFIG = 'PARENT_SET_CONFIG', + } + export type Message = { + command: SerialPlotter.Protocol.Command; + data?: any; + }; + } +} diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 01c8e1a95..bcdcf9448 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -39,16 +39,10 @@ export interface MonitorConfig { readonly baudRate?: MonitorConfig.BaudRate; } export namespace MonitorConfig { - export type BaudRate = - | 300 - | 1200 - | 2400 - | 4800 - | 9600 - | 19200 - | 38400 - | 57600 - | 115200; + export const BaudRates = [ + 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, + ] as const; + export type BaudRate = typeof MonitorConfig.BaudRates[number]; export namespace BaudRate { export const DEFAULT: BaudRate = 9600; } diff --git a/arduino-ide-extension/src/common/protocol/plotter-service.ts b/arduino-ide-extension/src/common/protocol/plotter-service.ts new file mode 100644 index 000000000..4233f726c --- /dev/null +++ b/arduino-ide-extension/src/common/protocol/plotter-service.ts @@ -0,0 +1,16 @@ +export const PlotterPath = '/services/plotter-service'; +export const PlotterService = Symbol('PlotterService'); + +export interface PlotterService { + start(config: PlotterConfig | undefined): Promise; + stop(): Promise; + setOptions(config: PlotterConfig): Promise; +} + +export interface PlotterConfig { + currentBaudrate: number; + baudrates: number[]; + darkTheme: boolean; + wsPort: number; + generate?: boolean; +} diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 76d9e6f68..41940a16d 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -104,6 +104,9 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { }, }); event.newGuest = new BrowserWindow(options); + event.newGuest?.on('closed', (e: any) => { + electronWindow?.webContents.send('CLOSE_CHILD_WINDOW'); + }); event.newGuest?.loadURL(url); } } diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 642388bbc..e5dd12f80 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -86,6 +86,7 @@ import { AuthenticationServicePath, } from '../common/protocol/authentication-service'; import { ArduinoFirmwareUploaderImpl } from './arduino-firmware-uploader-impl'; +import { PlotterBackendContribution } from './plotter/plotter-backend-contribution'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -331,4 +332,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { ) ) .inSingletonScope(); + + bind(PlotterBackendContribution).toSelf().inSingletonScope(); + bind(BackendApplicationContribution).toService(PlotterBackendContribution); }); diff --git a/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts b/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts new file mode 100644 index 000000000..18dda2f26 --- /dev/null +++ b/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts @@ -0,0 +1,30 @@ +import * as express from 'express'; +import { injectable } from 'inversify'; +import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application'; +import path = require('path'); + +@injectable() +export class PlotterBackendContribution + implements BackendApplicationContribution +{ + async initialize(): Promise {} + + configure(app: express.Application): void { + app.use( + express.static( + path.join( + __dirname, + '../../../node_modules/arduino-serial-plotter-webapp/build' + ) + ) + ); + app.get('/plotter', (req, res) => + res.sendFile( + path.join( + __dirname, + '../../../node_modules/arduino-serial-plotter-webapp/build/index.html' + ) + ) + ); + } +} diff --git a/yarn.lock b/yarn.lock index ee92004da..d4ed6e36e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11945,6 +11945,16 @@ query-string@^6.13.8: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +query-string@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.0.1.tgz#45bd149cf586aaa582dffc7ec7a8ad97dd02f75d" + integrity sha512-uIw3iRvHnk9to1blJCG3BTc+Ro56CBowJXKmNNAm3RulvPBzWLRqKSiiDk+IplJhsydwtuNMHi8UGQFcCLVfkA== + dependencies: + decode-uri-component "^0.2.0" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" From a145ad9c59fb7b186ee2d9837922dec95d91c3ca Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 21 Oct 2021 12:45:39 +0200 Subject: [PATCH 03/27] WIP connect serial plotter app with websocket --- .../browser/arduino-ide-frontend-module.ts | 2 + .../src/browser/monitor/monitor-connection.ts | 37 +++++++++++------- .../src/browser/plotter/plotter-connection.ts | 16 ++++++++ .../plotter/plotter-frontend-contribution.ts | 39 +++++++++++-------- .../src/browser/plotter/protocol.ts | 2 +- .../src/node/monitor/monitor-service-impl.ts | 8 ++-- 6 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 arduino-ide-extension/src/browser/plotter/plotter-connection.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index e6d22cb2d..6184bd4e9 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -258,6 +258,7 @@ import { PlotterPath, PlotterService, } from '../common/protocol/plotter-service'; +import { PlotterConnection } from './plotter/plotter-connection'; import { nls } from '@theia/core/lib/browser/nls'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -410,6 +411,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { }) .inSingletonScope(); bind(MonitorConnection).toSelf().inSingletonScope(); + bind(PlotterConnection).toSelf().inSingletonScope(); // Serial monitor service client to receive and delegate notifications from the backend. bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 354f71ce3..e2d664332 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -20,6 +20,7 @@ import { import { BoardsConfig } from '../boards/boards-config'; import { MonitorModel } from './monitor-model'; import { NotificationCenter } from '../notification-center'; +import { Disposable } from '@theia/core'; import { nls } from '@theia/core/lib/browser/nls'; @injectable() @@ -48,7 +49,8 @@ export class MonitorConnection { @inject(FrontendApplicationStateService) protected readonly applicationState: FrontendApplicationStateService; - protected state: MonitorConnection.State | undefined; + protected _state: MonitorConnection.State | undefined; + /** * Note: The idea is to toggle this property from the UI (`Monitor` view) * and the boards config and the boards attachment/detachment logic can be at on place, here. @@ -62,6 +64,8 @@ export class MonitorConnection { */ protected readonly onReadEmitter = new Emitter<{ messages: string[] }>(); + protected toDisposeOnDisconnect: Disposable[] = []; + /** * Array for storing previous monitor errors received from the server, and based on the number of elements in this array, * we adjust the reconnection delay. @@ -72,15 +76,6 @@ export class MonitorConnection { @postConstruct() protected init(): void { - this.monitorServiceClient.onMessage(this.handleMessage.bind(this)); - this.monitorServiceClient.onError(this.handleError.bind(this)); - this.boardsServiceProvider.onBoardsConfigChanged( - this.handleBoardConfigChange.bind(this) - ); - this.notificationCenter.onAttachedBoardsChanged( - this.handleAttachedBoardsChanged.bind(this) - ); - // Handles the `baudRate` changes by reconnecting if required. this.monitorModel.onChange(({ property }) => { if (property === 'baudRate' && this.autoConnect && this.connected) { @@ -98,6 +93,12 @@ export class MonitorConnection { }; } + protected set state(s: MonitorConnection.State | undefined) { + this.onConnectionChangedEmitter.fire(this.state); + this._state = s; + if (!this.connected) this.toDisposeOnDisconnect.forEach((d) => d.dispose()); + } + get connected(): boolean { return !!this.state; } @@ -188,7 +189,7 @@ export class MonitorConnection { } const oldState = this.state; this.state = undefined; - this.onConnectionChangedEmitter.fire(this.state); + if (shouldReconnect) { if (this.monitorErrors.length >= 10) { this.messageService.warn( @@ -254,6 +255,16 @@ export class MonitorConnection { } async connect(config: MonitorConfig): Promise { + this.toDisposeOnDisconnect.push( + this.monitorServiceClient.onMessage(this.handleMessage.bind(this)), + this.monitorServiceClient.onError(this.handleError.bind(this)), + this.boardsServiceProvider.onBoardsConfigChanged( + this.handleBoardConfigChange.bind(this) + ), + this.notificationCenter.onAttachedBoardsChanged( + this.handleAttachedBoardsChanged.bind(this) + ) + ); if (this.connected) { const disconnectStatus = await this.disconnect(); if (!Status.isOK(disconnectStatus)) { @@ -275,7 +286,7 @@ export class MonitorConnection { )} on port ${Port.toString(config.port)}.` ); } - this.onConnectionChangedEmitter.fire(this.state); + return Status.isOK(connectStatus); } @@ -303,7 +314,7 @@ export class MonitorConnection { ); } this.state = undefined; - this.onConnectionChangedEmitter.fire(this.state); + return status; } diff --git a/arduino-ide-extension/src/browser/plotter/plotter-connection.ts b/arduino-ide-extension/src/browser/plotter/plotter-connection.ts new file mode 100644 index 000000000..b4b3a1916 --- /dev/null +++ b/arduino-ide-extension/src/browser/plotter/plotter-connection.ts @@ -0,0 +1,16 @@ +import { injectable } from 'inversify'; +import { Emitter, Event } from '@theia/core/lib/common/event'; +import { MonitorConnection } from '../monitor/monitor-connection'; + +@injectable() +export class PlotterConnection extends MonitorConnection { + protected readonly onWebSocketChangedEmitter = new Emitter(); + + async handleMessage(port: string): Promise { + this.onWebSocketChangedEmitter.fire(port); + } + + get onWebSocketChanged(): Event { + return this.onWebSocketChangedEmitter.event; + } +} diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index 933d92903..d3ca73ecb 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -2,17 +2,18 @@ import { injectable, inject } from 'inversify'; import { Command, CommandRegistry, + DisposableCollection, MaybePromise, MenuModelRegistry, } from '@theia/core'; import { MonitorModel } from '../monitor/monitor-model'; import { ArduinoMenus } from '../menu/arduino-menus'; import { Contribution } from '../contributions/contribution'; -import { PlotterService } from '../../common/protocol/plotter-service'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/core/shared/electron'; -import { SerialPlotter } from './protocol'; import { MonitorConfig } from '../../common/protocol'; +import { PlotterConnection } from './plotter-connection'; +import { SerialPlotter } from './protocol'; const queryString = require('query-string'); export namespace SerialPlotterContribution { @@ -29,38 +30,38 @@ export namespace SerialPlotterContribution { export class PlotterFrontendContribution extends Contribution { protected window: Window | null; protected url: string; - protected initConfig: SerialPlotter.Config; + protected wsPort: number; + protected toDispose = new DisposableCollection(); @inject(MonitorModel) protected readonly model: MonitorModel; - @inject(PlotterService) - protected readonly plotter: PlotterService; + @inject(PlotterConnection) + protected readonly plotterConnection: PlotterConnection; onStart(app: FrontendApplication): MaybePromise { this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); - this.initConfig = { - baudrates: MonitorConfig.BaudRates.map((b) => b), - currentBaudrate: this.model.baudRate, - darkTheme: true, - wsPort: 0, - generate: true, - }; - ipcRenderer.on('CLOSE_CHILD_WINDOW', () => { if (this.window) { if (!this.window.closed) this.window?.close(); this.window = null; + this.plotterConnection.autoConnect = false; } }); + this.toDispose.pushAll([ + this.plotterConnection.onWebSocketChanged((wsPort) => { + this.open(wsPort); + }), + ]); + return super.onStart(app); } registerCommands(registry: CommandRegistry): void { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { - execute: async () => this.open(), + execute: async () => (this.plotterConnection.autoConnect = true), }); } @@ -72,14 +73,20 @@ export class PlotterFrontendContribution extends Contribution { }); } - protected async open(): Promise { + protected async open(wsPort: string): Promise { + const initConfig: SerialPlotter.Config = { + baudrates: MonitorConfig.BaudRates.map((b) => b), + currentBaudrate: this.model.baudRate, + darkTheme: true, + wsPort: parseInt(wsPort, 10), + }; if (this.window) { this.window.focus(); } else { const urlWithParams = queryString.stringifyUrl( { url: this.url, - query: this.initConfig, + query: initConfig, }, { arrayFormat: 'comma' } ); diff --git a/arduino-ide-extension/src/browser/plotter/protocol.ts b/arduino-ide-extension/src/browser/plotter/protocol.ts index c2d0dfde6..b81b714eb 100644 --- a/arduino-ide-extension/src/browser/plotter/protocol.ts +++ b/arduino-ide-extension/src/browser/plotter/protocol.ts @@ -4,7 +4,7 @@ export namespace SerialPlotter { baudrates: number[]; darkTheme: boolean; wsPort: number; - generate: boolean; + generate?: boolean; }; export namespace Protocol { export enum Command { diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index c00404fe2..295bf3e11 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -126,14 +126,16 @@ export class MonitorServiceImpl implements MonitorService { const ws = new WebSocket.Server({ port: 0 }); const address: any = ws.address(); this.client?.notifyMessage(address.port); - let wsConn: WebSocket | null = null; + const wsConn: WebSocket[] = []; ws.on('connection', (ws) => { - wsConn = ws; + wsConn.push(ws); }); const flushMessagesToFrontend = () => { if (this.messages.length) { - wsConn?.send(JSON.stringify(this.messages)); + wsConn.forEach((w) => { + w.send(JSON.stringify(this.messages)); + }); this.messages = []; } }; From d7c85fbfa068a5f5c1d38ba31cf6510d649b1feb Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Mon, 25 Oct 2021 12:50:50 +0200 Subject: [PATCH 04/27] use npm serial-plotter package --- arduino-ide-extension/package.json | 4 ++- .../plotter/plotter-backend-contribution.ts | 31 +++++++++---------- yarn.lock | 5 +++ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 04912fae8..86c34f8ad 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -4,10 +4,11 @@ "description": "An extension for Theia building the Arduino IDE", "license": "AGPL-3.0-or-later", "scripts": { - "prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn clean && yarn download-examples && yarn build && yarn test", + "prepare": "yarn download-cli && yarn download-fwuploader && yarn download-ls && yarn copy-serial-plotter && yarn clean && yarn download-examples && yarn build && yarn test", "clean": "rimraf lib", "download-cli": "node ./scripts/download-cli.js", "download-fwuploader": "node ./scripts/download-fwuploader.js", + "copy-serial-plotter": "npx ncp ../node_modules/arduino-serial-plotter-webapp ./build/arduino-serial-plotter-webapp", "download-ls": "node ./scripts/download-ls.js", "download-examples": "node ./scripts/download-examples.js", "generate-protocol": "node ./scripts/generate-protocol.js", @@ -18,6 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { + "arduino-serial-plotter-webapp": "0.0.2", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts b/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts index 18dda2f26..911303a64 100644 --- a/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts +++ b/arduino-ide-extension/src/node/plotter/plotter-backend-contribution.ts @@ -10,21 +10,20 @@ export class PlotterBackendContribution async initialize(): Promise {} configure(app: express.Application): void { - app.use( - express.static( - path.join( - __dirname, - '../../../node_modules/arduino-serial-plotter-webapp/build' - ) - ) - ); - app.get('/plotter', (req, res) => - res.sendFile( - path.join( - __dirname, - '../../../node_modules/arduino-serial-plotter-webapp/build/index.html' - ) - ) - ); + const relativePath = [ + '..', + '..', + '..', + 'build', + 'arduino-serial-plotter-webapp', + 'build', + ]; + app.use(express.static(path.join(__dirname, ...relativePath))); + app.get('/plotter', (req, res) => { + console.log( + `Serving serial plotter on http://${req.headers.host}${req.url}` + ); + res.sendFile(path.join(__dirname, ...relativePath, 'index.html')); + }); } } diff --git a/yarn.lock b/yarn.lock index d4ed6e36e..671d5a715 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,6 +4109,11 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" +arduino-serial-plotter-webapp@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.1.tgz#a66b512df72432bdb0f48495955351e637190943" + integrity sha512-6k+8MF6LtMdEZUSDh1FY/pmrI2wCTpFTVriEt5/sa9vJKnoZxkxwoSZzbYNtsqbJU2D4knzoVyS4J/LjaTbkOg== + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" From f0fdc24a12fa3e1349645a949543dd314557721d Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 21 Oct 2021 12:45:39 +0200 Subject: [PATCH 05/27] connect serial plotter app with websocket --- .../browser/arduino-ide-frontend-module.ts | 3 +- .../browser/contributions/burn-bootloader.ts | 6 +- .../browser/contributions/upload-sketch.ts | 14 +- .../src/browser/monitor/monitor-connection.ts | 206 ++++++++++++++---- .../monitor/monitor-service-client-impl.ts | 6 +- .../src/browser/monitor/monitor-widget.tsx | 12 +- .../src/browser/plotter/plotter-connection.ts | 16 -- .../plotter/plotter-frontend-contribution.ts | 77 ++++--- .../src/common/protocol/monitor-service.ts | 4 +- .../src/node/arduino-ide-backend-module.ts | 5 + .../src/node/monitor/monitor-service-impl.ts | 19 +- .../web-socket/web-socket-service-impl.ts | 36 +++ .../src/node/web-socket/web-socket-service.ts | 7 + yarn.lock | 8 +- 14 files changed, 295 insertions(+), 124 deletions(-) delete mode 100644 arduino-ide-extension/src/browser/plotter/plotter-connection.ts create mode 100644 arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts create mode 100644 arduino-ide-extension/src/node/web-socket/web-socket-service.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 6184bd4e9..b22d329d0 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -258,7 +258,6 @@ import { PlotterPath, PlotterService, } from '../common/protocol/plotter-service'; -import { PlotterConnection } from './plotter/plotter-connection'; import { nls } from '@theia/core/lib/browser/nls'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -411,7 +410,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { }) .inSingletonScope(); bind(MonitorConnection).toSelf().inSingletonScope(); - bind(PlotterConnection).toSelf().inSingletonScope(); + // Serial monitor service client to receive and delegate notifications from the backend. bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index 3ea8602b1..db7771d48 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -49,8 +49,10 @@ export class BurnBootloader extends SketchContribution { async burnBootloader(): Promise { const monitorConfig = this.monitorConnection.monitorConfig; + const serialConnection = this.monitorConnection.connectionType; + if (monitorConfig) { - await this.monitorConnection.disconnect(); + await this.monitorConnection.disconnect(serialConnection); } try { const { boardsConfig } = this.boardsServiceClientImpl; @@ -85,7 +87,7 @@ export class BurnBootloader extends SketchContribution { this.messageService.error(e.toString()); } finally { if (monitorConfig) { - await this.monitorConnection.connect(monitorConfig); + await this.monitorConnection.connect(serialConnection, monitorConfig); } } } diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 4388f410b..76467f57f 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -110,8 +110,9 @@ export class UploadSketch extends SketchContribution { } let shouldAutoConnect = false; const monitorConfig = this.monitorConnection.monitorConfig; + const serialConnection = this.monitorConnection.connectionType; if (monitorConfig) { - await this.monitorConnection.disconnect(); + await this.monitorConnection.disconnect(serialConnection); if (this.monitorConnection.autoConnect) { shouldAutoConnect = true; } @@ -182,12 +183,11 @@ export class UploadSketch extends SketchContribution { Object.assign(board, { port }), 10_000 ); - if (shouldAutoConnect) { - // Enabling auto-connect will trigger a connect. - this.monitorConnection.autoConnect = true; - } else { - await this.monitorConnection.connect(monitorConfig); - } + this.monitorConnection.connect( + serialConnection, + monitorConfig, + shouldAutoConnect + ); } catch (waitError) { this.messageService.error( nls.localize( diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index e2d664332..c9344d221 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -23,6 +23,13 @@ import { NotificationCenter } from '../notification-center'; import { Disposable } from '@theia/core'; import { nls } from '@theia/core/lib/browser/nls'; +export enum SerialType { + Monitor = 'Monitor', + Plotter = 'Plotter', + All = 'All', + None = 'None', +} + @injectable() export class MonitorConnection { @inject(MonitorModel) @@ -49,16 +56,21 @@ export class MonitorConnection { @inject(FrontendApplicationStateService) protected readonly applicationState: FrontendApplicationStateService; - protected _state: MonitorConnection.State | undefined; + @inject(MonitorModel) + protected readonly model: MonitorModel; + + protected _state: MonitorConnection.State = { + connected: SerialType.None, + config: undefined, + }; /** * Note: The idea is to toggle this property from the UI (`Monitor` view) * and the boards config and the boards attachment/detachment logic can be at on place, here. */ protected _autoConnect = false; - protected readonly onConnectionChangedEmitter = new Emitter< - MonitorConnection.State | undefined - >(); + protected readonly onConnectionChangedEmitter = + new Emitter(); /** * This emitter forwards all read events **iff** the connection is established. */ @@ -74,6 +86,9 @@ export class MonitorConnection { protected monitorErrors: MonitorError[] = []; protected reconnectTimeout?: number; + protected wsPort?: number; + protected webSocket?: WebSocket; + @postConstruct() protected init(): void { // Handles the `baudRate` changes by reconnecting if required. @@ -85,22 +100,46 @@ export class MonitorConnection { }); } - async handleMessage(port: string): Promise { - const w = new WebSocket(`ws://localhost:${port}`); - w.onmessage = (res) => { - const messages = JSON.parse(res.data); - this.onReadEmitter.fire({ messages }); - }; + getWsPort() { + return this.wsPort; + } + + handleWebSocketChanged(wsPort: number): void { + this.wsPort = wsPort; + } + + protected createWsConnection(): boolean { + if (this.wsPort) { + try { + this.webSocket = new WebSocket(`ws://localhost:${this.wsPort}`); + this.webSocket.onmessage = (res) => { + const messages = JSON.parse(res.data); + this.onReadEmitter.fire({ messages }); + }; + return true; + } catch { + return false; + } + } + return false; } - protected set state(s: MonitorConnection.State | undefined) { - this.onConnectionChangedEmitter.fire(this.state); + protected set state(s: MonitorConnection.State) { + this.onConnectionChangedEmitter.fire(this._state); this._state = s; - if (!this.connected) this.toDisposeOnDisconnect.forEach((d) => d.dispose()); + if (!this._state) this.toDisposeOnDisconnect.forEach((d) => d.dispose()); + } + + protected get state(): MonitorConnection.State { + return this._state; + } + + get connectionType(): SerialType { + return this.state.connected; } get connected(): boolean { - return !!this.state; + return this.state.connected !== SerialType.None; } get monitorConfig(): MonitorConfig | undefined { @@ -139,7 +178,7 @@ export class MonitorConnection { switch (code) { case MonitorError.ErrorCodes.CLIENT_CANCEL: { console.debug( - `Connection was canceled by client: ${MonitorConnection.State.toString( + `Serial connection was canceled by client: ${MonitorConnection.State.toString( this.state )}.` ); @@ -188,14 +227,14 @@ export class MonitorConnection { } } const oldState = this.state; - this.state = undefined; + this.state = { connected: SerialType.None }; if (shouldReconnect) { if (this.monitorErrors.length >= 10) { this.messageService.warn( nls.localize( 'arduino/monitor/failedReconnect', - 'Failed to reconnect {0} to the the serial-monitor after 10 consecutive attempts. The {1} serial port is busy.', + 'Failed to reconnect {0} to serial after 10 consecutive attempts. The {1} serial port is busy.', Board.toString(board, { useFqbn: false, }), @@ -223,7 +262,9 @@ export class MonitorConnection { { timeout } ); this.reconnectTimeout = window.setTimeout( - () => this.connect(oldState.config), + () => + oldState.config && + this.connect(oldState.connected, oldState.config), timeout ); } @@ -248,15 +289,67 @@ export class MonitorConnection { ) { const { selectedBoard: board, selectedPort: port } = boardsConfig; const { baudRate } = this.monitorModel; - this.disconnect().then(() => this.connect({ board, port, baudRate })); + const connectionType = this.state.connected; + this.disconnect(connectionType).then(() => + this.connect(connectionType, { board, port, baudRate }) + ); } } } } - async connect(config: MonitorConfig): Promise { + async connect( + type: SerialType, + newConfig?: MonitorConfig, + autoConnect = false + ): Promise { + if (this.connected) { + switch (this.state.connected) { + case SerialType.All: + // const disconnectStatus = await this.disconnect(type); + // if (!Status.isOK(disconnectStatus)) { + // return disconnectStatus; + // } + // break; + return Status.OK; + case SerialType.Plotter: + if (type === SerialType.Monitor) { + if (this.createWsConnection()) { + this.state = { ...this.state, connected: SerialType.All }; + return Status.OK; + } + return Status.NOT_CONNECTED; + } + return Status.OK; + case SerialType.Monitor: + if (type === SerialType.Plotter) + this.state = { ...this.state, connected: SerialType.All }; + return SerialType.All; + } + } + + this.autoConnect = autoConnect; + let config = newConfig; + if (!config) { + const { boardsConfig } = this.boardsServiceProvider; + const { selectedBoard: board, selectedPort: port } = boardsConfig; + const { baudRate } = this.model; + if (!board || !port) { + this.messageService.error( + `Please select a board and a port to open the serial connection.` + ); + return Status.NOT_CONNECTED; + } + config = { + board, + port, + baudRate, + }; + } this.toDisposeOnDisconnect.push( - this.monitorServiceClient.onMessage(this.handleMessage.bind(this)), + this.monitorServiceClient.onWebSocketChanged( + this.handleWebSocketChanged.bind(this) + ), this.monitorServiceClient.onError(this.handleError.bind(this)), this.boardsServiceProvider.onBoardsConfigChanged( this.handleBoardConfigChange.bind(this) @@ -265,56 +358,72 @@ export class MonitorConnection { this.handleAttachedBoardsChanged.bind(this) ) ); - if (this.connected) { - const disconnectStatus = await this.disconnect(); - if (!Status.isOK(disconnectStatus)) { - return disconnectStatus; - } - } + console.info( - `>>> Creating serial monitor connection for ${Board.toString( + `>>> Creating serial connection for ${Board.toString( config.board )} on port ${Port.toString(config.port)}...` ); const connectStatus = await this.monitorService.connect(config); if (Status.isOK(connectStatus)) { - this.state = { config }; + this.state = { config, connected: type }; + if (type === SerialType.Monitor) this.createWsConnection(); console.info( - `<<< Serial monitor connection created for ${Board.toString( - config.board, - { useFqbn: false } - )} on port ${Port.toString(config.port)}.` + `<<< Serial connection created for ${Board.toString(config.board, { + useFqbn: false, + })} on port ${Port.toString(config.port)}.` ); } return Status.isOK(connectStatus); } - async disconnect(): Promise { - if (!this.connected) { + async disconnect(type: SerialType): Promise { + if (!this.connected || type === SerialType.None) { return Status.OK; } - const stateCopy = deepClone(this.state); - if (!stateCopy) { + let newState = deepClone(this.state); + + if (type === SerialType.All || type === this.state.connected) { + newState = { connected: SerialType.None }; + } + if (this.state.connected === SerialType.All) { + newState = { + ...this.state, + connected: + type === SerialType.Monitor ? SerialType.Plotter : SerialType.Monitor, + }; + } + if ( + (type === SerialType.All || type === SerialType.Monitor) && + this.webSocket + ) { + this.webSocket.close(); + this.webSocket = undefined; + } + + if (newState.connected !== SerialType.None) { return Status.OK; } - console.log('>>> Disposing existing monitor connection...'); + + console.log('>>> Disposing existing serial connection...'); const status = await this.monitorService.disconnect(); if (Status.isOK(status)) { console.log( - `<<< Disposed connection. Was: ${MonitorConnection.State.toString( - stateCopy + `<<< Disposed serial connection. Was: ${MonitorConnection.State.toString( + newState )}` ); + this.wsPort = undefined; } else { console.warn( - `<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString( - stateCopy + `<<< Could not dispose serial connection. Activate connection: ${MonitorConnection.State.toString( + newState )}` ); } - this.state = undefined; + this.state = newState; return status; } @@ -334,7 +443,7 @@ export class MonitorConnection { }); } - get onConnectionChanged(): Event { + get onConnectionChanged(): Event { return this.onConnectionChangedEmitter.event; } @@ -353,6 +462,7 @@ export class MonitorConnection { ) { // Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports. // The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881 + const connectionType = this.state.connected; this.boardsService.getAvailablePorts().then((ports) => { if ( ports.some((port) => Port.equals(port, boardsConfig.selectedPort)) @@ -360,7 +470,7 @@ export class MonitorConnection { new Promise((resolve) => { // First, disconnect if connected. if (this.connected) { - this.disconnect().then(() => resolve()); + this.disconnect(connectionType).then(() => resolve()); return; } resolve(); @@ -368,7 +478,7 @@ export class MonitorConnection { // Then (re-)connect. const { selectedBoard: board, selectedPort: port } = boardsConfig; const { baudRate } = this.monitorModel; - this.connect({ board, port, baudRate }); + this.connect(this.state.connected, { board, port, baudRate }); }); } }); @@ -379,12 +489,14 @@ export class MonitorConnection { export namespace MonitorConnection { export interface State { - readonly config: MonitorConfig; + readonly config?: MonitorConfig; + readonly connected: SerialType; } export namespace State { export function toString(state: State): string { const { config } = state; + if (!config) return ''; const { board, port } = config; return `${Board.toString(board)} ${Port.toString(port)}`; } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts index 9ab757ef4..2b97adde1 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts @@ -10,14 +10,14 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { protected readonly onErrorEmitter = new Emitter(); readonly onError = this.onErrorEmitter.event; - protected readonly onMessageEmitter = new Emitter(); - readonly onMessage = this.onMessageEmitter.event; + protected readonly onMessageEmitter = new Emitter(); + readonly onWebSocketChanged = this.onMessageEmitter.event; notifyError(error: MonitorError): void { this.onErrorEmitter.fire(error); } - notifyMessage(message: string): void { + notifyWebSocketChanged(message: number): void { this.onMessageEmitter.fire(message); } } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx index aa0ff5ee1..268a8b76c 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx @@ -12,10 +12,11 @@ import { import { MonitorConfig } from '../../common/protocol/monitor-service'; import { ArduinoSelect } from '../widgets/arduino-select'; import { MonitorModel } from './monitor-model'; -import { MonitorConnection } from './monitor-connection'; +import { MonitorConnection, SerialType } from './monitor-connection'; import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { nls } from '@theia/core/lib/browser/nls'; +import { BoardsServiceProvider } from '../boards/boards-service-provider'; @injectable() export class MonitorWidget extends ReactWidget { @@ -31,6 +32,9 @@ export class MonitorWidget extends ReactWidget { @inject(MonitorConnection) protected readonly monitorConnection: MonitorConnection; + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider; + protected widgetHeight: number; /** @@ -56,7 +60,7 @@ export class MonitorWidget extends ReactWidget { Disposable.create(() => { this.monitorConnection.autoConnect = false; if (this.monitorConnection.connected) { - this.monitorConnection.disconnect(); + this.monitorConnection.disconnect(SerialType.Monitor); } }) ); @@ -81,7 +85,9 @@ export class MonitorWidget extends ReactWidget { protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); - this.monitorConnection.autoConnect = true; + // const { boardsConfig } = this.boardsServiceProvider; + + this.monitorConnection.connect(SerialType.Monitor, undefined, true); } onCloseRequest(msg: Message): void { diff --git a/arduino-ide-extension/src/browser/plotter/plotter-connection.ts b/arduino-ide-extension/src/browser/plotter/plotter-connection.ts deleted file mode 100644 index b4b3a1916..000000000 --- a/arduino-ide-extension/src/browser/plotter/plotter-connection.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { injectable } from 'inversify'; -import { Emitter, Event } from '@theia/core/lib/common/event'; -import { MonitorConnection } from '../monitor/monitor-connection'; - -@injectable() -export class PlotterConnection extends MonitorConnection { - protected readonly onWebSocketChangedEmitter = new Emitter(); - - async handleMessage(port: string): Promise { - this.onWebSocketChangedEmitter.fire(port); - } - - get onWebSocketChanged(): Event { - return this.onWebSocketChangedEmitter.event; - } -} diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index d3ca73ecb..557623ec5 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -12,8 +12,9 @@ import { Contribution } from '../contributions/contribution'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/core/shared/electron'; import { MonitorConfig } from '../../common/protocol'; -import { PlotterConnection } from './plotter-connection'; +import { MonitorConnection, SerialType } from '../monitor/monitor-connection'; import { SerialPlotter } from './protocol'; +import { BoardsServiceProvider } from '../boards/boards-service-provider'; const queryString = require('query-string'); export namespace SerialPlotterContribution { @@ -36,32 +37,29 @@ export class PlotterFrontendContribution extends Contribution { @inject(MonitorModel) protected readonly model: MonitorModel; - @inject(PlotterConnection) - protected readonly plotterConnection: PlotterConnection; + @inject(MonitorConnection) + protected readonly monitorConnection: MonitorConnection; + + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider; onStart(app: FrontendApplication): MaybePromise { this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); - ipcRenderer.on('CLOSE_CHILD_WINDOW', () => { + ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { if (this.window) { - if (!this.window.closed) this.window?.close(); this.window = null; - this.plotterConnection.autoConnect = false; + await this.monitorConnection.disconnect(SerialType.Plotter); + this.monitorConnection.autoConnect = false; } }); - this.toDispose.pushAll([ - this.plotterConnection.onWebSocketChanged((wsPort) => { - this.open(wsPort); - }), - ]); - return super.onStart(app); } registerCommands(registry: CommandRegistry): void { registry.registerCommand(SerialPlotterContribution.Commands.OPEN, { - execute: async () => (this.plotterConnection.autoConnect = true), + execute: this.connect.bind(this), }); } @@ -73,24 +71,49 @@ export class PlotterFrontendContribution extends Contribution { }); } - protected async open(wsPort: string): Promise { + async connect(): Promise { + if (this.monitorConnection.connected) { + if (this.window) { + this.window.focus(); + return; + } + } + const { boardsConfig } = this.boardsServiceProvider; + const { selectedBoard: board, selectedPort: port } = boardsConfig; + const { baudRate } = this.model; + if (board && port) { + const status = await this.monitorConnection.connect(SerialType.Plotter, { + board, + port, + baudRate, + }); + const wsPort = this.monitorConnection.getWsPort(); + if (status && wsPort) { + this.open(wsPort); + } else { + this.messageService.error(`Couldn't open serial plotter`); + } + } else { + this.messageService.error( + `Please select a board and a port to open the Serial Plotter.` + ); + } + } + + protected open(wsPort: number): void { const initConfig: SerialPlotter.Config = { baudrates: MonitorConfig.BaudRates.map((b) => b), currentBaudrate: this.model.baudRate, darkTheme: true, - wsPort: parseInt(wsPort, 10), + wsPort, }; - if (this.window) { - this.window.focus(); - } else { - const urlWithParams = queryString.stringifyUrl( - { - url: this.url, - query: initConfig, - }, - { arrayFormat: 'comma' } - ); - this.window = window.open(urlWithParams, 'serialPlotter'); - } + const urlWithParams = queryString.stringifyUrl( + { + url: this.url, + query: initConfig, + }, + { arrayFormat: 'comma' } + ); + this.window = window.open(urlWithParams, 'serialPlotter'); } } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index bcdcf9448..e7cf93fb4 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -55,9 +55,9 @@ export namespace MonitorConfig { export const MonitorServiceClient = Symbol('MonitorServiceClient'); export interface MonitorServiceClient { onError: Event; - onMessage: Event; + onWebSocketChanged: Event; notifyError(event: MonitorError): void; - notifyMessage(message: string): void; + notifyWebSocketChanged(message: number): void; } export interface MonitorError { diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index e5dd12f80..d78eec6a5 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -87,6 +87,8 @@ import { } from '../common/protocol/authentication-service'; import { ArduinoFirmwareUploaderImpl } from './arduino-firmware-uploader-impl'; import { PlotterBackendContribution } from './plotter/plotter-backend-contribution'; +import WebSocketServiceImpl from './web-socket/web-socket-service-impl'; +import { WebSocketService } from './web-socket/web-socket-service'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -170,6 +172,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { }) ); + // Shared WebSocketService for the backend. This will manage all websocket conenctions + bind(WebSocketService).to(WebSocketServiceImpl).inSingletonScope(); + // Shared Arduino core client provider service for the backend. bind(CoreClientProvider).toSelf().inSingletonScope(); diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index 295bf3e11..6ea310df4 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -18,7 +18,7 @@ import { } from '../cli-protocol/cc/arduino/cli/monitor/v1/monitor_pb'; import { MonitorClientProvider } from './monitor-client-provider'; import { Board, Port } from '../../common/protocol/boards-service'; -import * as WebSocket from 'ws'; +import { WebSocketService } from '../web-socket/web-socket-service'; interface ErrorWithCode extends Error { readonly code: number; @@ -67,6 +67,9 @@ export class MonitorServiceImpl implements MonitorService { @inject(MonitorClientProvider) protected readonly monitorClientProvider: MonitorClientProvider; + @inject(WebSocketService) + protected readonly webSocketService: WebSocketService; + protected client?: MonitorServiceClient; protected connection?: { duplex: ClientDuplexStream; @@ -123,19 +126,13 @@ export class MonitorServiceImpl implements MonitorService { }).bind(this) ); - const ws = new WebSocket.Server({ port: 0 }); - const address: any = ws.address(); - this.client?.notifyMessage(address.port); - const wsConn: WebSocket[] = []; - ws.on('connection', (ws) => { - wsConn.push(ws); - }); + this.client?.notifyWebSocketChanged( + this.webSocketService.getAddress().port + ); const flushMessagesToFrontend = () => { if (this.messages.length) { - wsConn.forEach((w) => { - w.send(JSON.stringify(this.messages)); - }); + this.webSocketService.sendMessage(JSON.stringify(this.messages)); this.messages = []; } }; diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts b/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts new file mode 100644 index 000000000..35635ca23 --- /dev/null +++ b/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts @@ -0,0 +1,36 @@ +import { injectable } from 'inversify'; +import * as WebSocket from 'ws'; +import { WebSocketService } from './web-socket-service'; + +@injectable() +export default class WebSocketServiceImpl implements WebSocketService { + protected wsClients: WebSocket[]; + protected server: WebSocket.Server; + + constructor() { + this.server = new WebSocket.Server({ port: 0 }); + this.server.on('connection', this.addClient.bind(this)); + this.wsClients = []; + } + + private addClient(ws: WebSocket): void { + this.wsClients.push(ws); + ws.onclose = () => { + this.wsClients.splice(this.wsClients.indexOf(ws), 1); + }; + } + + getAddress(): WebSocket.AddressInfo { + return this.server.address() as WebSocket.AddressInfo; + } + + sendMessage(message: string): void { + this.wsClients.forEach((w) => { + try { + w.send(message); + } catch { + w.close(); + } + }); + } +} diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service.ts b/arduino-ide-extension/src/node/web-socket/web-socket-service.ts new file mode 100644 index 000000000..c22b39cfc --- /dev/null +++ b/arduino-ide-extension/src/node/web-socket/web-socket-service.ts @@ -0,0 +1,7 @@ +import * as WebSocket from 'ws'; + +export const WebSocketService = Symbol('WebSocketService'); +export interface WebSocketService { + getAddress(): WebSocket.AddressInfo; + sendMessage(message: string): void; +} diff --git a/yarn.lock b/yarn.lock index 671d5a715..c50e3679e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.1.tgz#a66b512df72432bdb0f48495955351e637190943" - integrity sha512-6k+8MF6LtMdEZUSDh1FY/pmrI2wCTpFTVriEt5/sa9vJKnoZxkxwoSZzbYNtsqbJU2D4knzoVyS4J/LjaTbkOg== +arduino-serial-plotter-webapp@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.2.tgz#491950c30b5d29c0169e2b90047c9dcd6f90c5b3" + integrity sha512-nP1G8F+i2WeGzoNlkuEZiQnFvPfHfK1z5y5lEfa3rTXH18OGrpejmfK2SPBiBMuSirNOni9/FmTxGHIyxaxzDA== are-we-there-yet@~1.1.2: version "1.1.5" From 3d616c5aebfbc819b68b43c2b927e2607564c069 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Fri, 29 Oct 2021 12:17:51 +0200 Subject: [PATCH 06/27] refactor monito connection and fix some connection issues --- .../browser/contributions/burn-bootloader.ts | 11 +- .../browser/contributions/upload-sketch.ts | 24 +- .../src/browser/monitor/monitor-connection.ts | 528 ++++++++---------- .../src/browser/monitor/monitor-widget.tsx | 11 +- .../plotter/plotter-frontend-contribution.ts | 28 +- 5 files changed, 265 insertions(+), 337 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index db7771d48..9891a26dd 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -48,12 +48,7 @@ export class BurnBootloader extends SketchContribution { } async burnBootloader(): Promise { - const monitorConfig = this.monitorConnection.monitorConfig; - const serialConnection = this.monitorConnection.connectionType; - - if (monitorConfig) { - await this.monitorConnection.disconnect(serialConnection); - } + await this.monitorConnection.disconnect(); try { const { boardsConfig } = this.boardsServiceClientImpl; const port = boardsConfig.selectedPort; @@ -86,8 +81,8 @@ export class BurnBootloader extends SketchContribution { } catch (e) { this.messageService.error(e.toString()); } finally { - if (monitorConfig) { - await this.monitorConnection.connect(serialConnection, monitorConfig); + if (this.monitorConnection.isSerialOpen()) { + await this.monitorConnection.connect(); } } } diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 76467f57f..ef54c0f0d 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -108,16 +108,7 @@ export class UploadSketch extends SketchContribution { if (!sketch) { return; } - let shouldAutoConnect = false; - const monitorConfig = this.monitorConnection.monitorConfig; - const serialConnection = this.monitorConnection.connectionType; - if (monitorConfig) { - await this.monitorConnection.disconnect(serialConnection); - if (this.monitorConnection.autoConnect) { - shouldAutoConnect = true; - } - this.monitorConnection.autoConnect = false; - } + await this.monitorConnection.disconnect(); try { const { boardsConfig } = this.boardsServiceClientImpl; const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = @@ -176,18 +167,17 @@ export class UploadSketch extends SketchContribution { this.uploadInProgress = false; this.onDidChangeEmitter.fire(); - if (monitorConfig) { - const { board, port } = monitorConfig; + if ( + this.monitorConnection.isSerialOpen() && + this.monitorConnection.monitorConfig + ) { + const { board, port } = this.monitorConnection.monitorConfig; try { await this.boardsServiceClientImpl.waitUntilAvailable( Object.assign(board, { port }), 10_000 ); - this.monitorConnection.connect( - serialConnection, - monitorConfig, - shouldAutoConnect - ); + await this.monitorConnection.connect(); } catch (waitError) { this.messageService.error( nls.localize( diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index c9344d221..4959c2b59 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -20,14 +20,11 @@ import { import { BoardsConfig } from '../boards/boards-config'; import { MonitorModel } from './monitor-model'; import { NotificationCenter } from '../notification-center'; -import { Disposable } from '@theia/core'; import { nls } from '@theia/core/lib/browser/nls'; export enum SerialType { Monitor = 'Monitor', Plotter = 'Plotter', - All = 'All', - None = 'None', } @injectable() @@ -59,16 +56,13 @@ export class MonitorConnection { @inject(MonitorModel) protected readonly model: MonitorModel; - protected _state: MonitorConnection.State = { - connected: SerialType.None, - config: undefined, - }; + protected _state: MonitorConnection.State = []; + protected _connected: boolean; /** * Note: The idea is to toggle this property from the UI (`Monitor` view) * and the boards config and the boards attachment/detachment logic can be at on place, here. */ - protected _autoConnect = false; protected readonly onConnectionChangedEmitter = new Emitter(); /** @@ -76,8 +70,6 @@ export class MonitorConnection { */ protected readonly onReadEmitter = new Emitter<{ messages: string[] }>(); - protected toDisposeOnDisconnect: Disposable[] = []; - /** * Array for storing previous monitor errors received from the server, and based on the number of elements in this array, * we adjust the reconnection delay. @@ -88,19 +80,47 @@ export class MonitorConnection { protected wsPort?: number; protected webSocket?: WebSocket; + protected config: Partial = { + board: undefined, + port: undefined, + baudRate: undefined, + }; @postConstruct() protected init(): void { + this.monitorServiceClient.onWebSocketChanged( + this.handleWebSocketChanged.bind(this) + ), + this.monitorServiceClient.onError(this.handleError.bind(this)), + this.boardsServiceProvider.onBoardsConfigChanged( + this.handleBoardConfigChange.bind(this) + ), + this.notificationCenter.onAttachedBoardsChanged( + this.handleAttachedBoardsChanged.bind(this) + ); // Handles the `baudRate` changes by reconnecting if required. this.monitorModel.onChange(({ property }) => { - if (property === 'baudRate' && this.autoConnect && this.connected) { + if (property === 'baudRate' && this.connected) { const { boardsConfig } = this.boardsServiceProvider; this.handleBoardConfigChange(boardsConfig); } }); } - getWsPort() { + protected setConfig(newConfig: Partial): void { + let shouldReconnect = false; + Object.keys(this.config).forEach((key: keyof MonitorConfig) => { + if (newConfig[key] && newConfig[key] !== this.config[key]) { + shouldReconnect = true; + this.config = { ...this.config, [key]: newConfig[key] }; + } + }); + if (shouldReconnect && this.isSerialOpen()) { + this.disconnect().then(() => this.connect()); + } + } + + getWsPort(): number | undefined { return this.wsPort; } @@ -124,306 +144,259 @@ export class MonitorConnection { return false; } - protected set state(s: MonitorConnection.State) { - this.onConnectionChangedEmitter.fire(this._state); + protected async setState(s: MonitorConnection.State): Promise { + const oldState = deepClone(this._state); this._state = s; - if (!this._state) this.toDisposeOnDisconnect.forEach((d) => d.dispose()); + this.onConnectionChangedEmitter.fire(this._state); + let status = Status.OK; + + if (this.isSerialOpen(oldState) && !this.isSerialOpen()) { + status = await this.disconnect(); + } else if (!this.isSerialOpen(oldState) && this.isSerialOpen()) { + status = await this.connect(); + } + + // if (this.connected) { + // switch (this.state.connected) { + // case SerialType.All: + // return Status.OK; + // case SerialType.Plotter: + // if (type === SerialType.Monitor) { + // if (this.createWsConnection()) { + // this.state = { ...this.state, connected: SerialType.All }; + // return Status.OK; + // } + // return Status.NOT_CONNECTED; + // } + // return Status.OK; + // case SerialType.Monitor: + // if (type === SerialType.Plotter) + // this.state = { ...this.state, connected: SerialType.All }; + // return SerialType.All; + // } + // } + + return status; } protected get state(): MonitorConnection.State { return this._state; } - get connectionType(): SerialType { - return this.state.connected; + isSerialOpen(state?: MonitorConnection.State): boolean { + return (state ? state : this._state).length > 0; + } + + get monitorConfig(): MonitorConfig | undefined { + return isMonitorConfig(this.config) + ? (this.config as MonitorConfig) + : undefined; } get connected(): boolean { - return this.state.connected !== SerialType.None; + return this._connected; } - get monitorConfig(): MonitorConfig | undefined { - return this.state ? this.state.config : undefined; + set connected(c: boolean) { + this._connected = c; } - get autoConnect(): boolean { - return this._autoConnect; + async openSerial(type: SerialType): Promise { + if (this.state.includes(type)) return Status.OK; + const newState = deepClone(this.state); + newState.push(type); + const status = await this.setState(newState); + if (Status.isOK(status) && type === SerialType.Monitor) + this.createWsConnection(); + return status; } - set autoConnect(value: boolean) { - const oldValue = this._autoConnect; - this._autoConnect = value; - // When we enable the auto-connect, we have to connect - if (!oldValue && value) { - // We have to make sure the previous boards config has been restored. - // Otherwise, we might start the auto-connection without configured boards. - this.applicationState.reachedState('started_contributions').then(() => { - const { boardsConfig } = this.boardsServiceProvider; - this.handleBoardConfigChange(boardsConfig); - }); - } else if (oldValue && !value) { - if (this.reconnectTimeout !== undefined) { - window.clearTimeout(this.reconnectTimeout); - this.monitorErrors.length = 0; + async closeSerial(type: SerialType): Promise { + const index = this.state.indexOf(type); + let status = Status.OK; + if (index >= 0) { + const newState = deepClone(this.state); + newState.splice(index, 1); + status = await this.setState(newState); + if ( + Status.isOK(status) && + type === SerialType.Monitor && + this.webSocket + ) { + this.webSocket.close(); + this.webSocket = undefined; } } + return status; } handleError(error: MonitorError): void { - let shouldReconnect = false; - if (this.state) { - const { code, config } = error; - const { board, port } = config; - const options = { timeout: 3000 }; - switch (code) { - case MonitorError.ErrorCodes.CLIENT_CANCEL: { - console.debug( - `Serial connection was canceled by client: ${MonitorConnection.State.toString( - this.state - )}.` - ); - break; - } - case MonitorError.ErrorCodes.DEVICE_BUSY: { - this.messageService.warn( - nls.localize( - 'arduino/monitor/connectionBusy', - 'Connection failed. Serial port is busy: {0}', - Port.toString(port) - ), - options - ); - shouldReconnect = this.autoConnect; - this.monitorErrors.push(error); - break; - } - case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: { - this.messageService.info( - nls.localize( - 'arduino/monitor/disconnected', - 'Disconnected {0} from {1}.', - Board.toString(board, { - useFqbn: false, - }), - Port.toString(port) - ), - options - ); - break; - } - case undefined: { - this.messageService.error( - nls.localize( - 'arduino/monitor/unexpectedError', - 'Unexpected error. Reconnecting {0} on port {1}.', - Board.toString(board), - Port.toString(port) - ), - options - ); - console.error(JSON.stringify(error)); - shouldReconnect = this.connected && this.autoConnect; - break; - } + if (!this.connected) return; + const { code, config } = error; + const { board, port } = config; + const options = { timeout: 3000 }; + switch (code) { + case MonitorError.ErrorCodes.CLIENT_CANCEL: { + console.debug( + `Serial connection was canceled by client: ${MonitorConnection.Config.toString( + this.config + )}.` + ); + break; + } + case MonitorError.ErrorCodes.DEVICE_BUSY: { + this.messageService.warn( + nls.localize( + 'arduino/monitor/connectionBusy', + 'Connection failed. Serial port is busy: {0}', + Port.toString(port) + ), + options + ); + this.monitorErrors.push(error); + break; + } + case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: { + this.messageService.info( + nls.localize( + 'arduino/monitor/disconnected', + 'Disconnected {0} from {1}.', + Board.toString(board, { + useFqbn: false, + }), + Port.toString(port) + ), + options + ); + break; + } + case undefined: { + this.messageService.error( + nls.localize( + 'arduino/monitor/unexpectedError', + 'Unexpected error. Reconnecting {0} on port {1}.', + Board.toString(board), + Port.toString(port) + ), + options + ); + console.error(JSON.stringify(error)); + break; } - const oldState = this.state; - this.state = { connected: SerialType.None }; - - if (shouldReconnect) { - if (this.monitorErrors.length >= 10) { - this.messageService.warn( - nls.localize( - 'arduino/monitor/failedReconnect', - 'Failed to reconnect {0} to serial after 10 consecutive attempts. The {1} serial port is busy.', - Board.toString(board, { - useFqbn: false, - }), - Port.toString(port) - ) - ); - this.monitorErrors.length = 0; - } else { - const attempts = this.monitorErrors.length || 1; - if (this.reconnectTimeout !== undefined) { - // Clear the previous timer. - window.clearTimeout(this.reconnectTimeout); - } - const timeout = attempts * 1000; - this.messageService.warn( - nls.localize( - 'arduino/monitor/reconnect', - 'Reconnecting {0} to {1} in {2] seconds...', - Board.toString(board, { - useFqbn: false, - }), - Port.toString(port), - attempts.toString() - ), - { timeout } - ); - this.reconnectTimeout = window.setTimeout( - () => - oldState.config && - this.connect(oldState.connected, oldState.config), - timeout - ); + } + this.connected = false; + + if (this.isSerialOpen()) { + if (this.monitorErrors.length >= 10) { + this.messageService.warn( + nls.localize( + 'arduino/monitor/failedReconnect', + 'Failed to reconnect {0} to serial after 10 consecutive attempts. The {1} serial port is busy.', + Board.toString(board, { + useFqbn: false, + }), + Port.toString(port) + ) + ); + this.monitorErrors.length = 0; + } else { + const attempts = this.monitorErrors.length || 1; + if (this.reconnectTimeout !== undefined) { + // Clear the previous timer. + window.clearTimeout(this.reconnectTimeout); } + const timeout = attempts * 1000; + this.messageService.warn( + nls.localize( + 'arduino/monitor/reconnect', + 'Reconnecting {0} to {1} in {2] seconds...', + Board.toString(board, { + useFqbn: false, + }), + Port.toString(port), + attempts.toString() + ) + ); + this.reconnectTimeout = window.setTimeout( + () => this.connect(), + timeout + ); } } } handleAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void { - if (this.autoConnect && this.connected) { - const { boardsConfig } = this.boardsServiceProvider; + const { boardsConfig } = this.boardsServiceProvider; + if ( + this.boardsServiceProvider.canUploadTo(boardsConfig, { + silent: false, + }) + ) { + const { attached } = AttachedBoardsChangeEvent.diff(event); if ( - this.boardsServiceProvider.canUploadTo(boardsConfig, { - silent: false, - }) + attached.boards.some( + (board) => + !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board) + ) ) { - const { attached } = AttachedBoardsChangeEvent.diff(event); - if ( - attached.boards.some( - (board) => - !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board) - ) - ) { - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.monitorModel; - const connectionType = this.state.connected; - this.disconnect(connectionType).then(() => - this.connect(connectionType, { board, port, baudRate }) - ); - } + const { selectedBoard: board, selectedPort: port } = boardsConfig; + const { baudRate } = this.monitorModel; + const newConfig = { board, port, baudRate }; + this.setConfig(newConfig); } } } - async connect( - type: SerialType, - newConfig?: MonitorConfig, - autoConnect = false - ): Promise { - if (this.connected) { - switch (this.state.connected) { - case SerialType.All: - // const disconnectStatus = await this.disconnect(type); - // if (!Status.isOK(disconnectStatus)) { - // return disconnectStatus; - // } - // break; - return Status.OK; - case SerialType.Plotter: - if (type === SerialType.Monitor) { - if (this.createWsConnection()) { - this.state = { ...this.state, connected: SerialType.All }; - return Status.OK; - } - return Status.NOT_CONNECTED; - } - return Status.OK; - case SerialType.Monitor: - if (type === SerialType.Plotter) - this.state = { ...this.state, connected: SerialType.All }; - return SerialType.All; - } - } - - this.autoConnect = autoConnect; - let config = newConfig; - if (!config) { - const { boardsConfig } = this.boardsServiceProvider; - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.model; - if (!board || !port) { - this.messageService.error( - `Please select a board and a port to open the serial connection.` - ); - return Status.NOT_CONNECTED; - } - config = { - board, - port, - baudRate, - }; + async connect(): Promise { + if (this.connected) return Status.ALREADY_CONNECTED; + if (!isMonitorConfig(this.config)) { + this.messageService.error( + `Please select a board and a port to open the serial connection.` + ); + return Status.NOT_CONNECTED; } - this.toDisposeOnDisconnect.push( - this.monitorServiceClient.onWebSocketChanged( - this.handleWebSocketChanged.bind(this) - ), - this.monitorServiceClient.onError(this.handleError.bind(this)), - this.boardsServiceProvider.onBoardsConfigChanged( - this.handleBoardConfigChange.bind(this) - ), - this.notificationCenter.onAttachedBoardsChanged( - this.handleAttachedBoardsChanged.bind(this) - ) - ); console.info( `>>> Creating serial connection for ${Board.toString( - config.board - )} on port ${Port.toString(config.port)}...` + this.config.board + )} on port ${Port.toString(this.config.port)}...` ); - const connectStatus = await this.monitorService.connect(config); + const connectStatus = await this.monitorService.connect(this.config); if (Status.isOK(connectStatus)) { - this.state = { config, connected: type }; - if (type === SerialType.Monitor) this.createWsConnection(); + this.connected = true; console.info( - `<<< Serial connection created for ${Board.toString(config.board, { + `<<< Serial connection created for ${Board.toString(this.config.board, { useFqbn: false, - })} on port ${Port.toString(config.port)}.` + })} on port ${Port.toString(this.config.port)}.` ); } return Status.isOK(connectStatus); } - async disconnect(type: SerialType): Promise { - if (!this.connected || type === SerialType.None) { - return Status.OK; - } - let newState = deepClone(this.state); - - if (type === SerialType.All || type === this.state.connected) { - newState = { connected: SerialType.None }; - } - if (this.state.connected === SerialType.All) { - newState = { - ...this.state, - connected: - type === SerialType.Monitor ? SerialType.Plotter : SerialType.Monitor, - }; - } - if ( - (type === SerialType.All || type === SerialType.Monitor) && - this.webSocket - ) { - this.webSocket.close(); - this.webSocket = undefined; - } - - if (newState.connected !== SerialType.None) { + async disconnect(): Promise { + if (!this.connected) { return Status.OK; } console.log('>>> Disposing existing serial connection...'); const status = await this.monitorService.disconnect(); if (Status.isOK(status)) { + this.connected = false; console.log( - `<<< Disposed serial connection. Was: ${MonitorConnection.State.toString( - newState + `<<< Disposed serial connection. Was: ${MonitorConnection.Config.toString( + this.config )}` ); this.wsPort = undefined; } else { console.warn( - `<<< Could not dispose serial connection. Activate connection: ${MonitorConnection.State.toString( - newState + `<<< Could not dispose serial connection. Activate connection: ${MonitorConnection.Config.toString( + this.config )}` ); } - this.state = newState; return status; } @@ -454,51 +427,38 @@ export class MonitorConnection { protected async handleBoardConfigChange( boardsConfig: BoardsConfig.Config ): Promise { - if (this.autoConnect) { - if ( - this.boardsServiceProvider.canUploadTo(boardsConfig, { - silent: false, - }) - ) { - // Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports. - // The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881 - const connectionType = this.state.connected; - this.boardsService.getAvailablePorts().then((ports) => { - if ( - ports.some((port) => Port.equals(port, boardsConfig.selectedPort)) - ) { - new Promise((resolve) => { - // First, disconnect if connected. - if (this.connected) { - this.disconnect(connectionType).then(() => resolve()); - return; - } - resolve(); - }).then(() => { - // Then (re-)connect. - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.monitorModel; - this.connect(this.state.connected, { board, port, baudRate }); - }); - } - }); + if ( + this.boardsServiceProvider.canUploadTo(boardsConfig, { + silent: false, + }) + ) { + // Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports. + // The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881 + const ports = await this.boardsService.getAvailablePorts(); + if (ports.some((port) => Port.equals(port, boardsConfig.selectedPort))) { + const { selectedBoard: board, selectedPort: port } = boardsConfig; + const { baudRate } = this.monitorModel; + const newConfig: MonitorConfig = { board, port, baudRate }; + this.setConfig(newConfig); } } } } export namespace MonitorConnection { - export interface State { - readonly config?: MonitorConfig; - readonly connected: SerialType; - } + export type State = SerialType[]; - export namespace State { - export function toString(state: State): string { - const { config } = state; - if (!config) return ''; + export namespace Config { + export function toString(config: Partial): string { + if (!isMonitorConfig(config)) return ''; const { board, port } = config; return `${Board.toString(board)} ${Port.toString(port)}`; } } } + +function isMonitorConfig( + config: Partial +): config is MonitorConfig { + return !!config.board && !!config.baudRate && !!config.port; +} diff --git a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx index 268a8b76c..eba894c8f 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx @@ -57,12 +57,9 @@ export class MonitorWidget extends ReactWidget { this.scrollOptions = undefined; this.toDispose.push(this.clearOutputEmitter); this.toDispose.push( - Disposable.create(() => { - this.monitorConnection.autoConnect = false; - if (this.monitorConnection.connected) { - this.monitorConnection.disconnect(SerialType.Monitor); - } - }) + Disposable.create(() => + this.monitorConnection.closeSerial(SerialType.Monitor) + ) ); } @@ -87,7 +84,7 @@ export class MonitorWidget extends ReactWidget { super.onAfterAttach(msg); // const { boardsConfig } = this.boardsServiceProvider; - this.monitorConnection.connect(SerialType.Monitor, undefined, true); + this.monitorConnection.openSerial(SerialType.Monitor); } onCloseRequest(msg: Message): void { diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index 557623ec5..3ff25a092 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -11,7 +11,7 @@ import { ArduinoMenus } from '../menu/arduino-menus'; import { Contribution } from '../contributions/contribution'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/core/shared/electron'; -import { MonitorConfig } from '../../common/protocol'; +import { MonitorConfig, Status } from '../../common/protocol'; import { MonitorConnection, SerialType } from '../monitor/monitor-connection'; import { SerialPlotter } from './protocol'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; @@ -49,8 +49,7 @@ export class PlotterFrontendContribution extends Contribution { ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { if (this.window) { this.window = null; - await this.monitorConnection.disconnect(SerialType.Plotter); - this.monitorConnection.autoConnect = false; + await this.monitorConnection.closeSerial(SerialType.Plotter); } }); @@ -78,25 +77,12 @@ export class PlotterFrontendContribution extends Contribution { return; } } - const { boardsConfig } = this.boardsServiceProvider; - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.model; - if (board && port) { - const status = await this.monitorConnection.connect(SerialType.Plotter, { - board, - port, - baudRate, - }); - const wsPort = this.monitorConnection.getWsPort(); - if (status && wsPort) { - this.open(wsPort); - } else { - this.messageService.error(`Couldn't open serial plotter`); - } + const status = await this.monitorConnection.openSerial(SerialType.Plotter); + const wsPort = this.monitorConnection.getWsPort(); + if (Status.isOK(status) && wsPort) { + this.open(wsPort); } else { - this.messageService.error( - `Please select a board and a port to open the Serial Plotter.` - ); + this.messageService.error(`Couldn't open serial plotter`); } } From eead91d62c8b17e827b60392664e7606a4974e5e Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Wed, 3 Nov 2021 17:48:21 +0100 Subject: [PATCH 07/27] plotter communication --- arduino-ide-extension/package.json | 2 +- .../src/browser/monitor/monitor-connection.ts | 32 +++++++--- .../plotter/plotter-frontend-contribution.ts | 7 ++- .../src/browser/plotter/protocol.ts | 5 +- .../src/common/protocol/monitor-service.ts | 4 +- .../src/node/monitor/monitor-service-impl.ts | 60 ++++++++++++++----- .../web-socket/web-socket-service-impl.ts | 8 +++ .../src/node/web-socket/web-socket-service.ts | 2 + yarn.lock | 8 +-- 9 files changed, 95 insertions(+), 33 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 86c34f8ad..c31cc9ef6 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.2", + "arduino-serial-plotter-webapp": "0.0.3", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 4959c2b59..5f899cf04 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -20,6 +20,7 @@ import { import { BoardsConfig } from '../boards/boards-config'; import { MonitorModel } from './monitor-model'; import { NotificationCenter } from '../notification-center'; +import { ThemeService } from '@theia/core/lib/browser/theming'; import { nls } from '@theia/core/lib/browser/nls'; export enum SerialType { @@ -56,6 +57,9 @@ export class MonitorConnection { @inject(MonitorModel) protected readonly model: MonitorModel; + @inject(ThemeService) + protected readonly themeService: ThemeService; + protected _state: MonitorConnection.State = []; protected _connected: boolean; @@ -90,14 +94,14 @@ export class MonitorConnection { protected init(): void { this.monitorServiceClient.onWebSocketChanged( this.handleWebSocketChanged.bind(this) - ), - this.monitorServiceClient.onError(this.handleError.bind(this)), - this.boardsServiceProvider.onBoardsConfigChanged( - this.handleBoardConfigChange.bind(this) - ), - this.notificationCenter.onAttachedBoardsChanged( - this.handleAttachedBoardsChanged.bind(this) - ); + ); + this.monitorServiceClient.onError(this.handleError.bind(this)); + this.boardsServiceProvider.onBoardsConfigChanged( + this.handleBoardConfigChange.bind(this) + ); + this.notificationCenter.onAttachedBoardsChanged( + this.handleAttachedBoardsChanged.bind(this) + ); // Handles the `baudRate` changes by reconnecting if required. this.monitorModel.onChange(({ property }) => { if (property === 'baudRate' && this.connected) { @@ -105,6 +109,12 @@ export class MonitorConnection { this.handleBoardConfigChange(boardsConfig); } }); + + this.themeService.onDidColorThemeChange((theme) => { + this.monitorService.updateWsConfigParam({ + darkTheme: theme.newTheme.type === 'dark', + }); + }); } protected setConfig(newConfig: Partial): void { @@ -411,7 +421,7 @@ export class MonitorConnection { } return new Promise((resolve) => { this.monitorService - .send(data + this.monitorModel.lineEnding) + .sendMessageToSerial(data + this.monitorModel.lineEnding) .then(() => resolve(Status.OK)); }); } @@ -438,6 +448,10 @@ export class MonitorConnection { if (ports.some((port) => Port.equals(port, boardsConfig.selectedPort))) { const { selectedBoard: board, selectedPort: port } = boardsConfig; const { baudRate } = this.monitorModel; + // update the baudrate on the config + this.monitorService.updateWsConfigParam({ + currentBaudrate: baudRate, + }); const newConfig: MonitorConfig = { board, port, baudRate }; this.setConfig(newConfig); } diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index 3ff25a092..dc3c5e522 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -1,3 +1,4 @@ +import { ThemeService } from '@theia/core/lib/browser/theming'; import { injectable, inject } from 'inversify'; import { Command, @@ -37,6 +38,9 @@ export class PlotterFrontendContribution extends Contribution { @inject(MonitorModel) protected readonly model: MonitorModel; + @inject(ThemeService) + protected readonly themeService: ThemeService; + @inject(MonitorConnection) protected readonly monitorConnection: MonitorConnection; @@ -90,7 +94,8 @@ export class PlotterFrontendContribution extends Contribution { const initConfig: SerialPlotter.Config = { baudrates: MonitorConfig.BaudRates.map((b) => b), currentBaudrate: this.model.baudRate, - darkTheme: true, + currentLineEnding: this.model.lineEnding, + darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, }; const urlWithParams = queryString.stringifyUrl( diff --git a/arduino-ide-extension/src/browser/plotter/protocol.ts b/arduino-ide-extension/src/browser/plotter/protocol.ts index b81b714eb..5ae2a2292 100644 --- a/arduino-ide-extension/src/browser/plotter/protocol.ts +++ b/arduino-ide-extension/src/browser/plotter/protocol.ts @@ -2,18 +2,17 @@ export namespace SerialPlotter { export type Config = { currentBaudrate: number; baudrates: number[]; + currentLineEnding: string; darkTheme: boolean; wsPort: number; generate?: boolean; }; export namespace Protocol { export enum Command { - PLOTTER_REQUEST_CONFIG = 'PLOTTER_REQUEST_CONFIG', - PLOTTER_READY = 'PLOTTER_READY', PLOTTER_SET_BAUDRATE = 'PLOTTER_SET_BAUDRATE', PLOTTER_SET_LINE_ENDING = 'PLOTTER_SET_LINE_ENDING', PLOTTER_SEND_MESSAGE = 'PLOTTER_SEND_MESSAGE', - PARENT_SET_CONFIG = 'PARENT_SET_CONFIG', + MIDDLEWARE_CONFIG_CHANGED = 'MIDDLEWARE_CONFIG_CHANGED', } export type Message = { command: SerialPlotter.Protocol.Command; diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index e7cf93fb4..497a398eb 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -1,6 +1,7 @@ import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import { Board, Port } from './boards-service'; import { Event } from '@theia/core/lib/common/event'; +import { SerialPlotter } from '../../browser/plotter/protocol'; export interface Status {} export type OK = Status; @@ -23,7 +24,8 @@ export const MonitorService = Symbol('MonitorService'); export interface MonitorService extends JsonRpcServer { connect(config: MonitorConfig): Promise; disconnect(): Promise; - send(message: string): Promise; + sendMessageToSerial(message: string): Promise; + updateWsConfigParam(config: Partial): Promise; } export interface MonitorConfig { diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index 6ea310df4..361df0175 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -19,6 +19,7 @@ import { import { MonitorClientProvider } from './monitor-client-provider'; import { Board, Port } from '../../common/protocol/boards-service'; import { WebSocketService } from '../web-socket/web-socket-service'; +import { SerialPlotter } from '../../browser/plotter/protocol'; interface ErrorWithCode extends Error { readonly code: number; @@ -71,7 +72,7 @@ export class MonitorServiceImpl implements MonitorService { protected readonly webSocketService: WebSocketService; protected client?: MonitorServiceClient; - protected connection?: { + protected serialConnection?: { duplex: ClientDuplexStream; config: MonitorConfig; }; @@ -84,20 +85,30 @@ export class MonitorServiceImpl implements MonitorService { dispose(): void { this.logger.info('>>> Disposing monitor service...'); - if (this.connection) { + if (this.serialConnection) { this.disconnect(); } this.logger.info('<<< Disposed monitor service.'); this.client = undefined; } + async updateWsConfigParam( + config: Partial + ): Promise { + const msg: SerialPlotter.Protocol.Message = { + command: SerialPlotter.Protocol.Command.MIDDLEWARE_CONFIG_CHANGED, + data: config, + }; + this.webSocketService.sendMessage(JSON.stringify(msg)); + } + async connect(config: MonitorConfig): Promise { this.logger.info( `>>> Creating serial monitor connection for ${Board.toString( config.board )} on port ${Port.toString(config.port)}...` ); - if (this.connection) { + if (this.serialConnection) { return Status.ALREADY_CONNECTED; } const client = await this.monitorClientProvider.client(); @@ -108,7 +119,7 @@ export class MonitorServiceImpl implements MonitorService { return { message: client.message }; } const duplex = client.streamingOpen(); - this.connection = { duplex, config }; + this.serialConnection = { duplex, config }; duplex.on( 'error', @@ -137,6 +148,27 @@ export class MonitorServiceImpl implements MonitorService { } }; + this.webSocketService.onMessageReceived((msg: string) => { + try { + const message: SerialPlotter.Protocol.Message = JSON.parse(msg); + + switch (message.command) { + case SerialPlotter.Protocol.Command.PLOTTER_SEND_MESSAGE: + this.sendMessageToSerial(message.data); + break; + + case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: + break; + + case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: + break; + + default: + break; + } + } catch (error) {} + }); + // empty the queue every 16ms (~60fps) setInterval(flushMessagesToFrontend, 32); @@ -187,8 +219,8 @@ export class MonitorServiceImpl implements MonitorService { req.setConfig(monitorConfig); return new Promise((resolve) => { - if (this.connection) { - this.connection.duplex.write(req, () => { + if (this.serialConnection) { + this.serialConnection.duplex.write(req, () => { this.logger.info( `<<< Serial monitor connection created for ${Board.toString( config.board, @@ -206,40 +238,40 @@ export class MonitorServiceImpl implements MonitorService { async disconnect(reason?: MonitorError): Promise { try { if ( - !this.connection && + !this.serialConnection && reason && reason.code === MonitorError.ErrorCodes.CLIENT_CANCEL ) { return Status.OK; } this.logger.info('>>> Disposing monitor connection...'); - if (!this.connection) { + if (!this.serialConnection) { this.logger.warn('<<< Not connected. Nothing to dispose.'); return Status.NOT_CONNECTED; } - const { duplex, config } = this.connection; + const { duplex, config } = this.serialConnection; duplex.cancel(); this.logger.info( `<<< Disposed monitor connection for ${Board.toString(config.board, { useFqbn: false, })} on port ${Port.toString(config.port)}.` ); - this.connection = undefined; + this.serialConnection = undefined; return Status.OK; } finally { this.messages.length = 0; } } - async send(message: string): Promise { - if (!this.connection) { + async sendMessageToSerial(message: string): Promise { + if (!this.serialConnection) { return Status.NOT_CONNECTED; } const req = new StreamingOpenRequest(); req.setData(new TextEncoder().encode(message)); return new Promise((resolve) => { - if (this.connection) { - this.connection.duplex.write(req, () => { + if (this.serialConnection) { + this.serialConnection.duplex.write(req, () => { resolve(Status.OK); }); return; diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts b/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts index 35635ca23..41bf00b7a 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts @@ -1,3 +1,4 @@ +import { Emitter } from '@theia/core'; import { injectable } from 'inversify'; import * as WebSocket from 'ws'; import { WebSocketService } from './web-socket-service'; @@ -7,6 +8,9 @@ export default class WebSocketServiceImpl implements WebSocketService { protected wsClients: WebSocket[]; protected server: WebSocket.Server; + protected readonly onMessage = new Emitter(); + public readonly onMessageReceived = this.onMessage.event; + constructor() { this.server = new WebSocket.Server({ port: 0 }); this.server.on('connection', this.addClient.bind(this)); @@ -18,6 +22,10 @@ export default class WebSocketServiceImpl implements WebSocketService { ws.onclose = () => { this.wsClients.splice(this.wsClients.indexOf(ws), 1); }; + + ws.onmessage = (res) => { + this.onMessage.fire(res.data.toString()); + }; } getAddress(): WebSocket.AddressInfo { diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service.ts b/arduino-ide-extension/src/node/web-socket/web-socket-service.ts index c22b39cfc..5d612560d 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-service.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-service.ts @@ -1,7 +1,9 @@ +import { Event } from '@theia/core/lib/common/event'; import * as WebSocket from 'ws'; export const WebSocketService = Symbol('WebSocketService'); export interface WebSocketService { getAddress(): WebSocket.AddressInfo; sendMessage(message: string): void; + onMessageReceived: Event; } diff --git a/yarn.lock b/yarn.lock index c50e3679e..2a4219268 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.2.tgz#491950c30b5d29c0169e2b90047c9dcd6f90c5b3" - integrity sha512-nP1G8F+i2WeGzoNlkuEZiQnFvPfHfK1z5y5lEfa3rTXH18OGrpejmfK2SPBiBMuSirNOni9/FmTxGHIyxaxzDA== +arduino-serial-plotter-webapp@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.3.tgz#ff7136676fa1cf23950751ea6a675372b3d1eb25" + integrity sha512-qGxok+UQsuOVk5aOPxGjcXorQYHBNPA5fUgwMt/EbusqOnwphMHDeC3wJ3K82bNwcjg1Yf2xNJI4bRLRv+tNxw== are-we-there-yet@~1.1.2: version "1.1.5" From 6ab7a38853e242f55ad31ff29ef3280d66d44d81 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 4 Nov 2021 10:29:25 +0100 Subject: [PATCH 08/27] fix clearConsole + refactor monitor connection --- .../browser/arduino-ide-frontend-module.ts | 4 +- .../browser/contributions/burn-bootloader.ts | 6 +- .../browser/contributions/upload-sketch.ts | 6 +- .../src/browser/monitor/monitor-connection.ts | 128 ++++++++++-------- .../src/browser/monitor/monitor-widget.tsx | 12 +- .../monitor/serial-monitor-send-output.tsx | 4 +- .../plotter/plotter-frontend-contribution.ts | 10 +- 7 files changed, 91 insertions(+), 79 deletions(-) diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index b22d329d0..11ef3ee74 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -81,7 +81,7 @@ import { } from '../common/protocol/config-service'; import { MonitorWidget } from './monitor/monitor-widget'; import { MonitorViewContribution } from './monitor/monitor-view-contribution'; -import { MonitorConnection } from './monitor/monitor-connection'; +import { SerialConnectionManager } from './monitor/monitor-connection'; import { MonitorModel } from './monitor/monitor-model'; import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; @@ -409,7 +409,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { return connection.createProxy(MonitorServicePath, client); }) .inSingletonScope(); - bind(MonitorConnection).toSelf().inSingletonScope(); + bind(SerialConnectionManager).toSelf().inSingletonScope(); // Serial monitor service client to receive and delegate notifications from the backend. bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope(); diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index 9891a26dd..d60c7d66d 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -3,7 +3,7 @@ import { OutputChannelManager } from '@theia/output/lib/common/output-channel'; import { CoreService } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { BoardsDataStore } from '../boards/boards-data-store'; -import { MonitorConnection } from '../monitor/monitor-connection'; +import { SerialConnectionManager } from '../monitor/monitor-connection'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { SketchContribution, @@ -18,8 +18,8 @@ export class BurnBootloader extends SketchContribution { @inject(CoreService) protected readonly coreService: CoreService; - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; + @inject(SerialConnectionManager) + protected readonly monitorConnection: SerialConnectionManager; @inject(BoardsDataStore) protected readonly boardsDataStore: BoardsDataStore; diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index ef54c0f0d..9fc302efe 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -4,7 +4,7 @@ import { CoreService } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { BoardsDataStore } from '../boards/boards-data-store'; -import { MonitorConnection } from '../monitor/monitor-connection'; +import { SerialConnectionManager } from '../monitor/monitor-connection'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { SketchContribution, @@ -21,8 +21,8 @@ export class UploadSketch extends SketchContribution { @inject(CoreService) protected readonly coreService: CoreService; - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; + @inject(SerialConnectionManager) + protected readonly monitorConnection: SerialConnectionManager; @inject(BoardsDataStore) protected readonly boardsDataStore: BoardsDataStore; diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 5f899cf04..35470d681 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -2,7 +2,6 @@ import { injectable, inject, postConstruct } from 'inversify'; import { deepClone } from '@theia/core/lib/common/objects'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { MessageService } from '@theia/core/lib/common/message-service'; -import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state'; import { MonitorService, MonitorConfig, @@ -23,13 +22,8 @@ import { NotificationCenter } from '../notification-center'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { nls } from '@theia/core/lib/browser/nls'; -export enum SerialType { - Monitor = 'Monitor', - Plotter = 'Plotter', -} - @injectable() -export class MonitorConnection { +export class SerialConnectionManager { @inject(MonitorModel) protected readonly monitorModel: MonitorModel; @@ -51,26 +45,19 @@ export class MonitorConnection { @inject(MessageService) protected messageService: MessageService; - @inject(FrontendApplicationStateService) - protected readonly applicationState: FrontendApplicationStateService; - - @inject(MonitorModel) - protected readonly model: MonitorModel; - @inject(ThemeService) protected readonly themeService: ThemeService; - - protected _state: MonitorConnection.State = []; + protected _state: Serial.State = []; protected _connected: boolean; /** * Note: The idea is to toggle this property from the UI (`Monitor` view) * and the boards config and the boards attachment/detachment logic can be at on place, here. */ - protected readonly onConnectionChangedEmitter = - new Emitter(); + protected readonly onConnectionChangedEmitter = new Emitter(); + /** - * This emitter forwards all read events **iff** the connection is established. + * This emitter forwards all read events **if** the connection is established. */ protected readonly onReadEmitter = new Emitter<{ messages: string[] }>(); @@ -82,8 +69,13 @@ export class MonitorConnection { protected monitorErrors: MonitorError[] = []; protected reconnectTimeout?: number; + /** + * When the websocket server is up on the backend, we save the port here, so that the client knows how to connect to it + * */ protected wsPort?: number; + protected webSocket?: WebSocket; + protected config: Partial = { board: undefined, port: undefined, @@ -117,15 +109,21 @@ export class MonitorConnection { }); } + /** + * Set the config passing only the properties that has changed. If some has changed and the serial is open, + * we try to reconnect + * + * @param newConfig the porperties of the config that has changed + */ protected setConfig(newConfig: Partial): void { - let shouldReconnect = false; + let configHasChanged = false; Object.keys(this.config).forEach((key: keyof MonitorConfig) => { if (newConfig[key] && newConfig[key] !== this.config[key]) { - shouldReconnect = true; + configHasChanged = true; this.config = { ...this.config, [key]: newConfig[key] }; } }); - if (shouldReconnect && this.isSerialOpen()) { + if (configHasChanged && this.isSerialOpen()) { this.disconnect().then(() => this.connect()); } } @@ -134,10 +132,13 @@ export class MonitorConnection { return this.wsPort; } - handleWebSocketChanged(wsPort: number): void { + protected handleWebSocketChanged(wsPort: number): void { this.wsPort = wsPort; } + /** + * When the serial monitor is open and the frontend is connected to the serial, we create the websocket here + */ protected createWsConnection(): boolean { if (this.wsPort) { try { @@ -154,10 +155,17 @@ export class MonitorConnection { return false; } - protected async setState(s: MonitorConnection.State): Promise { + /** + * Sets the types of connections needed by the client. + * + * @param s The new types of connections (can be 'Monitor', 'Plotter', none or both). + * If the previuos state was empty and 's' is not, it tries to reconnect to the serial service + * If the provios state was NOT empty and now it is, it disconnects to the serial service + * @returns The status of the operation + */ + protected async setState(s: Serial.State): Promise { const oldState = deepClone(this._state); this._state = s; - this.onConnectionChangedEmitter.fire(this._state); let status = Status.OK; if (this.isSerialOpen(oldState) && !this.isSerialOpen()) { @@ -166,34 +174,14 @@ export class MonitorConnection { status = await this.connect(); } - // if (this.connected) { - // switch (this.state.connected) { - // case SerialType.All: - // return Status.OK; - // case SerialType.Plotter: - // if (type === SerialType.Monitor) { - // if (this.createWsConnection()) { - // this.state = { ...this.state, connected: SerialType.All }; - // return Status.OK; - // } - // return Status.NOT_CONNECTED; - // } - // return Status.OK; - // case SerialType.Monitor: - // if (type === SerialType.Plotter) - // this.state = { ...this.state, connected: SerialType.All }; - // return SerialType.All; - // } - // } - return status; } - protected get state(): MonitorConnection.State { + protected get state(): Serial.State { return this._state; } - isSerialOpen(state?: MonitorConnection.State): boolean { + isSerialOpen(state?: Serial.State): boolean { return (state ? state : this._state).length > 0; } @@ -209,19 +197,32 @@ export class MonitorConnection { set connected(c: boolean) { this._connected = c; + this.onConnectionChangedEmitter.fire(this._connected); } - - async openSerial(type: SerialType): Promise { + /** + * Called when a client opens the serial from the GUI + * + * @param type could be either 'Monitor' or 'Plotter'. If it's 'Monitor' we also connect to the websocket and + * listen to the message events + * @returns the status of the operation + */ + async openSerial(type: Serial.Type): Promise { if (this.state.includes(type)) return Status.OK; const newState = deepClone(this.state); newState.push(type); const status = await this.setState(newState); - if (Status.isOK(status) && type === SerialType.Monitor) + if (Status.isOK(status) && type === Serial.Type.Monitor) this.createWsConnection(); return status; } - async closeSerial(type: SerialType): Promise { + /** + * Called when a client closes the serial from the GUI + * + * @param type could be either 'Monitor' or 'Plotter'. If it's 'Monitor' we close the websocket connection + * @returns the status of the operation + */ + async closeSerial(type: Serial.Type): Promise { const index = this.state.indexOf(type); let status = Status.OK; if (index >= 0) { @@ -230,7 +231,7 @@ export class MonitorConnection { status = await this.setState(newState); if ( Status.isOK(status) && - type === SerialType.Monitor && + type === Serial.Type.Monitor && this.webSocket ) { this.webSocket.close(); @@ -240,6 +241,9 @@ export class MonitorConnection { return status; } + /** + * Handles error on the MonitorServiceClient and try to reconnect, eventually + */ handleError(error: MonitorError): void { if (!this.connected) return; const { code, config } = error; @@ -248,7 +252,7 @@ export class MonitorConnection { switch (code) { case MonitorError.ErrorCodes.CLIENT_CANCEL: { console.debug( - `Serial connection was canceled by client: ${MonitorConnection.Config.toString( + `Serial connection was canceled by client: ${Serial.Config.toString( this.config )}.` ); @@ -394,14 +398,14 @@ export class MonitorConnection { if (Status.isOK(status)) { this.connected = false; console.log( - `<<< Disposed serial connection. Was: ${MonitorConnection.Config.toString( + `<<< Disposed serial connection. Was: ${Serial.Config.toString( this.config )}` ); this.wsPort = undefined; } else { console.warn( - `<<< Could not dispose serial connection. Activate connection: ${MonitorConnection.Config.toString( + `<<< Could not dispose serial connection. Activate connection: ${Serial.Config.toString( this.config )}` ); @@ -426,7 +430,7 @@ export class MonitorConnection { }); } - get onConnectionChanged(): Event { + get onConnectionChanged(): Event { return this.onConnectionChangedEmitter.event; } @@ -459,8 +463,18 @@ export class MonitorConnection { } } -export namespace MonitorConnection { - export type State = SerialType[]; +export namespace Serial { + export enum Type { + Monitor = 'Monitor', + Plotter = 'Plotter', + } + + /** + * The state represents which types of connections are needed by the client, and it should match whether the Serial Monitor + * or the Serial Plotter are open or not in the GUI. It's an array cause it's possible to have both, none or only one of + * them open + */ + export type State = Serial.Type[]; export namespace Config { export function toString(config: Partial): string { diff --git a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx index eba894c8f..c6bb6db13 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx @@ -12,7 +12,7 @@ import { import { MonitorConfig } from '../../common/protocol/monitor-service'; import { ArduinoSelect } from '../widgets/arduino-select'; import { MonitorModel } from './monitor-model'; -import { MonitorConnection, SerialType } from './monitor-connection'; +import { Serial, SerialConnectionManager } from './monitor-connection'; import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { nls } from '@theia/core/lib/browser/nls'; @@ -29,8 +29,8 @@ export class MonitorWidget extends ReactWidget { @inject(MonitorModel) protected readonly monitorModel: MonitorModel; - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; + @inject(SerialConnectionManager) + protected readonly monitorConnection: SerialConnectionManager; @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; @@ -58,7 +58,7 @@ export class MonitorWidget extends ReactWidget { this.toDispose.push(this.clearOutputEmitter); this.toDispose.push( Disposable.create(() => - this.monitorConnection.closeSerial(SerialType.Monitor) + this.monitorConnection.closeSerial(Serial.Type.Monitor) ) ); } @@ -82,9 +82,7 @@ export class MonitorWidget extends ReactWidget { protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); - // const { boardsConfig } = this.boardsServiceProvider; - - this.monitorConnection.openSerial(SerialType.Monitor); + this.monitorConnection.openSerial(Serial.Type.Monitor); } onCloseRequest(msg: Message): void { diff --git a/arduino-ide-extension/src/browser/monitor/serial-monitor-send-output.tsx b/arduino-ide-extension/src/browser/monitor/serial-monitor-send-output.tsx index f5b95b8f7..8a265d748 100644 --- a/arduino-ide-extension/src/browser/monitor/serial-monitor-send-output.tsx +++ b/arduino-ide-extension/src/browser/monitor/serial-monitor-send-output.tsx @@ -3,7 +3,7 @@ import { Event } from '@theia/core/lib/common/event'; import { DisposableCollection } from '@theia/core/lib/common/disposable'; import { areEqual, FixedSizeList as List } from 'react-window'; import { MonitorModel } from './monitor-model'; -import { MonitorConnection } from './monitor-connection'; +import { SerialConnectionManager } from './monitor-connection'; import dateFormat = require('dateformat'); import { messagesToLines, truncateLines } from './monitor-utils'; @@ -126,7 +126,7 @@ const Row = React.memo(_Row, areEqual); export namespace SerialMonitorOutput { export interface Props { readonly monitorModel: MonitorModel; - readonly monitorConnection: MonitorConnection; + readonly monitorConnection: SerialConnectionManager; readonly clearConsoleEvent: Event; readonly height: number; } diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index dc3c5e522..afc99a220 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -13,7 +13,7 @@ import { Contribution } from '../contributions/contribution'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/core/shared/electron'; import { MonitorConfig, Status } from '../../common/protocol'; -import { MonitorConnection, SerialType } from '../monitor/monitor-connection'; +import { Serial, SerialConnectionManager } from '../monitor/monitor-connection'; import { SerialPlotter } from './protocol'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; const queryString = require('query-string'); @@ -41,8 +41,8 @@ export class PlotterFrontendContribution extends Contribution { @inject(ThemeService) protected readonly themeService: ThemeService; - @inject(MonitorConnection) - protected readonly monitorConnection: MonitorConnection; + @inject(SerialConnectionManager) + protected readonly monitorConnection: SerialConnectionManager; @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; @@ -53,7 +53,7 @@ export class PlotterFrontendContribution extends Contribution { ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { if (this.window) { this.window = null; - await this.monitorConnection.closeSerial(SerialType.Plotter); + await this.monitorConnection.closeSerial(Serial.Type.Plotter); } }); @@ -81,7 +81,7 @@ export class PlotterFrontendContribution extends Contribution { return; } } - const status = await this.monitorConnection.openSerial(SerialType.Plotter); + const status = await this.monitorConnection.openSerial(Serial.Type.Plotter); const wsPort = this.monitorConnection.getWsPort(); if (Status.isOK(status) && wsPort) { this.open(wsPort); From ef1377e6c07908bbc7d9d7bc6f25c1045222b2ee Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 4 Nov 2021 11:00:47 +0100 Subject: [PATCH 09/27] fixed duplicated message issue --- .../src/node/web-socket/web-socket-service-impl.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts b/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts index 41bf00b7a..0f05759aa 100644 --- a/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts +++ b/arduino-ide-extension/src/node/web-socket/web-socket-service-impl.ts @@ -12,9 +12,11 @@ export default class WebSocketServiceImpl implements WebSocketService { public readonly onMessageReceived = this.onMessage.event; constructor() { - this.server = new WebSocket.Server({ port: 0 }); - this.server.on('connection', this.addClient.bind(this)); this.wsClients = []; + this.server = new WebSocket.Server({ port: 0 }); + + const addClient = this.addClient.bind(this); + this.server.on('connection', addClient); } private addClient(ws: WebSocket): void { From eed73667b476e9898ef4b9a7242df5ae205dcc8e Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 4 Nov 2021 16:43:41 +0100 Subject: [PATCH 10/27] sync EoL and baudRates --- arduino-ide-extension/package.json | 2 +- .../src/browser/monitor/monitor-connection.ts | 25 ++++++++++++++++--- .../monitor/monitor-service-client-impl.ts | 16 ++++++++++++ .../src/browser/monitor/monitor-widget.tsx | 5 ++-- .../src/common/protocol/monitor-service.ts | 5 ++++ .../src/node/monitor/monitor-service-impl.ts | 4 +++ 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index c31cc9ef6..3f48df4be 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.3", + "arduino-serial-plotter-webapp": "0.0.4", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 35470d681..44a4af21e 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -87,6 +87,17 @@ export class SerialConnectionManager { this.monitorServiceClient.onWebSocketChanged( this.handleWebSocketChanged.bind(this) ); + this.monitorServiceClient.onBaudRateChanged((baudRate) => { + if (this.monitorModel.baudRate !== baudRate) { + this.monitorModel.baudRate = baudRate; + } + }); + this.monitorServiceClient.onLineEndingChanged((lineending) => { + if (this.monitorModel.lineEnding !== lineending) { + this.monitorModel.lineEnding = lineending; + } + }); + this.monitorServiceClient.onError(this.handleError.bind(this)); this.boardsServiceProvider.onBoardsConfigChanged( this.handleBoardConfigChange.bind(this) @@ -100,6 +111,16 @@ export class SerialConnectionManager { const { boardsConfig } = this.boardsServiceProvider; this.handleBoardConfigChange(boardsConfig); } + + // update the current values in the backend and propagate to websocket clients + this.monitorService.updateWsConfigParam({ + ...(property === 'baudRate' && { + currentBaudrate: this.monitorModel.baudRate, + }), + ...(property === 'lineEnding' && { + currentLineEnding: this.monitorModel.lineEnding, + }), + }); }); this.themeService.onDidColorThemeChange((theme) => { @@ -452,10 +473,6 @@ export class SerialConnectionManager { if (ports.some((port) => Port.equals(port, boardsConfig.selectedPort))) { const { selectedBoard: board, selectedPort: port } = boardsConfig; const { baudRate } = this.monitorModel; - // update the baudrate on the config - this.monitorService.updateWsConfigParam({ - currentBaudrate: baudRate, - }); const newConfig: MonitorConfig = { board, port, baudRate }; this.setConfig(newConfig); } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts index 2b97adde1..e18a1f1ba 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts @@ -3,7 +3,9 @@ import { Emitter } from '@theia/core/lib/common/event'; import { MonitorServiceClient, MonitorError, + MonitorConfig, } from '../../common/protocol/monitor-service'; +import { MonitorModel } from './monitor-model'; @injectable() export class MonitorServiceClientImpl implements MonitorServiceClient { @@ -13,6 +15,12 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { protected readonly onMessageEmitter = new Emitter(); readonly onWebSocketChanged = this.onMessageEmitter.event; + protected readonly onBaudEmitter = new Emitter(); + readonly onBaudRateChanged = this.onBaudEmitter.event; + + protected readonly onEolEmitter = new Emitter(); + readonly onLineEndingChanged = this.onEolEmitter.event; + notifyError(error: MonitorError): void { this.onErrorEmitter.fire(error); } @@ -20,4 +28,12 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { notifyWebSocketChanged(message: number): void { this.onMessageEmitter.fire(message); } + + notifyBaudRateChanged(message: MonitorConfig.BaudRate): void { + this.onBaudEmitter.fire(message); + } + + notifyLineEndingChanged(message: MonitorModel.EOL): void { + this.onEolEmitter.fire(message); + } } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx index c6bb6db13..5204a8654 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx @@ -69,6 +69,7 @@ export class MonitorWidget extends ReactWidget { this.toDispose.push( this.monitorConnection.onConnectionChanged(() => this.clearConsole()) ); + this.toDispose.push(this.monitorModel.onChange(() => this.update())); } clearConsole(): void { @@ -183,7 +184,7 @@ export class MonitorWidget extends ReactWidget { @@ -192,7 +193,7 @@ export class MonitorWidget extends ReactWidget { className="select" maxMenuHeight={this.widgetHeight - 40} options={baudRates} - defaultValue={baudRate} + value={baudRate} onChange={this.onChangeBaudRate} /> diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 497a398eb..52e1c6ffc 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -2,6 +2,7 @@ import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import { Board, Port } from './boards-service'; import { Event } from '@theia/core/lib/common/event'; import { SerialPlotter } from '../../browser/plotter/protocol'; +import { MonitorModel } from '../../browser/monitor/monitor-model'; export interface Status {} export type OK = Status; @@ -58,8 +59,12 @@ export const MonitorServiceClient = Symbol('MonitorServiceClient'); export interface MonitorServiceClient { onError: Event; onWebSocketChanged: Event; + onLineEndingChanged: Event; + onBaudRateChanged: Event; notifyError(event: MonitorError): void; notifyWebSocketChanged(message: number): void; + notifyLineEndingChanged(message: MonitorModel.EOL): void; + notifyBaudRateChanged(message: MonitorConfig.BaudRate): void; } export interface MonitorError { diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index 361df0175..aedfa5de5 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -158,9 +158,13 @@ export class MonitorServiceImpl implements MonitorService { break; case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: + this.client?.notifyBaudRateChanged( + parseInt(message.data, 10) as MonitorConfig.BaudRate + ); break; case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: + this.client?.notifyLineEndingChanged(message.data); break; default: From 8f1223c0a26ed917772c703a1c75f38dbf37f233 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 9 Nov 2021 13:11:36 +0100 Subject: [PATCH 11/27] add serial unit tests --- .../browser/boards/boards-service-provider.ts | 2 +- .../src/browser/monitor/monitor-connection.ts | 64 ++- .../monitor/monitor-service-client-impl.ts | 6 +- .../src/common/protocol/monitor-service.ts | 2 +- .../browser/boards-auto-installer.test.ts | 35 +- .../src/test/browser/fixtures/boards.ts | 47 +++ .../src/test/browser/fixtures/serial.ts | 22 + .../browser/serial-connection-manager.test.ts | 375 ++++++++++++++++++ yarn.lock | 8 +- 9 files changed, 483 insertions(+), 78 deletions(-) create mode 100644 arduino-ide-extension/src/test/browser/fixtures/boards.ts create mode 100644 arduino-ide-extension/src/test/browser/fixtures/serial.ts create mode 100644 arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts diff --git a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts index 356589d4f..b9f5fd0e9 100644 --- a/arduino-ide-extension/src/browser/boards/boards-service-provider.ts +++ b/arduino-ide-extension/src/browser/boards/boards-service-provider.ts @@ -64,7 +64,7 @@ export class BoardsServiceProvider implements FrontendApplicationContribution { * This even also fires, when the boards package was not available for the currently selected board, * and the user installs the board package. Note: installing a board package will set the `fqbn` of the * currently selected board.\ - * This even also emitted when the board package for the currently selected board was uninstalled. + * This event is also emitted when the board package for the currently selected board was uninstalled. */ readonly onBoardsConfigChanged = this.onBoardsConfigChangedEmitter.event; readonly onAvailableBoardsChanged = diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 44a4af21e..3088eb584 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -1,4 +1,4 @@ -import { injectable, inject, postConstruct } from 'inversify'; +import { injectable, inject } from 'inversify'; import { deepClone } from '@theia/core/lib/common/objects'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { MessageService } from '@theia/core/lib/common/message-service'; @@ -24,31 +24,13 @@ import { nls } from '@theia/core/lib/browser/nls'; @injectable() export class SerialConnectionManager { - @inject(MonitorModel) - protected readonly monitorModel: MonitorModel; - - @inject(MonitorService) - protected readonly monitorService: MonitorService; - - @inject(MonitorServiceClient) - protected readonly monitorServiceClient: MonitorServiceClient; - - @inject(BoardsService) - protected readonly boardsService: BoardsService; - - @inject(BoardsServiceProvider) - protected readonly boardsServiceProvider: BoardsServiceProvider; - - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter; - - @inject(MessageService) - protected messageService: MessageService; - - @inject(ThemeService) - protected readonly themeService: ThemeService; protected _state: Serial.State = []; - protected _connected: boolean; + protected _connected = false; + protected config: Partial = { + board: undefined, + port: undefined, + baudRate: undefined, + }; /** * Note: The idea is to toggle this property from the UI (`Monitor` view) @@ -73,17 +55,21 @@ export class SerialConnectionManager { * When the websocket server is up on the backend, we save the port here, so that the client knows how to connect to it * */ protected wsPort?: number; - protected webSocket?: WebSocket; - protected config: Partial = { - board: undefined, - port: undefined, - baudRate: undefined, - }; - - @postConstruct() - protected init(): void { + constructor( + @inject(MonitorModel) protected readonly monitorModel: MonitorModel, + @inject(MonitorService) protected readonly monitorService: MonitorService, + @inject(MonitorServiceClient) + protected readonly monitorServiceClient: MonitorServiceClient, + @inject(BoardsService) protected readonly boardsService: BoardsService, + @inject(BoardsServiceProvider) + protected readonly boardsServiceProvider: BoardsServiceProvider, + @inject(NotificationCenter) + protected readonly notificationCenter: NotificationCenter, + @inject(MessageService) protected messageService: MessageService, + @inject(ThemeService) protected readonly themeService: ThemeService + ) { this.monitorServiceClient.onWebSocketChanged( this.handleWebSocketChanged.bind(this) ); @@ -136,7 +122,7 @@ export class SerialConnectionManager { * * @param newConfig the porperties of the config that has changed */ - protected setConfig(newConfig: Partial): void { + setConfig(newConfig: Partial): void { let configHasChanged = false; Object.keys(this.config).forEach((key: keyof MonitorConfig) => { if (newConfig[key] && newConfig[key] !== this.config[key]) { @@ -149,10 +135,18 @@ export class SerialConnectionManager { } } + getConfig(): Partial { + return this.config; + } + getWsPort(): number | undefined { return this.wsPort; } + isWebSocketConnected(): boolean { + return !!this.webSocket?.url; + } + protected handleWebSocketChanged(wsPort: number): void { this.wsPort = wsPort; } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts index e18a1f1ba..16a5323e8 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts @@ -12,8 +12,8 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { protected readonly onErrorEmitter = new Emitter(); readonly onError = this.onErrorEmitter.event; - protected readonly onMessageEmitter = new Emitter(); - readonly onWebSocketChanged = this.onMessageEmitter.event; + protected readonly onWebSocketChangedEmitter = new Emitter(); + readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; protected readonly onBaudEmitter = new Emitter(); readonly onBaudRateChanged = this.onBaudEmitter.event; @@ -26,7 +26,7 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { } notifyWebSocketChanged(message: number): void { - this.onMessageEmitter.fire(message); + this.onWebSocketChangedEmitter.fire(message); } notifyBaudRateChanged(message: MonitorConfig.BaudRate): void { diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 52e1c6ffc..266562cd5 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -11,7 +11,7 @@ export interface ErrorStatus extends Status { } export namespace Status { export function isOK(status: Status & { message?: string }): status is OK { - return typeof status.message !== 'string'; + return !!status && typeof status.message !== 'string'; } export const OK: OK = {}; export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' }; diff --git a/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts b/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts index efd75e943..030ae539b 100644 --- a/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts +++ b/arduino-ide-extension/src/test/browser/boards-auto-installer.test.ts @@ -11,52 +11,19 @@ import { MessageService } from '@theia/core'; import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; import { BoardsListWidgetFrontendContribution } from '../../browser/boards/boards-widget-frontend-contribution'; import { - Board, BoardsPackage, BoardsService, - Port, ResponseServiceArduino, } from '../../common/protocol'; import { IMock, It, Mock, Times } from 'typemoq'; import { Container, ContainerModule } from 'inversify'; import { BoardsAutoInstaller } from '../../browser/boards/boards-auto-installer'; -import { BoardsConfig } from '../../browser/boards/boards-config'; import { tick } from '../utils'; import { ListWidget } from '../../browser/widgets/component-list/list-widget'; +import { aBoardConfig, anInstalledPackage, aPackage } from './fixtures/boards'; disableJSDOM(); -const aBoard: Board = { - fqbn: 'some:board:fqbn', - name: 'Some Arduino Board', - port: { address: '/lol/port1234', protocol: 'serial' }, -}; -const aPort: Port = { - address: aBoard.port!.address, - protocol: aBoard.port!.protocol, -}; -const aBoardConfig: BoardsConfig.Config = { - selectedBoard: aBoard, - selectedPort: aPort, -}; -const aPackage: BoardsPackage = { - author: 'someAuthor', - availableVersions: ['some.ver.sion', 'some.other.version'], - boards: [aBoard], - deprecated: false, - description: 'Some Arduino Board, Some Other Arduino Board', - id: 'some:arduinoCoreId', - installable: true, - moreInfoLink: 'http://www.some-url.lol/', - name: 'Some Arduino Package', - summary: 'Boards included in this package:', -}; - -const anInstalledPackage: BoardsPackage = { - ...aPackage, - installedVersion: 'some.ver.sion', -}; - describe('BoardsAutoInstaller', () => { let subject: BoardsAutoInstaller; let messageService: IMock; diff --git a/arduino-ide-extension/src/test/browser/fixtures/boards.ts b/arduino-ide-extension/src/test/browser/fixtures/boards.ts new file mode 100644 index 000000000..a9783f026 --- /dev/null +++ b/arduino-ide-extension/src/test/browser/fixtures/boards.ts @@ -0,0 +1,47 @@ +import { BoardsConfig } from '../../../browser/boards/boards-config'; +import { Board, BoardsPackage, Port } from '../../../common/protocol'; + +export const aBoard: Board = { + fqbn: 'some:board:fqbn', + name: 'Some Arduino Board', + port: { address: '/lol/port1234', protocol: 'serial' }, +}; +export const aPort: Port = { + address: aBoard.port!.address, + protocol: aBoard.port!.protocol, +}; +export const aBoardConfig: BoardsConfig.Config = { + selectedBoard: aBoard, + selectedPort: aPort, +}; +export const anotherBoard: Board = { + fqbn: 'another:board:fqbn', + name: 'Another Arduino Board', + port: { address: '/kek/port5678', protocol: 'serial' }, +}; +export const anotherPort: Port = { + address: anotherBoard.port!.address, + protocol: anotherBoard.port!.protocol, +}; +export const anotherBoardConfig: BoardsConfig.Config = { + selectedBoard: anotherBoard, + selectedPort: anotherPort, +}; + +export const aPackage: BoardsPackage = { + author: 'someAuthor', + availableVersions: ['some.ver.sion', 'some.other.version'], + boards: [aBoard], + deprecated: false, + description: 'Some Arduino Board, Some Other Arduino Board', + id: 'some:arduinoCoreId', + installable: true, + moreInfoLink: 'http://www.some-url.lol/', + name: 'Some Arduino Package', + summary: 'Boards included in this package:', +}; + +export const anInstalledPackage: BoardsPackage = { + ...aPackage, + installedVersion: 'some.ver.sion', +}; diff --git a/arduino-ide-extension/src/test/browser/fixtures/serial.ts b/arduino-ide-extension/src/test/browser/fixtures/serial.ts new file mode 100644 index 000000000..85003d66d --- /dev/null +++ b/arduino-ide-extension/src/test/browser/fixtures/serial.ts @@ -0,0 +1,22 @@ +import { MonitorConfig } from '../../../common/protocol/monitor-service'; +import { aBoard, anotherBoard, anotherPort, aPort } from './boards'; + +export const aSerialConfig: MonitorConfig = { + board: aBoard, + port: aPort, + baudRate: 9600, +}; + +export const anotherSerialConfig: MonitorConfig = { + board: anotherBoard, + port: anotherPort, + baudRate: 9600, +}; + +export class WebSocketMock { + readonly url: string; + constructor(url: string) { + this.url = url; + } + close() {} +} diff --git a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts new file mode 100644 index 000000000..309a55067 --- /dev/null +++ b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts @@ -0,0 +1,375 @@ +import { enableJSDOM } from '@theia/core/lib/browser/test/jsdom'; +const disableJSDOM = enableJSDOM(); + +import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider'; +import { ApplicationProps } from '@theia/application-package/lib/application-props'; +FrontendApplicationConfigProvider.set({ + ...ApplicationProps.DEFAULT.frontend.config, +}); + +import { Emitter, MessageService } from '@theia/core'; +import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; +import { + BoardsService, + MonitorService, + MonitorServiceClient, + Status, +} from '../../common/protocol'; +import { IMock, It, Mock, Times } from 'typemoq'; +import { + Serial, + SerialConnectionManager, +} from '../../browser/monitor/monitor-connection'; +import { ThemeService } from '@theia/core/lib/browser/theming'; +import { MonitorModel } from '../../browser/monitor/monitor-model'; +import { NotificationCenter } from '../../browser/notification-center'; +import { + aBoardConfig, + anotherBoardConfig, + anotherPort, + aPort, +} from './fixtures/boards'; +import { BoardsConfig } from '../../browser/boards/boards-config'; +import { + anotherSerialConfig, + aSerialConfig, + WebSocketMock, +} from './fixtures/serial'; +import { expect } from 'chai'; +import { tick } from '../utils'; + +disableJSDOM(); + +global.WebSocket = WebSocketMock as any; + +describe.only('SerialConnectionManager', () => { + let subject: SerialConnectionManager; + + let monitorModel: IMock; + let monitorService: IMock; + let monitorServiceClient: IMock; + let boardsService: IMock; + let boardsServiceProvider: IMock; + let notificationCenter: IMock; + let messageService: IMock; + let themeService: IMock; + + let handleBoardConfigChange: ( + boardsConfig: BoardsConfig.Config + ) => Promise; + let handleWebSocketChanged: (wsPort: number) => void; + const wsPort = 1234; + + const onBoardsConfigChangedEmitter = new Emitter(); + console.log(onBoardsConfigChangedEmitter); + beforeEach(() => { + monitorModel = Mock.ofType(); + monitorService = Mock.ofType(); + monitorServiceClient = Mock.ofType(); + boardsService = Mock.ofType(); + boardsServiceProvider = Mock.ofType(); + notificationCenter = Mock.ofType(); + messageService = Mock.ofType(); + themeService = Mock.ofType(); + + boardsServiceProvider + .setup((b) => b.boardsConfig) + .returns(() => aBoardConfig); + + boardsServiceProvider + .setup((b) => b.onBoardsConfigChanged(It.isAny())) + .returns((h) => { + handleBoardConfigChange = h; + return { dispose: () => {} }; + }); + + boardsServiceProvider + .setup((b) => b.canUploadTo(It.isAny(), It.isValue({ silent: false }))) + .returns(() => true); + + boardsService + .setup((b) => b.getAvailablePorts()) + .returns(() => Promise.resolve([aPort, anotherPort])); + + monitorModel + .setup((m) => m.baudRate) + .returns(() => aSerialConfig.baudRate || 9600); + + monitorServiceClient + .setup((m) => m.onWebSocketChanged(It.isAny())) + .returns((h) => { + handleWebSocketChanged = h; + return { dispose: () => {} }; + }); + + monitorService + .setup((m) => m.disconnect()) + .returns(() => Promise.resolve(Status.OK)); + + subject = new SerialConnectionManager( + monitorModel.object, + monitorService.object, + monitorServiceClient.object, + boardsService.object, + boardsServiceProvider.object, + notificationCenter.object, + messageService.object, + themeService.object + ); + }); + + context('when no serial config is set', () => { + context('and the serial is NOT open', () => { + context('and it tries to open the serial plotter', () => { + it('should not try to connect and show an error', async () => { + await subject.openSerial(Serial.Type.Plotter); + messageService.verify((m) => m.error(It.isAnyString()), Times.once()); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify((m) => m.connect(It.isAny()), Times.never()); + }); + }); + context('and a serial config is set', () => { + it('should not try to reconnect', async () => { + await handleBoardConfigChange(aBoardConfig); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify((m) => m.connect(It.isAny()), Times.never()); + expect(subject.getConfig()).to.deep.equal(aSerialConfig); + }); + }); + }); + }); + context('when a serial config is set', () => { + beforeEach(() => { + subject.setConfig(aSerialConfig); + }); + context('and the serial is NOT open', () => { + context('and it tries to disconnect', () => { + it('should do nothing', async () => { + const status = await subject.disconnect(); + expect(status).to.be.ok; + expect(subject.connected).to.be.false; + }); + }); + context('and the config changes', () => { + beforeEach(() => { + subject.setConfig(anotherSerialConfig); + }); + it('should not try to reconnect', async () => { + await tick(); + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify((m) => m.connect(It.isAny()), Times.never()); + }); + }); + context( + 'and the connection to the serial succeeds with the config', + () => { + beforeEach(() => { + monitorService + .setup((m) => m.connect(It.isValue(aSerialConfig))) + .returns(() => { + handleWebSocketChanged(wsPort); + return Promise.resolve(Status.OK); + }); + }); + context('and it tries to open the serial plotter', () => { + let status: Status; + beforeEach(async () => { + status = await subject.openSerial(Serial.Type.Plotter); + }); + it('should successfully connect to the serial', async () => { + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify((m) => m.connect(It.isAny()), Times.once()); + expect(status).to.be.ok; + expect(subject.connected).to.be.true; + expect(subject.getWsPort()).to.equal(wsPort); + expect(subject.isSerialOpen()).to.be.true; + expect(subject.isWebSocketConnected()).to.be.false; + }); + context('and it tries to open the serial monitor', () => { + let status: Status; + beforeEach(async () => { + status = await subject.openSerial(Serial.Type.Monitor); + }); + it('should open it using the same serial connection', () => { + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify( + (m) => m.connect(It.isAny()), + Times.once() + ); + expect(status).to.be.ok; + expect(subject.connected).to.be.true; + expect(subject.isSerialOpen()).to.be.true; + }); + it('should create a websocket connection', () => { + expect(subject.getWsPort()).to.equal(wsPort); + expect(subject.isWebSocketConnected()).to.be.true; + }); + context('and then it closes the serial plotter', () => { + beforeEach(async () => { + status = await subject.closeSerial(Serial.Type.Plotter); + }); + it('should close the plotter without disconnecting from the serial', () => { + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify( + (m) => m.connect(It.isAny()), + Times.once() + ); + expect(status).to.be.ok; + expect(subject.connected).to.be.true; + expect(subject.isSerialOpen()).to.be.true; + expect(subject.getWsPort()).to.equal(wsPort); + }); + it('should not close the websocket connection', () => { + expect(subject.isWebSocketConnected()).to.be.true; + }); + }); + context('and then it closes the serial monitor', () => { + beforeEach(async () => { + status = await subject.closeSerial(Serial.Type.Monitor); + }); + it('should close the monitor without disconnecting from the serial', () => { + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify( + (m) => m.connect(It.isAny()), + Times.once() + ); + expect(status).to.be.ok; + expect(subject.connected).to.be.true; + expect(subject.getWsPort()).to.equal(wsPort); + expect(subject.isSerialOpen()).to.be.true; + }); + it('should close the websocket connection', () => { + expect(subject.isWebSocketConnected()).to.be.false; + }); + }); + }); + context('and then it closes the serial plotter', () => { + beforeEach(async () => { + status = await subject.closeSerial(Serial.Type.Plotter); + }); + it('should successfully disconnect from the serial', () => { + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.once()); + monitorService.verify( + (m) => m.connect(It.isAny()), + Times.once() + ); + expect(status).to.be.ok; + expect(subject.connected).to.be.false; + expect(subject.getWsPort()).to.be.undefined; + expect(subject.isSerialOpen()).to.be.false; + expect(subject.isWebSocketConnected()).to.be.false; + }); + }); + context('and the config changes', () => { + beforeEach(() => { + subject.setConfig(anotherSerialConfig); + }); + it('should try to reconnect', async () => { + await tick(); + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.once()); + monitorService.verify( + (m) => m.connect(It.isAny()), + Times.exactly(2) + ); + }); + }); + }); + } + ); + context( + 'and the connection to the serial does NOT succeed with the config', + () => { + beforeEach(() => { + monitorService + .setup((m) => m.connect(It.isValue(aSerialConfig))) + .returns(() => { + return Promise.resolve(Status.NOT_CONNECTED); + }); + monitorService + .setup((m) => m.connect(It.isValue(anotherSerialConfig))) + .returns(() => { + handleWebSocketChanged(wsPort); + return Promise.resolve(Status.OK); + }); + }); + context('and it tries to open the serial plotter', () => { + let status: Status; + beforeEach(async () => { + status = await subject.openSerial(Serial.Type.Plotter); + }); + + it('should fail to connect to the serial', async () => { + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify( + (m) => m.connect(It.isValue(aSerialConfig)), + Times.once() + ); + expect(status).to.be.false; + expect(subject.connected).to.be.false; + expect(subject.getWsPort()).to.be.undefined; + expect(subject.isSerialOpen()).to.be.true; + }); + + context( + 'and the board config changes with an acceptable one', + () => { + beforeEach(async () => { + await handleBoardConfigChange(anotherBoardConfig); + }); + + it('should successfully connect to the serial', async () => { + await tick(); + messageService.verify( + (m) => m.error(It.isAnyString()), + Times.never() + ); + monitorService.verify((m) => m.disconnect(), Times.never()); + monitorService.verify( + (m) => m.connect(It.isValue(anotherSerialConfig)), + Times.once() + ); + expect(subject.connected).to.be.true; + expect(subject.getWsPort()).to.equal(wsPort); + expect(subject.isSerialOpen()).to.be.true; + expect(subject.isWebSocketConnected()).to.be.false; + }); + } + ); + }); + } + ); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 2a4219268..9e40e34e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.3.tgz#ff7136676fa1cf23950751ea6a675372b3d1eb25" - integrity sha512-qGxok+UQsuOVk5aOPxGjcXorQYHBNPA5fUgwMt/EbusqOnwphMHDeC3wJ3K82bNwcjg1Yf2xNJI4bRLRv+tNxw== +arduino-serial-plotter-webapp@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.4.tgz#4cb41e4360dfe3f9c28e0956d764fcf0aaaa03a5" + integrity sha512-t1BEqr02zPBmSV/jwFAUNlKu6GPa4p0TU3x3E0uco27SCaRIKT4qwPXfUlMkVfCGZB3JoNngdpAr7J082wzxxA== are-we-there-yet@~1.1.2: version "1.1.5" From dbaf2446fbe8fe34a6f2ff425adf893f3a731ba0 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Wed, 10 Nov 2021 10:07:30 +0100 Subject: [PATCH 12/27] updated serial plotter webapp --- arduino-ide-extension/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 3f48df4be..ff706d287 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.4", + "arduino-serial-plotter-webapp": "0.0.5", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/yarn.lock b/yarn.lock index 9e40e34e4..db9253485 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.4.tgz#4cb41e4360dfe3f9c28e0956d764fcf0aaaa03a5" - integrity sha512-t1BEqr02zPBmSV/jwFAUNlKu6GPa4p0TU3x3E0uco27SCaRIKT4qwPXfUlMkVfCGZB3JoNngdpAr7J082wzxxA== +arduino-serial-plotter-webapp@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.5.tgz#aaf21557de52abe2392372c15aba7d75cc7c9a43" + integrity sha512-ZNVwM67e/Xz+4PKFkJyPruxsZXuTvmOTwDrq1hKSjZHbpdMytQIqR+rT9pY+mg4jqFAn2Nd79Ycbazm8zXgJsQ== are-we-there-yet@~1.1.2: version "1.1.5" From df9902d6b9a6154e01ef8901e69010033ac08c99 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 11 Nov 2021 10:30:32 +0100 Subject: [PATCH 13/27] updated serial plotter webapp to 0.0.6 --- arduino-ide-extension/package.json | 2 +- .../src/node/monitor/monitor-service-impl.ts | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index ff706d287..e19271db1 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.5", + "arduino-serial-plotter-webapp": "0.0.6", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index aedfa5de5..a35e2d09f 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -173,7 +173,7 @@ export class MonitorServiceImpl implements MonitorService { } catch (error) {} }); - // empty the queue every 16ms (~60fps) + // empty the queue every 32ms (~30fps) setInterval(flushMessagesToFrontend, 32); // converts 'ab\nc\nd' => [ab\n,c\n,d] diff --git a/yarn.lock b/yarn.lock index db9253485..fdc935cdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.5.tgz#aaf21557de52abe2392372c15aba7d75cc7c9a43" - integrity sha512-ZNVwM67e/Xz+4PKFkJyPruxsZXuTvmOTwDrq1hKSjZHbpdMytQIqR+rT9pY+mg4jqFAn2Nd79Ycbazm8zXgJsQ== +arduino-serial-plotter-webapp@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.6.tgz#29f594efb0f4d1ac73ccdad9151f0a57e1e30c1e" + integrity sha512-rl4zot5in3V6KKhTQY+l3X/CWHL7DOPcAygtYNpnzhXMaXu3zxG1MWRnyZznuKZHa1RwajXlURXfYQDyBLlePw== are-we-there-yet@~1.1.2: version "1.1.5" From 31fde3e21399b0b4ae66d181b69dca05ac1c6807 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 11 Nov 2021 15:09:58 +0100 Subject: [PATCH 14/27] handle interpolate message --- .../browser/arduino-ide-frontend-module.ts | 9 -------- .../src/browser/monitor/monitor-connection.ts | 17 ++++++++++++--- .../src/browser/monitor/monitor-model.ts | 19 +++++++++++++++++ .../monitor/monitor-service-client-impl.ts | 21 +++++++++++++------ .../plotter/plotter-frontend-contribution.ts | 16 +++++++++----- .../src/browser/plotter/protocol.ts | 4 ++++ .../src/common/protocol/monitor-service.ts | 2 ++ .../src/common/protocol/plotter-service.ts | 16 -------------- .../src/node/monitor/monitor-service-impl.ts | 4 ++++ 9 files changed, 69 insertions(+), 39 deletions(-) delete mode 100644 arduino-ide-extension/src/common/protocol/plotter-service.ts diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index 11ef3ee74..a247b4d51 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -254,10 +254,6 @@ import { UploadCertificateDialogWidget, } from './dialogs/certificate-uploader/certificate-uploader-dialog'; import { PlotterFrontendContribution } from './plotter/plotter-frontend-contribution'; -import { - PlotterPath, - PlotterService, -} from '../common/protocol/plotter-service'; import { nls } from '@theia/core/lib/browser/nls'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -743,9 +739,4 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(UploadCertificateDialogProps).toConstantValue({ title: 'UploadCertificate', }); - bind(PlotterService) - .toDynamicValue((ctx) => - ctx.container.get(WebSocketConnectionProvider).createProxy(PlotterPath) - ) - .inSingletonScope(); }); diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 3088eb584..6012a8658 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -83,6 +83,11 @@ export class SerialConnectionManager { this.monitorModel.lineEnding = lineending; } }); + this.monitorServiceClient.onInterpolateChanged((interpolate) => { + if (this.monitorModel.interpolate !== interpolate) { + this.monitorModel.interpolate = interpolate; + } + }); this.monitorServiceClient.onError(this.handleError.bind(this)); this.boardsServiceProvider.onBoardsConfigChanged( @@ -100,12 +105,12 @@ export class SerialConnectionManager { // update the current values in the backend and propagate to websocket clients this.monitorService.updateWsConfigParam({ - ...(property === 'baudRate' && { - currentBaudrate: this.monitorModel.baudRate, - }), ...(property === 'lineEnding' && { currentLineEnding: this.monitorModel.lineEnding, }), + ...(property === 'interpolate' && { + interpolate: this.monitorModel.interpolate, + }), }); }); @@ -131,6 +136,10 @@ export class SerialConnectionManager { } }); if (configHasChanged && this.isSerialOpen()) { + this.monitorService.updateWsConfigParam({ + currentBaudrate: this.config.baudRate, + serialPort: this.config.port?.address, + }); this.disconnect().then(() => this.connect()); } } @@ -212,6 +221,8 @@ export class SerialConnectionManager { set connected(c: boolean) { this._connected = c; + this.monitorService.updateWsConfigParam({ connected: c }); + this.onConnectionChangedEmitter.fire(this._connected); } /** diff --git a/arduino-ide-extension/src/browser/monitor/monitor-model.ts b/arduino-ide-extension/src/browser/monitor/monitor-model.ts index ba3860725..621018750 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-model.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-model.ts @@ -24,12 +24,14 @@ export class MonitorModel implements FrontendApplicationContribution { protected _timestamp: boolean; protected _baudRate: MonitorConfig.BaudRate; protected _lineEnding: MonitorModel.EOL; + protected _interpolate: boolean; constructor() { this._autoscroll = true; this._timestamp = false; this._baudRate = MonitorConfig.BaudRate.DEFAULT; this._lineEnding = MonitorModel.EOL.DEFAULT; + this._interpolate = false; this.onChangeEmitter = new Emitter< MonitorModel.State.Change >(); @@ -106,11 +108,26 @@ export class MonitorModel implements FrontendApplicationContribution { ); } + get interpolate(): boolean { + return this._interpolate; + } + + set interpolate(i: boolean) { + this._interpolate = i; + this.storeState().then(() => + this.onChangeEmitter.fire({ + property: 'interpolate', + value: this._interpolate, + }) + ); + } + protected restoreState(state: MonitorModel.State): void { this._autoscroll = state.autoscroll; this._timestamp = state.timestamp; this._baudRate = state.baudRate; this._lineEnding = state.lineEnding; + this._interpolate = state.interpolate; } protected async storeState(): Promise { @@ -119,6 +136,7 @@ export class MonitorModel implements FrontendApplicationContribution { timestamp: this._timestamp, baudRate: this._baudRate, lineEnding: this._lineEnding, + interpolate: this._interpolate, }); } } @@ -129,6 +147,7 @@ export namespace MonitorModel { timestamp: boolean; baudRate: MonitorConfig.BaudRate; lineEnding: EOL; + interpolate: boolean; } export namespace State { export interface Change { diff --git a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts index 16a5323e8..d223c77ee 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts @@ -15,11 +15,16 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { protected readonly onWebSocketChangedEmitter = new Emitter(); readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; - protected readonly onBaudEmitter = new Emitter(); - readonly onBaudRateChanged = this.onBaudEmitter.event; + protected readonly onBaudRateChangedEmitter = + new Emitter(); + readonly onBaudRateChanged = this.onBaudRateChangedEmitter.event; - protected readonly onEolEmitter = new Emitter(); - readonly onLineEndingChanged = this.onEolEmitter.event; + protected readonly onLineEndingChangedEmitter = + new Emitter(); + readonly onLineEndingChanged = this.onLineEndingChangedEmitter.event; + + protected readonly onInterpolateChangedEmitter = new Emitter(); + readonly onInterpolateChanged = this.onInterpolateChangedEmitter.event; notifyError(error: MonitorError): void { this.onErrorEmitter.fire(error); @@ -30,10 +35,14 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { } notifyBaudRateChanged(message: MonitorConfig.BaudRate): void { - this.onBaudEmitter.fire(message); + this.onBaudRateChangedEmitter.fire(message); } notifyLineEndingChanged(message: MonitorModel.EOL): void { - this.onEolEmitter.fire(message); + this.onLineEndingChangedEmitter.fire(message); + } + + notifyInterpolateChanged(message: boolean): void { + this.onInterpolateChangedEmitter.fire(message); } } diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index afc99a220..59b050bbf 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -3,7 +3,6 @@ import { injectable, inject } from 'inversify'; import { Command, CommandRegistry, - DisposableCollection, MaybePromise, MenuModelRegistry, } from '@theia/core'; @@ -33,7 +32,6 @@ export class PlotterFrontendContribution extends Contribution { protected window: Window | null; protected url: string; protected wsPort: number; - protected toDispose = new DisposableCollection(); @inject(MonitorModel) protected readonly model: MonitorModel; @@ -51,12 +49,17 @@ export class PlotterFrontendContribution extends Contribution { this.url = new Endpoint({ path: '/plotter' }).getRestUrl().toString(); ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { - if (this.window) { + if (!!this.window) { this.window = null; await this.monitorConnection.closeSerial(Serial.Type.Plotter); } }); + this.monitorConnection.onConnectionChanged((connected) => { + if (!!this.window) { + } + }); + return super.onStart(app); } @@ -76,7 +79,7 @@ export class PlotterFrontendContribution extends Contribution { async connect(): Promise { if (this.monitorConnection.connected) { - if (this.window) { + if (!!this.window) { this.window.focus(); return; } @@ -91,12 +94,15 @@ export class PlotterFrontendContribution extends Contribution { } protected open(wsPort: number): void { - const initConfig: SerialPlotter.Config = { + const initConfig: Partial = { baudrates: MonitorConfig.BaudRates.map((b) => b), currentBaudrate: this.model.baudRate, currentLineEnding: this.model.lineEnding, darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, + interpolate: this.model.interpolate, + connected: this.monitorConnection.connected, + serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address, }; const urlWithParams = queryString.stringifyUrl( { diff --git a/arduino-ide-extension/src/browser/plotter/protocol.ts b/arduino-ide-extension/src/browser/plotter/protocol.ts index 5ae2a2292..c38c9fcb9 100644 --- a/arduino-ide-extension/src/browser/plotter/protocol.ts +++ b/arduino-ide-extension/src/browser/plotter/protocol.ts @@ -5,12 +5,16 @@ export namespace SerialPlotter { currentLineEnding: string; darkTheme: boolean; wsPort: number; + interpolate: boolean; + serialPort: string; + connected: boolean; generate?: boolean; }; export namespace Protocol { export enum Command { PLOTTER_SET_BAUDRATE = 'PLOTTER_SET_BAUDRATE', PLOTTER_SET_LINE_ENDING = 'PLOTTER_SET_LINE_ENDING', + PLOTTER_SET_INTERPOLATE = 'PLOTTER_SET_INTERPOLATE', PLOTTER_SEND_MESSAGE = 'PLOTTER_SEND_MESSAGE', MIDDLEWARE_CONFIG_CHANGED = 'MIDDLEWARE_CONFIG_CHANGED', } diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/monitor-service.ts index 266562cd5..6d427bc60 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/monitor-service.ts @@ -61,10 +61,12 @@ export interface MonitorServiceClient { onWebSocketChanged: Event; onLineEndingChanged: Event; onBaudRateChanged: Event; + onInterpolateChanged: Event; notifyError(event: MonitorError): void; notifyWebSocketChanged(message: number): void; notifyLineEndingChanged(message: MonitorModel.EOL): void; notifyBaudRateChanged(message: MonitorConfig.BaudRate): void; + notifyInterpolateChanged(message: boolean): void; } export interface MonitorError { diff --git a/arduino-ide-extension/src/common/protocol/plotter-service.ts b/arduino-ide-extension/src/common/protocol/plotter-service.ts deleted file mode 100644 index 4233f726c..000000000 --- a/arduino-ide-extension/src/common/protocol/plotter-service.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const PlotterPath = '/services/plotter-service'; -export const PlotterService = Symbol('PlotterService'); - -export interface PlotterService { - start(config: PlotterConfig | undefined): Promise; - stop(): Promise; - setOptions(config: PlotterConfig): Promise; -} - -export interface PlotterConfig { - currentBaudrate: number; - baudrates: number[]; - darkTheme: boolean; - wsPort: number; - generate?: boolean; -} diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index a35e2d09f..0a42bbdc5 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -167,6 +167,10 @@ export class MonitorServiceImpl implements MonitorService { this.client?.notifyLineEndingChanged(message.data); break; + case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE: + this.client?.notifyInterpolateChanged(message.data); + break; + default: break; } From f96a04a4803c0a25f171cde40ce370bfdcb2a933 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Thu, 11 Nov 2021 17:38:07 +0100 Subject: [PATCH 15/27] fix reconnecting issues --- .../src/browser/monitor/monitor-connection.ts | 52 +++---------------- .../browser/serial-connection-manager.test.ts | 4 -- 2 files changed, 6 insertions(+), 50 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index 6012a8658..d961178d1 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -14,11 +14,9 @@ import { Port, Board, BoardsService, - AttachedBoardsChangeEvent, } from '../../common/protocol/boards-service'; import { BoardsConfig } from '../boards/boards-config'; import { MonitorModel } from './monitor-model'; -import { NotificationCenter } from '../notification-center'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { nls } from '@theia/core/lib/browser/nls'; @@ -65,8 +63,6 @@ export class SerialConnectionManager { @inject(BoardsService) protected readonly boardsService: BoardsService, @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider, - @inject(NotificationCenter) - protected readonly notificationCenter: NotificationCenter, @inject(MessageService) protected messageService: MessageService, @inject(ThemeService) protected readonly themeService: ThemeService ) { @@ -93,9 +89,7 @@ export class SerialConnectionManager { this.boardsServiceProvider.onBoardsConfigChanged( this.handleBoardConfigChange.bind(this) ); - this.notificationCenter.onAttachedBoardsChanged( - this.handleAttachedBoardsChanged.bind(this) - ); + // Handles the `baudRate` changes by reconnecting if required. this.monitorModel.onChange(({ property }) => { if (property === 'baudRate' && this.connected) { @@ -130,7 +124,7 @@ export class SerialConnectionManager { setConfig(newConfig: Partial): void { let configHasChanged = false; Object.keys(this.config).forEach((key: keyof MonitorConfig) => { - if (newConfig[key] && newConfig[key] !== this.config[key]) { + if (newConfig[key] !== this.config[key]) { configHasChanged = true; this.config = { ...this.config, [key]: newConfig[key] }; } @@ -222,7 +216,6 @@ export class SerialConnectionManager { set connected(c: boolean) { this._connected = c; this.monitorService.updateWsConfigParam({ connected: c }); - this.onConnectionChangedEmitter.fire(this._connected); } /** @@ -365,28 +358,6 @@ export class SerialConnectionManager { } } - handleAttachedBoardsChanged(event: AttachedBoardsChangeEvent): void { - const { boardsConfig } = this.boardsServiceProvider; - if ( - this.boardsServiceProvider.canUploadTo(boardsConfig, { - silent: false, - }) - ) { - const { attached } = AttachedBoardsChangeEvent.diff(event); - if ( - attached.boards.some( - (board) => - !!board.port && BoardsConfig.Config.sameAs(boardsConfig, board) - ) - ) { - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.monitorModel; - const newConfig = { board, port, baudRate }; - this.setConfig(newConfig); - } - } - } - async connect(): Promise { if (this.connected) return Status.ALREADY_CONNECTED; if (!isMonitorConfig(this.config)) { @@ -467,21 +438,10 @@ export class SerialConnectionManager { protected async handleBoardConfigChange( boardsConfig: BoardsConfig.Config ): Promise { - if ( - this.boardsServiceProvider.canUploadTo(boardsConfig, { - silent: false, - }) - ) { - // Instead of calling `getAttachedBoards` and filtering for `AttachedSerialBoard` we have to check the available ports. - // The connected board might be unknown. See: https://github.com/arduino/arduino-pro-ide/issues/127#issuecomment-563251881 - const ports = await this.boardsService.getAvailablePorts(); - if (ports.some((port) => Port.equals(port, boardsConfig.selectedPort))) { - const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.monitorModel; - const newConfig: MonitorConfig = { board, port, baudRate }; - this.setConfig(newConfig); - } - } + const { selectedBoard: board, selectedPort: port } = boardsConfig; + const { baudRate } = this.monitorModel; + const newConfig: Partial = { board, port, baudRate }; + this.setConfig(newConfig); } } diff --git a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts index 309a55067..af2ffe4cc 100644 --- a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts +++ b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts @@ -22,7 +22,6 @@ import { } from '../../browser/monitor/monitor-connection'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { MonitorModel } from '../../browser/monitor/monitor-model'; -import { NotificationCenter } from '../../browser/notification-center'; import { aBoardConfig, anotherBoardConfig, @@ -50,7 +49,6 @@ describe.only('SerialConnectionManager', () => { let monitorServiceClient: IMock; let boardsService: IMock; let boardsServiceProvider: IMock; - let notificationCenter: IMock; let messageService: IMock; let themeService: IMock; @@ -68,7 +66,6 @@ describe.only('SerialConnectionManager', () => { monitorServiceClient = Mock.ofType(); boardsService = Mock.ofType(); boardsServiceProvider = Mock.ofType(); - notificationCenter = Mock.ofType(); messageService = Mock.ofType(); themeService = Mock.ofType(); @@ -112,7 +109,6 @@ describe.only('SerialConnectionManager', () => { monitorServiceClient.object, boardsService.object, boardsServiceProvider.object, - notificationCenter.object, messageService.object, themeService.object ); From 505286202a579d7da3a032cdc9aa4963bd67d4bf Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Thu, 11 Nov 2021 22:53:45 +0100 Subject: [PATCH 16/27] updated plotter to 0.0.8 --- arduino-ide-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index e19271db1..15e905a93 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.6", + "arduino-serial-plotter-webapp": "0.0.8", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", From ee607afe1d33aee0ce4b42f6862c6faf38a7ae9a Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Mon, 15 Nov 2021 13:11:07 +0100 Subject: [PATCH 17/27] bump arduino-serial-plotter-webapp to 0.0.10 --- arduino-ide-extension/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 15e905a93..8a7670e57 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.8", + "arduino-serial-plotter-webapp": "0.0.10", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/yarn.lock b/yarn.lock index fdc935cdd..1a66dfada 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.6.tgz#29f594efb0f4d1ac73ccdad9151f0a57e1e30c1e" - integrity sha512-rl4zot5in3V6KKhTQY+l3X/CWHL7DOPcAygtYNpnzhXMaXu3zxG1MWRnyZznuKZHa1RwajXlURXfYQDyBLlePw== +arduino-serial-plotter-webapp@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.10.tgz#1379e91705a0462ae31badffabaea8563ef59a51" + integrity sha512-FqFPk2k8iPrMr2vuZfkEbxhbWPy7zn5a9+PMZp2C4akmjdWD6V2ZO89rDOs8ODr0IZgEJxyKpGG212css5vS6g== are-we-there-yet@~1.1.2: version "1.1.5" From f4938b5163abebeec8de02714e33f2e9825b4dd7 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 16 Nov 2021 12:50:56 +0100 Subject: [PATCH 18/27] bump arduino-serial-plotter-webapp to 0.0.11 --- arduino-ide-extension/package.json | 2 +- .../src/electron-main/theia/electron-main-application.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 8a7670e57..df510fa86 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.10", + "arduino-serial-plotter-webapp": "0.0.11", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 41940a16d..07a44a54f 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -97,6 +97,13 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { if (frameName === 'serialPlotter') { event.preventDefault(); Object.assign(options, { + width: 800, + minWidth: 620, + height: 500, + minHeight: 320, + center: true, + x: 100, + y: 100, webPreferences: { devTools: true, nativeWindowOpen: true, From e17960b333331d0d863ebcebd84f574c348fe2ee Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 16 Nov 2021 15:59:45 +0100 Subject: [PATCH 19/27] fix upload when serial port is open --- .../src/browser/monitor/monitor-connection.ts | 43 ++++++++++++------- .../src/browser/monitor/monitor-utils.ts | 1 + .../plotter/plotter-frontend-contribution.ts | 14 ++---- .../src/common/protocol/core-service.ts | 1 + .../src/node/core-service-impl.ts | 9 ++++ .../browser/serial-connection-manager.test.ts | 8 +++- yarn.lock | 8 ++-- 7 files changed, 53 insertions(+), 31 deletions(-) diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts index d961178d1..5f50ee877 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-connection.ts @@ -19,6 +19,7 @@ import { BoardsConfig } from '../boards/boards-config'; import { MonitorModel } from './monitor-model'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { nls } from '@theia/core/lib/browser/nls'; +import { CoreService } from '../../common/protocol'; @injectable() export class SerialConnectionManager { @@ -64,7 +65,8 @@ export class SerialConnectionManager { @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider, @inject(MessageService) protected messageService: MessageService, - @inject(ThemeService) protected readonly themeService: ThemeService + @inject(ThemeService) protected readonly themeService: ThemeService, + @inject(CoreService) protected readonly core: CoreService ) { this.monitorServiceClient.onWebSocketChanged( this.handleWebSocketChanged.bind(this) @@ -121,7 +123,7 @@ export class SerialConnectionManager { * * @param newConfig the porperties of the config that has changed */ - setConfig(newConfig: Partial): void { + async setConfig(newConfig: Partial): Promise { let configHasChanged = false; Object.keys(this.config).forEach((key: keyof MonitorConfig) => { if (newConfig[key] !== this.config[key]) { @@ -129,12 +131,17 @@ export class SerialConnectionManager { this.config = { ...this.config, [key]: newConfig[key] }; } }); - if (configHasChanged && this.isSerialOpen()) { + if ( + configHasChanged && + this.isSerialOpen() && + !(await this.core.isUploading()) + ) { this.monitorService.updateWsConfigParam({ currentBaudrate: this.config.baudRate, serialPort: this.config.port?.address, }); - this.disconnect().then(() => this.connect()); + await this.disconnect(); + await this.connect(); } } @@ -176,22 +183,25 @@ export class SerialConnectionManager { /** * Sets the types of connections needed by the client. * - * @param s The new types of connections (can be 'Monitor', 'Plotter', none or both). + * @param newState The new types of connections (can be 'Monitor', 'Plotter', none or both). * If the previuos state was empty and 's' is not, it tries to reconnect to the serial service * If the provios state was NOT empty and now it is, it disconnects to the serial service * @returns The status of the operation */ - protected async setState(s: Serial.State): Promise { + protected async setState(newState: Serial.State): Promise { const oldState = deepClone(this._state); - this._state = s; let status = Status.OK; - if (this.isSerialOpen(oldState) && !this.isSerialOpen()) { + if (this.isSerialOpen(oldState) && !this.isSerialOpen(newState)) { status = await this.disconnect(); - } else if (!this.isSerialOpen(oldState) && this.isSerialOpen()) { + } else if (!this.isSerialOpen(oldState) && this.isSerialOpen(newState)) { + if (await this.core.isUploading()) { + this.messageService.error(`Cannot open serial port when uploading`); + return Status.NOT_CONNECTED; + } status = await this.connect(); } - + this._state = newState; return status; } @@ -226,6 +236,12 @@ export class SerialConnectionManager { * @returns the status of the operation */ async openSerial(type: Serial.Type): Promise { + if (!isMonitorConfig(this.config)) { + this.messageService.error( + `Please select a board and a port to open the serial connection.` + ); + return Status.NOT_CONNECTED; + } if (this.state.includes(type)) return Status.OK; const newState = deepClone(this.state); newState.push(type); @@ -360,12 +376,7 @@ export class SerialConnectionManager { async connect(): Promise { if (this.connected) return Status.ALREADY_CONNECTED; - if (!isMonitorConfig(this.config)) { - this.messageService.error( - `Please select a board and a port to open the serial connection.` - ); - return Status.NOT_CONNECTED; - } + if (!isMonitorConfig(this.config)) return Status.NOT_CONNECTED; console.info( `>>> Creating serial connection for ${Board.toString( diff --git a/arduino-ide-extension/src/browser/monitor/monitor-utils.ts b/arduino-ide-extension/src/browser/monitor/monitor-utils.ts index 586eea146..41cb4f450 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-utils.ts +++ b/arduino-ide-extension/src/browser/monitor/monitor-utils.ts @@ -9,6 +9,7 @@ export function messagesToLines( const linesToAdd: Line[] = prevLines.length ? [prevLines[prevLines.length - 1]] : [{ message: '', lineLen: 0 }]; + if (!(Symbol.iterator in Object(messages))) return [prevLines, charCount]; for (const message of messages) { const messageLen = message.length; diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts index 59b050bbf..e803ca638 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts @@ -55,11 +55,6 @@ export class PlotterFrontendContribution extends Contribution { } }); - this.monitorConnection.onConnectionChanged((connected) => { - if (!!this.window) { - } - }); - return super.onStart(app); } @@ -78,17 +73,16 @@ export class PlotterFrontendContribution extends Contribution { } async connect(): Promise { - if (this.monitorConnection.connected) { - if (!!this.window) { - this.window.focus(); - return; - } + if (!!this.window) { + this.window.focus(); + return; } const status = await this.monitorConnection.openSerial(Serial.Type.Plotter); const wsPort = this.monitorConnection.getWsPort(); if (Status.isOK(status) && wsPort) { this.open(wsPort); } else { + this.monitorConnection.closeSerial(Serial.Type.Plotter); this.messageService.error(`Couldn't open serial plotter`); } } diff --git a/arduino-ide-extension/src/common/protocol/core-service.ts b/arduino-ide-extension/src/common/protocol/core-service.ts index 1454ffbd2..7e252bc2a 100644 --- a/arduino-ide-extension/src/common/protocol/core-service.ts +++ b/arduino-ide-extension/src/common/protocol/core-service.ts @@ -22,6 +22,7 @@ export interface CoreService { upload(options: CoreService.Upload.Options): Promise; uploadUsingProgrammer(options: CoreService.Upload.Options): Promise; burnBootloader(options: CoreService.Bootloader.Options): Promise; + isUploading(): Promise; } export namespace CoreService { diff --git a/arduino-ide-extension/src/node/core-service-impl.ts b/arduino-ide-extension/src/node/core-service-impl.ts index e1a76eea1..f5ebe270c 100644 --- a/arduino-ide-extension/src/node/core-service-impl.ts +++ b/arduino-ide-extension/src/node/core-service-impl.ts @@ -32,6 +32,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { @inject(NotificationServiceServer) protected readonly notificationService: NotificationServiceServer; + protected uploading = false; + async compile( options: CoreService.Compile.Options & { exportBinaries?: boolean; @@ -110,6 +112,10 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { ); } + isUploading(): Promise { + return Promise.resolve(this.uploading); + } + protected async doUpload( options: CoreService.Upload.Options, requestProvider: () => UploadRequest | UploadUsingProgrammerRequest, @@ -120,6 +126,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { ) => ClientReadableStream, task = 'upload' ): Promise { + this.uploading = true; await this.compile(Object.assign(options, { exportBinaries: false })); const { sketchUri, fqbn, port, programmer } = options; const sketchPath = FileUri.fsPath(sketchUri); @@ -173,6 +180,8 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService { severity: 'error', }); throw e; + } finally { + this.uploading = false; } } diff --git a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts index af2ffe4cc..9a5b43e74 100644 --- a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts +++ b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts @@ -11,6 +11,7 @@ import { Emitter, MessageService } from '@theia/core'; import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; import { BoardsService, + CoreService, MonitorService, MonitorServiceClient, Status, @@ -51,6 +52,7 @@ describe.only('SerialConnectionManager', () => { let boardsServiceProvider: IMock; let messageService: IMock; let themeService: IMock; + let core: IMock; let handleBoardConfigChange: ( boardsConfig: BoardsConfig.Config @@ -68,6 +70,7 @@ describe.only('SerialConnectionManager', () => { boardsServiceProvider = Mock.ofType(); messageService = Mock.ofType(); themeService = Mock.ofType(); + core = Mock.ofType(); boardsServiceProvider .setup((b) => b.boardsConfig) @@ -103,6 +106,8 @@ describe.only('SerialConnectionManager', () => { .setup((m) => m.disconnect()) .returns(() => Promise.resolve(Status.OK)); + core.setup((u) => u.isUploading()).returns(() => Promise.resolve(false)); + subject = new SerialConnectionManager( monitorModel.object, monitorService.object, @@ -110,7 +115,8 @@ describe.only('SerialConnectionManager', () => { boardsService.object, boardsServiceProvider.object, messageService.object, - themeService.object + themeService.object, + core.object ); }); diff --git a/yarn.lock b/yarn.lock index 1a66dfada..1f8b222e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.10.tgz#1379e91705a0462ae31badffabaea8563ef59a51" - integrity sha512-FqFPk2k8iPrMr2vuZfkEbxhbWPy7zn5a9+PMZp2C4akmjdWD6V2ZO89rDOs8ODr0IZgEJxyKpGG212css5vS6g== +arduino-serial-plotter-webapp@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.11.tgz#a4197f8b2d005ba782bef57ce1aa9b73398ef8a3" + integrity sha512-WcPsCC0S6GefRVdsvFlng4+6ohjiIcTfolr/39zDMV3OjoRPOCqBTEEiYtrymWX9ujaaas1m6aFhTGDjisZwmQ== are-we-there-yet@~1.1.2: version "1.1.5" From 1f0d4bf61013773458e4ffe7a9917ff27516f953 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 16 Nov 2021 17:49:33 +0100 Subject: [PATCH 20/27] remove menu bar on windows/linux --- .../src/electron-main/theia/electron-main-application.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 07a44a54f..98023ca43 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -111,6 +111,7 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { }, }); event.newGuest = new BrowserWindow(options); + event.newGuest.setMenu(null); event.newGuest?.on('closed', (e: any) => { electronWindow?.webContents.send('CLOSE_CHILD_WINDOW'); }); From 89e7bab7901b137d047a0c23917fa262bbdf641e Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Wed, 17 Nov 2021 16:48:32 +0100 Subject: [PATCH 21/27] dispose send message event listener on disconnect --- .../src/node/monitor/monitor-service-impl.ts | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index 0a42bbdc5..08a2fe303 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -2,7 +2,6 @@ import { ClientDuplexStream } from '@grpc/grpc-js'; import { TextEncoder } from 'util'; import { injectable, inject, named } from 'inversify'; import { Struct } from 'google-protobuf/google/protobuf/struct_pb'; -import { Emitter } from '@theia/core/lib/common/event'; import { ILogger } from '@theia/core/lib/common/logger'; import { MonitorService, @@ -20,6 +19,7 @@ import { MonitorClientProvider } from './monitor-client-provider'; import { Board, Port } from '../../common/protocol/boards-service'; import { WebSocketService } from '../web-socket/web-socket-service'; import { SerialPlotter } from '../../browser/plotter/protocol'; +import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; interface ErrorWithCode extends Error { readonly code: number; @@ -77,7 +77,7 @@ export class MonitorServiceImpl implements MonitorService { config: MonitorConfig; }; protected messages: string[] = []; - protected onMessageDidReadEmitter = new Emitter(); + protected onMessageReceived: Disposable; setClient(client: MonitorServiceClient | undefined): void { this.client = client; @@ -148,34 +148,36 @@ export class MonitorServiceImpl implements MonitorService { } }; - this.webSocketService.onMessageReceived((msg: string) => { - try { - const message: SerialPlotter.Protocol.Message = JSON.parse(msg); + this.onMessageReceived = this.webSocketService.onMessageReceived( + (msg: string) => { + try { + const message: SerialPlotter.Protocol.Message = JSON.parse(msg); - switch (message.command) { - case SerialPlotter.Protocol.Command.PLOTTER_SEND_MESSAGE: - this.sendMessageToSerial(message.data); - break; + switch (message.command) { + case SerialPlotter.Protocol.Command.PLOTTER_SEND_MESSAGE: + this.sendMessageToSerial(message.data); + break; - case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: - this.client?.notifyBaudRateChanged( - parseInt(message.data, 10) as MonitorConfig.BaudRate - ); - break; + case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: + this.client?.notifyBaudRateChanged( + parseInt(message.data, 10) as MonitorConfig.BaudRate + ); + break; - case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: - this.client?.notifyLineEndingChanged(message.data); - break; + case SerialPlotter.Protocol.Command.PLOTTER_SET_LINE_ENDING: + this.client?.notifyLineEndingChanged(message.data); + break; - case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE: - this.client?.notifyInterpolateChanged(message.data); - break; + case SerialPlotter.Protocol.Command.PLOTTER_SET_INTERPOLATE: + this.client?.notifyInterpolateChanged(message.data); + break; - default: - break; - } - } catch (error) {} - }); + default: + break; + } + } catch (error) {} + } + ); // empty the queue every 32ms (~30fps) setInterval(flushMessagesToFrontend, 32); @@ -245,6 +247,7 @@ export class MonitorServiceImpl implements MonitorService { async disconnect(reason?: MonitorError): Promise { try { + this.onMessageReceived.dispose(); if ( !this.serialConnection && reason && From ad1cb25b5ed180e046d1287530d764b494970630 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Mon, 22 Nov 2021 17:09:11 +0100 Subject: [PATCH 22/27] update serial plotter app to 0.0.13 --- arduino-ide-extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index df510fa86..09da0291a 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.11", + "arduino-serial-plotter-webapp": "0.0.13", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", From f333bbdae4c93ab7d2c1a5d61776ebd039a9be23 Mon Sep 17 00:00:00 2001 From: Francesco Stasi Date: Mon, 22 Nov 2021 23:37:48 +0100 Subject: [PATCH 23/27] i18n generation --- i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en.json b/i18n/en.json index bbd23fd7f..cc49fc153 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -257,7 +257,7 @@ "connectionBusy": "Connection failed. Serial port is busy: {0}", "disconnected": "Disconnected {0} from {1}.", "unexpectedError": "Unexpected error. Reconnecting {0} on port {1}.", - "failedReconnect": "Failed to reconnect {0} to the the serial-monitor after 10 consecutive attempts. The {1} serial port is busy.", + "failedReconnect": "Failed to reconnect {0} to serial after 10 consecutive attempts. The {1} serial port is busy.", "reconnect": "Reconnecting {0} to {1} in {2] seconds...", "toggleTimestamp": "Toggle Timestamp", "autoscroll": "Autoscroll", From 03eb1b37e50df7ba4d3fae43fc36c97a56cca784 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 23 Nov 2021 09:38:36 +0100 Subject: [PATCH 24/27] clear flush message interval when disconnecting --- .../theia/electron-main-application.ts | 1 - .../src/node/monitor/monitor-service-impl.ts | 15 ++++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts index 98023ca43..80c531da0 100644 --- a/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts +++ b/arduino-ide-extension/src/electron-main/theia/electron-main-application.ts @@ -101,7 +101,6 @@ export class ElectronMainApplication extends TheiaElectronMainApplication { minWidth: 620, height: 500, minHeight: 320, - center: true, x: 100, y: 100, webPreferences: { diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts index 08a2fe303..68aa66da4 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts @@ -77,7 +77,8 @@ export class MonitorServiceImpl implements MonitorService { config: MonitorConfig; }; protected messages: string[] = []; - protected onMessageReceived: Disposable; + protected onMessageReceived: Disposable | null; + protected flushMessagesInterval: NodeJS.Timeout | null; setClient(client: MonitorServiceClient | undefined): void { this.client = client; @@ -180,7 +181,7 @@ export class MonitorServiceImpl implements MonitorService { ); // empty the queue every 32ms (~30fps) - setInterval(flushMessagesToFrontend, 32); + this.flushMessagesInterval = setInterval(flushMessagesToFrontend, 32); // converts 'ab\nc\nd' => [ab\n,c\n,d] const stringToArray = (string: string, separator = '\n') => { @@ -247,7 +248,15 @@ export class MonitorServiceImpl implements MonitorService { async disconnect(reason?: MonitorError): Promise { try { - this.onMessageReceived.dispose(); + if (this.onMessageReceived) { + this.onMessageReceived.dispose(); + this.onMessageReceived = null; + } + if (this.flushMessagesInterval) { + clearInterval(this.flushMessagesInterval); + this.flushMessagesInterval = null; + } + if ( !this.serialConnection && reason && From 90f9afe515aa665325635663a94088926c95f556 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 23 Nov 2021 12:37:26 +0100 Subject: [PATCH 25/27] bump arduino-serial-plotter-webapp to 0.0.15 --- arduino-ide-extension/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/arduino-ide-extension/package.json b/arduino-ide-extension/package.json index 09da0291a..ecd248e00 100644 --- a/arduino-ide-extension/package.json +++ b/arduino-ide-extension/package.json @@ -19,7 +19,7 @@ "test:watch": "mocha --watch --watch-files lib \"./lib/test/**/*.test.js\"" }, "dependencies": { - "arduino-serial-plotter-webapp": "0.0.13", + "arduino-serial-plotter-webapp": "0.0.15", "@grpc/grpc-js": "^1.3.7", "@theia/application-package": "1.18.0", "@theia/core": "1.18.0", diff --git a/yarn.lock b/yarn.lock index 1f8b222e3..dee18a725 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,10 +4109,10 @@ archive-type@^4.0.0: dependencies: file-type "^4.2.0" -arduino-serial-plotter-webapp@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.11.tgz#a4197f8b2d005ba782bef57ce1aa9b73398ef8a3" - integrity sha512-WcPsCC0S6GefRVdsvFlng4+6ohjiIcTfolr/39zDMV3OjoRPOCqBTEEiYtrymWX9ujaaas1m6aFhTGDjisZwmQ== +arduino-serial-plotter-webapp@0.0.13: + version "0.0.13" + resolved "https://registry.yarnpkg.com/arduino-serial-plotter-webapp/-/arduino-serial-plotter-webapp-0.0.13.tgz#b8d943a39f2c218bca36bb81bb6c5cabe4695ad7" + integrity sha512-Rn1shl6c1pUt1vtcdsAzhHIlHuHAmC829z0nR4JW4mYdYA+1MEY2VbbhfDf/tXiAFm8XAGfH63f//h1t99eGWQ== are-we-there-yet@~1.1.2: version "1.1.5" From e352f5a2df4e5f99e7278033018db04d25196fba Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 23 Nov 2021 16:15:36 +0100 Subject: [PATCH 26/27] refactoring and cleaning code --- arduino-ide-extension/README.md | 13 +- .../browser/arduino-frontend-contribution.tsx | 2 +- .../browser/arduino-ide-frontend-module.ts | 36 ++--- .../browser/contributions/board-selection.ts | 24 +++- .../browser/contributions/burn-bootloader.ts | 10 +- .../browser/contributions/upload-sketch.ts | 14 +- .../{ => serial}/monitor/monitor-utils.ts | 0 .../monitor/monitor-view-contribution.tsx | 8 +- .../{ => serial}/monitor/monitor-widget.tsx | 50 +++---- .../monitor/serial-monitor-send-input.tsx | 14 +- .../monitor/serial-monitor-send-output.tsx | 18 +-- .../plotter/plotter-frontend-contribution.ts | 30 ++-- .../browser/{ => serial}/plotter/protocol.ts | 0 .../serial-connection-manager.ts} | 128 +++++++++--------- .../serial-model.ts} | 38 +++--- .../serial-service-client-impl.ts} | 24 ++-- .../src/common/protocol/index.ts | 2 +- .../{monitor-service.ts => serial-service.ts} | 48 +++---- .../src/node/arduino-ide-backend-module.ts | 31 +++-- .../monitor-client-provider.ts | 0 .../serial-service-impl.ts} | 79 +++++------ .../src/test/browser/fixtures/serial.ts | 6 +- .../src/test/browser/monitor-utils.test.ts | 4 +- .../browser/serial-connection-manager.test.ts | 86 ++++++------ 24 files changed, 337 insertions(+), 328 deletions(-) rename arduino-ide-extension/src/browser/{ => serial}/monitor/monitor-utils.ts (100%) rename arduino-ide-extension/src/browser/{ => serial}/monitor/monitor-view-contribution.tsx (95%) rename arduino-ide-extension/src/browser/{ => serial}/monitor/monitor-widget.tsx (78%) rename arduino-ide-extension/src/browser/{ => serial}/monitor/serial-monitor-send-input.tsx (86%) rename arduino-ide-extension/src/browser/{ => serial}/monitor/serial-monitor-send-output.tsx (86%) rename arduino-ide-extension/src/browser/{ => serial}/plotter/plotter-frontend-contribution.ts (74%) rename arduino-ide-extension/src/browser/{ => serial}/plotter/protocol.ts (100%) rename arduino-ide-extension/src/browser/{monitor/monitor-connection.ts => serial/serial-connection-manager.ts} (76%) rename arduino-ide-extension/src/browser/{monitor/monitor-model.ts => serial/serial-model.ts} (75%) rename arduino-ide-extension/src/browser/{monitor/monitor-service-client-impl.ts => serial/serial-service-client-impl.ts} (66%) rename arduino-ide-extension/src/common/protocol/{monitor-service.ts => serial-service.ts} (60%) rename arduino-ide-extension/src/node/{monitor => serial}/monitor-client-provider.ts (100%) rename arduino-ide-extension/src/node/{monitor/monitor-service-impl.ts => serial/serial-service-impl.ts} (80%) diff --git a/arduino-ide-extension/README.md b/arduino-ide-extension/README.md index 1758bfc7a..ff25fd6eb 100644 --- a/arduino-ide-extension/README.md +++ b/arduino-ide-extension/README.md @@ -30,17 +30,20 @@ The Core Service is responsible for building your sketches and uploading them to - compiling a sketch for a selected board type - uploading a sketch to a connected board -#### Monitor Service +#### Serial Service -The Monitor Service allows getting information back from sketches running on your Arduino boards. +The Serial Service allows getting information back from sketches running on your Arduino boards. -- [src/common/protocol/monitor-service.ts](./src/common/protocol/monitor-service.ts) implements the common classes and interfaces -- [src/node/monitor/monitor-service-impl.ts](./src/node/monitor/monitor-service-impl.ts) implements the service backend: +- [src/common/protocol/serial-service.ts](./src/common/protocol/serial-service.ts) implements the common classes and interfaces +- [src/node/serial/serial-service-impl.ts](./src/node/serial/serial-service-impl.ts) implements the service backend: - connecting to / disconnecting from a board - receiving and sending data -- [src/browser/monitor/monitor-widget.tsx](./src/browser/monitor/monitor-widget.tsx) implements the serial monitor front-end: +- [src/browser/serial/serial-connection-manager.ts](./src/browser/serial/serial-connection-manager.ts) handles the serial connection in the frontend +- [src/browser/serial/monitor/monitor-widget.tsx](./src/browser/serial/monitor/monitor-widget.tsx) implements the serial monitor front-end: - viewing the output from a connected board - entering data to send to the board +- [src/browser/serial/plotter/plotter-frontend-contribution.ts](./src/browser/serial/plotter/plotter-frontend-contribution.ts) implements the serial plotter front-end: + - opening a new window running the [Serial Plotter Web App](https://github.com/arduino/arduino-serial-plotter-webapp) #### Config Service diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index 34b7a31fd..f9ec9ccbf 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -58,7 +58,7 @@ import { BoardsServiceProvider } from './boards/boards-service-provider'; import { BoardsToolBarItem } from './boards/boards-toolbar-item'; import { EditorMode } from './editor-mode'; import { ArduinoMenus } from './menu/arduino-menus'; -import { MonitorViewContribution } from './monitor/monitor-view-contribution'; +import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; import { ArduinoToolbar } from './toolbar/arduino-toolbar'; import { HostedPluginSupport } from '@theia/plugin-ext/lib/hosted/browser/hosted-plugin'; import { FileService } from '@theia/filesystem/lib/browser/file-service'; diff --git a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts index a247b4d51..459e6be39 100644 --- a/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts +++ b/arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts @@ -69,20 +69,20 @@ import { ScmContribution } from './theia/scm/scm-contribution'; import { SearchInWorkspaceFrontendContribution as TheiaSearchInWorkspaceFrontendContribution } from '@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'; import { SearchInWorkspaceFrontendContribution } from './theia/search-in-workspace/search-in-workspace-frontend-contribution'; import { LibraryListWidgetFrontendContribution } from './library/library-widget-frontend-contribution'; -import { MonitorServiceClientImpl } from './monitor/monitor-service-client-impl'; +import { SerialServiceClientImpl } from './serial/serial-service-client-impl'; import { - MonitorServicePath, - MonitorService, - MonitorServiceClient, -} from '../common/protocol/monitor-service'; + SerialServicePath, + SerialService, + SerialServiceClient, +} from '../common/protocol/serial-service'; import { ConfigService, ConfigServicePath, } from '../common/protocol/config-service'; -import { MonitorWidget } from './monitor/monitor-widget'; -import { MonitorViewContribution } from './monitor/monitor-view-contribution'; -import { SerialConnectionManager } from './monitor/monitor-connection'; -import { MonitorModel } from './monitor/monitor-model'; +import { MonitorWidget } from './serial/monitor/monitor-widget'; +import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution'; +import { SerialConnectionManager } from './serial/serial-connection-manager'; +import { SerialModel } from './serial/serial-model'; import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator'; import { TabBarDecoratorService } from './theia/core/tab-bar-decorator'; import { ProblemManager as TheiaProblemManager } from '@theia/markers/lib/browser'; @@ -253,7 +253,7 @@ import { UploadCertificateDialogProps, UploadCertificateDialogWidget, } from './dialogs/certificate-uploader/certificate-uploader-dialog'; -import { PlotterFrontendContribution } from './plotter/plotter-frontend-contribution'; +import { PlotterFrontendContribution } from './serial/plotter/plotter-frontend-contribution'; import { nls } from '@theia/core/lib/browser/nls'; const ElementQueries = require('css-element-queries/src/ElementQueries'); @@ -387,8 +387,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope(); // Serial monitor - bind(MonitorModel).toSelf().inSingletonScope(); - bind(FrontendApplicationContribution).toService(MonitorModel); + bind(SerialModel).toSelf().inSingletonScope(); + bind(FrontendApplicationContribution).toService(SerialModel); bind(MonitorWidget).toSelf(); bindViewContribution(bind, MonitorViewContribution); bind(TabBarToolbarContribution).toService(MonitorViewContribution); @@ -396,19 +396,19 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { id: MonitorWidget.ID, createWidget: () => context.container.get(MonitorWidget), })); - // Frontend binding for the serial monitor service - bind(MonitorService) + // Frontend binding for the serial service + bind(SerialService) .toDynamicValue((context) => { const connection = context.container.get(WebSocketConnectionProvider); const client = - context.container.get(MonitorServiceClient); - return connection.createProxy(MonitorServicePath, client); + context.container.get(SerialServiceClient); + return connection.createProxy(SerialServicePath, client); }) .inSingletonScope(); bind(SerialConnectionManager).toSelf().inSingletonScope(); - // Serial monitor service client to receive and delegate notifications from the backend. - bind(MonitorServiceClient).to(MonitorServiceClientImpl).inSingletonScope(); + // Serial service client to receive and delegate notifications from the backend. + bind(SerialServiceClient).to(SerialServiceClientImpl).inSingletonScope(); bind(WorkspaceService).toSelf().inSingletonScope(); rebind(TheiaWorkspaceService).toService(WorkspaceService); diff --git a/arduino-ide-extension/src/browser/contributions/board-selection.ts b/arduino-ide-extension/src/browser/contributions/board-selection.ts index 11ea70ca5..a401164d5 100644 --- a/arduino-ide-extension/src/browser/contributions/board-selection.ts +++ b/arduino-ide-extension/src/browser/contributions/board-selection.ts @@ -138,7 +138,11 @@ PID: ${PID}`; // The board specific items, and the rest, have order with `z`. We needed something between `0` and `z` with natural-order. this.menuModelRegistry.registerSubmenu( boardsSubmenuPath, - nls.localize('arduino/board/board', 'Board{0}', !!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : ''), + nls.localize( + 'arduino/board/board', + 'Board{0}', + !!boardsSubmenuLabel ? `: "${boardsSubmenuLabel}"` : '' + ), { order: '100' } ); this.toDisposeBeforeMenuRebuild.push( @@ -155,7 +159,11 @@ PID: ${PID}`; const portsSubmenuLabel = config.selectedPort?.address; this.menuModelRegistry.registerSubmenu( portsSubmenuPath, - nls.localize('arduino/board/port', 'Port{0}', portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : ''), + nls.localize( + 'arduino/board/port', + 'Port{0}', + portsSubmenuLabel ? `: "${portsSubmenuLabel}"` : '' + ), { order: '101' } ); this.toDisposeBeforeMenuRebuild.push( @@ -193,9 +201,10 @@ PID: ${PID}`; const packageLabel = packageName + - `${manuallyInstalled - ? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)') - : '' + `${ + manuallyInstalled + ? nls.localize('arduino/board/inSketchbook', ' (in Sketchbook)') + : '' }`; // Platform submenu const platformMenuPath = [...boardsPackagesGroup, packageId]; @@ -268,8 +277,9 @@ PID: ${PID}`; }); } for (const { name, fqbn } of boards) { - const id = `arduino-select-port--${address}${fqbn ? `--${fqbn}` : '' - }`; + const id = `arduino-select-port--${address}${ + fqbn ? `--${fqbn}` : '' + }`; const command = { id }; const handler = { execute: () => { diff --git a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts index d60c7d66d..0f24cc185 100644 --- a/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts +++ b/arduino-ide-extension/src/browser/contributions/burn-bootloader.ts @@ -3,7 +3,7 @@ import { OutputChannelManager } from '@theia/output/lib/common/output-channel'; import { CoreService } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { BoardsDataStore } from '../boards/boards-data-store'; -import { SerialConnectionManager } from '../monitor/monitor-connection'; +import { SerialConnectionManager } from '../serial/serial-connection-manager'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { SketchContribution, @@ -19,7 +19,7 @@ export class BurnBootloader extends SketchContribution { protected readonly coreService: CoreService; @inject(SerialConnectionManager) - protected readonly monitorConnection: SerialConnectionManager; + protected readonly serialConnection: SerialConnectionManager; @inject(BoardsDataStore) protected readonly boardsDataStore: BoardsDataStore; @@ -48,7 +48,7 @@ export class BurnBootloader extends SketchContribution { } async burnBootloader(): Promise { - await this.monitorConnection.disconnect(); + await this.serialConnection.disconnect(); try { const { boardsConfig } = this.boardsServiceClientImpl; const port = boardsConfig.selectedPort; @@ -81,8 +81,8 @@ export class BurnBootloader extends SketchContribution { } catch (e) { this.messageService.error(e.toString()); } finally { - if (this.monitorConnection.isSerialOpen()) { - await this.monitorConnection.connect(); + if (this.serialConnection.isSerialOpen()) { + await this.serialConnection.connect(); } } } diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index 9fc302efe..fa298c603 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -4,7 +4,7 @@ import { CoreService } from '../../common/protocol'; import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; import { BoardsDataStore } from '../boards/boards-data-store'; -import { SerialConnectionManager } from '../monitor/monitor-connection'; +import { SerialConnectionManager } from '../serial/serial-connection-manager'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { SketchContribution, @@ -22,7 +22,7 @@ export class UploadSketch extends SketchContribution { protected readonly coreService: CoreService; @inject(SerialConnectionManager) - protected readonly monitorConnection: SerialConnectionManager; + protected readonly serialConnection: SerialConnectionManager; @inject(BoardsDataStore) protected readonly boardsDataStore: BoardsDataStore; @@ -108,7 +108,7 @@ export class UploadSketch extends SketchContribution { if (!sketch) { return; } - await this.monitorConnection.disconnect(); + await this.serialConnection.disconnect(); try { const { boardsConfig } = this.boardsServiceClientImpl; const [fqbn, { selectedProgrammer }, verify, verbose, sourceOverride] = @@ -168,16 +168,16 @@ export class UploadSketch extends SketchContribution { this.onDidChangeEmitter.fire(); if ( - this.monitorConnection.isSerialOpen() && - this.monitorConnection.monitorConfig + this.serialConnection.isSerialOpen() && + this.serialConnection.serialConfig ) { - const { board, port } = this.monitorConnection.monitorConfig; + const { board, port } = this.serialConnection.serialConfig; try { await this.boardsServiceClientImpl.waitUntilAvailable( Object.assign(board, { port }), 10_000 ); - await this.monitorConnection.connect(); + await this.serialConnection.connect(); } catch (waitError) { this.messageService.error( nls.localize( diff --git a/arduino-ide-extension/src/browser/monitor/monitor-utils.ts b/arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts similarity index 100% rename from arduino-ide-extension/src/browser/monitor/monitor-utils.ts rename to arduino-ide-extension/src/browser/serial/monitor/monitor-utils.ts diff --git a/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx similarity index 95% rename from arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx rename to arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index 3eb48c321..40261928b 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -7,9 +7,9 @@ import { TabBarToolbarContribution, TabBarToolbarRegistry, } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; -import { ArduinoToolbar } from '../toolbar/arduino-toolbar'; -import { MonitorModel } from './monitor-model'; -import { ArduinoMenus } from '../menu/arduino-menus'; +import { ArduinoToolbar } from '../../toolbar/arduino-toolbar'; +import { SerialModel } from '../serial-model'; +import { ArduinoMenus } from '../../menu/arduino-menus'; import { nls } from '@theia/core/lib/browser/nls'; export namespace SerialMonitor { @@ -48,7 +48,7 @@ export class MonitorViewContribution static readonly TOGGLE_SERIAL_MONITOR_TOOLBAR = MonitorWidget.ID + ':toggle-toolbar'; - @inject(MonitorModel) protected readonly model: MonitorModel; + @inject(SerialModel) protected readonly model: SerialModel; constructor() { super({ diff --git a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx similarity index 78% rename from arduino-ide-extension/src/browser/monitor/monitor-widget.tsx rename to arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx index 5204a8654..12cb8853c 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-widget.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-widget.tsx @@ -9,14 +9,14 @@ import { Widget, MessageLoop, } from '@theia/core/lib/browser/widgets'; -import { MonitorConfig } from '../../common/protocol/monitor-service'; -import { ArduinoSelect } from '../widgets/arduino-select'; -import { MonitorModel } from './monitor-model'; -import { Serial, SerialConnectionManager } from './monitor-connection'; +import { SerialConfig } from '../../../common/protocol/serial-service'; +import { ArduinoSelect } from '../../widgets/arduino-select'; +import { SerialModel } from '../serial-model'; +import { Serial, SerialConnectionManager } from '../serial-connection-manager'; import { SerialMonitorSendInput } from './serial-monitor-send-input'; import { SerialMonitorOutput } from './serial-monitor-send-output'; import { nls } from '@theia/core/lib/browser/nls'; -import { BoardsServiceProvider } from '../boards/boards-service-provider'; +import { BoardsServiceProvider } from '../../boards/boards-service-provider'; @injectable() export class MonitorWidget extends ReactWidget { @@ -26,11 +26,11 @@ export class MonitorWidget extends ReactWidget { ); static readonly ID = 'serial-monitor'; - @inject(MonitorModel) - protected readonly monitorModel: MonitorModel; + @inject(SerialModel) + protected readonly serialModel: SerialModel; @inject(SerialConnectionManager) - protected readonly monitorConnection: SerialConnectionManager; + protected readonly serialConnection: SerialConnectionManager; @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; @@ -58,7 +58,7 @@ export class MonitorWidget extends ReactWidget { this.toDispose.push(this.clearOutputEmitter); this.toDispose.push( Disposable.create(() => - this.monitorConnection.closeSerial(Serial.Type.Monitor) + this.serialConnection.closeSerial(Serial.Type.Monitor) ) ); } @@ -67,9 +67,9 @@ export class MonitorWidget extends ReactWidget { protected init(): void { this.update(); this.toDispose.push( - this.monitorConnection.onConnectionChanged(() => this.clearConsole()) + this.serialConnection.onConnectionChanged(() => this.clearConsole()) ); - this.toDispose.push(this.monitorModel.onChange(() => this.update())); + this.toDispose.push(this.serialModel.onChange(() => this.update())); } clearConsole(): void { @@ -83,7 +83,7 @@ export class MonitorWidget extends ReactWidget { protected onAfterAttach(msg: Message): void { super.onAfterAttach(msg); - this.monitorConnection.openSerial(Serial.Type.Monitor); + this.serialConnection.openSerial(Serial.Type.Monitor); } onCloseRequest(msg: Message): void { @@ -121,7 +121,7 @@ export class MonitorWidget extends ReactWidget { }; protected get lineEndings(): OptionsType< - SerialMonitorOutput.SelectOption + SerialMonitorOutput.SelectOption > { return [ { @@ -150,9 +150,9 @@ export class MonitorWidget extends ReactWidget { } protected get baudRates(): OptionsType< - SerialMonitorOutput.SelectOption + SerialMonitorOutput.SelectOption > { - const baudRates: Array = [ + const baudRates: Array = [ 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, ]; return baudRates.map((baudRate) => ({ @@ -164,17 +164,17 @@ export class MonitorWidget extends ReactWidget { protected render(): React.ReactNode { const { baudRates, lineEndings } = this; const lineEnding = - lineEndings.find((item) => item.value === this.monitorModel.lineEnding) || + lineEndings.find((item) => item.value === this.serialModel.lineEnding) || lineEndings[1]; // Defaults to `\n`. const baudRate = - baudRates.find((item) => item.value === this.monitorModel.baudRate) || + baudRates.find((item) => item.value === this.serialModel.baudRate) || baudRates[4]; // Defaults to `9600`. return (
@@ -201,8 +201,8 @@ export class MonitorWidget extends ReactWidget {
@@ -213,18 +213,18 @@ export class MonitorWidget extends ReactWidget { protected readonly onSend = (value: string) => this.doSend(value); protected async doSend(value: string): Promise { - this.monitorConnection.send(value); + this.serialConnection.send(value); } protected readonly onChangeLineEnding = ( - option: SerialMonitorOutput.SelectOption + option: SerialMonitorOutput.SelectOption ) => { - this.monitorModel.lineEnding = option.value; + this.serialModel.lineEnding = option.value; }; protected readonly onChangeBaudRate = ( - option: SerialMonitorOutput.SelectOption + option: SerialMonitorOutput.SelectOption ) => { - this.monitorModel.baudRate = option.value; + this.serialModel.baudRate = option.value; }; } diff --git a/arduino-ide-extension/src/browser/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx similarity index 86% rename from arduino-ide-extension/src/browser/monitor/serial-monitor-send-input.tsx rename to arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index 2e197e33a..df4001978 100644 --- a/arduino-ide-extension/src/browser/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -1,13 +1,13 @@ import * as React from 'react'; import { Key, KeyCode } from '@theia/core/lib/browser/keys'; -import { Board, Port } from '../../common/protocol/boards-service'; -import { MonitorConfig } from '../../common/protocol/monitor-service'; +import { Board, Port } from '../../../common/protocol/boards-service'; +import { SerialConfig } from '../../../common/protocol/serial-service'; import { isOSX } from '@theia/core/lib/common/os'; import { nls } from '@theia/core/lib/browser/nls'; export namespace SerialMonitorSendInput { export interface Props { - readonly monitorConfig?: MonitorConfig; + readonly serialConfig?: SerialConfig; readonly onSend: (text: string) => void; readonly resolveFocus: (element: HTMLElement | undefined) => void; } @@ -33,7 +33,7 @@ export class SerialMonitorSendInput extends React.Component< { + this.props.serialConnection.onRead(({ messages }) => { const [newLines, totalCharCount] = messagesToLines( messages, this.state.lines, @@ -74,9 +74,9 @@ export class SerialMonitorOutput extends React.Component< this.props.clearConsoleEvent(() => this.setState({ lines: [], charCount: 0 }) ), - this.props.monitorModel.onChange(({ property }) => { + this.props.serialModel.onChange(({ property }) => { if (property === 'timestamp') { - const { timestamp } = this.props.monitorModel; + const { timestamp } = this.props.serialModel; this.setState({ timestamp }); } if (property === 'autoscroll') { @@ -92,7 +92,7 @@ export class SerialMonitorOutput extends React.Component< } scrollToBottom = ((): void => { - if (this.listRef.current && this.props.monitorModel.autoscroll) { + if (this.listRef.current && this.props.serialModel.autoscroll) { this.listRef.current.scrollToItem(this.state.lines.length, 'end'); } }).bind(this); @@ -125,8 +125,8 @@ const Row = React.memo(_Row, areEqual); export namespace SerialMonitorOutput { export interface Props { - readonly monitorModel: MonitorModel; - readonly monitorConnection: SerialConnectionManager; + readonly serialModel: SerialModel; + readonly serialConnection: SerialConnectionManager; readonly clearConsoleEvent: Event; readonly height: number; } diff --git a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts similarity index 74% rename from arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts rename to arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts index e803ca638..bbc54b27e 100644 --- a/arduino-ide-extension/src/browser/plotter/plotter-frontend-contribution.ts +++ b/arduino-ide-extension/src/browser/serial/plotter/plotter-frontend-contribution.ts @@ -6,15 +6,15 @@ import { MaybePromise, MenuModelRegistry, } from '@theia/core'; -import { MonitorModel } from '../monitor/monitor-model'; -import { ArduinoMenus } from '../menu/arduino-menus'; -import { Contribution } from '../contributions/contribution'; +import { SerialModel } from '../serial-model'; +import { ArduinoMenus } from '../../menu/arduino-menus'; +import { Contribution } from '../../contributions/contribution'; import { Endpoint, FrontendApplication } from '@theia/core/lib/browser'; import { ipcRenderer } from '@theia/core/shared/electron'; -import { MonitorConfig, Status } from '../../common/protocol'; -import { Serial, SerialConnectionManager } from '../monitor/monitor-connection'; +import { SerialConfig, Status } from '../../../common/protocol'; +import { Serial, SerialConnectionManager } from '../serial-connection-manager'; import { SerialPlotter } from './protocol'; -import { BoardsServiceProvider } from '../boards/boards-service-provider'; +import { BoardsServiceProvider } from '../../boards/boards-service-provider'; const queryString = require('query-string'); export namespace SerialPlotterContribution { @@ -33,14 +33,14 @@ export class PlotterFrontendContribution extends Contribution { protected url: string; protected wsPort: number; - @inject(MonitorModel) - protected readonly model: MonitorModel; + @inject(SerialModel) + protected readonly model: SerialModel; @inject(ThemeService) protected readonly themeService: ThemeService; @inject(SerialConnectionManager) - protected readonly monitorConnection: SerialConnectionManager; + protected readonly serialConnection: SerialConnectionManager; @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider; @@ -51,7 +51,7 @@ export class PlotterFrontendContribution extends Contribution { ipcRenderer.on('CLOSE_CHILD_WINDOW', async () => { if (!!this.window) { this.window = null; - await this.monitorConnection.closeSerial(Serial.Type.Plotter); + await this.serialConnection.closeSerial(Serial.Type.Plotter); } }); @@ -77,25 +77,25 @@ export class PlotterFrontendContribution extends Contribution { this.window.focus(); return; } - const status = await this.monitorConnection.openSerial(Serial.Type.Plotter); - const wsPort = this.monitorConnection.getWsPort(); + const status = await this.serialConnection.openSerial(Serial.Type.Plotter); + const wsPort = this.serialConnection.getWsPort(); if (Status.isOK(status) && wsPort) { this.open(wsPort); } else { - this.monitorConnection.closeSerial(Serial.Type.Plotter); + this.serialConnection.closeSerial(Serial.Type.Plotter); this.messageService.error(`Couldn't open serial plotter`); } } protected open(wsPort: number): void { const initConfig: Partial = { - baudrates: MonitorConfig.BaudRates.map((b) => b), + baudrates: SerialConfig.BaudRates.map((b) => b), currentBaudrate: this.model.baudRate, currentLineEnding: this.model.lineEnding, darkTheme: this.themeService.getCurrentTheme().type === 'dark', wsPort, interpolate: this.model.interpolate, - connected: this.monitorConnection.connected, + connected: this.serialConnection.connected, serialPort: this.boardsServiceProvider.boardsConfig.selectedPort?.address, }; const urlWithParams = queryString.stringifyUrl( diff --git a/arduino-ide-extension/src/browser/plotter/protocol.ts b/arduino-ide-extension/src/browser/serial/plotter/protocol.ts similarity index 100% rename from arduino-ide-extension/src/browser/plotter/protocol.ts rename to arduino-ide-extension/src/browser/serial/plotter/protocol.ts diff --git a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts b/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts similarity index 76% rename from arduino-ide-extension/src/browser/monitor/monitor-connection.ts rename to arduino-ide-extension/src/browser/serial/serial-connection-manager.ts index 5f50ee877..75ea4d978 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-connection.ts +++ b/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts @@ -3,12 +3,12 @@ import { deepClone } from '@theia/core/lib/common/objects'; import { Emitter, Event } from '@theia/core/lib/common/event'; import { MessageService } from '@theia/core/lib/common/message-service'; import { - MonitorService, - MonitorConfig, - MonitorError, + SerialService, + SerialConfig, + SerialError, Status, - MonitorServiceClient, -} from '../../common/protocol/monitor-service'; + SerialServiceClient, +} from '../../common/protocol/serial-service'; import { BoardsServiceProvider } from '../boards/boards-service-provider'; import { Port, @@ -16,7 +16,7 @@ import { BoardsService, } from '../../common/protocol/boards-service'; import { BoardsConfig } from '../boards/boards-config'; -import { MonitorModel } from './monitor-model'; +import { SerialModel } from './serial-model'; import { ThemeService } from '@theia/core/lib/browser/theming'; import { nls } from '@theia/core/lib/browser/nls'; import { CoreService } from '../../common/protocol'; @@ -25,16 +25,12 @@ import { CoreService } from '../../common/protocol'; export class SerialConnectionManager { protected _state: Serial.State = []; protected _connected = false; - protected config: Partial = { + protected config: Partial = { board: undefined, port: undefined, baudRate: undefined, }; - /** - * Note: The idea is to toggle this property from the UI (`Monitor` view) - * and the boards config and the boards attachment/detachment logic can be at on place, here. - */ protected readonly onConnectionChangedEmitter = new Emitter(); /** @@ -43,11 +39,11 @@ export class SerialConnectionManager { protected readonly onReadEmitter = new Emitter<{ messages: string[] }>(); /** - * Array for storing previous monitor errors received from the server, and based on the number of elements in this array, + * Array for storing previous serial errors received from the server, and based on the number of elements in this array, * we adjust the reconnection delay. * Super naive way: we wait `array.length * 1000` ms. Once we hit 10 errors, we do not try to reconnect and clean the array. */ - protected monitorErrors: MonitorError[] = []; + protected serialErrors: SerialError[] = []; protected reconnectTimeout?: number; /** @@ -57,10 +53,10 @@ export class SerialConnectionManager { protected webSocket?: WebSocket; constructor( - @inject(MonitorModel) protected readonly monitorModel: MonitorModel, - @inject(MonitorService) protected readonly monitorService: MonitorService, - @inject(MonitorServiceClient) - protected readonly monitorServiceClient: MonitorServiceClient, + @inject(SerialModel) protected readonly serialModel: SerialModel, + @inject(SerialService) protected readonly serialService: SerialService, + @inject(SerialServiceClient) + protected readonly serialServiceClient: SerialServiceClient, @inject(BoardsService) protected readonly boardsService: BoardsService, @inject(BoardsServiceProvider) protected readonly boardsServiceProvider: BoardsServiceProvider, @@ -68,50 +64,50 @@ export class SerialConnectionManager { @inject(ThemeService) protected readonly themeService: ThemeService, @inject(CoreService) protected readonly core: CoreService ) { - this.monitorServiceClient.onWebSocketChanged( + this.serialServiceClient.onWebSocketChanged( this.handleWebSocketChanged.bind(this) ); - this.monitorServiceClient.onBaudRateChanged((baudRate) => { - if (this.monitorModel.baudRate !== baudRate) { - this.monitorModel.baudRate = baudRate; + this.serialServiceClient.onBaudRateChanged((baudRate) => { + if (this.serialModel.baudRate !== baudRate) { + this.serialModel.baudRate = baudRate; } }); - this.monitorServiceClient.onLineEndingChanged((lineending) => { - if (this.monitorModel.lineEnding !== lineending) { - this.monitorModel.lineEnding = lineending; + this.serialServiceClient.onLineEndingChanged((lineending) => { + if (this.serialModel.lineEnding !== lineending) { + this.serialModel.lineEnding = lineending; } }); - this.monitorServiceClient.onInterpolateChanged((interpolate) => { - if (this.monitorModel.interpolate !== interpolate) { - this.monitorModel.interpolate = interpolate; + this.serialServiceClient.onInterpolateChanged((interpolate) => { + if (this.serialModel.interpolate !== interpolate) { + this.serialModel.interpolate = interpolate; } }); - this.monitorServiceClient.onError(this.handleError.bind(this)); + this.serialServiceClient.onError(this.handleError.bind(this)); this.boardsServiceProvider.onBoardsConfigChanged( this.handleBoardConfigChange.bind(this) ); // Handles the `baudRate` changes by reconnecting if required. - this.monitorModel.onChange(({ property }) => { + this.serialModel.onChange(({ property }) => { if (property === 'baudRate' && this.connected) { const { boardsConfig } = this.boardsServiceProvider; this.handleBoardConfigChange(boardsConfig); } // update the current values in the backend and propagate to websocket clients - this.monitorService.updateWsConfigParam({ + this.serialService.updateWsConfigParam({ ...(property === 'lineEnding' && { - currentLineEnding: this.monitorModel.lineEnding, + currentLineEnding: this.serialModel.lineEnding, }), ...(property === 'interpolate' && { - interpolate: this.monitorModel.interpolate, + interpolate: this.serialModel.interpolate, }), }); }); this.themeService.onDidColorThemeChange((theme) => { - this.monitorService.updateWsConfigParam({ + this.serialService.updateWsConfigParam({ darkTheme: theme.newTheme.type === 'dark', }); }); @@ -123,9 +119,9 @@ export class SerialConnectionManager { * * @param newConfig the porperties of the config that has changed */ - async setConfig(newConfig: Partial): Promise { + async setConfig(newConfig: Partial): Promise { let configHasChanged = false; - Object.keys(this.config).forEach((key: keyof MonitorConfig) => { + Object.keys(this.config).forEach((key: keyof SerialConfig) => { if (newConfig[key] !== this.config[key]) { configHasChanged = true; this.config = { ...this.config, [key]: newConfig[key] }; @@ -136,7 +132,7 @@ export class SerialConnectionManager { this.isSerialOpen() && !(await this.core.isUploading()) ) { - this.monitorService.updateWsConfigParam({ + this.serialService.updateWsConfigParam({ currentBaudrate: this.config.baudRate, serialPort: this.config.port?.address, }); @@ -145,7 +141,7 @@ export class SerialConnectionManager { } } - getConfig(): Partial { + getConfig(): Partial { return this.config; } @@ -162,7 +158,7 @@ export class SerialConnectionManager { } /** - * When the serial monitor is open and the frontend is connected to the serial, we create the websocket here + * When the serial is open and the frontend is connected to the serial, we create the websocket here */ protected createWsConnection(): boolean { if (this.wsPort) { @@ -183,8 +179,8 @@ export class SerialConnectionManager { /** * Sets the types of connections needed by the client. * - * @param newState The new types of connections (can be 'Monitor', 'Plotter', none or both). - * If the previuos state was empty and 's' is not, it tries to reconnect to the serial service + * @param newState The array containing the list of desired connections. + * If the previuos state was empty and 'newState' is not, it tries to reconnect to the serial service * If the provios state was NOT empty and now it is, it disconnects to the serial service * @returns The status of the operation */ @@ -213,9 +209,9 @@ export class SerialConnectionManager { return (state ? state : this._state).length > 0; } - get monitorConfig(): MonitorConfig | undefined { - return isMonitorConfig(this.config) - ? (this.config as MonitorConfig) + get serialConfig(): SerialConfig | undefined { + return isSerialConfig(this.config) + ? (this.config as SerialConfig) : undefined; } @@ -225,7 +221,7 @@ export class SerialConnectionManager { set connected(c: boolean) { this._connected = c; - this.monitorService.updateWsConfigParam({ connected: c }); + this.serialService.updateWsConfigParam({ connected: c }); this.onConnectionChangedEmitter.fire(this._connected); } /** @@ -236,7 +232,7 @@ export class SerialConnectionManager { * @returns the status of the operation */ async openSerial(type: Serial.Type): Promise { - if (!isMonitorConfig(this.config)) { + if (!isSerialConfig(this.config)) { this.messageService.error( `Please select a board and a port to open the serial connection.` ); @@ -277,15 +273,15 @@ export class SerialConnectionManager { } /** - * Handles error on the MonitorServiceClient and try to reconnect, eventually + * Handles error on the SerialServiceClient and try to reconnect, eventually */ - handleError(error: MonitorError): void { + handleError(error: SerialError): void { if (!this.connected) return; const { code, config } = error; const { board, port } = config; const options = { timeout: 3000 }; switch (code) { - case MonitorError.ErrorCodes.CLIENT_CANCEL: { + case SerialError.ErrorCodes.CLIENT_CANCEL: { console.debug( `Serial connection was canceled by client: ${Serial.Config.toString( this.config @@ -293,7 +289,7 @@ export class SerialConnectionManager { ); break; } - case MonitorError.ErrorCodes.DEVICE_BUSY: { + case SerialError.ErrorCodes.DEVICE_BUSY: { this.messageService.warn( nls.localize( 'arduino/monitor/connectionBusy', @@ -302,10 +298,10 @@ export class SerialConnectionManager { ), options ); - this.monitorErrors.push(error); + this.serialErrors.push(error); break; } - case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: { + case SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED: { this.messageService.info( nls.localize( 'arduino/monitor/disconnected', @@ -336,7 +332,7 @@ export class SerialConnectionManager { this.connected = false; if (this.isSerialOpen()) { - if (this.monitorErrors.length >= 10) { + if (this.serialErrors.length >= 10) { this.messageService.warn( nls.localize( 'arduino/monitor/failedReconnect', @@ -347,9 +343,9 @@ export class SerialConnectionManager { Port.toString(port) ) ); - this.monitorErrors.length = 0; + this.serialErrors.length = 0; } else { - const attempts = this.monitorErrors.length || 1; + const attempts = this.serialErrors.length || 1; if (this.reconnectTimeout !== undefined) { // Clear the previous timer. window.clearTimeout(this.reconnectTimeout); @@ -376,14 +372,14 @@ export class SerialConnectionManager { async connect(): Promise { if (this.connected) return Status.ALREADY_CONNECTED; - if (!isMonitorConfig(this.config)) return Status.NOT_CONNECTED; + if (!isSerialConfig(this.config)) return Status.NOT_CONNECTED; console.info( `>>> Creating serial connection for ${Board.toString( this.config.board )} on port ${Port.toString(this.config.port)}...` ); - const connectStatus = await this.monitorService.connect(this.config); + const connectStatus = await this.serialService.connect(this.config); if (Status.isOK(connectStatus)) { this.connected = true; console.info( @@ -402,7 +398,7 @@ export class SerialConnectionManager { } console.log('>>> Disposing existing serial connection...'); - const status = await this.monitorService.disconnect(); + const status = await this.serialService.disconnect(); if (Status.isOK(status)) { this.connected = false; console.log( @@ -423,7 +419,7 @@ export class SerialConnectionManager { } /** - * Sends the data to the connected serial monitor. + * Sends the data to the connected serial port. * The desired EOL is appended to `data`, you do not have to add it. * It is a NOOP if connected. */ @@ -432,8 +428,8 @@ export class SerialConnectionManager { return Status.NOT_CONNECTED; } return new Promise((resolve) => { - this.monitorService - .sendMessageToSerial(data + this.monitorModel.lineEnding) + this.serialService + .sendMessageToSerial(data + this.serialModel.lineEnding) .then(() => resolve(Status.OK)); }); } @@ -450,8 +446,8 @@ export class SerialConnectionManager { boardsConfig: BoardsConfig.Config ): Promise { const { selectedBoard: board, selectedPort: port } = boardsConfig; - const { baudRate } = this.monitorModel; - const newConfig: Partial = { board, port, baudRate }; + const { baudRate } = this.serialModel; + const newConfig: Partial = { board, port, baudRate }; this.setConfig(newConfig); } } @@ -470,16 +466,14 @@ export namespace Serial { export type State = Serial.Type[]; export namespace Config { - export function toString(config: Partial): string { - if (!isMonitorConfig(config)) return ''; + export function toString(config: Partial): string { + if (!isSerialConfig(config)) return ''; const { board, port } = config; return `${Board.toString(board)} ${Port.toString(port)}`; } } } -function isMonitorConfig( - config: Partial -): config is MonitorConfig { +function isSerialConfig(config: Partial): config is SerialConfig { return !!config.board && !!config.baudRate && !!config.port; } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-model.ts b/arduino-ide-extension/src/browser/serial/serial-model.ts similarity index 75% rename from arduino-ide-extension/src/browser/monitor/monitor-model.ts rename to arduino-ide-extension/src/browser/serial/serial-model.ts index 621018750..fc6e352ec 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-model.ts +++ b/arduino-ide-extension/src/browser/serial/serial-model.ts @@ -1,6 +1,6 @@ import { injectable, inject } from 'inversify'; import { Emitter, Event } from '@theia/core/lib/common/event'; -import { MonitorConfig } from '../../common/protocol/monitor-service'; +import { SerialConfig } from '../../common/protocol'; import { FrontendApplicationContribution, LocalStorageService, @@ -8,8 +8,8 @@ import { import { BoardsServiceProvider } from '../boards/boards-service-provider'; @injectable() -export class MonitorModel implements FrontendApplicationContribution { - protected static STORAGE_ID = 'arduino-monitor-model'; +export class SerialModel implements FrontendApplicationContribution { + protected static STORAGE_ID = 'arduino-serial-model'; @inject(LocalStorageService) protected readonly localStorageService: LocalStorageService; @@ -18,28 +18,28 @@ export class MonitorModel implements FrontendApplicationContribution { protected readonly boardsServiceClient: BoardsServiceProvider; protected readonly onChangeEmitter: Emitter< - MonitorModel.State.Change + SerialModel.State.Change >; protected _autoscroll: boolean; protected _timestamp: boolean; - protected _baudRate: MonitorConfig.BaudRate; - protected _lineEnding: MonitorModel.EOL; + protected _baudRate: SerialConfig.BaudRate; + protected _lineEnding: SerialModel.EOL; protected _interpolate: boolean; constructor() { this._autoscroll = true; this._timestamp = false; - this._baudRate = MonitorConfig.BaudRate.DEFAULT; - this._lineEnding = MonitorModel.EOL.DEFAULT; + this._baudRate = SerialConfig.BaudRate.DEFAULT; + this._lineEnding = SerialModel.EOL.DEFAULT; this._interpolate = false; this.onChangeEmitter = new Emitter< - MonitorModel.State.Change + SerialModel.State.Change >(); } onStart(): void { this.localStorageService - .getData(MonitorModel.STORAGE_ID) + .getData(SerialModel.STORAGE_ID) .then((state) => { if (state) { this.restoreState(state); @@ -47,7 +47,7 @@ export class MonitorModel implements FrontendApplicationContribution { }); } - get onChange(): Event> { + get onChange(): Event> { return this.onChangeEmitter.event; } @@ -80,11 +80,11 @@ export class MonitorModel implements FrontendApplicationContribution { ); } - get baudRate(): MonitorConfig.BaudRate { + get baudRate(): SerialConfig.BaudRate { return this._baudRate; } - set baudRate(baudRate: MonitorConfig.BaudRate) { + set baudRate(baudRate: SerialConfig.BaudRate) { this._baudRate = baudRate; this.storeState().then(() => this.onChangeEmitter.fire({ @@ -94,11 +94,11 @@ export class MonitorModel implements FrontendApplicationContribution { ); } - get lineEnding(): MonitorModel.EOL { + get lineEnding(): SerialModel.EOL { return this._lineEnding; } - set lineEnding(lineEnding: MonitorModel.EOL) { + set lineEnding(lineEnding: SerialModel.EOL) { this._lineEnding = lineEnding; this.storeState().then(() => this.onChangeEmitter.fire({ @@ -122,7 +122,7 @@ export class MonitorModel implements FrontendApplicationContribution { ); } - protected restoreState(state: MonitorModel.State): void { + protected restoreState(state: SerialModel.State): void { this._autoscroll = state.autoscroll; this._timestamp = state.timestamp; this._baudRate = state.baudRate; @@ -131,7 +131,7 @@ export class MonitorModel implements FrontendApplicationContribution { } protected async storeState(): Promise { - return this.localStorageService.setData(MonitorModel.STORAGE_ID, { + return this.localStorageService.setData(SerialModel.STORAGE_ID, { autoscroll: this._autoscroll, timestamp: this._timestamp, baudRate: this._baudRate, @@ -141,11 +141,11 @@ export class MonitorModel implements FrontendApplicationContribution { } } -export namespace MonitorModel { +export namespace SerialModel { export interface State { autoscroll: boolean; timestamp: boolean; - baudRate: MonitorConfig.BaudRate; + baudRate: SerialConfig.BaudRate; lineEnding: EOL; interpolate: boolean; } diff --git a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts b/arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts similarity index 66% rename from arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts rename to arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts index d223c77ee..5a025fcf5 100644 --- a/arduino-ide-extension/src/browser/monitor/monitor-service-client-impl.ts +++ b/arduino-ide-extension/src/browser/serial/serial-service-client-impl.ts @@ -1,32 +1,32 @@ import { injectable } from 'inversify'; import { Emitter } from '@theia/core/lib/common/event'; import { - MonitorServiceClient, - MonitorError, - MonitorConfig, -} from '../../common/protocol/monitor-service'; -import { MonitorModel } from './monitor-model'; + SerialServiceClient, + SerialError, + SerialConfig, +} from '../../common/protocol/serial-service'; +import { SerialModel } from './serial-model'; @injectable() -export class MonitorServiceClientImpl implements MonitorServiceClient { - protected readonly onErrorEmitter = new Emitter(); +export class SerialServiceClientImpl implements SerialServiceClient { + protected readonly onErrorEmitter = new Emitter(); readonly onError = this.onErrorEmitter.event; protected readonly onWebSocketChangedEmitter = new Emitter(); readonly onWebSocketChanged = this.onWebSocketChangedEmitter.event; protected readonly onBaudRateChangedEmitter = - new Emitter(); + new Emitter(); readonly onBaudRateChanged = this.onBaudRateChangedEmitter.event; protected readonly onLineEndingChangedEmitter = - new Emitter(); + new Emitter(); readonly onLineEndingChanged = this.onLineEndingChangedEmitter.event; protected readonly onInterpolateChangedEmitter = new Emitter(); readonly onInterpolateChanged = this.onInterpolateChangedEmitter.event; - notifyError(error: MonitorError): void { + notifyError(error: SerialError): void { this.onErrorEmitter.fire(error); } @@ -34,11 +34,11 @@ export class MonitorServiceClientImpl implements MonitorServiceClient { this.onWebSocketChangedEmitter.fire(message); } - notifyBaudRateChanged(message: MonitorConfig.BaudRate): void { + notifyBaudRateChanged(message: SerialConfig.BaudRate): void { this.onBaudRateChangedEmitter.fire(message); } - notifyLineEndingChanged(message: MonitorModel.EOL): void { + notifyLineEndingChanged(message: SerialModel.EOL): void { this.onLineEndingChangedEmitter.fire(message); } diff --git a/arduino-ide-extension/src/common/protocol/index.ts b/arduino-ide-extension/src/common/protocol/index.ts index de17ea88b..101905752 100644 --- a/arduino-ide-extension/src/common/protocol/index.ts +++ b/arduino-ide-extension/src/common/protocol/index.ts @@ -6,7 +6,7 @@ export * from './core-service'; export * from './filesystem-ext'; export * from './installable'; export * from './library-service'; -export * from './monitor-service'; +export * from './serial-service'; export * from './searchable'; export * from './sketches-service'; export * from './examples-service'; diff --git a/arduino-ide-extension/src/common/protocol/monitor-service.ts b/arduino-ide-extension/src/common/protocol/serial-service.ts similarity index 60% rename from arduino-ide-extension/src/common/protocol/monitor-service.ts rename to arduino-ide-extension/src/common/protocol/serial-service.ts index 6d427bc60..0aa2793fa 100644 --- a/arduino-ide-extension/src/common/protocol/monitor-service.ts +++ b/arduino-ide-extension/src/common/protocol/serial-service.ts @@ -1,8 +1,8 @@ import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory'; import { Board, Port } from './boards-service'; import { Event } from '@theia/core/lib/common/event'; -import { SerialPlotter } from '../../browser/plotter/protocol'; -import { MonitorModel } from '../../browser/monitor/monitor-model'; +import { SerialPlotter } from '../../browser/serial/plotter/protocol'; +import { SerialModel } from '../../browser/serial/serial-model'; export interface Status {} export type OK = Status; @@ -20,32 +20,32 @@ export namespace Status { }; } -export const MonitorServicePath = '/services/serial-monitor'; -export const MonitorService = Symbol('MonitorService'); -export interface MonitorService extends JsonRpcServer { - connect(config: MonitorConfig): Promise; +export const SerialServicePath = '/services/serial'; +export const SerialService = Symbol('SerialService'); +export interface SerialService extends JsonRpcServer { + connect(config: SerialConfig): Promise; disconnect(): Promise; sendMessageToSerial(message: string): Promise; updateWsConfigParam(config: Partial): Promise; } -export interface MonitorConfig { +export interface SerialConfig { readonly board: Board; readonly port: Port; /** * Defaults to [`SERIAL`](MonitorConfig#ConnectionType#SERIAL). */ - readonly type?: MonitorConfig.ConnectionType; + readonly type?: SerialConfig.ConnectionType; /** * Defaults to `9600`. */ - readonly baudRate?: MonitorConfig.BaudRate; + readonly baudRate?: SerialConfig.BaudRate; } -export namespace MonitorConfig { +export namespace SerialConfig { export const BaudRates = [ 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, ] as const; - export type BaudRate = typeof MonitorConfig.BaudRates[number]; + export type BaudRate = typeof SerialConfig.BaudRates[number]; export namespace BaudRate { export const DEFAULT: BaudRate = 9600; } @@ -55,29 +55,29 @@ export namespace MonitorConfig { } } -export const MonitorServiceClient = Symbol('MonitorServiceClient'); -export interface MonitorServiceClient { - onError: Event; +export const SerialServiceClient = Symbol('SerialServiceClient'); +export interface SerialServiceClient { + onError: Event; onWebSocketChanged: Event; - onLineEndingChanged: Event; - onBaudRateChanged: Event; + onLineEndingChanged: Event; + onBaudRateChanged: Event; onInterpolateChanged: Event; - notifyError(event: MonitorError): void; + notifyError(event: SerialError): void; notifyWebSocketChanged(message: number): void; - notifyLineEndingChanged(message: MonitorModel.EOL): void; - notifyBaudRateChanged(message: MonitorConfig.BaudRate): void; + notifyLineEndingChanged(message: SerialModel.EOL): void; + notifyBaudRateChanged(message: SerialConfig.BaudRate): void; notifyInterpolateChanged(message: boolean): void; } -export interface MonitorError { +export interface SerialError { readonly message: string; /** - * If no `code` is available, clients must reestablish the serial-monitor connection. + * If no `code` is available, clients must reestablish the serial connection. */ readonly code: number | undefined; - readonly config: MonitorConfig; + readonly config: SerialConfig; } -export namespace MonitorError { +export namespace SerialError { export namespace ErrorCodes { /** * The frontend has refreshed the browser, for instance. @@ -88,7 +88,7 @@ export namespace MonitorError { */ export const DEVICE_NOT_CONFIGURED = 2; /** - * Another serial monitor was opened on this port. For another electron-instance, Java IDE. + * Another serial connection was opened on this port. For another electron-instance, Java IDE. */ export const DEVICE_BUSY = 3; } diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index d78eec6a5..a75dc6d78 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -40,13 +40,16 @@ import { ArduinoDaemon, ArduinoDaemonPath, } from '../common/protocol/arduino-daemon'; -import { MonitorServiceImpl } from './monitor/monitor-service-impl'; import { - MonitorService, - MonitorServicePath, - MonitorServiceClient, -} from '../common/protocol/monitor-service'; -import { MonitorClientProvider } from './monitor/monitor-client-provider'; + SerialServiceImpl, + SerialServiceName, +} from './serial/serial-service-impl'; +import { + SerialService, + SerialServicePath, + SerialServiceClient, +} from '../common/protocol/serial-service'; +import { MonitorClientProvider } from './serial/monitor-client-provider'; import { ConfigServiceImpl } from './config-service-impl'; import { EnvVariablesServer as TheiaEnvVariablesServer } from '@theia/core/lib/common/env-variables'; import { EnvVariablesServer } from './theia/env-variables/env-variables-server'; @@ -204,11 +207,11 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(ConnectionContainerModule).toConstantValue( ConnectionContainerModule.create(({ bind, bindBackendService }) => { bind(MonitorClientProvider).toSelf().inSingletonScope(); - bind(MonitorServiceImpl).toSelf().inSingletonScope(); - bind(MonitorService).toService(MonitorServiceImpl); - bindBackendService( - MonitorServicePath, - MonitorService, + bind(SerialServiceImpl).toSelf().inSingletonScope(); + bind(SerialService).toService(SerialServiceImpl); + bindBackendService( + SerialServicePath, + SerialService, (service, client) => { service.setClient(client); client.onDidCloseConnection(() => service.dispose()); @@ -305,14 +308,14 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { .inSingletonScope() .whenTargetNamed('config'); - // Logger for the monitor service. + // Logger for the serial service. bind(ILogger) .toDynamicValue((ctx) => { const parentLogger = ctx.container.get(ILogger); - return parentLogger.child('monitor-service'); + return parentLogger.child(SerialServiceName); }) .inSingletonScope() - .whenTargetNamed('monitor-service'); + .whenTargetNamed(SerialServiceName); bind(DefaultGitInit).toSelf(); rebind(GitInit).toService(DefaultGitInit); diff --git a/arduino-ide-extension/src/node/monitor/monitor-client-provider.ts b/arduino-ide-extension/src/node/serial/monitor-client-provider.ts similarity index 100% rename from arduino-ide-extension/src/node/monitor/monitor-client-provider.ts rename to arduino-ide-extension/src/node/serial/monitor-client-provider.ts diff --git a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts b/arduino-ide-extension/src/node/serial/serial-service-impl.ts similarity index 80% rename from arduino-ide-extension/src/node/monitor/monitor-service-impl.ts rename to arduino-ide-extension/src/node/serial/serial-service-impl.ts index 68aa66da4..c60bca36a 100644 --- a/arduino-ide-extension/src/node/monitor/monitor-service-impl.ts +++ b/arduino-ide-extension/src/node/serial/serial-service-impl.ts @@ -4,12 +4,12 @@ import { injectable, inject, named } from 'inversify'; import { Struct } from 'google-protobuf/google/protobuf/struct_pb'; import { ILogger } from '@theia/core/lib/common/logger'; import { - MonitorService, - MonitorServiceClient, - MonitorConfig, - MonitorError, + SerialService, + SerialServiceClient, + SerialConfig, + SerialError, Status, -} from '../../common/protocol/monitor-service'; +} from '../../common/protocol/serial-service'; import { StreamingOpenRequest, StreamingOpenResponse, @@ -18,17 +18,19 @@ import { import { MonitorClientProvider } from './monitor-client-provider'; import { Board, Port } from '../../common/protocol/boards-service'; import { WebSocketService } from '../web-socket/web-socket-service'; -import { SerialPlotter } from '../../browser/plotter/protocol'; +import { SerialPlotter } from '../../browser/serial/plotter/protocol'; import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol'; +export const SerialServiceName = 'serial-service'; + interface ErrorWithCode extends Error { readonly code: number; } namespace ErrorWithCode { - export function toMonitorError( + export function toSerialError( error: Error, - config: MonitorConfig - ): MonitorError { + config: SerialConfig + ): SerialError { const { message } = error; let code = undefined; if (is(error)) { @@ -36,15 +38,15 @@ namespace ErrorWithCode { const mapping = new Map(); mapping.set( '1 CANCELLED: Cancelled on client', - MonitorError.ErrorCodes.CLIENT_CANCEL + SerialError.ErrorCodes.CLIENT_CANCEL ); mapping.set( '2 UNKNOWN: device not configured', - MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED + SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED ); mapping.set( - '2 UNKNOWN: error opening serial monitor: Serial port busy', - MonitorError.ErrorCodes.DEVICE_BUSY + '2 UNKNOWN: error opening serial connection: Serial port busy', + SerialError.ErrorCodes.DEVICE_BUSY ); code = mapping.get(message); } @@ -60,36 +62,36 @@ namespace ErrorWithCode { } @injectable() -export class MonitorServiceImpl implements MonitorService { +export class SerialServiceImpl implements SerialService { + @named(SerialServiceName) @inject(ILogger) - @named('monitor-service') protected readonly logger: ILogger; @inject(MonitorClientProvider) - protected readonly monitorClientProvider: MonitorClientProvider; + protected readonly serialClientProvider: MonitorClientProvider; @inject(WebSocketService) protected readonly webSocketService: WebSocketService; - protected client?: MonitorServiceClient; + protected client?: SerialServiceClient; protected serialConnection?: { duplex: ClientDuplexStream; - config: MonitorConfig; + config: SerialConfig; }; protected messages: string[] = []; protected onMessageReceived: Disposable | null; protected flushMessagesInterval: NodeJS.Timeout | null; - setClient(client: MonitorServiceClient | undefined): void { + setClient(client: SerialServiceClient | undefined): void { this.client = client; } dispose(): void { - this.logger.info('>>> Disposing monitor service...'); + this.logger.info('>>> Disposing serial service...'); if (this.serialConnection) { this.disconnect(); } - this.logger.info('<<< Disposed monitor service.'); + this.logger.info('<<< Disposed serial service.'); this.client = undefined; } @@ -103,16 +105,16 @@ export class MonitorServiceImpl implements MonitorService { this.webSocketService.sendMessage(JSON.stringify(msg)); } - async connect(config: MonitorConfig): Promise { + async connect(config: SerialConfig): Promise { this.logger.info( - `>>> Creating serial monitor connection for ${Board.toString( + `>>> Creating serial connection for ${Board.toString( config.board )} on port ${Port.toString(config.port)}...` ); if (this.serialConnection) { return Status.ALREADY_CONNECTED; } - const client = await this.monitorClientProvider.client(); + const client = await this.serialClientProvider.client(); if (!client) { return Status.NOT_CONNECTED; } @@ -125,12 +127,12 @@ export class MonitorServiceImpl implements MonitorService { duplex.on( 'error', ((error: Error) => { - const monitorError = ErrorWithCode.toMonitorError(error, config); - this.disconnect(monitorError).then(() => { + const serialError = ErrorWithCode.toSerialError(error, config); + this.disconnect(serialError).then(() => { if (this.client) { - this.client.notifyError(monitorError); + this.client.notifyError(serialError); } - if (monitorError.code === undefined) { + if (serialError.code === undefined) { // Log the original, unexpected error. this.logger.error(error); } @@ -161,7 +163,7 @@ export class MonitorServiceImpl implements MonitorService { case SerialPlotter.Protocol.Command.PLOTTER_SET_BAUDRATE: this.client?.notifyBaudRateChanged( - parseInt(message.data, 10) as MonitorConfig.BaudRate + parseInt(message.data, 10) as SerialConfig.BaudRate ); break; @@ -233,10 +235,9 @@ export class MonitorServiceImpl implements MonitorService { if (this.serialConnection) { this.serialConnection.duplex.write(req, () => { this.logger.info( - `<<< Serial monitor connection created for ${Board.toString( - config.board, - { useFqbn: false } - )} on port ${Port.toString(config.port)}.` + `<<< Serial connection created for ${Board.toString(config.board, { + useFqbn: false, + })} on port ${Port.toString(config.port)}.` ); resolve(Status.OK); }); @@ -246,7 +247,7 @@ export class MonitorServiceImpl implements MonitorService { }); } - async disconnect(reason?: MonitorError): Promise { + async disconnect(reason?: SerialError): Promise { try { if (this.onMessageReceived) { this.onMessageReceived.dispose(); @@ -260,11 +261,11 @@ export class MonitorServiceImpl implements MonitorService { if ( !this.serialConnection && reason && - reason.code === MonitorError.ErrorCodes.CLIENT_CANCEL + reason.code === SerialError.ErrorCodes.CLIENT_CANCEL ) { return Status.OK; } - this.logger.info('>>> Disposing monitor connection...'); + this.logger.info('>>> Disposing serial connection...'); if (!this.serialConnection) { this.logger.warn('<<< Not connected. Nothing to dispose.'); return Status.NOT_CONNECTED; @@ -272,7 +273,7 @@ export class MonitorServiceImpl implements MonitorService { const { duplex, config } = this.serialConnection; duplex.cancel(); this.logger.info( - `<<< Disposed monitor connection for ${Board.toString(config.board, { + `<<< Disposed serial connection for ${Board.toString(config.board, { useFqbn: false, })} on port ${Port.toString(config.port)}.` ); @@ -301,10 +302,10 @@ export class MonitorServiceImpl implements MonitorService { } protected mapType( - type?: MonitorConfig.ConnectionType + type?: SerialConfig.ConnectionType ): GrpcMonitorConfig.TargetType { switch (type) { - case MonitorConfig.ConnectionType.SERIAL: + case SerialConfig.ConnectionType.SERIAL: return GrpcMonitorConfig.TargetType.TARGET_TYPE_SERIAL; default: return GrpcMonitorConfig.TargetType.TARGET_TYPE_SERIAL; diff --git a/arduino-ide-extension/src/test/browser/fixtures/serial.ts b/arduino-ide-extension/src/test/browser/fixtures/serial.ts index 85003d66d..ab8b333a6 100644 --- a/arduino-ide-extension/src/test/browser/fixtures/serial.ts +++ b/arduino-ide-extension/src/test/browser/fixtures/serial.ts @@ -1,13 +1,13 @@ -import { MonitorConfig } from '../../../common/protocol/monitor-service'; +import { SerialConfig } from '../../../common/protocol/serial-service'; import { aBoard, anotherBoard, anotherPort, aPort } from './boards'; -export const aSerialConfig: MonitorConfig = { +export const aSerialConfig: SerialConfig = { board: aBoard, port: aPort, baudRate: 9600, }; -export const anotherSerialConfig: MonitorConfig = { +export const anotherSerialConfig: SerialConfig = { board: anotherBoard, port: anotherPort, baudRate: 9600, diff --git a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts index f3056da48..cf1025740 100644 --- a/arduino-ide-extension/src/test/browser/monitor-utils.test.ts +++ b/arduino-ide-extension/src/test/browser/monitor-utils.test.ts @@ -2,8 +2,8 @@ import { expect } from 'chai'; import { messagesToLines, truncateLines, -} from '../../browser/monitor/monitor-utils'; -import { Line } from '../../browser/monitor/serial-monitor-send-output'; +} from '../../browser/serial/monitor/monitor-utils'; +import { Line } from '../../browser/serial/monitor/serial-monitor-send-output'; import { set, reset } from 'mockdate'; type TestLine = { diff --git a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts index 9a5b43e74..6d9a96730 100644 --- a/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts +++ b/arduino-ide-extension/src/test/browser/serial-connection-manager.test.ts @@ -7,22 +7,22 @@ FrontendApplicationConfigProvider.set({ ...ApplicationProps.DEFAULT.frontend.config, }); -import { Emitter, MessageService } from '@theia/core'; +import { MessageService } from '@theia/core'; import { BoardsServiceProvider } from '../../browser/boards/boards-service-provider'; import { BoardsService, CoreService, - MonitorService, - MonitorServiceClient, + SerialService, + SerialServiceClient, Status, } from '../../common/protocol'; import { IMock, It, Mock, Times } from 'typemoq'; import { Serial, SerialConnectionManager, -} from '../../browser/monitor/monitor-connection'; +} from '../../browser/serial/serial-connection-manager'; import { ThemeService } from '@theia/core/lib/browser/theming'; -import { MonitorModel } from '../../browser/monitor/monitor-model'; +import { SerialModel } from '../../browser/serial/serial-model'; import { aBoardConfig, anotherBoardConfig, @@ -45,9 +45,9 @@ global.WebSocket = WebSocketMock as any; describe.only('SerialConnectionManager', () => { let subject: SerialConnectionManager; - let monitorModel: IMock; - let monitorService: IMock; - let monitorServiceClient: IMock; + let serialModel: IMock; + let serialService: IMock; + let serialServiceClient: IMock; let boardsService: IMock; let boardsServiceProvider: IMock; let messageService: IMock; @@ -60,12 +60,10 @@ describe.only('SerialConnectionManager', () => { let handleWebSocketChanged: (wsPort: number) => void; const wsPort = 1234; - const onBoardsConfigChangedEmitter = new Emitter(); - console.log(onBoardsConfigChangedEmitter); beforeEach(() => { - monitorModel = Mock.ofType(); - monitorService = Mock.ofType(); - monitorServiceClient = Mock.ofType(); + serialModel = Mock.ofType(); + serialService = Mock.ofType(); + serialServiceClient = Mock.ofType(); boardsService = Mock.ofType(); boardsServiceProvider = Mock.ofType(); messageService = Mock.ofType(); @@ -91,27 +89,27 @@ describe.only('SerialConnectionManager', () => { .setup((b) => b.getAvailablePorts()) .returns(() => Promise.resolve([aPort, anotherPort])); - monitorModel + serialModel .setup((m) => m.baudRate) .returns(() => aSerialConfig.baudRate || 9600); - monitorServiceClient + serialServiceClient .setup((m) => m.onWebSocketChanged(It.isAny())) .returns((h) => { handleWebSocketChanged = h; return { dispose: () => {} }; }); - monitorService + serialService .setup((m) => m.disconnect()) .returns(() => Promise.resolve(Status.OK)); core.setup((u) => u.isUploading()).returns(() => Promise.resolve(false)); subject = new SerialConnectionManager( - monitorModel.object, - monitorService.object, - monitorServiceClient.object, + serialModel.object, + serialService.object, + serialServiceClient.object, boardsService.object, boardsServiceProvider.object, messageService.object, @@ -126,15 +124,15 @@ describe.only('SerialConnectionManager', () => { it('should not try to connect and show an error', async () => { await subject.openSerial(Serial.Type.Plotter); messageService.verify((m) => m.error(It.isAnyString()), Times.once()); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify((m) => m.connect(It.isAny()), Times.never()); + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify((m) => m.connect(It.isAny()), Times.never()); }); }); context('and a serial config is set', () => { it('should not try to reconnect', async () => { await handleBoardConfigChange(aBoardConfig); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify((m) => m.connect(It.isAny()), Times.never()); + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify((m) => m.connect(It.isAny()), Times.never()); expect(subject.getConfig()).to.deep.equal(aSerialConfig); }); }); @@ -162,15 +160,15 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify((m) => m.connect(It.isAny()), Times.never()); + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify((m) => m.connect(It.isAny()), Times.never()); }); }); context( 'and the connection to the serial succeeds with the config', () => { beforeEach(() => { - monitorService + serialService .setup((m) => m.connect(It.isValue(aSerialConfig))) .returns(() => { handleWebSocketChanged(wsPort); @@ -187,8 +185,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify((m) => m.connect(It.isAny()), Times.once()); + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify((m) => m.connect(It.isAny()), Times.once()); expect(status).to.be.ok; expect(subject.connected).to.be.true; expect(subject.getWsPort()).to.equal(wsPort); @@ -205,8 +203,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify( (m) => m.connect(It.isAny()), Times.once() ); @@ -227,8 +225,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify( (m) => m.connect(It.isAny()), Times.once() ); @@ -250,8 +248,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify( (m) => m.connect(It.isAny()), Times.once() ); @@ -274,8 +272,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.once()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.once()); + serialService.verify( (m) => m.connect(It.isAny()), Times.once() ); @@ -296,8 +294,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.once()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.once()); + serialService.verify( (m) => m.connect(It.isAny()), Times.exactly(2) ); @@ -310,12 +308,12 @@ describe.only('SerialConnectionManager', () => { 'and the connection to the serial does NOT succeed with the config', () => { beforeEach(() => { - monitorService + serialService .setup((m) => m.connect(It.isValue(aSerialConfig))) .returns(() => { return Promise.resolve(Status.NOT_CONNECTED); }); - monitorService + serialService .setup((m) => m.connect(It.isValue(anotherSerialConfig))) .returns(() => { handleWebSocketChanged(wsPort); @@ -333,8 +331,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify( (m) => m.connect(It.isValue(aSerialConfig)), Times.once() ); @@ -357,8 +355,8 @@ describe.only('SerialConnectionManager', () => { (m) => m.error(It.isAnyString()), Times.never() ); - monitorService.verify((m) => m.disconnect(), Times.never()); - monitorService.verify( + serialService.verify((m) => m.disconnect(), Times.never()); + serialService.verify( (m) => m.connect(It.isValue(anotherSerialConfig)), Times.once() ); From 118f7f0a601c7d03a72baff0dc12fdada8588794 Mon Sep 17 00:00:00 2001 From: Alberto Iannaccone Date: Tue, 23 Nov 2021 16:32:35 +0100 Subject: [PATCH 27/27] update localisation for serial --- .../src/browser/contributions/upload-sketch.ts | 4 ++-- .../monitor/monitor-view-contribution.tsx | 6 +++--- .../browser/serial/monitor/monitor-widget.tsx | 13 +++++-------- .../monitor/serial-monitor-send-input.tsx | 4 ++-- .../browser/serial/serial-connection-manager.ts | 12 ++++++------ i18n/en.json | 17 ++++++++--------- 6 files changed, 26 insertions(+), 30 deletions(-) diff --git a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts index fa298c603..c464ec811 100644 --- a/arduino-ide-extension/src/browser/contributions/upload-sketch.ts +++ b/arduino-ide-extension/src/browser/contributions/upload-sketch.ts @@ -181,8 +181,8 @@ export class UploadSketch extends SketchContribution { } catch (waitError) { this.messageService.error( nls.localize( - 'arduino/sketch/couldNotConnectToMonitor', - 'Could not reconnect to serial monitor. {0}', + 'arduino/sketch/couldNotConnectToSerial', + 'Could not reconnect to serial port. {0}', waitError.toString() ) ); diff --git a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx index 40261928b..0f2d0257f 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/monitor-view-contribution.tsx @@ -19,14 +19,14 @@ export namespace SerialMonitor { id: 'serial-monitor-autoscroll', label: 'Autoscroll', }, - 'arduino/monitor/autoscroll' + 'arduino/serial/autoscroll' ); export const TIMESTAMP = Command.toLocalizedCommand( { id: 'serial-monitor-timestamp', label: 'Timestamp', }, - 'arduino/monitor/timestamp' + 'arduino/serial/timestamp' ); export const CLEAR_OUTPUT = Command.toLocalizedCommand( { @@ -156,7 +156,7 @@ export class MonitorViewContribution
{ return [ { - label: nls.localize('arduino/monitor/noLineEndings', 'No Line Ending'), + label: nls.localize('arduino/serial/noLineEndings', 'No Line Ending'), value: '', }, { - label: nls.localize('arduino/monitor/newLine', 'New Line'), + label: nls.localize('arduino/serial/newLine', 'New Line'), value: '\n', }, { - label: nls.localize( - 'arduino/monitor/carriageReturn', - 'Carriage Return' - ), + label: nls.localize('arduino/serial/carriageReturn', 'Carriage Return'), value: '\r', }, { label: nls.localize( - 'arduino/monitor/newLineCarriageReturn', + 'arduino/serial/newLineCarriageReturn', 'Both NL & CR' ), value: '\r\n', diff --git a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx index df4001978..8e2335a48 100644 --- a/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx +++ b/arduino-ide-extension/src/browser/serial/monitor/serial-monitor-send-input.tsx @@ -46,13 +46,13 @@ export class SerialMonitorSendInput extends React.Component< const { serialConfig } = this.props; if (!serialConfig) { return nls.localize( - 'arduino/monitor/notConnected', + 'arduino/serial/notConnected', 'Not connected. Select a board and a port to connect automatically.' ); } const { board, port } = serialConfig; return nls.localize( - 'arduino/monitor/message', + 'arduino/serial/message', "Message ({0} + Enter to send message to '{1}' on '{2}'", isOSX ? '⌘' : nls.localize('vscode/keybindingLabels/ctrlKey', 'Ctrl'), Board.toString(board, { diff --git a/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts b/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts index 75ea4d978..ca7ef1eb0 100644 --- a/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts +++ b/arduino-ide-extension/src/browser/serial/serial-connection-manager.ts @@ -292,7 +292,7 @@ export class SerialConnectionManager { case SerialError.ErrorCodes.DEVICE_BUSY: { this.messageService.warn( nls.localize( - 'arduino/monitor/connectionBusy', + 'arduino/serial/connectionBusy', 'Connection failed. Serial port is busy: {0}', Port.toString(port) ), @@ -304,7 +304,7 @@ export class SerialConnectionManager { case SerialError.ErrorCodes.DEVICE_NOT_CONFIGURED: { this.messageService.info( nls.localize( - 'arduino/monitor/disconnected', + 'arduino/serial/disconnected', 'Disconnected {0} from {1}.', Board.toString(board, { useFqbn: false, @@ -318,7 +318,7 @@ export class SerialConnectionManager { case undefined: { this.messageService.error( nls.localize( - 'arduino/monitor/unexpectedError', + 'arduino/serial/unexpectedError', 'Unexpected error. Reconnecting {0} on port {1}.', Board.toString(board), Port.toString(port) @@ -335,8 +335,8 @@ export class SerialConnectionManager { if (this.serialErrors.length >= 10) { this.messageService.warn( nls.localize( - 'arduino/monitor/failedReconnect', - 'Failed to reconnect {0} to serial after 10 consecutive attempts. The {1} serial port is busy.', + 'arduino/serial/failedReconnect', + 'Failed to reconnect {0} to serial port after 10 consecutive attempts. The {1} serial port is busy.', Board.toString(board, { useFqbn: false, }), @@ -353,7 +353,7 @@ export class SerialConnectionManager { const timeout = attempts * 1000; this.messageService.warn( nls.localize( - 'arduino/monitor/reconnect', + 'arduino/serial/reconnect', 'Reconnecting {0} to {1} in {2] seconds...', Board.toString(board, { useFqbn: false, diff --git a/i18n/en.json b/i18n/en.json index cc49fc153..706d7b1be 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -179,7 +179,7 @@ "upload": "Upload", "uploadUsingProgrammer": "Upload Using Programmer", "doneUploading": "Done uploading.", - "couldNotConnectToMonitor": "Could not reconnect to serial monitor. {0}", + "couldNotConnectToSerial": "Could not reconnect to serial port. {0}", "verifyOrCompile": "Verify/Compile", "exportBinary": "Export Compiled Binary", "verify": "Verify", @@ -253,22 +253,21 @@ "dialog": { "dontAskAgain": "Don't ask again" }, - "monitor": { - "connectionBusy": "Connection failed. Serial port is busy: {0}", - "disconnected": "Disconnected {0} from {1}.", - "unexpectedError": "Unexpected error. Reconnecting {0} on port {1}.", - "failedReconnect": "Failed to reconnect {0} to serial after 10 consecutive attempts. The {1} serial port is busy.", - "reconnect": "Reconnecting {0} to {1} in {2] seconds...", + "serial": { "toggleTimestamp": "Toggle Timestamp", "autoscroll": "Autoscroll", "timestamp": "Timestamp", - "title": "Serial Monitor", "noLineEndings": "No Line Ending", "newLine": "New Line", "carriageReturn": "Carriage Return", "newLineCarriageReturn": "Both NL & CR", "notConnected": "Not connected. Select a board and a port to connect automatically.", - "message": "Message ({0} + Enter to send message to '{1}' on '{2}'" + "message": "Message ({0} + Enter to send message to '{1}' on '{2}'", + "connectionBusy": "Connection failed. Serial port is busy: {0}", + "disconnected": "Disconnected {0} from {1}.", + "unexpectedError": "Unexpected error. Reconnecting {0} on port {1}.", + "failedReconnect": "Failed to reconnect {0} to serial port after 10 consecutive attempts. The {1} serial port is busy.", + "reconnect": "Reconnecting {0} to {1} in {2] seconds..." }, "component": { "uninstall": "Uninstall",