mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-08 03:46:33 +00:00
simplified monitor connection API.
we have one connenction per editor anyways. Signed-off-by: Akos Kitta <kittaakos@typefox.io>
This commit is contained in:
parent
80549db289
commit
a4e5e65286
101
.vscode/launch.json
vendored
101
.vscode/launch.json
vendored
@ -1,39 +1,64 @@
|
|||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
// Use IntelliSense to learn about possible attributes.
|
||||||
// Hover to view descriptions of existing attributes.
|
// Hover to view descriptions of existing attributes.
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Electron Packager",
|
"name": "Launch Electron Packager",
|
||||||
"program": "${workspaceRoot}/electron/packager/index.js",
|
"program": "${workspaceRoot}/electron/packager/index.js",
|
||||||
"cwd": "${workspaceFolder}/electron/packager"
|
"cwd": "${workspaceFolder}/electron/packager"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Backend",
|
"name": "Launch Backend",
|
||||||
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
|
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
|
||||||
"args": [
|
"args": [
|
||||||
"--hostname=0.0.0.0",
|
"--hostname=0.0.0.0",
|
||||||
"--port=3000",
|
"--port=3000",
|
||||||
"--no-cluster",
|
"--no-cluster",
|
||||||
"--no-app-auto-install"
|
"--no-app-auto-install"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "development"
|
"NODE_ENV": "development"
|
||||||
},
|
},
|
||||||
"sourceMaps": true,
|
"sourceMaps": true,
|
||||||
"outFiles": [
|
"outFiles": [
|
||||||
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
||||||
"${workspaceRoot}/browser-app/lib/**/*.js",
|
"${workspaceRoot}/browser-app/lib/**/*.js",
|
||||||
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
||||||
],
|
],
|
||||||
"smartStep": true,
|
"smartStep": true,
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
"outputCapture": "std"
|
"outputCapture": "std"
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
}
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Backend (Debug CLI daemon)",
|
||||||
|
"program": "${workspaceRoot}/browser-app/src-gen/backend/main.js",
|
||||||
|
"args": [
|
||||||
|
"--hostname=0.0.0.0",
|
||||||
|
"--port=3000",
|
||||||
|
"--no-cluster",
|
||||||
|
"--no-app-auto-install",
|
||||||
|
"--debug-cli=true"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"NODE_ENV": "development"
|
||||||
|
},
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceRoot}/browser-app/src-gen/backend/*.js",
|
||||||
|
"${workspaceRoot}/browser-app/lib/**/*.js",
|
||||||
|
"${workspaceRoot}/arduino-ide-extension/*/lib/**/*.js"
|
||||||
|
],
|
||||||
|
"smartStep": true,
|
||||||
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
|
"outputCapture": "std"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { injectable, inject, postConstruct } from 'inversify';
|
|||||||
import { Emitter, Event } from '@theia/core/lib/common/event';
|
import { Emitter, Event } from '@theia/core/lib/common/event';
|
||||||
// import { ConnectionStatusService } from '@theia/core/lib/browser/connection-status-service';
|
// import { ConnectionStatusService } from '@theia/core/lib/browser/connection-status-service';
|
||||||
import { MessageService } from '@theia/core/lib/common/message-service';
|
import { MessageService } from '@theia/core/lib/common/message-service';
|
||||||
import { MonitorService, MonitorConfig, MonitorError } 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 } from '../../common/protocol/boards-service';
|
||||||
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
import { MonitorServiceClientImpl } from './monitor-service-client-impl';
|
||||||
@ -26,82 +26,81 @@ 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<string | undefined>();
|
protected readonly onConnectionChangedEmitter = new Emitter<boolean>();
|
||||||
|
|
||||||
readonly onConnectionChanged: Event<string | undefined> = this.onConnectionChangedEmitter.event;
|
readonly onConnectionChanged: Event<boolean> = this.onConnectionChangedEmitter.event;
|
||||||
|
|
||||||
@postConstruct()
|
@postConstruct()
|
||||||
protected init(): void {
|
protected init(): void {
|
||||||
this.monitorServiceClient.onError(async error => {
|
this.monitorServiceClient.onError(async error => {
|
||||||
let shouldReconnect = false;
|
let shouldReconnect = false;
|
||||||
if (this.state) {
|
if (this.state) {
|
||||||
const { code, connectionId, config } = error;
|
const { code, config } = error;
|
||||||
if (this.state.connectionId === connectionId) {
|
switch (code) {
|
||||||
switch (code) {
|
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
|
||||||
case MonitorError.ErrorCodes.CLIENT_CANCEL: {
|
console.debug(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`);
|
||||||
console.debug(`Connection was canceled by client: ${MonitorConnection.State.toString(this.state)}.`);
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MonitorError.ErrorCodes.DEVICE_BUSY: {
|
|
||||||
const { port } = config;
|
|
||||||
this.messageService.warn(`Connection failed. Serial port is busy: ${Port.toString(port)}.`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
|
|
||||||
const { port } = config;
|
|
||||||
this.messageService.info(`Disconnected from ${Port.toString(port)}.`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MonitorError.ErrorCodes.interrupted_system_call: {
|
|
||||||
const { board, port } = config;
|
|
||||||
this.messageService.warn(`Unexpectedly interrupted by backend. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`);
|
|
||||||
shouldReconnect = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const oldState = this.state;
|
case MonitorError.ErrorCodes.DEVICE_BUSY: {
|
||||||
this.state = undefined;
|
const { port } = config;
|
||||||
if (shouldReconnect) {
|
this.messageService.warn(`Connection failed. Serial port is busy: ${Port.toString(port)}.`);
|
||||||
await this.connect(oldState.config);
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
case MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED: {
|
||||||
console.warn(`Received an error from unexpected connection: ${MonitorConnection.State.toString({ connectionId, config })}.`);
|
const { port } = config;
|
||||||
|
this.messageService.info(`Disconnected from ${Port.toString(port)}.`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case undefined: {
|
||||||
|
const { board, port } = config;
|
||||||
|
this.messageService.error(`Unexpected error. Reconnecting ${Board.toString(board)} on port ${Port.toString(port)}.`);
|
||||||
|
console.error(JSON.stringify(error));
|
||||||
|
shouldReconnect = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const oldState = this.state;
|
||||||
|
this.state = undefined;
|
||||||
|
if (shouldReconnect) {
|
||||||
|
await this.connect(oldState.config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get connectionId(): string | undefined {
|
get connected(): boolean {
|
||||||
return this.state ? this.state.connectionId : undefined;
|
return !!this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
get connectionConfig(): MonitorConfig | undefined {
|
get connectionConfig(): MonitorConfig | undefined {
|
||||||
return this.state ? this.state.config : undefined;
|
return this.state ? this.state.config : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(config: MonitorConfig): Promise<string | undefined> {
|
async connect(config: MonitorConfig): Promise<Status> {
|
||||||
if (this.state) {
|
if (this.state) {
|
||||||
throw new Error(`Already connected to ${MonitorConnection.State.toString(this.state)}.`);
|
throw new Error(`Already connected to ${MonitorConnection.State.toString(this.state)}.`);
|
||||||
}
|
}
|
||||||
const { connectionId } = await this.monitorService.connect(config);
|
const status = await this.monitorService.connect(config);
|
||||||
this.state = { connectionId, config };
|
if (Status.isOK(status)) {
|
||||||
this.onConnectionChangedEmitter.fire(connectionId);
|
this.state = { config };
|
||||||
return connectionId;
|
this.onConnectionChangedEmitter.fire(true);
|
||||||
|
}
|
||||||
|
return Status.isOK(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect(): Promise<boolean> {
|
async disconnect(): Promise<Status> {
|
||||||
if (!this.state) {
|
if (!this.state) {
|
||||||
throw new Error('Not connected. Nothing to disconnect.');
|
throw new Error('Not connected. Nothing to disconnect.');
|
||||||
}
|
}
|
||||||
console.log('>>> Disposing existing monitor connection before establishing a new one...');
|
console.log('>>> Disposing existing monitor connection before establishing a new one...');
|
||||||
const result = await this.monitorService.disconnect(this.state.connectionId);
|
const status = await this.monitorService.disconnect();
|
||||||
if (result) {
|
if (Status.isOK(status)) {
|
||||||
console.log(`<<< Disposed connection. Was: ${MonitorConnection.State.toString(this.state)}`);
|
console.log(`<<< Disposed connection. Was: ${MonitorConnection.State.toString(this.state)}`);
|
||||||
} else {
|
} else {
|
||||||
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(undefined);
|
this.onConnectionChangedEmitter.fire(false);
|
||||||
return result;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -109,15 +108,14 @@ export class MonitorConnection {
|
|||||||
export namespace MonitorConnection {
|
export namespace MonitorConnection {
|
||||||
|
|
||||||
export interface State {
|
export interface State {
|
||||||
readonly connectionId: string;
|
|
||||||
readonly config: MonitorConfig;
|
readonly config: MonitorConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace State {
|
export namespace State {
|
||||||
export function toString(state: State): string {
|
export function toString(state: State): string {
|
||||||
const { connectionId, config } = state;
|
const { config } = state;
|
||||||
const { board, port } = config;
|
const { board, port } = config;
|
||||||
return `${Board.toString(board)} ${Port.toString(port)} [ID: ${connectionId}]`;
|
return `${Board.toString(board)} ${Port.toString(port)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,8 +12,8 @@ export class MonitorServiceClientImpl implements MonitorServiceClient {
|
|||||||
|
|
||||||
notifyRead(event: MonitorReadEvent): void {
|
notifyRead(event: MonitorReadEvent): void {
|
||||||
this.onReadEmitter.fire(event);
|
this.onReadEmitter.fire(event);
|
||||||
const { connectionId, data } = event;
|
const { data } = event;
|
||||||
console.debug(`Received data from ${connectionId}: ${data}`);
|
console.debug(`Received data: ${data}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyError(error: MonitorError): void {
|
notifyError(error: MonitorError): void {
|
||||||
|
@ -202,7 +202,7 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
|
|
||||||
protected onBeforeDetach(msg: Message): void {
|
protected onBeforeDetach(msg: Message): void {
|
||||||
super.onBeforeDetach(msg);
|
super.onBeforeDetach(msg);
|
||||||
if (this.connection.connectionId) {
|
if (this.connection.connected) {
|
||||||
this.connection.disconnect();
|
this.connection.disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -294,9 +294,8 @@ export class MonitorWidget extends ReactWidget {
|
|||||||
|
|
||||||
protected readonly onSend = (value: string) => this.doSend(value);
|
protected readonly onSend = (value: string) => this.doSend(value);
|
||||||
protected async doSend(value: string) {
|
protected async doSend(value: string) {
|
||||||
const { connectionId } = this.connection;
|
if (this.connection.connected) {
|
||||||
if (connectionId) {
|
this.monitorService.send(value + this.model.lineEnding);
|
||||||
this.monitorService.send(connectionId, value + this.model.lineEnding);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
import { JsonRpcServer } from '@theia/core/lib/common/messaging/proxy-factory';
|
||||||
import { Board, Port } from './boards-service';
|
import { Board, Port } from './boards-service';
|
||||||
|
|
||||||
|
export interface Status { }
|
||||||
|
export interface OK extends Status { }
|
||||||
|
export interface ErrorStatus extends Status {
|
||||||
|
readonly message: string;
|
||||||
|
}
|
||||||
|
export namespace Status {
|
||||||
|
export function isOK(status: Status & { message?: string }): status is OK {
|
||||||
|
return typeof status.message !== 'string';
|
||||||
|
}
|
||||||
|
export const OK: OK = {};
|
||||||
|
export const NOT_CONNECTED: ErrorStatus = { message: 'Not connected.' };
|
||||||
|
export const ALREADY_CONNECTED: ErrorStatus = { message: 'Already connected.' };
|
||||||
|
}
|
||||||
|
|
||||||
export const MonitorServicePath = '/services/serial-monitor';
|
export const MonitorServicePath = '/services/serial-monitor';
|
||||||
export const MonitorService = Symbol('MonitorService');
|
export const MonitorService = Symbol('MonitorService');
|
||||||
export interface MonitorService extends JsonRpcServer<MonitorServiceClient> {
|
export interface MonitorService extends JsonRpcServer<MonitorServiceClient> {
|
||||||
connect(config: MonitorConfig): Promise<{ connectionId: string }>;
|
connect(config: MonitorConfig): Promise<Status>;
|
||||||
disconnect(connectionId: string): Promise<boolean>;
|
disconnect(): Promise<Status>;
|
||||||
send(connectionId: string, data: string | Uint8Array): Promise<void>;
|
send(data: string | Uint8Array): Promise<Status>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitorConfig {
|
export interface MonitorConfig {
|
||||||
@ -42,14 +56,15 @@ export interface MonitorServiceClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitorReadEvent {
|
export interface MonitorReadEvent {
|
||||||
readonly connectionId: string;
|
|
||||||
readonly data: string;
|
readonly data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MonitorError {
|
export interface MonitorError {
|
||||||
readonly connectionId: string;
|
|
||||||
readonly message: string;
|
readonly message: string;
|
||||||
readonly code: number;
|
/**
|
||||||
|
* If no `code` is available, clients must reestablish the serial-monitor connection.
|
||||||
|
*/
|
||||||
|
readonly code: number | undefined;
|
||||||
readonly config: MonitorConfig;
|
readonly config: MonitorConfig;
|
||||||
}
|
}
|
||||||
export namespace MonitorError {
|
export namespace MonitorError {
|
||||||
@ -66,9 +81,5 @@ export namespace MonitorError {
|
|||||||
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
|
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
|
||||||
*/
|
*/
|
||||||
export const DEVICE_BUSY = 3;
|
export const DEVICE_BUSY = 3;
|
||||||
/**
|
|
||||||
* Another serial monitor was opened on this port. For another electron-instance, Java IDE.
|
|
||||||
*/
|
|
||||||
export const interrupted_system_call = 4;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,36 @@
|
|||||||
import { v4 } from 'uuid';
|
|
||||||
import { Chance } from 'chance';
|
|
||||||
import { ClientDuplexStream } from '@grpc/grpc-js';
|
import { ClientDuplexStream } from '@grpc/grpc-js';
|
||||||
import { TextDecoder, TextEncoder } from 'util';
|
import { TextDecoder, TextEncoder } from 'util';
|
||||||
import { injectable, inject, named } from 'inversify';
|
import { injectable, inject, named } from 'inversify';
|
||||||
import { Struct } from 'google-protobuf/google/protobuf/struct_pb';
|
import { Struct } from 'google-protobuf/google/protobuf/struct_pb';
|
||||||
import { ILogger, Disposable, DisposableCollection } from '@theia/core';
|
import { ILogger } from '@theia/core/lib/common/logger';
|
||||||
import { MonitorService, MonitorServiceClient, MonitorConfig, MonitorError } from '../../common/protocol/monitor-service';
|
import { MonitorService, MonitorServiceClient, MonitorConfig, MonitorError, Status } from '../../common/protocol/monitor-service';
|
||||||
import { StreamingOpenReq, StreamingOpenResp, MonitorConfig as GrpcMonitorConfig } from '../cli-protocol/monitor/monitor_pb';
|
import { StreamingOpenReq, StreamingOpenResp, MonitorConfig as GrpcMonitorConfig } from '../cli-protocol/monitor/monitor_pb';
|
||||||
import { MonitorClientProvider } from './monitor-client-provider';
|
import { MonitorClientProvider } from './monitor-client-provider';
|
||||||
import { Board, Port } from '../../common/protocol/boards-service';
|
import { Board, Port } from '../../common/protocol/boards-service';
|
||||||
|
|
||||||
export interface MonitorDuplex {
|
|
||||||
readonly toDispose: Disposable;
|
|
||||||
readonly duplex: ClientDuplexStream<StreamingOpenReq, StreamingOpenResp>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ErrorWithCode extends Error {
|
interface ErrorWithCode extends Error {
|
||||||
readonly code: number;
|
readonly code: number;
|
||||||
}
|
}
|
||||||
namespace ErrorWithCode {
|
namespace ErrorWithCode {
|
||||||
export function is(error: Error & { code?: number }): error is ErrorWithCode {
|
export function toMonitorError(error: Error, config: MonitorConfig): MonitorError {
|
||||||
return typeof error.code === 'number';
|
const { message } = error;
|
||||||
}
|
let code = undefined;
|
||||||
export function toMonitorError(error: Error, connectionId: string, config: MonitorConfig): MonitorError | undefined {
|
|
||||||
if (is(error)) {
|
if (is(error)) {
|
||||||
// TODO: const `mapping`. Use regex for the `message`.
|
// TODO: const `mapping`. Use regex for the `message`.
|
||||||
const mapping = new Map<string, number>();
|
const mapping = new Map<string, number>();
|
||||||
mapping.set('1 CANCELLED: Cancelled on client', MonitorError.ErrorCodes.CLIENT_CANCEL);
|
mapping.set('1 CANCELLED: Cancelled on client', MonitorError.ErrorCodes.CLIENT_CANCEL);
|
||||||
mapping.set('2 UNKNOWN: device not configured', MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED);
|
mapping.set('2 UNKNOWN: device not configured', MonitorError.ErrorCodes.DEVICE_NOT_CONFIGURED);
|
||||||
mapping.set('2 UNKNOWN: error opening serial monitor: Serial port busy', MonitorError.ErrorCodes.DEVICE_BUSY);
|
mapping.set('2 UNKNOWN: error opening serial monitor: Serial port busy', MonitorError.ErrorCodes.DEVICE_BUSY);
|
||||||
mapping.set('2 UNKNOWN: interrupted system call', MonitorError.ErrorCodes.interrupted_system_call);
|
code = mapping.get(message);
|
||||||
const { message } = error;
|
|
||||||
const code = mapping.get(message);
|
|
||||||
if (typeof code === 'number') {
|
|
||||||
return {
|
|
||||||
connectionId,
|
|
||||||
message,
|
|
||||||
code,
|
|
||||||
config
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.warn(`Unhandled error with code:`, error);
|
|
||||||
}
|
}
|
||||||
return undefined;
|
return {
|
||||||
|
message,
|
||||||
|
code,
|
||||||
|
config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function is(error: Error & { code?: number }): error is ErrorWithCode {
|
||||||
|
return typeof error.code === 'number';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +45,7 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
protected readonly monitorClientProvider: MonitorClientProvider;
|
protected readonly monitorClientProvider: MonitorClientProvider;
|
||||||
|
|
||||||
protected client?: MonitorServiceClient;
|
protected client?: MonitorServiceClient;
|
||||||
protected readonly connections = new Map<string, MonitorDuplex>();
|
protected connection?: ClientDuplexStream<StreamingOpenReq, StreamingOpenResp>;
|
||||||
|
|
||||||
setClient(client: MonitorServiceClient | undefined): void {
|
setClient(client: MonitorServiceClient | undefined): void {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
@ -65,51 +53,37 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.logger.info('>>> Disposing monitor service...');
|
this.logger.info('>>> Disposing monitor service...');
|
||||||
for (const [connectionId, duplex] of this.connections.entries()) {
|
if (this.connection) {
|
||||||
this.doDisconnect(connectionId, duplex);
|
this.disconnect();
|
||||||
}
|
}
|
||||||
this.logger.info('<<< Disposing monitor service...');
|
this.logger.info('<<< Disposing monitor service...');
|
||||||
this.client = undefined;
|
this.client = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(config: MonitorConfig): Promise<{ connectionId: string }> {
|
async connect(config: MonitorConfig): Promise<Status> {
|
||||||
this.logger.info(`>>> Creating serial monitor connection for ${Board.toString(config.board)} on port ${Port.toString(config.port)}...`);
|
this.logger.info(`>>> Creating serial monitor connection for ${Board.toString(config.board)} on port ${Port.toString(config.port)}...`);
|
||||||
|
if (this.connection) {
|
||||||
|
return Status.ALREADY_CONNECTED;
|
||||||
|
}
|
||||||
const client = await this.monitorClientProvider.client;
|
const client = await this.monitorClientProvider.client;
|
||||||
const duplex = client.streamingOpen();
|
this.connection = client.streamingOpen();
|
||||||
const connectionId = `${new Chance(v4()).animal().replace(/\s+/g, '-').toLowerCase()}-monitor-connection`;
|
this.connection.on('error', ((error: Error) => {
|
||||||
const toDispose = new DisposableCollection(
|
const monitorError = ErrorWithCode.toMonitorError(error, config);
|
||||||
Disposable.create(() => this.disconnect(connectionId))
|
if (monitorError.code === undefined) {
|
||||||
);
|
this.logger.error(error);
|
||||||
|
}
|
||||||
duplex.on('error', ((error: Error) => {
|
((monitorError.code === undefined ? this.disconnect() : Promise.resolve()) as Promise<any>).then(() => {
|
||||||
if (ErrorWithCode.is(error)) {
|
if (this.client) {
|
||||||
const monitorError = ErrorWithCode.toMonitorError(error, connectionId, config);
|
this.client.notifyError(monitorError);
|
||||||
if (monitorError) {
|
|
||||||
if (this.client) {
|
|
||||||
this.client.notifyError(monitorError);
|
|
||||||
}
|
|
||||||
// Do not log the error, it was expected. The client will take care of the rest.
|
|
||||||
if (monitorError.code === MonitorError.ErrorCodes.interrupted_system_call) {
|
|
||||||
console.log('jajjajaja');
|
|
||||||
if (!toDispose.disposed) {
|
|
||||||
toDispose.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
if (!toDispose.disposed) {
|
|
||||||
toDispose.dispose();
|
|
||||||
}
|
|
||||||
this.logger.error(`Error occurred for connection ${connectionId}.`, error);
|
|
||||||
}).bind(this));
|
}).bind(this));
|
||||||
|
|
||||||
duplex.on('data', ((resp: StreamingOpenResp) => {
|
this.connection.on('data', ((resp: StreamingOpenResp) => {
|
||||||
if (this.client) {
|
if (this.client) {
|
||||||
const raw = resp.getData();
|
const raw = resp.getData();
|
||||||
const data = typeof raw === 'string' ? raw : new TextDecoder('utf8').decode(raw);
|
const data = typeof raw === 'string' ? raw : new TextDecoder('utf8').decode(raw);
|
||||||
this.logger.info('NOTIFY READ', data);
|
this.client.notifyRead({ data });
|
||||||
this.client.notifyRead({ connectionId, data });
|
|
||||||
}
|
}
|
||||||
}).bind(this));
|
}).bind(this));
|
||||||
|
|
||||||
@ -123,54 +97,42 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
}
|
}
|
||||||
req.setMonitorconfig(monitorConfig);
|
req.setMonitorconfig(monitorConfig);
|
||||||
|
|
||||||
return new Promise<{ connectionId: string }>(resolve => {
|
return new Promise<Status>(resolve => {
|
||||||
duplex.write(req, () => {
|
if (this.connection) {
|
||||||
this.connections.set(connectionId, { toDispose, duplex });
|
this.connection.write(req, () => {
|
||||||
this.logger.info(`<<< Serial monitor connection created for ${Board.toString(config.board)} on port ${Port.toString(config.port)}. ID: [${connectionId}]`);
|
this.logger.info(`<<< Serial monitor connection created for ${Board.toString(config.board)} on port ${Port.toString(config.port)}.`);
|
||||||
resolve({ connectionId });
|
resolve(Status.OK);
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(Status.NOT_CONNECTED);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnect(connectionId: string): Promise<boolean> {
|
async disconnect(): Promise<Status> {
|
||||||
this.logger.info(`>>> Received disconnect request for connection: ${connectionId}`);
|
if (!this.connection) {
|
||||||
const disposable = this.connections.get(connectionId);
|
return Status.NOT_CONNECTED;
|
||||||
if (!disposable) {
|
|
||||||
this.logger.warn(`<<< No connection was found for ID: ${connectionId}`);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
const result = await this.doDisconnect(connectionId, disposable);
|
this.connection.cancel();
|
||||||
if (result) {
|
this.connection = undefined;
|
||||||
this.logger.info(`<<< Successfully disconnected from ${connectionId}.`);
|
return Status.OK;
|
||||||
} else {
|
|
||||||
this.logger.info(`<<< Could not disconnected from ${connectionId}.`);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async doDisconnect(connectionId: string, monitorDuplex: MonitorDuplex): Promise<boolean> {
|
async send(data: string): Promise<Status> {
|
||||||
const { duplex } = monitorDuplex;
|
if (!this.connection) {
|
||||||
this.logger.info(`>>> Disposing monitor connection: ${connectionId}...`);
|
return Status.NOT_CONNECTED;
|
||||||
try {
|
|
||||||
duplex.cancel();
|
|
||||||
this.connections.delete(connectionId);
|
|
||||||
this.logger.info(`<<< Connection disposed: ${connectionId}.`);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
this.logger.error(`<<< Error occurred when disposing monitor connection: ${connectionId}. ${e}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async send(connectionId: string, data: string): Promise<void> {
|
|
||||||
const duplex = this.duplex(connectionId);
|
|
||||||
if (duplex) {
|
|
||||||
const req = new StreamingOpenReq();
|
|
||||||
req.setData(new TextEncoder().encode(data));
|
|
||||||
return new Promise<void>(resolve => duplex.duplex.write(req, resolve));
|
|
||||||
} else {
|
|
||||||
throw new Error(`No connection with ID: ${connectionId}.`);
|
|
||||||
}
|
}
|
||||||
|
const req = new StreamingOpenReq();
|
||||||
|
req.setData(new TextEncoder().encode(data));
|
||||||
|
return new Promise<Status>(resolve => {
|
||||||
|
if (this.connection) {
|
||||||
|
this.connection.write(req, () => {
|
||||||
|
resolve(Status.OK);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(Status.NOT_CONNECTED);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected mapType(type?: MonitorConfig.ConnectionType): GrpcMonitorConfig.TargetType {
|
protected mapType(type?: MonitorConfig.ConnectionType): GrpcMonitorConfig.TargetType {
|
||||||
@ -180,12 +142,4 @@ export class MonitorServiceImpl implements MonitorService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected duplex(connectionId: string): MonitorDuplex | undefined {
|
|
||||||
const monitorClient = this.connections.get(connectionId);
|
|
||||||
if (!monitorClient) {
|
|
||||||
this.logger.warn(`Could not find monitor client for connection ID: ${connectionId}`);
|
|
||||||
}
|
|
||||||
return monitorClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user