mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-13 14:26:37 +00:00
Removed more logic from the widget.
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
5aeb2d388e
commit
85bf50213d
@ -5,6 +5,7 @@ import { LocalStorageService } from '@theia/core/lib/browser/storage-service';
|
|||||||
import { RecursiveRequired } from '../../common/types';
|
import { RecursiveRequired } from '../../common/types';
|
||||||
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port, BoardUninstalledEvent } from '../../common/protocol/boards-service';
|
import { BoardsServiceClient, AttachedBoardsChangeEvent, BoardInstalledEvent, AttachedSerialBoard, Board, Port, BoardUninstalledEvent } from '../../common/protocol/boards-service';
|
||||||
import { BoardsConfig } from './boards-config';
|
import { BoardsConfig } from './boards-config';
|
||||||
|
import { MessageService } from '@theia/core';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class BoardsServiceClientImpl implements BoardsServiceClient {
|
export class BoardsServiceClientImpl implements BoardsServiceClient {
|
||||||
@ -12,6 +13,9 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
|
|||||||
@inject(ILogger)
|
@inject(ILogger)
|
||||||
protected logger: ILogger;
|
protected logger: ILogger;
|
||||||
|
|
||||||
|
@inject(MessageService)
|
||||||
|
protected messageService: MessageService;
|
||||||
|
|
||||||
@inject(LocalStorageService)
|
@inject(LocalStorageService)
|
||||||
protected storageService: LocalStorageService;
|
protected storageService: LocalStorageService;
|
||||||
|
|
||||||
@ -110,15 +114,51 @@ export class BoardsServiceClientImpl implements BoardsServiceClient {
|
|||||||
/**
|
/**
|
||||||
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
|
* `true` if the `config.selectedBoard` is defined; hence can compile against the board. Otherwise, `false`.
|
||||||
*/
|
*/
|
||||||
canVerify(config: BoardsConfig.Config | undefined = this.boardsConfig): config is BoardsConfig.Config & { selectedBoard: Board } {
|
canVerify(
|
||||||
return !!config && !!config.selectedBoard;
|
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||||
|
options: { silent: boolean } = { silent: true }): config is BoardsConfig.Config & { selectedBoard: Board } {
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.selectedBoard) {
|
||||||
|
if (!options.silent) {
|
||||||
|
this.messageService.warn('No boards selected.');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `true` if the `canVerify` and the `config.selectedPort` is also set with FQBN, hence can upload to board. Otherwise, `false`.
|
* `true` if the `canVerify` and the `config.selectedPort` is also set with FQBN, hence can upload to board. Otherwise, `false`.
|
||||||
*/
|
*/
|
||||||
canUploadTo(config: BoardsConfig.Config | undefined = this.boardsConfig): config is RecursiveRequired<BoardsConfig.Config> {
|
canUploadTo(
|
||||||
return this.canVerify(config) && !!config.selectedPort && !!config.selectedBoard.fqbn;
|
config: BoardsConfig.Config | undefined = this.boardsConfig,
|
||||||
|
options: { silent: boolean } = { silent: true }): config is RecursiveRequired<BoardsConfig.Config> {
|
||||||
|
|
||||||
|
if (!this.canVerify(config, options)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name } = config.selectedBoard;
|
||||||
|
if (!config.selectedPort) {
|
||||||
|
if (!options.silent) {
|
||||||
|
this.messageService.warn(`No ports selected for board: '${name}'.`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.selectedBoard.fqbn) {
|
||||||
|
if (!options.silent) {
|
||||||
|
this.messageService.warn(`The FQBN is not available for the selected board ${name}. Do you have the corresponding core installed?`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected saveState(): Promise<void> {
|
protected saveState(): Promise<void> {
|
||||||
|
@ -4,18 +4,26 @@ import { Emitter, Event } from '@theia/core/lib/common/event';
|
|||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { MonitorService, MonitorConfig, MonitorError, Status } from '../../common/protocol/monitor-service';
|
import { MonitorService, MonitorConfig, MonitorError, Status } from '../../common/protocol/monitor-service';
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||||
import { Port, Board } from '../../common/protocol/boards-service';
|
import { Port, Board, BoardsService, AttachedSerialBoard, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
||||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||||
|
import { BoardsConfig } from '../boards/boards-config';
|
||||||
|
import { MonitorModel } from './monitor-model';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MonitorConnection {
|
export class MonitorConnection {
|
||||||
|
|
||||||
|
@inject(MonitorModel)
|
||||||
|
protected readonly monitorModel: MonitorModel;
|
||||||
|
|
||||||
@inject(MonitorService)
|
@inject(MonitorService)
|
||||||
protected readonly monitorService: MonitorService;
|
protected readonly monitorService: MonitorService;
|
||||||
|
|
||||||
@inject(MonitorServiceClientImpl)
|
@inject(MonitorServiceClientImpl)
|
||||||
protected readonly monitorServiceClient: MonitorServiceClientImpl;
|
protected readonly monitorServiceClient: MonitorServiceClientImpl;
|
||||||
|
|
||||||
|
@inject(BoardsService)
|
||||||
|
protected readonly boardsService: BoardsService;
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
@inject(BoardsServiceClientImpl)
|
||||||
protected boardsServiceClient: BoardsServiceClientImpl;
|
protected boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
@ -26,9 +34,14 @@ export class MonitorConnection {
|
|||||||
// protected readonly connectionStatusService: ConnectionStatusService;
|
// protected readonly connectionStatusService: ConnectionStatusService;
|
||||||
|
|
||||||
protected state: MonitorConnection.State | undefined;
|
protected state: MonitorConnection.State | undefined;
|
||||||
protected readonly onConnectionChangedEmitter = new Emitter<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: boolean = false;
|
||||||
|
protected readonly onConnectionChangedEmitter = new Emitter<MonitorConnection.State | undefined>();
|
||||||
|
|
||||||
readonly onConnectionChanged: Event<boolean> = this.onConnectionChangedEmitter.event;
|
readonly onConnectionChanged: Event<MonitorConnection.State | undefined> = this.onConnectionChangedEmitter.event;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
@ -55,16 +68,39 @@ export class MonitorConnection {
|
|||||||
const { board, port } = config;
|
const { board, port } = config;
|
||||||
this.messageService.error(`Unexpected error. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`);
|
this.messageService.error(`Unexpected error. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`);
|
||||||
console.error(JSON.stringify(error));
|
console.error(JSON.stringify(error));
|
||||||
shouldReconnect = true;
|
shouldReconnect = this.connected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const oldState = this.state;
|
const oldState = this.state;
|
||||||
this.state = undefined;
|
this.state = undefined;
|
||||||
|
this.onConnectionChangedEmitter.fire(this.state);
|
||||||
if (shouldReconnect) {
|
if (shouldReconnect) {
|
||||||
await this.connect(oldState.config);
|
await this.connect(oldState.config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.boardsServiceClient.onBoardsConfigChanged(this.handleBoardConfigChange.bind(this));
|
||||||
|
this.boardsServiceClient.onBoardsChanged(event => {
|
||||||
|
if (this.autoConnect && this.connected) {
|
||||||
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
||||||
|
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
||||||
|
if (attached.boards.some(board => AttachedSerialBoard.is(board) && BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||||
|
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||||
|
const { baudRate } = this.monitorModel;
|
||||||
|
this.disconnect()
|
||||||
|
.then(() => this.connect({ board, port, baudRate }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Handles the `baudRate` changes by reconnecting if required.
|
||||||
|
this.monitorModel.onChange(() => {
|
||||||
|
if (this.autoConnect && this.connected) {
|
||||||
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
this.handleBoardConfigChange(boardsConfig);
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get connected(): boolean {
|
get connected(): boolean {
|
||||||
@ -75,8 +111,22 @@ export class MonitorConnection {
|
|||||||
return this.state ? this.state.config : undefined;
|
return this.state ? this.state.config : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get autoConnect(): boolean {
|
||||||
|
return this._autoConnect;
|
||||||
|
}
|
||||||
|
|
||||||
|
set autoConnect(value: boolean) {
|
||||||
|
const oldValue = this._autoConnect;
|
||||||
|
this._autoConnect = value;
|
||||||
|
// When we enable the auto-connect, we have to connect
|
||||||
|
if (!oldValue && value) {
|
||||||
|
const { boardsConfig } = this.boardsServiceClient;
|
||||||
|
this.handleBoardConfigChange(boardsConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async connect(config: MonitorConfig): Promise<Status> {
|
async connect(config: MonitorConfig): Promise<Status> {
|
||||||
if (this.state) {
|
if (this.connected) {
|
||||||
const disconnectStatus = await this.disconnect();
|
const disconnectStatus = await this.disconnect();
|
||||||
if (!Status.isOK(disconnectStatus)) {
|
if (!Status.isOK(disconnectStatus)) {
|
||||||
return disconnectStatus;
|
return disconnectStatus;
|
||||||
@ -85,13 +135,13 @@ export class MonitorConnection {
|
|||||||
const connectStatus = await this.monitorService.connect(config);
|
const connectStatus = await this.monitorService.connect(config);
|
||||||
if (Status.isOK(connectStatus)) {
|
if (Status.isOK(connectStatus)) {
|
||||||
this.state = { config };
|
this.state = { config };
|
||||||
this.onConnectionChangedEmitter.fire(true);
|
|
||||||
}
|
}
|
||||||
|
this.onConnectionChangedEmitter.fire(this.state);
|
||||||
return Status.isOK(connectStatus);
|
return Status.isOK(connectStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect(): Promise<Status> {
|
async disconnect(): Promise<Status> {
|
||||||
if (!this.state) {
|
if (!this.state) { // XXX: we user `this.state` instead of `this.connected` to make the type checker happy.
|
||||||
return Status.OK;
|
return Status.OK;
|
||||||
}
|
}
|
||||||
console.log('>>> Disposing existing monitor connection before establishing a new one...');
|
console.log('>>> Disposing existing monitor connection before establishing a new one...');
|
||||||
@ -102,10 +152,48 @@ export class MonitorConnection {
|
|||||||
console.warn(`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(this.state)}`);
|
console.warn(`<<< Could not dispose connection. Activate connection: ${MonitorConnection.State.toString(this.state)}`);
|
||||||
}
|
}
|
||||||
this.state = undefined;
|
this.state = undefined;
|
||||||
this.onConnectionChangedEmitter.fire(false);
|
this.onConnectionChangedEmitter.fire(this.state);
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the data to the connected serial monitor.
|
||||||
|
* The desired EOL is appended to `data`, you do not have to add it.
|
||||||
|
* It is a NOOP if connected.
|
||||||
|
*/
|
||||||
|
async send(data: string): Promise<Status> {
|
||||||
|
if (!this.connected) {
|
||||||
|
return Status.NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
return new Promise<Status>(resolve => {
|
||||||
|
this.monitorService.send(data + this.monitorModel.lineEnding)
|
||||||
|
.then(() => resolve(Status.OK));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async handleBoardConfigChange(boardsConfig: BoardsConfig.Config): Promise<void> {
|
||||||
|
if (this.autoConnect) {
|
||||||
|
if (this.boardsServiceClient.canUploadTo(boardsConfig, { silent: false })) {
|
||||||
|
this.boardsService.getAttachedBoards().then(({ boards }) => {
|
||||||
|
if (boards.filter(AttachedSerialBoard.is).some(board => BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
||||||
|
new Promise<void>(resolve => {
|
||||||
|
// First, disconnect if connected.
|
||||||
|
if (this.connected) {
|
||||||
|
this.disconnect().then(() => resolve());
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
}).then(() => {
|
||||||
|
// Then (re-)connect.
|
||||||
|
const { selectedBoard: board, selectedPort: port } = boardsConfig;
|
||||||
|
const { baudRate } = this.monitorModel;
|
||||||
|
this.connect({ board, port, baudRate });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace MonitorConnection {
|
export namespace MonitorConnection {
|
||||||
|
@ -2,6 +2,7 @@ import { injectable, inject } from 'inversify';
|
|||||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||||
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||||
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
import { FrontendApplicationContribution, LocalStorageService } from '@theia/core/lib/browser';
|
||||||
|
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class MonitorModel implements FrontendApplicationContribution {
|
export class MonitorModel implements FrontendApplicationContribution {
|
||||||
@ -11,6 +12,9 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
@inject(LocalStorageService)
|
@inject(LocalStorageService)
|
||||||
protected readonly localStorageService: LocalStorageService;
|
protected readonly localStorageService: LocalStorageService;
|
||||||
|
|
||||||
|
@inject(BoardsServiceClientImpl)
|
||||||
|
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
||||||
|
|
||||||
protected readonly onChangeEmitter: Emitter<void>;
|
protected readonly onChangeEmitter: Emitter<void>;
|
||||||
protected _autoscroll: boolean;
|
protected _autoscroll: boolean;
|
||||||
protected _timestamp: boolean;
|
protected _timestamp: boolean;
|
||||||
@ -70,7 +74,7 @@ export class MonitorModel implements FrontendApplicationContribution {
|
|||||||
|
|
||||||
set lineEnding(lineEnding: MonitorModel.EOL) {
|
set lineEnding(lineEnding: MonitorModel.EOL) {
|
||||||
this._lineEnding = lineEnding;
|
this._lineEnding = lineEnding;
|
||||||
this.storeState().then(() => this.onChangeEmitter.fire(undefined));
|
this.storeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected restoreState(state: MonitorModel.State) {
|
protected restoreState(state: MonitorModel.State) {
|
||||||
|
@ -5,15 +5,213 @@ import { ThemeConfig } from 'react-select/src/theme';
|
|||||||
import { OptionsType } from 'react-select/src/types';
|
import { OptionsType } from 'react-select/src/types';
|
||||||
import Select from 'react-select';
|
import Select from 'react-select';
|
||||||
import { Styles } from 'react-select/src/styles';
|
import { Styles } from 'react-select/src/styles';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { ReactWidget, Message, Widget } from '@theia/core/lib/browser/widgets';
|
||||||
import { ReactWidget, Message, Widget } from '@theia/core/lib/browser';
|
import { MonitorConfig } from '../../common/protocol/monitor-service';
|
||||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
|
||||||
import { MonitorConfig, MonitorService } from '../../common/protocol/monitor-service';
|
|
||||||
import { AttachedSerialBoard, BoardsService, AttachedBoardsChangeEvent } from '../../common/protocol/boards-service';
|
|
||||||
import { BoardsConfig } from '../boards/boards-config';
|
|
||||||
import { BoardsServiceClientImpl } from '../boards/boards-service-client-impl';
|
|
||||||
import { MonitorModel } from './monitor-model';
|
import { MonitorModel } from './monitor-model';
|
||||||
import { MonitorConnection } from './monitor-connection';
|
import { MonitorConnection } from './monitor-connection';
|
||||||
|
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class MonitorWidget extends ReactWidget {
|
||||||
|
|
||||||
|
static readonly ID = 'serial-monitor';
|
||||||
|
|
||||||
|
@inject(MonitorModel)
|
||||||
|
protected readonly model: MonitorModel;
|
||||||
|
|
||||||
|
@inject(MonitorConnection)
|
||||||
|
protected readonly monitorConnection: MonitorConnection;
|
||||||
|
|
||||||
|
@inject(MonitorServiceClientImpl)
|
||||||
|
protected readonly monitorServiceClient: MonitorServiceClientImpl;
|
||||||
|
|
||||||
|
protected lines: string[];
|
||||||
|
protected chunk: string;
|
||||||
|
protected widgetHeight: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
||||||
|
*/
|
||||||
|
protected focusNode: HTMLElement | undefined;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.id = MonitorWidget.ID;
|
||||||
|
this.title.label = 'Serial Monitor';
|
||||||
|
this.title.iconClass = 'arduino-serial-monitor-tab-icon';
|
||||||
|
|
||||||
|
this.lines = [];
|
||||||
|
this.chunk = '';
|
||||||
|
this.scrollOptions = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
@postConstruct()
|
||||||
|
protected init(): void {
|
||||||
|
this.toDisposeOnDetach.pushAll([
|
||||||
|
this.monitorServiceClient.onRead(({ data }) => {
|
||||||
|
this.chunk += data;
|
||||||
|
const eolIndex = this.chunk.indexOf('\n');
|
||||||
|
if (eolIndex !== -1) {
|
||||||
|
const line = this.chunk.substring(0, eolIndex + 1);
|
||||||
|
this.chunk = this.chunk.slice(eolIndex + 1);
|
||||||
|
this.lines.push(`${this.model.timestamp ? `${dateFormat(new Date(), 'H:M:ss.l')} -> ` : ''}${line}`);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
this.monitorConnection.onConnectionChanged(state => {
|
||||||
|
if (!state) {
|
||||||
|
this.clearConsole();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConsole(): void {
|
||||||
|
this.chunk = '';
|
||||||
|
this.lines = [];
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeAttach(msg: Message): void {
|
||||||
|
super.onBeforeAttach(msg);
|
||||||
|
this.clearConsole();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterAttach(msg: Message): void {
|
||||||
|
super.onAfterAttach(msg);
|
||||||
|
this.monitorConnection.autoConnect = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onBeforeDetach(msg: Message): void {
|
||||||
|
super.onBeforeDetach(msg);
|
||||||
|
this.monitorConnection.autoConnect = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onResize(msg: Widget.ResizeMessage): void {
|
||||||
|
super.onResize(msg);
|
||||||
|
this.widgetHeight = msg.height;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get lineEndings(): OptionsType<SelectOption<MonitorModel.EOL>> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'No Line Ending',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Newline',
|
||||||
|
value: '\n'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Carriage Return',
|
||||||
|
value: '\r'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Both NL & CR',
|
||||||
|
value: '\r\n'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get baudRates(): OptionsType<SelectOption<MonitorConfig.BaudRate>> {
|
||||||
|
const baudRates: Array<MonitorConfig.BaudRate> = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
|
||||||
|
return baudRates.map(baudRate => ({ label: baudRate + ' baud', value: baudRate }));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): React.ReactNode {
|
||||||
|
const { baudRates, lineEndings } = this;
|
||||||
|
const lineEnding = lineEndings.find(item => item.value === this.model.lineEnding) || lineEndings[1]; // Defaults to `\n`.
|
||||||
|
const baudRate = baudRates.find(item => item.value === this.model.baudRate) || baudRates[4]; // Defaults to `9600`.
|
||||||
|
return <div className='serial-monitor-container'>
|
||||||
|
<div className='head'>
|
||||||
|
<div className='send'>
|
||||||
|
<SerialMonitorSendField onSend={this.onSend} />
|
||||||
|
</div>
|
||||||
|
<div className='config'>
|
||||||
|
{this.renderSelectField('arduino-serial-monitor-line-endings', lineEndings, lineEnding, this.onChangeLineEnding)}
|
||||||
|
{this.renderSelectField('arduino-serial-monitor-baud-rates', baudRates, baudRate, this.onChangeBaudRate)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id='serial-monitor-output-container'>
|
||||||
|
<SerialMonitorOutput model={this.model} lines={this.lines} />
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly onSend = (value: string) => this.doSend(value);
|
||||||
|
protected async doSend(value: string): Promise<void> {
|
||||||
|
this.monitorConnection.send(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly onChangeLineEnding = (option: SelectOption<MonitorModel.EOL>) => {
|
||||||
|
this.model.lineEnding = option.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly onChangeBaudRate = async (option: SelectOption<MonitorConfig.BaudRate>) => {
|
||||||
|
await this.monitorConnection.disconnect();
|
||||||
|
this.model.baudRate = option.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderSelectField<T>(
|
||||||
|
id: string,
|
||||||
|
options: OptionsType<SelectOption<T>>,
|
||||||
|
defaultValue: SelectOption<T>,
|
||||||
|
onChange: (option: SelectOption<T>) => void): React.ReactNode {
|
||||||
|
|
||||||
|
const height = 25;
|
||||||
|
const styles: Styles = {
|
||||||
|
control: (styles, state) => ({
|
||||||
|
...styles,
|
||||||
|
width: 200,
|
||||||
|
color: 'var(--theia-ui-font-color1)'
|
||||||
|
}),
|
||||||
|
dropdownIndicator: styles => ({
|
||||||
|
...styles,
|
||||||
|
padding: 0
|
||||||
|
}),
|
||||||
|
indicatorSeparator: () => ({
|
||||||
|
display: 'none'
|
||||||
|
}),
|
||||||
|
indicatorsContainer: () => ({
|
||||||
|
padding: '0px 5px'
|
||||||
|
}),
|
||||||
|
menu: styles => ({
|
||||||
|
...styles,
|
||||||
|
marginTop: 0
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const theme: ThemeConfig = theme => ({
|
||||||
|
...theme,
|
||||||
|
borderRadius: 0,
|
||||||
|
spacing: {
|
||||||
|
controlHeight: height,
|
||||||
|
baseUnit: 2,
|
||||||
|
menuGutter: 4
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const DropdownIndicator = () => {
|
||||||
|
return (
|
||||||
|
<span className='fa fa-caret-down caret'></span>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return <Select
|
||||||
|
options={options}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
onChange={onChange}
|
||||||
|
components={{ DropdownIndicator }}
|
||||||
|
theme={theme}
|
||||||
|
styles={styles}
|
||||||
|
maxMenuHeight={this.widgetHeight - 40}
|
||||||
|
classNamePrefix='sms'
|
||||||
|
className='serial-monitor-select'
|
||||||
|
id={id}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export namespace SerialMonitorSendField {
|
export namespace SerialMonitorSendField {
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -109,272 +307,3 @@ export interface SelectOption<T> {
|
|||||||
readonly label: string;
|
readonly label: string;
|
||||||
readonly value: T;
|
readonly value: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class MonitorWidget extends ReactWidget {
|
|
||||||
|
|
||||||
static readonly ID = 'serial-monitor';
|
|
||||||
|
|
||||||
@inject(MonitorServiceClientImpl)
|
|
||||||
protected readonly serviceClient: MonitorServiceClientImpl;
|
|
||||||
|
|
||||||
@inject(MonitorConnection)
|
|
||||||
protected readonly connection: MonitorConnection;
|
|
||||||
|
|
||||||
@inject(MonitorService)
|
|
||||||
protected readonly monitorService: MonitorService;
|
|
||||||
|
|
||||||
@inject(BoardsServiceClientImpl)
|
|
||||||
protected readonly boardsServiceClient: BoardsServiceClientImpl;
|
|
||||||
|
|
||||||
@inject(MessageService)
|
|
||||||
protected readonly messageService: MessageService;
|
|
||||||
|
|
||||||
@inject(BoardsService)
|
|
||||||
protected readonly boardsService: BoardsService;
|
|
||||||
|
|
||||||
@inject(MonitorModel)
|
|
||||||
protected readonly model: MonitorModel;
|
|
||||||
|
|
||||||
protected lines: string[];
|
|
||||||
protected chunk: string;
|
|
||||||
protected widgetHeight: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Do not touch or use it. It is for setting the focus on the `input` after the widget activation.
|
|
||||||
*/
|
|
||||||
protected focusNode: HTMLElement | undefined;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.id = MonitorWidget.ID;
|
|
||||||
this.title.label = 'Serial Monitor';
|
|
||||||
this.title.iconClass = 'arduino-serial-monitor-tab-icon';
|
|
||||||
|
|
||||||
this.lines = [];
|
|
||||||
this.chunk = '';
|
|
||||||
this.scrollOptions = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
@postConstruct()
|
|
||||||
protected init(): void {
|
|
||||||
this.toDisposeOnDetach.pushAll([
|
|
||||||
this.serviceClient.onRead(({ data }) => {
|
|
||||||
this.chunk += data;
|
|
||||||
const eolIndex = this.chunk.indexOf('\n');
|
|
||||||
if (eolIndex !== -1) {
|
|
||||||
const line = this.chunk.substring(0, eolIndex + 1);
|
|
||||||
this.chunk = this.chunk.slice(eolIndex + 1);
|
|
||||||
this.lines.push(`${this.model.timestamp ? `${dateFormat(new Date(), 'H:M:ss.l')} -> ` : ''}${line}`);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
this.boardsServiceClient.onBoardsConfigChanged(config => {
|
|
||||||
if (this.boardsServiceClient.canUploadTo(config)) {
|
|
||||||
this.boardsService.getAttachedBoards().then(({ boards }) => {
|
|
||||||
if (boards.filter(AttachedSerialBoard.is).some(board => BoardsConfig.Config.sameAs(config, board))) {
|
|
||||||
this.connect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
this.boardsServiceClient.onBoardsChanged(event => {
|
|
||||||
const { attached } = AttachedBoardsChangeEvent.diff(event);
|
|
||||||
if (!this.connection.connected && this.boardsServiceClient.canUploadTo()) {
|
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
|
||||||
if (attached.boards.filter(AttachedSerialBoard.is).some(board => BoardsConfig.Config.sameAs(boardsConfig, board))) {
|
|
||||||
this.connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
clearConsole(): void {
|
|
||||||
this.chunk = '';
|
|
||||||
this.lines = [];
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
onBeforeAttach(msg: Message): void {
|
|
||||||
super.onBeforeAttach(msg);
|
|
||||||
this.clearConsole();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onAfterAttach(msg: Message): void {
|
|
||||||
super.onAfterAttach(msg);
|
|
||||||
this.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onBeforeDetach(msg: Message): void {
|
|
||||||
super.onBeforeDetach(msg);
|
|
||||||
if (this.connection.connected) {
|
|
||||||
this.connection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected onResize(msg: Widget.ResizeMessage): void {
|
|
||||||
super.onResize(msg);
|
|
||||||
this.widgetHeight = msg.height;
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async connect(): Promise<void> {
|
|
||||||
this.clearConsole();
|
|
||||||
const config = await this.getConnectionConfig();
|
|
||||||
if (config) {
|
|
||||||
this.connection.connect(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async getConnectionConfig(): Promise<MonitorConfig | undefined> {
|
|
||||||
const baudRate = this.model.baudRate;
|
|
||||||
const { boardsConfig } = this.boardsServiceClient;
|
|
||||||
const { selectedBoard, selectedPort } = boardsConfig;
|
|
||||||
if (!selectedBoard) {
|
|
||||||
this.messageService.warn('No boards selected.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { name } = selectedBoard;
|
|
||||||
if (!selectedPort) {
|
|
||||||
this.messageService.warn(`No ports selected for board: '${name}'.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const attachedBoards = await this.boardsService.getAttachedBoards();
|
|
||||||
const connectedBoard = attachedBoards.boards.filter(AttachedSerialBoard.is).find(board => BoardsConfig.Config.sameAs(boardsConfig, board));
|
|
||||||
if (!connectedBoard) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
baudRate,
|
|
||||||
board: selectedBoard,
|
|
||||||
port: selectedPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get lineEndings(): OptionsType<SelectOption<MonitorModel.EOL>> {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: 'No Line Ending',
|
|
||||||
value: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Newline',
|
|
||||||
value: '\n'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Carriage Return',
|
|
||||||
value: '\r'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Both NL & CR',
|
|
||||||
value: '\r\n'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get baudRates(): OptionsType<SelectOption<MonitorConfig.BaudRate>> {
|
|
||||||
const baudRates: Array<MonitorConfig.BaudRate> = [300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200];
|
|
||||||
return baudRates.map(baudRate => ({ label: baudRate + ' baud', value: baudRate }));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): React.ReactNode {
|
|
||||||
const { baudRates, lineEndings } = this;
|
|
||||||
const lineEnding = lineEndings.find(item => item.value === this.model.lineEnding) || lineEndings[1]; // Defaults to `\n`.
|
|
||||||
const baudRate = baudRates.find(item => item.value === this.model.baudRate) || baudRates[4]; // Defaults to `9600`.
|
|
||||||
return <div className='serial-monitor-container'>
|
|
||||||
<div className='head'>
|
|
||||||
<div className='send'>
|
|
||||||
<SerialMonitorSendField onSend={this.onSend} />
|
|
||||||
</div>
|
|
||||||
<div className='config'>
|
|
||||||
{this.renderSelectField('arduino-serial-monitor-line-endings', lineEndings, lineEnding, this.onChangeLineEnding)}
|
|
||||||
{this.renderSelectField('arduino-serial-monitor-baud-rates', baudRates, baudRate, this.onChangeBaudRate)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id='serial-monitor-output-container'>
|
|
||||||
<SerialMonitorOutput model={this.model} lines={this.lines} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly onSend = (value: string) => this.doSend(value);
|
|
||||||
protected async doSend(value: string) {
|
|
||||||
if (this.connection.connected) {
|
|
||||||
this.monitorService.send(value + this.model.lineEnding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly onChangeLineEnding = (option: SelectOption<MonitorModel.EOL>) => {
|
|
||||||
this.model.lineEnding = typeof option.value === 'string' ? option.value : MonitorModel.EOL.DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected readonly onChangeBaudRate = async (option: SelectOption<MonitorConfig.BaudRate>) => {
|
|
||||||
await this.connection.disconnect();
|
|
||||||
this.model.baudRate = typeof option.value === 'number' ? option.value : MonitorConfig.BaudRate.DEFAULT;
|
|
||||||
this.clearConsole();
|
|
||||||
const config = await this.getConnectionConfig();
|
|
||||||
if (config) {
|
|
||||||
await this.connection.connect(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderSelectField<T>(
|
|
||||||
id: string,
|
|
||||||
options: OptionsType<SelectOption<T>>,
|
|
||||||
defaultValue: SelectOption<T>,
|
|
||||||
onChange: (option: SelectOption<T>) => void): React.ReactNode {
|
|
||||||
|
|
||||||
const height = 25;
|
|
||||||
const styles: Styles = {
|
|
||||||
control: (styles, state) => ({
|
|
||||||
...styles,
|
|
||||||
width: 200,
|
|
||||||
color: 'var(--theia-ui-font-color1)'
|
|
||||||
}),
|
|
||||||
dropdownIndicator: styles => ({
|
|
||||||
...styles,
|
|
||||||
padding: 0
|
|
||||||
}),
|
|
||||||
indicatorSeparator: () => ({
|
|
||||||
display: 'none'
|
|
||||||
}),
|
|
||||||
indicatorsContainer: () => ({
|
|
||||||
padding: '0px 5px'
|
|
||||||
}),
|
|
||||||
menu: styles => ({
|
|
||||||
...styles,
|
|
||||||
marginTop: 0
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const theme: ThemeConfig = theme => ({
|
|
||||||
...theme,
|
|
||||||
borderRadius: 0,
|
|
||||||
spacing: {
|
|
||||||
controlHeight: height,
|
|
||||||
baseUnit: 2,
|
|
||||||
menuGutter: 4
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const DropdownIndicator = () => {
|
|
||||||
return (
|
|
||||||
<span className='fa fa-caret-down caret'></span>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return <Select
|
|
||||||
options={options}
|
|
||||||
defaultValue={defaultValue}
|
|
||||||
onChange={onChange}
|
|
||||||
components={{ DropdownIndicator }}
|
|
||||||
theme={theme}
|
|
||||||
styles={styles}
|
|
||||||
maxMenuHeight={this.widgetHeight - 40}
|
|
||||||
classNamePrefix='sms'
|
|
||||||
className='serial-monitor-select'
|
|
||||||
id={id}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user