mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-10 21:06:33 +00:00
fix: propagate monitor errors to the frontend
- Handle when the board's platform is not installed (Closes #1974) - UX: Smoother monitor widget reset (Closes #1985) - Fixed monitor <input> readOnly state (Closes #1984) - Set monitor widget header color (Ref #682) Closes #1508 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
parent
ab5c63c4b7
commit
80d5b5afa7
@ -496,15 +496,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
|||||||
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
|
bind(TabBarToolbarContribution).toService(MonitorViewContribution);
|
||||||
bind(WidgetFactory).toDynamicValue((context) => ({
|
bind(WidgetFactory).toDynamicValue((context) => ({
|
||||||
id: MonitorWidget.ID,
|
id: MonitorWidget.ID,
|
||||||
createWidget: () => {
|
createWidget: () => context.container.get(MonitorWidget),
|
||||||
return new MonitorWidget(
|
|
||||||
context.container.get<MonitorModel>(MonitorModel),
|
|
||||||
context.container.get<MonitorManagerProxyClient>(
|
|
||||||
MonitorManagerProxyClient
|
|
||||||
),
|
|
||||||
context.container.get<BoardsServiceProvider>(BoardsServiceProvider)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
bind(MonitorManagerProxyFactory).toFactory(
|
bind(MonitorManagerProxyFactory).toFactory(
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
CommandRegistry,
|
ApplicationError,
|
||||||
Disposable,
|
Disposable,
|
||||||
Emitter,
|
Emitter,
|
||||||
MessageService,
|
MessageService,
|
||||||
nls,
|
nls,
|
||||||
} from '@theia/core';
|
} from '@theia/core';
|
||||||
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
|
import { NotificationManager } from '@theia/messages/lib/browser/notifications-manager';
|
||||||
|
import { MessageType } from '@theia/core/lib/common/message-service-protocol';
|
||||||
import { Board, Port } from '../common/protocol';
|
import { Board, Port } from '../common/protocol';
|
||||||
import {
|
import {
|
||||||
Monitor,
|
Monitor,
|
||||||
@ -23,21 +26,31 @@ import { BoardsServiceProvider } from './boards/boards-service-provider';
|
|||||||
export class MonitorManagerProxyClientImpl
|
export class MonitorManagerProxyClientImpl
|
||||||
implements MonitorManagerProxyClient
|
implements MonitorManagerProxyClient
|
||||||
{
|
{
|
||||||
|
@inject(MessageService)
|
||||||
|
private readonly messageService: MessageService;
|
||||||
|
// This is necessary to call the backend methods from the frontend
|
||||||
|
@inject(MonitorManagerProxyFactory)
|
||||||
|
private readonly server: MonitorManagerProxyFactory;
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
@inject(NotificationManager)
|
||||||
|
private readonly notificationManager: NotificationManager;
|
||||||
|
|
||||||
// When pluggable monitor messages are received from the backend
|
// When pluggable monitor messages are received from the backend
|
||||||
// this event is triggered.
|
// this event is triggered.
|
||||||
// Ideally a frontend component is connected to this event
|
// Ideally a frontend component is connected to this event
|
||||||
// to update the UI.
|
// to update the UI.
|
||||||
protected readonly onMessagesReceivedEmitter = new Emitter<{
|
private readonly onMessagesReceivedEmitter = new Emitter<{
|
||||||
messages: string[];
|
messages: string[];
|
||||||
}>();
|
}>();
|
||||||
readonly onMessagesReceived = this.onMessagesReceivedEmitter.event;
|
readonly onMessagesReceived = this.onMessagesReceivedEmitter.event;
|
||||||
|
|
||||||
protected readonly onMonitorSettingsDidChangeEmitter =
|
private readonly onMonitorSettingsDidChangeEmitter =
|
||||||
new Emitter<MonitorSettings>();
|
new Emitter<MonitorSettings>();
|
||||||
readonly onMonitorSettingsDidChange =
|
readonly onMonitorSettingsDidChange =
|
||||||
this.onMonitorSettingsDidChangeEmitter.event;
|
this.onMonitorSettingsDidChangeEmitter.event;
|
||||||
|
|
||||||
protected readonly onMonitorShouldResetEmitter = new Emitter();
|
private readonly onMonitorShouldResetEmitter = new Emitter<void>();
|
||||||
readonly onMonitorShouldReset = this.onMonitorShouldResetEmitter.event;
|
readonly onMonitorShouldReset = this.onMonitorShouldResetEmitter.event;
|
||||||
|
|
||||||
// WebSocket used to handle pluggable monitor communication between
|
// WebSocket used to handle pluggable monitor communication between
|
||||||
@ -51,29 +64,16 @@ export class MonitorManagerProxyClientImpl
|
|||||||
return this.wsPort;
|
return this.wsPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
|
||||||
@inject(MessageService)
|
|
||||||
protected messageService: MessageService,
|
|
||||||
|
|
||||||
// This is necessary to call the backend methods from the frontend
|
|
||||||
@inject(MonitorManagerProxyFactory)
|
|
||||||
protected server: MonitorManagerProxyFactory,
|
|
||||||
|
|
||||||
@inject(CommandRegistry)
|
|
||||||
protected readonly commandRegistry: CommandRegistry,
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
|
||||||
protected readonly boardsServiceProvider: BoardsServiceProvider
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connects a localhost WebSocket using the specified port.
|
* Connects a localhost WebSocket using the specified port.
|
||||||
* @param addressPort port of the WebSocket
|
* @param addressPort port of the WebSocket
|
||||||
*/
|
*/
|
||||||
async connect(addressPort: number): Promise<void> {
|
async connect(addressPort: number): Promise<void> {
|
||||||
if (!!this.webSocket) {
|
if (this.webSocket) {
|
||||||
if (this.wsPort === addressPort) return;
|
if (this.wsPort === addressPort) {
|
||||||
else this.disconnect();
|
return;
|
||||||
|
}
|
||||||
|
this.disconnect();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this.webSocket = new WebSocket(`ws://localhost:${addressPort}`);
|
this.webSocket = new WebSocket(`ws://localhost:${addressPort}`);
|
||||||
@ -87,6 +87,9 @@ export class MonitorManagerProxyClientImpl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const opened = new Deferred<void>();
|
||||||
|
this.webSocket.onopen = () => opened.resolve();
|
||||||
|
this.webSocket.onerror = () => opened.reject();
|
||||||
this.webSocket.onmessage = (message) => {
|
this.webSocket.onmessage = (message) => {
|
||||||
const parsedMessage = JSON.parse(message.data);
|
const parsedMessage = JSON.parse(message.data);
|
||||||
if (Array.isArray(parsedMessage))
|
if (Array.isArray(parsedMessage))
|
||||||
@ -99,19 +102,26 @@ export class MonitorManagerProxyClientImpl
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.wsPort = addressPort;
|
this.wsPort = addressPort;
|
||||||
|
return opened.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnects the WebSocket if connected.
|
* Disconnects the WebSocket if connected.
|
||||||
*/
|
*/
|
||||||
disconnect(): void {
|
disconnect(): void {
|
||||||
if (!this.webSocket) return;
|
if (!this.webSocket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.onBoardsConfigChanged?.dispose();
|
this.onBoardsConfigChanged?.dispose();
|
||||||
this.onBoardsConfigChanged = undefined;
|
this.onBoardsConfigChanged = undefined;
|
||||||
try {
|
try {
|
||||||
this.webSocket?.close();
|
this.webSocket.close();
|
||||||
this.webSocket = undefined;
|
this.webSocket = undefined;
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
'Could not close the websocket connection for the monitor.',
|
||||||
|
err
|
||||||
|
);
|
||||||
this.messageService.error(
|
this.messageService.error(
|
||||||
nls.localize(
|
nls.localize(
|
||||||
'arduino/monitor/unableToCloseWebSocket',
|
'arduino/monitor/unableToCloseWebSocket',
|
||||||
@ -126,6 +136,7 @@ export class MonitorManagerProxyClientImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
async startMonitor(settings?: PluggableMonitorSettings): Promise<void> {
|
async startMonitor(settings?: PluggableMonitorSettings): Promise<void> {
|
||||||
|
await this.boardsServiceProvider.reconciled;
|
||||||
this.lastConnectedBoard = {
|
this.lastConnectedBoard = {
|
||||||
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
selectedBoard: this.boardsServiceProvider.boardsConfig.selectedBoard,
|
||||||
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort,
|
selectedPort: this.boardsServiceProvider.boardsConfig.selectedPort,
|
||||||
@ -150,11 +161,11 @@ export class MonitorManagerProxyClientImpl
|
|||||||
? Port.keyOf(this.lastConnectedBoard.selectedPort)
|
? Port.keyOf(this.lastConnectedBoard.selectedPort)
|
||||||
: undefined)
|
: undefined)
|
||||||
) {
|
) {
|
||||||
this.onMonitorShouldResetEmitter.fire(null);
|
|
||||||
this.lastConnectedBoard = {
|
this.lastConnectedBoard = {
|
||||||
selectedBoard: selectedBoard,
|
selectedBoard: selectedBoard,
|
||||||
selectedPort: selectedPort,
|
selectedPort: selectedPort,
|
||||||
};
|
};
|
||||||
|
this.onMonitorShouldResetEmitter.fire();
|
||||||
} else {
|
} else {
|
||||||
// a board is plugged and it's the same as prev, rerun "this.startMonitor" to
|
// a board is plugged and it's the same as prev, rerun "this.startMonitor" to
|
||||||
// recreate the listener callback
|
// recreate the listener callback
|
||||||
@ -167,7 +178,14 @@ export class MonitorManagerProxyClientImpl
|
|||||||
const { selectedBoard, selectedPort } =
|
const { selectedBoard, selectedPort } =
|
||||||
this.boardsServiceProvider.boardsConfig;
|
this.boardsServiceProvider.boardsConfig;
|
||||||
if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return;
|
if (!selectedBoard || !selectedBoard.fqbn || !selectedPort) return;
|
||||||
await this.server().startMonitor(selectedBoard, selectedPort, settings);
|
try {
|
||||||
|
this.clearVisibleNotification();
|
||||||
|
await this.server().startMonitor(selectedBoard, selectedPort, settings);
|
||||||
|
} catch (err) {
|
||||||
|
const message = ApplicationError.is(err) ? err.message : String(err);
|
||||||
|
this.previousNotificationId = this.notificationId(message);
|
||||||
|
this.messageService.error(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentSettings(board: Board, port: Port): Promise<MonitorSettings> {
|
getCurrentSettings(board: Board, port: Port): Promise<MonitorSettings> {
|
||||||
@ -199,4 +217,24 @@ export class MonitorManagerProxyClientImpl
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the internal (Theia) ID of the notification that is currently visible.
|
||||||
|
* It's stored here as a field to be able to close it before starting a new monitor connection. It's a hack.
|
||||||
|
*/
|
||||||
|
private previousNotificationId: string | undefined;
|
||||||
|
private clearVisibleNotification(): void {
|
||||||
|
if (this.previousNotificationId) {
|
||||||
|
this.notificationManager.clear(this.previousNotificationId);
|
||||||
|
this.previousNotificationId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private notificationId(message: string, ...actions: string[]): string {
|
||||||
|
return this.notificationManager['getMessageId']({
|
||||||
|
text: message,
|
||||||
|
actions,
|
||||||
|
type: MessageType.Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,14 @@ import {
|
|||||||
LocalStorageService,
|
LocalStorageService,
|
||||||
} from '@theia/core/lib/browser';
|
} from '@theia/core/lib/browser';
|
||||||
import { inject, injectable } from '@theia/core/shared/inversify';
|
import { inject, injectable } from '@theia/core/shared/inversify';
|
||||||
import { MonitorManagerProxyClient } from '../common/protocol';
|
import {
|
||||||
|
isMonitorConnected,
|
||||||
|
MonitorConnectionStatus,
|
||||||
|
monitorConnectionStatusEquals,
|
||||||
|
MonitorEOL,
|
||||||
|
MonitorManagerProxyClient,
|
||||||
|
MonitorState,
|
||||||
|
} from '../common/protocol';
|
||||||
import { isNullOrUndefined } from '../common/utils';
|
import { isNullOrUndefined } from '../common/utils';
|
||||||
import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider';
|
import { MonitorSettings } from '../node/monitor-settings/monitor-settings-provider';
|
||||||
|
|
||||||
@ -19,36 +26,36 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
protected readonly monitorManagerProxy: MonitorManagerProxyClient;
|
protected readonly monitorManagerProxy: MonitorManagerProxyClient;
|
||||||
|
|
||||||
protected readonly onChangeEmitter: Emitter<
|
protected readonly onChangeEmitter: Emitter<
|
||||||
MonitorModel.State.Change<keyof MonitorModel.State>
|
MonitorState.Change<keyof MonitorState>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
protected _autoscroll: boolean;
|
protected _autoscroll: boolean;
|
||||||
protected _timestamp: boolean;
|
protected _timestamp: boolean;
|
||||||
protected _lineEnding: MonitorModel.EOL;
|
protected _lineEnding: MonitorEOL;
|
||||||
protected _interpolate: boolean;
|
protected _interpolate: boolean;
|
||||||
protected _darkTheme: boolean;
|
protected _darkTheme: boolean;
|
||||||
protected _wsPort: number;
|
protected _wsPort: number;
|
||||||
protected _serialPort: string;
|
protected _serialPort: string;
|
||||||
protected _connected: boolean;
|
protected _connectionStatus: MonitorConnectionStatus;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._autoscroll = true;
|
this._autoscroll = true;
|
||||||
this._timestamp = false;
|
this._timestamp = false;
|
||||||
this._interpolate = false;
|
this._interpolate = false;
|
||||||
this._lineEnding = MonitorModel.EOL.DEFAULT;
|
this._lineEnding = MonitorEOL.DEFAULT;
|
||||||
this._darkTheme = false;
|
this._darkTheme = false;
|
||||||
this._wsPort = 0;
|
this._wsPort = 0;
|
||||||
this._serialPort = '';
|
this._serialPort = '';
|
||||||
this._connected = true;
|
this._connectionStatus = 'not-connected';
|
||||||
|
|
||||||
this.onChangeEmitter = new Emitter<
|
this.onChangeEmitter = new Emitter<
|
||||||
MonitorModel.State.Change<keyof MonitorModel.State>
|
MonitorState.Change<keyof MonitorState>
|
||||||
>();
|
>();
|
||||||
}
|
}
|
||||||
|
|
||||||
onStart(): void {
|
onStart(): void {
|
||||||
this.localStorageService
|
this.localStorageService
|
||||||
.getData<MonitorModel.State>(MonitorModel.STORAGE_ID)
|
.getData<MonitorState>(MonitorModel.STORAGE_ID)
|
||||||
.then(this.restoreState.bind(this));
|
.then(this.restoreState.bind(this));
|
||||||
|
|
||||||
this.monitorManagerProxy.onMonitorSettingsDidChange(
|
this.monitorManagerProxy.onMonitorSettingsDidChange(
|
||||||
@ -56,11 +63,11 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get onChange(): Event<MonitorModel.State.Change<keyof MonitorModel.State>> {
|
get onChange(): Event<MonitorState.Change<keyof MonitorState>> {
|
||||||
return this.onChangeEmitter.event;
|
return this.onChangeEmitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected restoreState(state: MonitorModel.State): void {
|
protected restoreState(state: MonitorState): void {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -125,11 +132,11 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
this.timestamp = !this._timestamp;
|
this.timestamp = !this._timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
get lineEnding(): MonitorModel.EOL {
|
get lineEnding(): MonitorEOL {
|
||||||
return this._lineEnding;
|
return this._lineEnding;
|
||||||
}
|
}
|
||||||
|
|
||||||
set lineEnding(lineEnding: MonitorModel.EOL) {
|
set lineEnding(lineEnding: MonitorEOL) {
|
||||||
if (lineEnding === this._lineEnding) return;
|
if (lineEnding === this._lineEnding) return;
|
||||||
this._lineEnding = lineEnding;
|
this._lineEnding = lineEnding;
|
||||||
this.monitorManagerProxy.changeSettings({
|
this.monitorManagerProxy.changeSettings({
|
||||||
@ -211,19 +218,26 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get connected(): boolean {
|
get connectionStatus(): MonitorConnectionStatus {
|
||||||
return this._connected;
|
return this._connectionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
set connected(connected: boolean) {
|
set connectionStatus(connectionStatus: MonitorConnectionStatus) {
|
||||||
if (connected === this._connected) return;
|
if (
|
||||||
this._connected = connected;
|
monitorConnectionStatusEquals(connectionStatus, this.connectionStatus)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._connectionStatus = connectionStatus;
|
||||||
this.monitorManagerProxy.changeSettings({
|
this.monitorManagerProxy.changeSettings({
|
||||||
monitorUISettings: { connected },
|
monitorUISettings: {
|
||||||
|
connectionStatus,
|
||||||
|
connected: isMonitorConnected(connectionStatus),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.onChangeEmitter.fire({
|
this.onChangeEmitter.fire({
|
||||||
property: 'connected',
|
property: 'connectionStatus',
|
||||||
value: this._connected,
|
value: this._connectionStatus,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +252,7 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
darkTheme,
|
darkTheme,
|
||||||
wsPort,
|
wsPort,
|
||||||
serialPort,
|
serialPort,
|
||||||
connected,
|
connectionStatus,
|
||||||
} = monitorUISettings;
|
} = monitorUISettings;
|
||||||
|
|
||||||
if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll;
|
if (!isNullOrUndefined(autoscroll)) this.autoscroll = autoscroll;
|
||||||
@ -248,31 +262,7 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
if (!isNullOrUndefined(darkTheme)) this.darkTheme = darkTheme;
|
if (!isNullOrUndefined(darkTheme)) this.darkTheme = darkTheme;
|
||||||
if (!isNullOrUndefined(wsPort)) this.wsPort = wsPort;
|
if (!isNullOrUndefined(wsPort)) this.wsPort = wsPort;
|
||||||
if (!isNullOrUndefined(serialPort)) this.serialPort = serialPort;
|
if (!isNullOrUndefined(serialPort)) this.serialPort = serialPort;
|
||||||
if (!isNullOrUndefined(connected)) this.connected = connected;
|
if (!isNullOrUndefined(connectionStatus))
|
||||||
|
this.connectionStatus = connectionStatus;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to /common
|
|
||||||
export namespace MonitorModel {
|
|
||||||
export interface State {
|
|
||||||
autoscroll: boolean;
|
|
||||||
timestamp: boolean;
|
|
||||||
lineEnding: EOL;
|
|
||||||
interpolate: boolean;
|
|
||||||
darkTheme: boolean;
|
|
||||||
wsPort: number;
|
|
||||||
serialPort: string;
|
|
||||||
connected: boolean;
|
|
||||||
}
|
|
||||||
export namespace State {
|
|
||||||
export interface Change<K extends keyof State> {
|
|
||||||
readonly property: K;
|
|
||||||
readonly value: State[K];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EOL = '' | '\n' | '\r' | '\r\n';
|
|
||||||
export namespace EOL {
|
|
||||||
export const DEFAULT: EOL = '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
|
import { ArduinoToolbar } from '../../toolbar/arduino-toolbar';
|
||||||
import { ArduinoMenus } from '../../menu/arduino-menus';
|
import { ArduinoMenus } from '../../menu/arduino-menus';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
|
import { Event } from '@theia/core/lib/common/event';
|
||||||
import { MonitorModel } from '../../monitor-model';
|
import { MonitorModel } from '../../monitor-model';
|
||||||
import { MonitorManagerProxyClient } from '../../../common/protocol';
|
import { MonitorManagerProxyClient } from '../../../common/protocol';
|
||||||
|
|
||||||
@ -84,13 +85,13 @@ export class MonitorViewContribution
|
|||||||
id: 'monitor-autoscroll',
|
id: 'monitor-autoscroll',
|
||||||
render: () => this.renderAutoScrollButton(),
|
render: () => this.renderAutoScrollButton(),
|
||||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||||
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
onDidChange: this.model.onChange as Event<unknown> as Event<void>,
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: 'monitor-timestamp',
|
id: 'monitor-timestamp',
|
||||||
render: () => this.renderTimestampButton(),
|
render: () => this.renderTimestampButton(),
|
||||||
isVisible: (widget) => widget instanceof MonitorWidget,
|
isVisible: (widget) => widget instanceof MonitorWidget,
|
||||||
onDidChange: this.model.onChange as any, // XXX: it's a hack. See: https://github.com/eclipse-theia/theia/pull/6696/
|
onDidChange: this.model.onChange as Event<unknown> as Event<void>,
|
||||||
});
|
});
|
||||||
registry.registerItem({
|
registry.registerItem({
|
||||||
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
id: SerialMonitor.Commands.CLEAR_OUTPUT.id,
|
||||||
@ -143,8 +144,7 @@ export class MonitorViewContribution
|
|||||||
protected async reset(): Promise<void> {
|
protected async reset(): Promise<void> {
|
||||||
const widget = this.tryGetWidget();
|
const widget = this.tryGetWidget();
|
||||||
if (widget) {
|
if (widget) {
|
||||||
widget.dispose();
|
widget.reset();
|
||||||
await this.openView({ activate: true, reveal: true });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
import * as React from '@theia/core/shared/react';
|
import * as React from '@theia/core/shared/react';
|
||||||
import { injectable, inject } from '@theia/core/shared/inversify';
|
import {
|
||||||
|
injectable,
|
||||||
|
inject,
|
||||||
|
postConstruct,
|
||||||
|
} from '@theia/core/shared/inversify';
|
||||||
import { Emitter } from '@theia/core/lib/common/event';
|
import { Emitter } from '@theia/core/lib/common/event';
|
||||||
import { Disposable } from '@theia/core/lib/common/disposable';
|
import {
|
||||||
|
Disposable,
|
||||||
|
DisposableCollection,
|
||||||
|
} from '@theia/core/lib/common/disposable';
|
||||||
import {
|
import {
|
||||||
ReactWidget,
|
ReactWidget,
|
||||||
Message,
|
Message,
|
||||||
@ -13,9 +20,13 @@ import { SerialMonitorSendInput } from './serial-monitor-send-input';
|
|||||||
import { SerialMonitorOutput } from './serial-monitor-send-output';
|
import { SerialMonitorOutput } from './serial-monitor-send-output';
|
||||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||||
import { nls } from '@theia/core/lib/common';
|
import { nls } from '@theia/core/lib/common';
|
||||||
import { MonitorManagerProxyClient } from '../../../common/protocol';
|
import {
|
||||||
|
MonitorEOL,
|
||||||
|
MonitorManagerProxyClient,
|
||||||
|
} from '../../../common/protocol';
|
||||||
import { MonitorModel } from '../../monitor-model';
|
import { MonitorModel } from '../../monitor-model';
|
||||||
import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider';
|
import { MonitorSettings } from '../../../node/monitor-settings/monitor-settings-provider';
|
||||||
|
import { FrontendApplicationStateService } from '@theia/core/lib/browser/frontend-application-state';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MonitorWidget extends ReactWidget {
|
export class MonitorWidget extends ReactWidget {
|
||||||
@ -40,40 +51,46 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
protected closing = false;
|
protected closing = false;
|
||||||
protected readonly clearOutputEmitter = new Emitter<void>();
|
protected readonly clearOutputEmitter = new Emitter<void>();
|
||||||
|
|
||||||
constructor(
|
@inject(MonitorModel)
|
||||||
@inject(MonitorModel)
|
private readonly monitorModel: MonitorModel;
|
||||||
protected readonly monitorModel: MonitorModel,
|
@inject(MonitorManagerProxyClient)
|
||||||
|
private readonly monitorManagerProxy: MonitorManagerProxyClient;
|
||||||
|
@inject(BoardsServiceProvider)
|
||||||
|
private readonly boardsServiceProvider: BoardsServiceProvider;
|
||||||
|
@inject(FrontendApplicationStateService)
|
||||||
|
private readonly appStateService: FrontendApplicationStateService;
|
||||||
|
|
||||||
@inject(MonitorManagerProxyClient)
|
private readonly toDisposeOnReset: DisposableCollection;
|
||||||
protected readonly monitorManagerProxy: MonitorManagerProxyClient,
|
|
||||||
|
|
||||||
@inject(BoardsServiceProvider)
|
constructor() {
|
||||||
protected readonly boardsServiceProvider: BoardsServiceProvider
|
|
||||||
) {
|
|
||||||
super();
|
super();
|
||||||
this.id = MonitorWidget.ID;
|
this.id = MonitorWidget.ID;
|
||||||
this.title.label = MonitorWidget.LABEL;
|
this.title.label = MonitorWidget.LABEL;
|
||||||
this.title.iconClass = 'monitor-tab-icon';
|
this.title.iconClass = 'monitor-tab-icon';
|
||||||
this.title.closable = true;
|
this.title.closable = true;
|
||||||
this.scrollOptions = undefined;
|
this.scrollOptions = undefined;
|
||||||
|
this.toDisposeOnReset = new DisposableCollection();
|
||||||
this.toDispose.push(this.clearOutputEmitter);
|
this.toDispose.push(this.clearOutputEmitter);
|
||||||
this.toDispose.push(
|
|
||||||
Disposable.create(() => this.monitorManagerProxy.disconnect())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override onBeforeAttach(msg: Message): void {
|
@postConstruct()
|
||||||
this.update();
|
protected init(): void {
|
||||||
this.toDispose.push(this.monitorModel.onChange(() => this.update()));
|
this.toDisposeOnReset.dispose();
|
||||||
this.getCurrentSettings().then(this.onMonitorSettingsDidChange.bind(this));
|
this.toDisposeOnReset.pushAll([
|
||||||
this.monitorManagerProxy.onMonitorSettingsDidChange(
|
Disposable.create(() => this.monitorManagerProxy.disconnect()),
|
||||||
this.onMonitorSettingsDidChange.bind(this)
|
this.monitorModel.onChange(() => this.update()),
|
||||||
);
|
this.monitorManagerProxy.onMonitorSettingsDidChange((event) =>
|
||||||
|
this.updateSettings(event)
|
||||||
this.monitorManagerProxy.startMonitor();
|
),
|
||||||
|
]);
|
||||||
|
this.startMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMonitorSettingsDidChange(settings: MonitorSettings): void {
|
reset(): void {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSettings(settings: MonitorSettings): void {
|
||||||
this.settings = {
|
this.settings = {
|
||||||
...this.settings,
|
...this.settings,
|
||||||
pluggableMonitorSettings: {
|
pluggableMonitorSettings: {
|
||||||
@ -90,6 +107,7 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override dispose(): void {
|
override dispose(): void {
|
||||||
|
this.toDisposeOnReset.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +140,7 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onFocusResolved = (element: HTMLElement | undefined) => {
|
protected onFocusResolved = (element: HTMLElement | undefined): void => {
|
||||||
if (this.closing || !this.isAttached) {
|
if (this.closing || !this.isAttached) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -132,7 +150,7 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
protected get lineEndings(): SerialMonitorOutput.SelectOption<MonitorModel.EOL>[] {
|
protected get lineEndings(): SerialMonitorOutput.SelectOption<MonitorEOL>[] {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: nls.localize('arduino/serial/noLineEndings', 'No Line Ending'),
|
label: nls.localize('arduino/serial/noLineEndings', 'No Line Ending'),
|
||||||
@ -156,11 +174,23 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentSettings(): Promise<MonitorSettings> {
|
private async startMonitor(): Promise<void> {
|
||||||
|
await this.appStateService.reachedState('ready');
|
||||||
|
await this.boardsServiceProvider.reconciled;
|
||||||
|
await this.syncSettings();
|
||||||
|
await this.monitorManagerProxy.startMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncSettings(): Promise<void> {
|
||||||
|
const settings = await this.getCurrentSettings();
|
||||||
|
this.updateSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getCurrentSettings(): Promise<MonitorSettings> {
|
||||||
const board = this.boardsServiceProvider.boardsConfig.selectedBoard;
|
const board = this.boardsServiceProvider.boardsConfig.selectedBoard;
|
||||||
const port = this.boardsServiceProvider.boardsConfig.selectedPort;
|
const port = this.boardsServiceProvider.boardsConfig.selectedPort;
|
||||||
if (!board || !port) {
|
if (!board || !port) {
|
||||||
return Promise.resolve(this.settings || {});
|
return this.settings || {};
|
||||||
}
|
}
|
||||||
return this.monitorManagerProxy.getCurrentSettings(board, port);
|
return this.monitorManagerProxy.getCurrentSettings(board, port);
|
||||||
}
|
}
|
||||||
@ -171,7 +201,7 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const baudrateOptions = baudrate?.values.map((b) => ({
|
const baudrateOptions = baudrate?.values.map((b) => ({
|
||||||
label: b + ' baud',
|
label: nls.localize('arduino/monitor/baudRate', '{0} baud', b),
|
||||||
value: b,
|
value: b,
|
||||||
}));
|
}));
|
||||||
const baudrateSelectedOption = baudrateOptions?.find(
|
const baudrateSelectedOption = baudrateOptions?.find(
|
||||||
@ -181,7 +211,7 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
const lineEnding =
|
const lineEnding =
|
||||||
this.lineEndings.find(
|
this.lineEndings.find(
|
||||||
(item) => item.value === this.monitorModel.lineEnding
|
(item) => item.value === this.monitorModel.lineEnding
|
||||||
) || this.lineEndings[1]; // Defaults to `\n`.
|
) || MonitorEOL.DEFAULT;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="serial-monitor">
|
<div className="serial-monitor">
|
||||||
@ -228,13 +258,13 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly onSend = (value: string) => this.doSend(value);
|
protected readonly onSend = (value: string): void => this.doSend(value);
|
||||||
protected async doSend(value: string): Promise<void> {
|
protected doSend(value: string): void {
|
||||||
this.monitorManagerProxy.send(value);
|
this.monitorManagerProxy.send(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly onChangeLineEnding = (
|
protected readonly onChangeLineEnding = (
|
||||||
option: SerialMonitorOutput.SelectOption<MonitorModel.EOL>
|
option: SerialMonitorOutput.SelectOption<MonitorEOL>
|
||||||
): void => {
|
): void => {
|
||||||
this.monitorModel.lineEnding = option.value;
|
this.monitorModel.lineEnding = option.value;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,10 @@ import { DisposableCollection, nls } from '@theia/core/lib/common';
|
|||||||
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
import { BoardsServiceProvider } from '../../boards/boards-service-provider';
|
||||||
import { MonitorModel } from '../../monitor-model';
|
import { MonitorModel } from '../../monitor-model';
|
||||||
import { Unknown } from '../../../common/nls';
|
import { Unknown } from '../../../common/nls';
|
||||||
|
import {
|
||||||
|
isMonitorConnectionError,
|
||||||
|
MonitorConnectionStatus,
|
||||||
|
} from '../../../common/protocol';
|
||||||
|
|
||||||
class HistoryList {
|
class HistoryList {
|
||||||
private readonly items: string[] = [];
|
private readonly items: string[] = [];
|
||||||
@ -62,7 +66,7 @@ export namespace SerialMonitorSendInput {
|
|||||||
}
|
}
|
||||||
export interface State {
|
export interface State {
|
||||||
text: string;
|
text: string;
|
||||||
connected: boolean;
|
connectionStatus: MonitorConnectionStatus;
|
||||||
history: HistoryList;
|
history: HistoryList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,18 +79,27 @@ export class SerialMonitorSendInput extends React.Component<
|
|||||||
|
|
||||||
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
|
constructor(props: Readonly<SerialMonitorSendInput.Props>) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { text: '', connected: true, history: new HistoryList() };
|
this.state = {
|
||||||
|
text: '',
|
||||||
|
connectionStatus: 'not-connected',
|
||||||
|
history: new HistoryList(),
|
||||||
|
};
|
||||||
this.onChange = this.onChange.bind(this);
|
this.onChange = this.onChange.bind(this);
|
||||||
this.onSend = this.onSend.bind(this);
|
this.onSend = this.onSend.bind(this);
|
||||||
this.onKeyDown = this.onKeyDown.bind(this);
|
this.onKeyDown = this.onKeyDown.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
override componentDidMount(): void {
|
override componentDidMount(): void {
|
||||||
this.setState({ connected: this.props.monitorModel.connected });
|
this.setState({
|
||||||
|
connectionStatus: this.props.monitorModel.connectionStatus,
|
||||||
|
});
|
||||||
this.toDisposeBeforeUnmount.push(
|
this.toDisposeBeforeUnmount.push(
|
||||||
this.props.monitorModel.onChange(({ property }) => {
|
this.props.monitorModel.onChange(({ property }) => {
|
||||||
if (property === 'connected')
|
if (property === 'connected' || property === 'connectionStatus') {
|
||||||
this.setState({ connected: this.props.monitorModel.connected });
|
this.setState({
|
||||||
|
connectionStatus: this.props.monitorModel.connectionStatus,
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -97,44 +110,83 @@ export class SerialMonitorSendInput extends React.Component<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override render(): React.ReactNode {
|
override render(): React.ReactNode {
|
||||||
|
const status = this.state.connectionStatus;
|
||||||
|
const input = this.renderInput(status);
|
||||||
|
if (status !== 'connecting') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
return <label>{input}</label>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderInput(status: MonitorConnectionStatus): React.ReactNode {
|
||||||
|
const inputClassName = this.inputClassName(status);
|
||||||
|
const placeholder = this.placeholder;
|
||||||
|
const readOnly = Boolean(inputClassName);
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
type="text"
|
type="text"
|
||||||
className={`theia-input ${this.shouldShowWarning() ? 'warning' : ''}`}
|
className={`theia-input ${inputClassName}`}
|
||||||
placeholder={this.placeholder}
|
readOnly={readOnly}
|
||||||
value={this.state.text}
|
placeholder={placeholder}
|
||||||
|
title={placeholder}
|
||||||
|
value={readOnly ? '' : this.state.text} // always show the placeholder if cannot edit the <input>
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inputClassName(
|
||||||
|
status: MonitorConnectionStatus
|
||||||
|
): 'error' | 'warning' | '' {
|
||||||
|
if (isMonitorConnectionError(status)) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
if (status === 'connected') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
|
||||||
protected shouldShowWarning(): boolean {
|
protected shouldShowWarning(): boolean {
|
||||||
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
|
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
|
||||||
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
|
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
|
||||||
return !this.state.connected || !board || !port;
|
return !this.state.connectionStatus || !board || !port;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get placeholder(): string {
|
protected get placeholder(): string {
|
||||||
if (this.shouldShowWarning()) {
|
const status = this.state.connectionStatus;
|
||||||
|
if (isMonitorConnectionError(status)) {
|
||||||
|
return status.errorMessage;
|
||||||
|
}
|
||||||
|
if (status === 'not-connected') {
|
||||||
return nls.localize(
|
return nls.localize(
|
||||||
'arduino/serial/notConnected',
|
'arduino/serial/notConnected',
|
||||||
'Not connected. Select a board and a port to connect automatically.'
|
'Not connected. Select a board and a port to connect automatically.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
|
const board = this.props.boardsServiceProvider.boardsConfig.selectedBoard;
|
||||||
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
|
const port = this.props.boardsServiceProvider.boardsConfig.selectedPort;
|
||||||
|
const boardLabel = board
|
||||||
|
? Board.toString(board, {
|
||||||
|
useFqbn: false,
|
||||||
|
})
|
||||||
|
: Unknown;
|
||||||
|
const portLabel = port ? port.address : Unknown;
|
||||||
|
if (status === 'connecting') {
|
||||||
|
return nls.localize(
|
||||||
|
'arduino/serial/connecting',
|
||||||
|
"Connecting to '{0}' on '{1}'...",
|
||||||
|
boardLabel,
|
||||||
|
portLabel
|
||||||
|
);
|
||||||
|
}
|
||||||
return nls.localize(
|
return nls.localize(
|
||||||
'arduino/serial/message',
|
'arduino/serial/message',
|
||||||
"Message (Enter to send message to '{0}' on '{1}')",
|
"Message (Enter to send message to '{0}' on '{1}')",
|
||||||
board
|
boardLabel,
|
||||||
? Board.toString(board, {
|
portLabel
|
||||||
useFqbn: false,
|
|
||||||
})
|
|
||||||
: Unknown,
|
|
||||||
port ? port.address : Unknown
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +29,11 @@
|
|||||||
/* https://github.com/arduino/arduino-ide/pull/1662#issuecomment-1324997134 */
|
/* https://github.com/arduino/arduino-ide/pull/1662#issuecomment-1324997134 */
|
||||||
body {
|
body {
|
||||||
--theia-icon-loading: url(../icons/loading-light.svg);
|
--theia-icon-loading: url(../icons/loading-light.svg);
|
||||||
|
--theia-icon-loading-warning: url(../icons/loading-dark.svg);
|
||||||
}
|
}
|
||||||
body.theia-dark {
|
body.theia-dark {
|
||||||
--theia-icon-loading: url(../icons/loading-dark.svg);
|
--theia-icon-loading: url(../icons/loading-dark.svg);
|
||||||
|
--theia-icon-loading-warning: url(../icons/loading-light.svg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theia-input.warning:focus {
|
.theia-input.warning:focus {
|
||||||
@ -48,22 +50,32 @@ body.theia-dark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theia-input.warning::placeholder {
|
.theia-input.warning::placeholder {
|
||||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
|
||||||
color: var(--theia-warningForeground);
|
|
||||||
background-color: var(--theia-warningBackground);
|
|
||||||
opacity: 1; /* Firefox */
|
|
||||||
}
|
|
||||||
|
|
||||||
.theia-input.warning:-ms-input-placeholder {
|
|
||||||
/* Internet Explorer 10-11 */
|
|
||||||
color: var(--theia-warningForeground);
|
color: var(--theia-warningForeground);
|
||||||
background-color: var(--theia-warningBackground);
|
background-color: var(--theia-warningBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.theia-input.warning::-ms-input-placeholder {
|
.hc-black.hc-theia.theia-hc .theia-input.warning,
|
||||||
/* Microsoft Edge */
|
.hc-black.hc-theia.theia-hc .theia-input.warning::placeholder {
|
||||||
color: var(--theia-warningForeground);
|
color: var(--theia-warningBackground);
|
||||||
background-color: var(--theia-warningBackground);
|
background-color: var(--theia-warningForeground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theia-input.error:focus {
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-style: solid;
|
||||||
|
outline-offset: -1px;
|
||||||
|
opacity: 1 !important;
|
||||||
|
color: var(--theia-errorForeground);
|
||||||
|
background-color: var(--theia-errorBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theia-input.error {
|
||||||
|
background-color: var(--theia-errorBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theia-input.error::placeholder {
|
||||||
|
color: var(--theia-errorForeground);
|
||||||
|
background-color: var(--theia-errorBackground);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Makes the sidepanel a bit wider when opening the widget */
|
/* Makes the sidepanel a bit wider when opening the widget */
|
||||||
|
@ -20,22 +20,47 @@
|
|||||||
|
|
||||||
.serial-monitor .head {
|
.serial-monitor .head {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 5px;
|
padding: 0px 5px 5px 0px;
|
||||||
height: 27px;
|
height: 27px;
|
||||||
|
background-color: var(--theia-activityBar-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.serial-monitor .head .send {
|
.serial-monitor .head .send {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-right: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.serial-monitor .head .send > input {
|
.serial-monitor .head .send > label:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
background: var(--theia-icon-loading-warning) center center no-repeat;
|
||||||
|
animation: theia-spin 1.25s linear infinite;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .send > label {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-self: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .send > input,
|
||||||
|
.serial-monitor .head .send > label > input {
|
||||||
line-height: var(--theia-content-line-height);
|
line-height: var(--theia-content-line-height);
|
||||||
|
height: 27px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.serial-monitor .head .send > input:focus {
|
.serial-monitor .head .send > label > input {
|
||||||
|
padding-left: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.serial-monitor .head .send > input:focus,
|
||||||
|
.serial-monitor .head .send > label > input:focus {
|
||||||
border-color: var(--theia-focusBorder);
|
border-color: var(--theia-focusBorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,12 +73,12 @@ export namespace CoreError {
|
|||||||
UploadUsingProgrammer: 4003,
|
UploadUsingProgrammer: 4003,
|
||||||
BurnBootloader: 4004,
|
BurnBootloader: 4004,
|
||||||
};
|
};
|
||||||
export const VerifyFailed = create(Codes.Verify);
|
export const VerifyFailed = declareCoreError(Codes.Verify);
|
||||||
export const UploadFailed = create(Codes.Upload);
|
export const UploadFailed = declareCoreError(Codes.Upload);
|
||||||
export const UploadUsingProgrammerFailed = create(
|
export const UploadUsingProgrammerFailed = declareCoreError(
|
||||||
Codes.UploadUsingProgrammer
|
Codes.UploadUsingProgrammer
|
||||||
);
|
);
|
||||||
export const BurnBootloaderFailed = create(Codes.BurnBootloader);
|
export const BurnBootloaderFailed = declareCoreError(Codes.BurnBootloader);
|
||||||
export function is(
|
export function is(
|
||||||
error: unknown
|
error: unknown
|
||||||
): error is ApplicationError<number, ErrorLocation[]> {
|
): error is ApplicationError<number, ErrorLocation[]> {
|
||||||
@ -88,7 +88,7 @@ export namespace CoreError {
|
|||||||
Object.values(Codes).includes(error.code)
|
Object.values(Codes).includes(error.code)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
function create(
|
function declareCoreError(
|
||||||
code: number
|
code: number
|
||||||
): ApplicationError.Constructor<number, ErrorLocation[]> {
|
): ApplicationError.Constructor<number, ErrorLocation[]> {
|
||||||
return ApplicationError.declare(
|
return ApplicationError.declare(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Event, JsonRpcServer } from '@theia/core';
|
import { ApplicationError, Event, JsonRpcServer, nls } from '@theia/core';
|
||||||
import {
|
import {
|
||||||
PluggableMonitorSettings,
|
PluggableMonitorSettings,
|
||||||
MonitorSettings,
|
MonitorSettings,
|
||||||
@ -31,7 +31,7 @@ export interface MonitorManagerProxyClient {
|
|||||||
onMessagesReceived: Event<{ messages: string[] }>;
|
onMessagesReceived: Event<{ messages: string[] }>;
|
||||||
onMonitorSettingsDidChange: Event<MonitorSettings>;
|
onMonitorSettingsDidChange: Event<MonitorSettings>;
|
||||||
onMonitorShouldReset: Event<void>;
|
onMonitorShouldReset: Event<void>;
|
||||||
connect(addressPort: number): void;
|
connect(addressPort: number): Promise<void>;
|
||||||
disconnect(): void;
|
disconnect(): void;
|
||||||
getWebSocketPort(): number | undefined;
|
getWebSocketPort(): number | undefined;
|
||||||
isWSConnected(): Promise<boolean>;
|
isWSConnected(): Promise<boolean>;
|
||||||
@ -46,7 +46,7 @@ export interface PluggableMonitorSetting {
|
|||||||
readonly id: string;
|
readonly id: string;
|
||||||
// A human-readable label of the setting (to be displayed on the GUI)
|
// A human-readable label of the setting (to be displayed on the GUI)
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
// The setting type (at the moment only "enum" is avaiable)
|
// The setting type (at the moment only "enum" is available)
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
// The values allowed on "enum" types
|
// The values allowed on "enum" types
|
||||||
readonly values: string[];
|
readonly values: string[];
|
||||||
@ -72,24 +72,168 @@ export namespace Monitor {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Status {}
|
export const MonitorErrorCodes = {
|
||||||
export type OK = Status;
|
ConnectionFailed: 6001,
|
||||||
export interface ErrorStatus extends Status {
|
NotConnected: 6002,
|
||||||
readonly message: string;
|
AlreadyConnected: 6003,
|
||||||
}
|
MissingConfiguration: 6004,
|
||||||
export namespace Status {
|
} as const;
|
||||||
export function isOK(status: Status & { message?: string }): status is OK {
|
|
||||||
return !!status && typeof status.message !== 'string';
|
export const ConnectionFailedError = declareMonitorError(
|
||||||
|
MonitorErrorCodes.ConnectionFailed
|
||||||
|
);
|
||||||
|
export const NotConnectedError = declareMonitorError(
|
||||||
|
MonitorErrorCodes.NotConnected
|
||||||
|
);
|
||||||
|
export const AlreadyConnectedError = declareMonitorError(
|
||||||
|
MonitorErrorCodes.AlreadyConnected
|
||||||
|
);
|
||||||
|
export const MissingConfigurationError = declareMonitorError(
|
||||||
|
MonitorErrorCodes.MissingConfiguration
|
||||||
|
);
|
||||||
|
|
||||||
|
export function createConnectionFailedError(
|
||||||
|
port: Port,
|
||||||
|
details?: string
|
||||||
|
): ApplicationError<number, PortDescriptor> {
|
||||||
|
const { protocol, address } = port;
|
||||||
|
let message;
|
||||||
|
if (details) {
|
||||||
|
const detailsWithPeriod = details.endsWith('.') ? details : `${details}.`;
|
||||||
|
message = nls.localize(
|
||||||
|
'arduino/monitor/connectionFailedErrorWithDetails',
|
||||||
|
'{0} Could not connect to {1} {2} port.',
|
||||||
|
detailsWithPeriod,
|
||||||
|
address,
|
||||||
|
protocol
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message = nls.localize(
|
||||||
|
'arduino/monitor/connectionFailedError',
|
||||||
|
'Could not connect to {0} {1} port.',
|
||||||
|
address,
|
||||||
|
protocol
|
||||||
|
);
|
||||||
}
|
}
|
||||||
export const OK: OK = {};
|
return ConnectionFailedError(message, { protocol, address });
|
||||||
export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' };
|
}
|
||||||
export const ALREADY_CONNECTED: ErrorStatus = {
|
export function createNotConnectedError(
|
||||||
message: 'Already connected.',
|
port: Port
|
||||||
};
|
): ApplicationError<number, PortDescriptor> {
|
||||||
export const CONFIG_MISSING: ErrorStatus = {
|
const { protocol, address } = port;
|
||||||
message: 'Serial Config missing.',
|
return NotConnectedError(
|
||||||
};
|
nls.localize(
|
||||||
export const UPLOAD_IN_PROGRESS: ErrorStatus = {
|
'arduino/monitor/notConnectedError',
|
||||||
message: 'Upload in progress.',
|
'Not connected to {0} {1} port.',
|
||||||
};
|
address,
|
||||||
|
protocol
|
||||||
|
),
|
||||||
|
{ protocol, address }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function createAlreadyConnectedError(
|
||||||
|
port: Port
|
||||||
|
): ApplicationError<number, PortDescriptor> {
|
||||||
|
const { protocol, address } = port;
|
||||||
|
return AlreadyConnectedError(
|
||||||
|
nls.localize(
|
||||||
|
'arduino/monitor/alreadyConnectedError',
|
||||||
|
'Could not connect to {0} {1} port. Already connected.',
|
||||||
|
address,
|
||||||
|
protocol
|
||||||
|
),
|
||||||
|
{ protocol, address }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function createMissingConfigurationError(
|
||||||
|
port: Port
|
||||||
|
): ApplicationError<number, PortDescriptor> {
|
||||||
|
const { protocol, address } = port;
|
||||||
|
return MissingConfigurationError(
|
||||||
|
nls.localize(
|
||||||
|
'arduino/monitor/missingConfigurationError',
|
||||||
|
'Could not connect to {0} {1} port. The monitor configuration is missing.',
|
||||||
|
address,
|
||||||
|
protocol
|
||||||
|
),
|
||||||
|
{ protocol, address }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bare minimum representation of a port. Supports neither UI labels nor properties.
|
||||||
|
*/
|
||||||
|
interface PortDescriptor {
|
||||||
|
readonly protocol: string;
|
||||||
|
readonly address: string;
|
||||||
|
}
|
||||||
|
function declareMonitorError(
|
||||||
|
code: number
|
||||||
|
): ApplicationError.Constructor<number, PortDescriptor> {
|
||||||
|
return ApplicationError.declare(
|
||||||
|
code,
|
||||||
|
(message: string, data: PortDescriptor) => ({ data, message })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonitorConnectionError {
|
||||||
|
readonly errorMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonitorConnectionStatus =
|
||||||
|
| 'connecting'
|
||||||
|
| 'connected'
|
||||||
|
| 'not-connected'
|
||||||
|
| MonitorConnectionError;
|
||||||
|
|
||||||
|
export function monitorConnectionStatusEquals(
|
||||||
|
left: MonitorConnectionStatus,
|
||||||
|
right: MonitorConnectionStatus
|
||||||
|
): boolean {
|
||||||
|
if (typeof left === 'object' && typeof right === 'object') {
|
||||||
|
return left.errorMessage === right.errorMessage;
|
||||||
|
}
|
||||||
|
return left === right;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated see `MonitorState#connected`
|
||||||
|
*/
|
||||||
|
export function isMonitorConnected(
|
||||||
|
status: MonitorConnectionStatus
|
||||||
|
): status is 'connected' {
|
||||||
|
return status === 'connected';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isMonitorConnectionError(
|
||||||
|
status: MonitorConnectionStatus
|
||||||
|
): status is MonitorConnectionError {
|
||||||
|
return typeof status === 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonitorState {
|
||||||
|
autoscroll: boolean;
|
||||||
|
timestamp: boolean;
|
||||||
|
lineEnding: MonitorEOL;
|
||||||
|
interpolate: boolean;
|
||||||
|
darkTheme: boolean;
|
||||||
|
wsPort: number;
|
||||||
|
serialPort: string;
|
||||||
|
connectionStatus: MonitorConnectionStatus;
|
||||||
|
/**
|
||||||
|
* @deprecated This property is never get by IDE2 only set. This value is present to be backward compatible with the plotter app.
|
||||||
|
* IDE2 uses `MonitorState#connectionStatus`.
|
||||||
|
*/
|
||||||
|
connected: boolean;
|
||||||
|
}
|
||||||
|
export namespace MonitorState {
|
||||||
|
export interface Change<K extends keyof MonitorState> {
|
||||||
|
readonly property: K;
|
||||||
|
readonly value: MonitorState[K];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MonitorEOL = '' | '\n' | '\r' | '\r\n';
|
||||||
|
export namespace MonitorEOL {
|
||||||
|
export const DEFAULT: MonitorEOL = '\n';
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
UploadUsingProgrammerResponse,
|
UploadUsingProgrammerResponse,
|
||||||
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
} from './cli-protocol/cc/arduino/cli/commands/v1/upload_pb';
|
||||||
import { ResponseService } from '../common/protocol/response-service';
|
import { ResponseService } from '../common/protocol/response-service';
|
||||||
import { OutputMessage, Port, Status } from '../common/protocol';
|
import { OutputMessage, Port } from '../common/protocol';
|
||||||
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
|
import { ArduinoCoreServiceClient } from './cli-protocol/cc/arduino/cli/commands/v1/commands_grpc_pb';
|
||||||
import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
|
import { Port as RpcPort } from './cli-protocol/cc/arduino/cli/commands/v1/port_pb';
|
||||||
import { ApplicationError, CommandService, Disposable, nls } from '@theia/core';
|
import { ApplicationError, CommandService, Disposable, nls } from '@theia/core';
|
||||||
@ -392,7 +392,7 @@ export class CoreServiceImpl extends CoreClientAware implements CoreService {
|
|||||||
}: {
|
}: {
|
||||||
fqbn?: string | undefined;
|
fqbn?: string | undefined;
|
||||||
port?: Port | undefined;
|
port?: Port | undefined;
|
||||||
}): Promise<Status> {
|
}): Promise<void> {
|
||||||
this.boardDiscovery.setUploadInProgress(false);
|
this.boardDiscovery.setUploadInProgress(false);
|
||||||
return this.monitorManager.notifyUploadFinished(fqbn, port);
|
return this.monitorManager.notifyUploadFinished(fqbn, port);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { inject, injectable } from '@theia/core/shared/inversify';
|
|||||||
import {
|
import {
|
||||||
MonitorManagerProxy,
|
MonitorManagerProxy,
|
||||||
MonitorManagerProxyClient,
|
MonitorManagerProxyClient,
|
||||||
Status,
|
|
||||||
} from '../common/protocol';
|
} from '../common/protocol';
|
||||||
import { Board, Port } from '../common/protocol';
|
import { Board, Port } from '../common/protocol';
|
||||||
import { MonitorManager } from './monitor-manager';
|
import { MonitorManager } from './monitor-manager';
|
||||||
@ -41,11 +40,16 @@ export class MonitorManagerProxyImpl implements MonitorManagerProxy {
|
|||||||
await this.changeMonitorSettings(board, port, settings);
|
await this.changeMonitorSettings(board, port, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectToClient = (status: Status) => {
|
const connectToClient = async () => {
|
||||||
if (status === Status.ALREADY_CONNECTED || status === Status.OK) {
|
const address = this.manager.getWebsocketAddressPort(board, port);
|
||||||
// Monitor started correctly, connect it with the frontend
|
if (!this.client) {
|
||||||
this.client.connect(this.manager.getWebsocketAddressPort(board, port));
|
throw new Error(
|
||||||
|
`No client was connected to this monitor manager. Board: ${
|
||||||
|
board.fqbn ?? board.name
|
||||||
|
}, port: ${port.address}, address: ${address}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
await this.client.connect(address);
|
||||||
};
|
};
|
||||||
return this.manager.startMonitor(board, port, connectToClient);
|
return this.manager.startMonitor(board, port, connectToClient);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { ILogger } from '@theia/core';
|
import { ILogger } from '@theia/core';
|
||||||
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
||||||
import { Board, BoardsService, Port, Status } from '../common/protocol';
|
import {
|
||||||
|
AlreadyConnectedError,
|
||||||
|
Board,
|
||||||
|
BoardsService,
|
||||||
|
Port,
|
||||||
|
} from '../common/protocol';
|
||||||
import { CoreClientAware } from './core-client-provider';
|
import { CoreClientAware } from './core-client-provider';
|
||||||
import { MonitorService } from './monitor-service';
|
import { MonitorService } from './monitor-service';
|
||||||
import { MonitorServiceFactory } from './monitor-service-factory';
|
import { MonitorServiceFactory } from './monitor-service-factory';
|
||||||
@ -36,7 +41,7 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
private monitorServiceStartQueue: {
|
private monitorServiceStartQueue: {
|
||||||
monitorID: string;
|
monitorID: string;
|
||||||
serviceStartParams: [Board, Port];
|
serviceStartParams: [Board, Port];
|
||||||
connectToClient: (status: Status) => void;
|
connectToClient: () => Promise<void>;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
@inject(MonitorServiceFactory)
|
@inject(MonitorServiceFactory)
|
||||||
@ -104,7 +109,7 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
async startMonitor(
|
async startMonitor(
|
||||||
board: Board,
|
board: Board,
|
||||||
port: Port,
|
port: Port,
|
||||||
connectToClient: (status: Status) => void
|
connectToClient: () => Promise<void>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const monitorID = this.monitorID(board.fqbn, port);
|
const monitorID = this.monitorID(board.fqbn, port);
|
||||||
|
|
||||||
@ -127,8 +132,14 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await monitor.start();
|
try {
|
||||||
connectToClient(result);
|
await connectToClient();
|
||||||
|
await monitor.start();
|
||||||
|
} catch (err) {
|
||||||
|
if (!AlreadyConnectedError.is(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -202,8 +213,7 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
async notifyUploadFinished(
|
async notifyUploadFinished(
|
||||||
fqbn?: string | undefined,
|
fqbn?: string | undefined,
|
||||||
port?: Port
|
port?: Port
|
||||||
): Promise<Status> {
|
): Promise<void> {
|
||||||
let status: Status = Status.NOT_CONNECTED;
|
|
||||||
let portDidChangeOnUpload = false;
|
let portDidChangeOnUpload = false;
|
||||||
|
|
||||||
// We have no way of knowing which monitor
|
// We have no way of knowing which monitor
|
||||||
@ -214,7 +224,7 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
|
|
||||||
const monitor = this.monitorServices.get(monitorID);
|
const monitor = this.monitorServices.get(monitorID);
|
||||||
if (monitor) {
|
if (monitor) {
|
||||||
status = await monitor.start();
|
await monitor.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// this monitorID will only be present in "disposedForUpload"
|
// this monitorID will only be present in "disposedForUpload"
|
||||||
@ -232,7 +242,6 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.startQueuedServices(portDidChangeOnUpload);
|
await this.startQueuedServices(portDidChangeOnUpload);
|
||||||
return status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async startQueuedServices(portDidChangeOnUpload: boolean): Promise<void> {
|
async startQueuedServices(portDidChangeOnUpload: boolean): Promise<void> {
|
||||||
@ -246,7 +255,7 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
|
|
||||||
for (const {
|
for (const {
|
||||||
monitorID,
|
monitorID,
|
||||||
serviceStartParams: [_, port],
|
serviceStartParams: [, port],
|
||||||
connectToClient,
|
connectToClient,
|
||||||
} of queued) {
|
} of queued) {
|
||||||
const boardsState = await this.boardsService.getState();
|
const boardsState = await this.boardsService.getState();
|
||||||
@ -261,8 +270,8 @@ export class MonitorManager extends CoreClientAware {
|
|||||||
const monitorService = this.monitorServices.get(monitorID);
|
const monitorService = this.monitorServices.get(monitorID);
|
||||||
|
|
||||||
if (monitorService) {
|
if (monitorService) {
|
||||||
const result = await monitorService.start();
|
await connectToClient();
|
||||||
connectToClient(result);
|
await monitorService.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,23 @@
|
|||||||
import { ClientDuplexStream } from '@grpc/grpc-js';
|
import { ClientDuplexStream, status } from '@grpc/grpc-js';
|
||||||
import { Disposable, Emitter, ILogger } from '@theia/core';
|
import {
|
||||||
|
ApplicationError,
|
||||||
|
Disposable,
|
||||||
|
Emitter,
|
||||||
|
ILogger,
|
||||||
|
nls,
|
||||||
|
} from '@theia/core';
|
||||||
import { inject, named, postConstruct } from '@theia/core/shared/inversify';
|
import { inject, named, postConstruct } from '@theia/core/shared/inversify';
|
||||||
import { diff, Operation } from 'just-diff';
|
import { diff, Operation } from 'just-diff';
|
||||||
import { Board, Port, Status, Monitor } from '../common/protocol';
|
import {
|
||||||
|
Board,
|
||||||
|
Port,
|
||||||
|
Monitor,
|
||||||
|
createAlreadyConnectedError,
|
||||||
|
createMissingConfigurationError,
|
||||||
|
createNotConnectedError,
|
||||||
|
createConnectionFailedError,
|
||||||
|
isMonitorConnected,
|
||||||
|
} from '../common/protocol';
|
||||||
import {
|
import {
|
||||||
EnumerateMonitorPortSettingsRequest,
|
EnumerateMonitorPortSettingsRequest,
|
||||||
EnumerateMonitorPortSettingsResponse,
|
EnumerateMonitorPortSettingsResponse,
|
||||||
@ -19,8 +34,13 @@ import {
|
|||||||
PluggableMonitorSettings,
|
PluggableMonitorSettings,
|
||||||
MonitorSettingsProvider,
|
MonitorSettingsProvider,
|
||||||
} from './monitor-settings/monitor-settings-provider';
|
} from './monitor-settings/monitor-settings-provider';
|
||||||
import { Deferred } from '@theia/core/lib/common/promise-util';
|
import {
|
||||||
|
Deferred,
|
||||||
|
retry,
|
||||||
|
timeoutReject,
|
||||||
|
} from '@theia/core/lib/common/promise-util';
|
||||||
import { MonitorServiceFactoryOptions } from './monitor-service-factory';
|
import { MonitorServiceFactoryOptions } from './monitor-service-factory';
|
||||||
|
import { ServiceError } from './service-error';
|
||||||
|
|
||||||
export const MonitorServiceName = 'monitor-service';
|
export const MonitorServiceName = 'monitor-service';
|
||||||
type DuplexHandlerKeys =
|
type DuplexHandlerKeys =
|
||||||
@ -76,7 +96,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
readonly onDispose = this.onDisposeEmitter.event;
|
readonly onDispose = this.onDisposeEmitter.event;
|
||||||
|
|
||||||
private _initialized = new Deferred<void>();
|
private _initialized = new Deferred<void>();
|
||||||
private creating: Deferred<Status>;
|
private creating: Deferred<void>;
|
||||||
private readonly board: Board;
|
private readonly board: Board;
|
||||||
private readonly port: Port;
|
private readonly port: Port;
|
||||||
private readonly monitorID: string;
|
private readonly monitorID: string;
|
||||||
@ -114,7 +134,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
this.updateClientsSettings(this.settings);
|
this.updateClientsSettings(this.settings);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.portMonitorSettings(this.port.protocol, this.board.fqbn!).then(
|
this.portMonitorSettings(this.port.protocol, this.board.fqbn!, true).then(
|
||||||
async (settings) => {
|
async (settings) => {
|
||||||
this.settings = {
|
this.settings = {
|
||||||
...this.settings,
|
...this.settings,
|
||||||
@ -154,74 +174,85 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Start and connects a monitor using currently set board and port.
|
* Start and connects a monitor using currently set board and port.
|
||||||
* If a monitor is already started or board fqbn, port address and/or protocol
|
* If a monitor is already started, the promise will reject with an `AlreadyConnectedError`.
|
||||||
* are missing nothing happens.
|
* If the board fqbn, port address and/or protocol are missing, the promise rejects with a `MissingConfigurationError`.
|
||||||
* @returns a status to verify connection has been established.
|
|
||||||
*/
|
*/
|
||||||
async start(): Promise<Status> {
|
async start(): Promise<void> {
|
||||||
if (this.creating?.state === 'unresolved') return this.creating.promise;
|
if (this.creating?.state === 'unresolved') return this.creating.promise;
|
||||||
this.creating = new Deferred();
|
this.creating = new Deferred();
|
||||||
if (this.duplex) {
|
if (this.duplex) {
|
||||||
this.updateClientsSettings({
|
this.updateClientsSettings({
|
||||||
monitorUISettings: { connected: true, serialPort: this.port.address },
|
monitorUISettings: {
|
||||||
|
connectionStatus: 'connected',
|
||||||
|
connected: true, // TODO: should be removed when plotter app understand the `connectionStatus` message
|
||||||
|
serialPort: this.port.address,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.creating.resolve(Status.ALREADY_CONNECTED);
|
this.creating.reject(createAlreadyConnectedError(this.port));
|
||||||
return this.creating.promise;
|
return this.creating.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) {
|
if (!this.board?.fqbn || !this.port?.address || !this.port?.protocol) {
|
||||||
this.updateClientsSettings({ monitorUISettings: { connected: false } });
|
this.updateClientsSettings({
|
||||||
|
monitorUISettings: {
|
||||||
|
connectionStatus: 'not-connected',
|
||||||
|
connected: false, // TODO: should be removed when plotter app understand the `connectionStatus` message
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.creating.resolve(Status.CONFIG_MISSING);
|
this.creating.reject(createMissingConfigurationError(this.port));
|
||||||
return this.creating.promise;
|
return this.creating.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info('starting monitor');
|
this.logger.info('starting monitor');
|
||||||
|
|
||||||
// get default monitor settings from the CLI
|
try {
|
||||||
const defaultSettings = await this.portMonitorSettings(
|
// get default monitor settings from the CLI
|
||||||
this.port.protocol,
|
const defaultSettings = await this.portMonitorSettings(
|
||||||
this.board.fqbn
|
this.port.protocol,
|
||||||
);
|
this.board.fqbn
|
||||||
// get actual settings from the settings provider
|
);
|
||||||
this.settings = {
|
|
||||||
...this.settings,
|
|
||||||
pluggableMonitorSettings: {
|
|
||||||
...this.settings.pluggableMonitorSettings,
|
|
||||||
...(await this.monitorSettingsProvider.getSettings(
|
|
||||||
this.monitorID,
|
|
||||||
defaultSettings
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const coreClient = await this.coreClient;
|
this.updateClientsSettings({
|
||||||
|
monitorUISettings: { connectionStatus: 'connecting' },
|
||||||
|
});
|
||||||
|
|
||||||
const { instance } = coreClient;
|
// get actual settings from the settings provider
|
||||||
const monitorRequest = new MonitorRequest();
|
this.settings = {
|
||||||
monitorRequest.setInstance(instance);
|
...this.settings,
|
||||||
if (this.board?.fqbn) {
|
pluggableMonitorSettings: {
|
||||||
monitorRequest.setFqbn(this.board.fqbn);
|
...this.settings.pluggableMonitorSettings,
|
||||||
}
|
...(await this.monitorSettingsProvider.getSettings(
|
||||||
if (this.port?.address && this.port?.protocol) {
|
this.monitorID,
|
||||||
const rpcPort = new RpcPort();
|
defaultSettings
|
||||||
rpcPort.setAddress(this.port.address);
|
)),
|
||||||
rpcPort.setProtocol(this.port.protocol);
|
},
|
||||||
monitorRequest.setPort(rpcPort);
|
};
|
||||||
}
|
|
||||||
const config = new MonitorPortConfiguration();
|
|
||||||
for (const id in this.settings.pluggableMonitorSettings) {
|
|
||||||
const s = new MonitorPortSetting();
|
|
||||||
s.setSettingId(id);
|
|
||||||
s.setValue(this.settings.pluggableMonitorSettings[id].selectedValue);
|
|
||||||
config.addSettings(s);
|
|
||||||
}
|
|
||||||
monitorRequest.setPortConfiguration(config);
|
|
||||||
|
|
||||||
const wroteToStreamSuccessfully = await this.pollWriteToStream(
|
const coreClient = await this.coreClient;
|
||||||
monitorRequest
|
|
||||||
);
|
const { instance } = coreClient;
|
||||||
if (wroteToStreamSuccessfully) {
|
const monitorRequest = new MonitorRequest();
|
||||||
|
monitorRequest.setInstance(instance);
|
||||||
|
if (this.board?.fqbn) {
|
||||||
|
monitorRequest.setFqbn(this.board.fqbn);
|
||||||
|
}
|
||||||
|
if (this.port?.address && this.port?.protocol) {
|
||||||
|
const rpcPort = new RpcPort();
|
||||||
|
rpcPort.setAddress(this.port.address);
|
||||||
|
rpcPort.setProtocol(this.port.protocol);
|
||||||
|
monitorRequest.setPort(rpcPort);
|
||||||
|
}
|
||||||
|
const config = new MonitorPortConfiguration();
|
||||||
|
for (const id in this.settings.pluggableMonitorSettings) {
|
||||||
|
const s = new MonitorPortSetting();
|
||||||
|
s.setSettingId(id);
|
||||||
|
s.setValue(this.settings.pluggableMonitorSettings[id].selectedValue);
|
||||||
|
config.addSettings(s);
|
||||||
|
}
|
||||||
|
monitorRequest.setPortConfiguration(config);
|
||||||
|
|
||||||
|
await this.pollWriteToStream(monitorRequest);
|
||||||
// Only store the config, if the monitor has successfully started.
|
// Only store the config, if the monitor has successfully started.
|
||||||
this.currentPortConfigSnapshot = MonitorPortConfiguration.toObject(
|
this.currentPortConfigSnapshot = MonitorPortConfiguration.toObject(
|
||||||
false,
|
false,
|
||||||
@ -237,15 +268,34 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
`started monitor to ${this.port?.address} using ${this.port?.protocol}`
|
`started monitor to ${this.port?.address} using ${this.port?.protocol}`
|
||||||
);
|
);
|
||||||
this.updateClientsSettings({
|
this.updateClientsSettings({
|
||||||
monitorUISettings: { connected: true, serialPort: this.port.address },
|
monitorUISettings: {
|
||||||
|
connectionStatus: 'connected',
|
||||||
|
connected: true, // TODO: should be removed when plotter app understand the `connectionStatus` message
|
||||||
|
serialPort: this.port.address,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.creating.resolve(Status.OK);
|
this.creating.resolve();
|
||||||
return this.creating.promise;
|
return this.creating.promise;
|
||||||
} else {
|
} catch (err) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
`failed starting monitor to ${this.port?.address} using ${this.port?.protocol}`
|
`failed starting monitor to ${this.port?.address} using ${this.port?.protocol}`
|
||||||
);
|
);
|
||||||
this.creating.resolve(Status.NOT_CONNECTED);
|
const appError = ApplicationError.is(err)
|
||||||
|
? err
|
||||||
|
: createConnectionFailedError(
|
||||||
|
this.port,
|
||||||
|
ServiceError.is(err)
|
||||||
|
? err.details
|
||||||
|
: err instanceof Error
|
||||||
|
? err.message
|
||||||
|
: String(err)
|
||||||
|
);
|
||||||
|
this.creating.reject(appError);
|
||||||
|
this.updateClientsSettings({
|
||||||
|
monitorUISettings: {
|
||||||
|
connectionStatus: { errorMessage: appError.message },
|
||||||
|
},
|
||||||
|
});
|
||||||
return this.creating.promise;
|
return this.creating.promise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,19 +314,29 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
// default handlers
|
// default handlers
|
||||||
duplex
|
duplex
|
||||||
.on('close', () => {
|
.on('close', () => {
|
||||||
this.duplex = null;
|
if (duplex === this.duplex) {
|
||||||
this.updateClientsSettings({
|
this.duplex = null;
|
||||||
monitorUISettings: { connected: false },
|
this.updateClientsSettings({
|
||||||
});
|
monitorUISettings: {
|
||||||
|
connected: false, // TODO: should be removed when plotter app understand the `connectionStatus` message
|
||||||
|
connectionStatus: 'not-connected',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`monitor to ${this.port?.address} using ${this.port?.protocol} closed by client`
|
`monitor to ${this.port?.address} using ${this.port?.protocol} closed by client`
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.on('end', () => {
|
.on('end', () => {
|
||||||
this.duplex = null;
|
if (duplex === this.duplex) {
|
||||||
this.updateClientsSettings({
|
this.duplex = null;
|
||||||
monitorUISettings: { connected: false },
|
this.updateClientsSettings({
|
||||||
});
|
monitorUISettings: {
|
||||||
|
connected: false, // TODO: should be removed when plotter app understand the `connectionStatus` message
|
||||||
|
connectionStatus: 'not-connected',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
this.logger.info(
|
this.logger.info(
|
||||||
`monitor to ${this.port?.address} using ${this.port?.protocol} closed by server`
|
`monitor to ${this.port?.address} using ${this.port?.protocol} closed by server`
|
||||||
);
|
);
|
||||||
@ -287,21 +347,17 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pollWriteToStream(request: MonitorRequest): Promise<boolean> {
|
pollWriteToStream(request: MonitorRequest): Promise<void> {
|
||||||
let attemptsRemaining = MAX_WRITE_TO_STREAM_TRIES;
|
|
||||||
const writeTimeoutMs = WRITE_TO_STREAM_TIMEOUT_MS;
|
|
||||||
|
|
||||||
const createWriteToStreamExecutor =
|
const createWriteToStreamExecutor =
|
||||||
(duplex: ClientDuplexStream<MonitorRequest, MonitorResponse>) =>
|
(duplex: ClientDuplexStream<MonitorRequest, MonitorResponse>) =>
|
||||||
(resolve: (value: boolean) => void, reject: () => void) => {
|
(resolve: () => void, reject: (reason?: unknown) => void) => {
|
||||||
const resolvingDuplexHandlers: DuplexHandler[] = [
|
const resolvingDuplexHandlers: DuplexHandler[] = [
|
||||||
{
|
{
|
||||||
key: 'error',
|
key: 'error',
|
||||||
callback: async (err: Error) => {
|
callback: async (err: Error) => {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
resolve(false);
|
const details = ServiceError.is(err) ? err.details : err.message;
|
||||||
// TODO
|
reject(createConnectionFailedError(this.port, details));
|
||||||
// this.theiaFEClient?.notifyError()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -313,79 +369,47 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (monitorResponse.getSuccess()) {
|
if (monitorResponse.getSuccess()) {
|
||||||
resolve(true);
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = monitorResponse.getRxData();
|
const data = monitorResponse.getRxData();
|
||||||
const message =
|
const message =
|
||||||
typeof data === 'string'
|
typeof data === 'string'
|
||||||
? data
|
? data
|
||||||
: this.streamingTextDecoder.decode(data, {stream:true});
|
: this.streamingTextDecoder.decode(data, { stream: true });
|
||||||
this.messages.push(...splitLines(message));
|
this.messages.push(...splitLines(message));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.setDuplexHandlers(duplex, resolvingDuplexHandlers);
|
this.setDuplexHandlers(duplex, resolvingDuplexHandlers);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
reject();
|
|
||||||
}, writeTimeoutMs);
|
|
||||||
duplex.write(request);
|
duplex.write(request);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pollWriteToStream = new Promise<boolean>((resolve) => {
|
return Promise.race([
|
||||||
const startPolling = async () => {
|
retry(
|
||||||
// here we create a new duplex but we don't yet
|
async () => {
|
||||||
// set "this.duplex", nor do we use "this.duplex" in our poll
|
let createdDuplex = undefined;
|
||||||
// as duplex 'end' / 'close' events (which we do not "await")
|
try {
|
||||||
// will set "this.duplex" to null
|
createdDuplex = await this.createDuplex();
|
||||||
const createdDuplex = await this.createDuplex();
|
await new Promise<void>(createWriteToStreamExecutor(createdDuplex));
|
||||||
|
this.duplex = createdDuplex;
|
||||||
let pollingIsSuccessful;
|
} catch (err) {
|
||||||
// attempt a "writeToStream" and "await" CLI response: success (true) or error (false)
|
createdDuplex?.end();
|
||||||
// if we get neither within WRITE_TO_STREAM_TIMEOUT_MS or an error we get undefined
|
throw err;
|
||||||
try {
|
|
||||||
const writeToStream = createWriteToStreamExecutor(createdDuplex);
|
|
||||||
pollingIsSuccessful = await new Promise(writeToStream);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI confirmed port opened successfully
|
|
||||||
if (pollingIsSuccessful) {
|
|
||||||
this.duplex = createdDuplex;
|
|
||||||
resolve(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if "pollingIsSuccessful" is false
|
|
||||||
// the CLI gave us an error, lets try again
|
|
||||||
// after waiting 2 seconds if we've not already
|
|
||||||
// reached MAX_WRITE_TO_STREAM_TRIES
|
|
||||||
if (pollingIsSuccessful === false) {
|
|
||||||
attemptsRemaining -= 1;
|
|
||||||
if (attemptsRemaining > 0) {
|
|
||||||
setTimeout(startPolling, 2000);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
resolve(false);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
2_000,
|
||||||
// "pollingIsSuccessful" remains undefined:
|
MAX_WRITE_TO_STREAM_TRIES
|
||||||
// we got no response from the CLI within 30 seconds
|
),
|
||||||
// resolve to false and end the duplex connection
|
timeoutReject(
|
||||||
resolve(false);
|
WRITE_TO_STREAM_TIMEOUT_MS,
|
||||||
createdDuplex.end();
|
nls.localize(
|
||||||
return;
|
'arduino/monitor/connectionTimeout',
|
||||||
};
|
"Timeout. The IDE has not received the 'success' message from the monitor after successfully connecting to it"
|
||||||
|
)
|
||||||
startPolling();
|
),
|
||||||
});
|
]) as Promise<unknown> as Promise<void>;
|
||||||
|
|
||||||
return pollWriteToStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -429,9 +453,9 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
* @param message string sent to running monitor
|
* @param message string sent to running monitor
|
||||||
* @returns a status to verify message has been sent.
|
* @returns a status to verify message has been sent.
|
||||||
*/
|
*/
|
||||||
async send(message: string): Promise<Status> {
|
async send(message: string): Promise<void> {
|
||||||
if (!this.duplex) {
|
if (!this.duplex) {
|
||||||
return Status.NOT_CONNECTED;
|
throw createNotConnectedError(this.port);
|
||||||
}
|
}
|
||||||
const coreClient = await this.coreClient;
|
const coreClient = await this.coreClient;
|
||||||
const { instance } = coreClient;
|
const { instance } = coreClient;
|
||||||
@ -439,14 +463,12 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
const req = new MonitorRequest();
|
const req = new MonitorRequest();
|
||||||
req.setInstance(instance);
|
req.setInstance(instance);
|
||||||
req.setTxData(new TextEncoder().encode(message));
|
req.setTxData(new TextEncoder().encode(message));
|
||||||
return new Promise<Status>((resolve) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
if (this.duplex) {
|
if (this.duplex) {
|
||||||
this.duplex?.write(req, () => {
|
this.duplex?.write(req, resolve);
|
||||||
resolve(Status.OK);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.stop().then(() => resolve(Status.NOT_CONNECTED));
|
this.stop().then(() => reject(createNotConnectedError(this.port)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,7 +491,8 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
*/
|
*/
|
||||||
private async portMonitorSettings(
|
private async portMonitorSettings(
|
||||||
protocol: string,
|
protocol: string,
|
||||||
fqbn: string
|
fqbn: string,
|
||||||
|
swallowsPlatformNotFoundError = false
|
||||||
): Promise<PluggableMonitorSettings> {
|
): Promise<PluggableMonitorSettings> {
|
||||||
const coreClient = await this.coreClient;
|
const coreClient = await this.coreClient;
|
||||||
const { client, instance } = coreClient;
|
const { client, instance } = coreClient;
|
||||||
@ -478,19 +501,33 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
req.setPortProtocol(protocol);
|
req.setPortProtocol(protocol);
|
||||||
req.setFqbn(fqbn);
|
req.setFqbn(fqbn);
|
||||||
|
|
||||||
const res = await new Promise<EnumerateMonitorPortSettingsResponse>(
|
const resp = await new Promise<
|
||||||
(resolve, reject) => {
|
EnumerateMonitorPortSettingsResponse | undefined
|
||||||
client.enumerateMonitorPortSettings(req, (err, resp) => {
|
>((resolve, reject) => {
|
||||||
if (!!err) {
|
client.enumerateMonitorPortSettings(req, async (err, resp) => {
|
||||||
reject(err);
|
if (err) {
|
||||||
|
// Check whether the platform is installed: https://github.com/arduino/arduino-ide/issues/1974.
|
||||||
|
// No error codes. Look for `Unknown FQBN: platform arduino:mbed_nano is not installed` message similarities: https://github.com/arduino/arduino-cli/issues/1762.
|
||||||
|
if (
|
||||||
|
swallowsPlatformNotFoundError &&
|
||||||
|
ServiceError.is(err) &&
|
||||||
|
err.code === status.NOT_FOUND &&
|
||||||
|
err.details.includes('FQBN') &&
|
||||||
|
err.details.includes(fqbn.split(':', 2).join(':')) // create a platform ID from the FQBN
|
||||||
|
) {
|
||||||
|
resolve(undefined);
|
||||||
}
|
}
|
||||||
resolve(resp);
|
reject(err);
|
||||||
});
|
}
|
||||||
}
|
resolve(resp);
|
||||||
);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const settings: PluggableMonitorSettings = {};
|
const settings: PluggableMonitorSettings = {};
|
||||||
for (const iterator of res.getSettingsList()) {
|
if (!resp) {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
for (const iterator of resp.getSettingsList()) {
|
||||||
settings[iterator.getSettingId()] = {
|
settings[iterator.getSettingId()] = {
|
||||||
id: iterator.getSettingId(),
|
id: iterator.getSettingId(),
|
||||||
label: iterator.getLabel(),
|
label: iterator.getLabel(),
|
||||||
@ -510,7 +547,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
* @param settings map of monitor settings to change
|
* @param settings map of monitor settings to change
|
||||||
* @returns a status to verify settings have been sent.
|
* @returns a status to verify settings have been sent.
|
||||||
*/
|
*/
|
||||||
async changeSettings(settings: MonitorSettings): Promise<Status> {
|
async changeSettings(settings: MonitorSettings): Promise<void> {
|
||||||
const config = new MonitorPortConfiguration();
|
const config = new MonitorPortConfiguration();
|
||||||
const { pluggableMonitorSettings } = settings;
|
const { pluggableMonitorSettings } = settings;
|
||||||
const reconciledSettings = await this.monitorSettingsProvider.setSettings(
|
const reconciledSettings = await this.monitorSettingsProvider.setSettings(
|
||||||
@ -527,17 +564,23 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectionStatus = Boolean(this.duplex)
|
||||||
|
? 'connected'
|
||||||
|
: 'not-connected';
|
||||||
this.updateClientsSettings({
|
this.updateClientsSettings({
|
||||||
monitorUISettings: {
|
monitorUISettings: {
|
||||||
...settings.monitorUISettings,
|
...settings.monitorUISettings,
|
||||||
connected: !!this.duplex,
|
connectionStatus,
|
||||||
serialPort: this.port.address,
|
serialPort: this.port.address,
|
||||||
|
connected: isMonitorConnected(connectionStatus), // TODO: should be removed when plotter app understand the `connectionStatus` message
|
||||||
},
|
},
|
||||||
pluggableMonitorSettings: reconciledSettings,
|
pluggableMonitorSettings: reconciledSettings,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.duplex) {
|
if (!this.duplex) {
|
||||||
return Status.NOT_CONNECTED;
|
// instead of throwing an error, return silently like the original logic
|
||||||
|
// https://github.com/arduino/arduino-ide/blob/9b49712669b06c97bda68a1e5f04eee4664c13f8/arduino-ide-extension/src/node/monitor-service.ts#L540
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const diffConfig = this.maybeUpdatePortConfigSnapshot(config);
|
const diffConfig = this.maybeUpdatePortConfigSnapshot(config);
|
||||||
@ -545,7 +588,7 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
this.logger.info(
|
this.logger.info(
|
||||||
`No port configuration changes have been detected. No need to send configure commands to the running monitor ${this.port.protocol}:${this.port.address}.`
|
`No port configuration changes have been detected. No need to send configure commands to the running monitor ${this.port.protocol}:${this.port.address}.`
|
||||||
);
|
);
|
||||||
return Status.OK;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coreClient = await this.coreClient;
|
const coreClient = await this.coreClient;
|
||||||
@ -560,7 +603,6 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
req.setInstance(instance);
|
req.setInstance(instance);
|
||||||
req.setPortConfiguration(diffConfig);
|
req.setPortConfiguration(diffConfig);
|
||||||
this.duplex.write(req);
|
this.duplex.write(req);
|
||||||
return Status.OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -688,6 +730,26 @@ export class MonitorService extends CoreClientAware implements Disposable {
|
|||||||
|
|
||||||
updateClientsSettings(settings: MonitorSettings): void {
|
updateClientsSettings(settings: MonitorSettings): void {
|
||||||
this.settings = { ...this.settings, ...settings };
|
this.settings = { ...this.settings, ...settings };
|
||||||
|
if (
|
||||||
|
settings.monitorUISettings?.connectionStatus &&
|
||||||
|
!('connected' in settings.monitorUISettings)
|
||||||
|
) {
|
||||||
|
// Make sure the deprecated `connected` prop is set.
|
||||||
|
settings.monitorUISettings.connected = isMonitorConnected(
|
||||||
|
settings.monitorUISettings.connectionStatus
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof settings.monitorUISettings?.connected === 'boolean' &&
|
||||||
|
!('connectionStatus' in settings.monitorUISettings)
|
||||||
|
) {
|
||||||
|
// Set the connectionStatus if the message was sent by the plotter which does not handle the new protocol. Assuming that the plotter can send anything.
|
||||||
|
// https://github.com/arduino/arduino-serial-plotter-webapp#monitor-settings
|
||||||
|
settings.monitorUISettings.connectionStatus = settings.monitorUISettings
|
||||||
|
.connected
|
||||||
|
? 'connected'
|
||||||
|
: 'not-connected';
|
||||||
|
}
|
||||||
const command: Monitor.Message = {
|
const command: Monitor.Message = {
|
||||||
command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE,
|
command: Monitor.MiddlewareCommand.ON_SETTINGS_DID_CHANGE,
|
||||||
data: settings,
|
data: settings,
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { MonitorModel } from '../../browser/monitor-model';
|
import { MonitorState, PluggableMonitorSetting } from '../../common/protocol';
|
||||||
import { PluggableMonitorSetting } from '../../common/protocol';
|
|
||||||
|
|
||||||
export type PluggableMonitorSettings = Record<string, PluggableMonitorSetting>;
|
export type PluggableMonitorSettings = Record<string, PluggableMonitorSetting>;
|
||||||
export interface MonitorSettings {
|
export interface MonitorSettings {
|
||||||
pluggableMonitorSettings?: PluggableMonitorSettings;
|
pluggableMonitorSettings?: PluggableMonitorSettings;
|
||||||
monitorUISettings?: Partial<MonitorModel.State>;
|
monitorUISettings?: Partial<MonitorState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider');
|
export const MonitorSettingsProvider = Symbol('MonitorSettingsProvider');
|
||||||
|
@ -328,6 +328,13 @@
|
|||||||
"tools": "Tools"
|
"tools": "Tools"
|
||||||
},
|
},
|
||||||
"monitor": {
|
"monitor": {
|
||||||
|
"alreadyConnectedError": "Could not connect to {0} {1} port. Already connected.",
|
||||||
|
"baudRate": "{0} baud",
|
||||||
|
"connectionFailedError": "Could not connect to {0} {1} port.",
|
||||||
|
"connectionFailedErrorWithDetails": "{0} Could not connect to {1} {2} port.",
|
||||||
|
"connectionTimeout": "Timeout. The IDE has not received the 'success' message from the monitor after successfully connecting to it",
|
||||||
|
"missingConfigurationError": "Could not connect to {0} {1} port. The monitor configuration is missing.",
|
||||||
|
"notConnectedError": "Not connected to {0} {1} port.",
|
||||||
"unableToCloseWebSocket": "Unable to close websocket",
|
"unableToCloseWebSocket": "Unable to close websocket",
|
||||||
"unableToConnectToWebSocket": "Unable to connect to websocket"
|
"unableToConnectToWebSocket": "Unable to connect to websocket"
|
||||||
},
|
},
|
||||||
@ -408,6 +415,7 @@
|
|||||||
"serial": {
|
"serial": {
|
||||||
"autoscroll": "Autoscroll",
|
"autoscroll": "Autoscroll",
|
||||||
"carriageReturn": "Carriage Return",
|
"carriageReturn": "Carriage Return",
|
||||||
|
"connecting": "Connecting to '{0}' on '{1}'...",
|
||||||
"message": "Message (Enter to send message to '{0}' on '{1}')",
|
"message": "Message (Enter to send message to '{0}' on '{1}')",
|
||||||
"newLine": "New Line",
|
"newLine": "New Line",
|
||||||
"newLineCarriageReturn": "Both NL & CR",
|
"newLineCarriageReturn": "Both NL & CR",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user