From db2336d96424096d5570606680824af180e33c3a Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Wed, 24 Aug 2022 18:39:50 +0200 Subject: [PATCH] Add WebSocket connection close and open support to the UI protocol MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/charging-station/ChargingStation.ts | 136 +++++++++--------- .../ChargingStationWorkerBroadcastChannel.ts | 8 +- .../UIServiceWorkerBroadcastChannel.ts | 2 +- .../ui-server/AbstractUIServer.ts | 2 +- .../ui-server/UIWebSocketServer.ts | 1 + .../ui-services/AbstractUIService.ts | 2 +- .../ui-server/ui-services/UIService001.ts | 24 ++++ src/types/UIProtocol.ts | 2 + src/types/WorkerBroadcastChannel.ts | 2 + 9 files changed, 107 insertions(+), 72 deletions(-) diff --git a/src/charging-station/ChargingStation.ts b/src/charging-station/ChargingStation.ts index 0c00bf20..25d9733b 100644 --- a/src/charging-station/ChargingStation.ts +++ b/src/charging-station/ChargingStation.ts @@ -675,6 +675,74 @@ export default class ChargingStation { this.messageBuffer.add(message); } + public openWSConnection( + options: WsOptions = this.stationInfo?.wsOptions ?? {}, + params: { closeOpened?: boolean; terminateOpened?: boolean } = { + closeOpened: false, + terminateOpened: false, + } + ): void { + options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000; + params.closeOpened = params?.closeOpened ?? false; + params.terminateOpened = params?.terminateOpened ?? false; + if ( + !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) && + !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword) + ) { + options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`; + } + if (params?.closeOpened) { + this.closeWSConnection(); + } + if (params?.terminateOpened) { + this.terminateWSConnection(); + } + let protocol: string; + switch (this.getOcppVersion()) { + case OCPPVersion.VERSION_16: + protocol = 'ocpp' + OCPPVersion.VERSION_16; + break; + default: + this.handleUnsupportedVersion(this.getOcppVersion()); + break; + } + + logger.info( + this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString() + ); + + this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options); + + // Handle WebSocket message + this.wsConnection.on( + 'message', + this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void + ); + // Handle WebSocket error + this.wsConnection.on( + 'error', + this.onError.bind(this) as (this: WebSocket, error: Error) => void + ); + // Handle WebSocket close + this.wsConnection.on( + 'close', + this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void + ); + // Handle WebSocket open + this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void); + // Handle WebSocket ping + this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void); + // Handle WebSocket pong + this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void); + } + + public closeWSConnection(): void { + if (this.isWebSocketConnectionOpened()) { + this.wsConnection.close(); + this.wsConnection = null; + } + } + private flushMessageBuffer() { if (this.messageBuffer.size > 0) { this.messageBuffer.forEach((message) => { @@ -1868,74 +1936,6 @@ export default class ChargingStation { } } - private openWSConnection( - options: WsOptions = this.stationInfo?.wsOptions ?? {}, - params: { closeOpened?: boolean; terminateOpened?: boolean } = { - closeOpened: false, - terminateOpened: false, - } - ): void { - options.handshakeTimeout = options?.handshakeTimeout ?? this.getConnectionTimeout() * 1000; - params.closeOpened = params?.closeOpened ?? false; - params.terminateOpened = params?.terminateOpened ?? false; - if ( - !Utils.isNullOrUndefined(this.stationInfo.supervisionUser) && - !Utils.isNullOrUndefined(this.stationInfo.supervisionPassword) - ) { - options.auth = `${this.stationInfo.supervisionUser}:${this.stationInfo.supervisionPassword}`; - } - if (params?.closeOpened) { - this.closeWSConnection(); - } - if (params?.terminateOpened) { - this.terminateWSConnection(); - } - let protocol: string; - switch (this.getOcppVersion()) { - case OCPPVersion.VERSION_16: - protocol = 'ocpp' + OCPPVersion.VERSION_16; - break; - default: - this.handleUnsupportedVersion(this.getOcppVersion()); - break; - } - - logger.info( - this.logPrefix() + ' Open OCPP connection to URL ' + this.wsConnectionUrl.toString() - ); - - this.wsConnection = new WebSocket(this.wsConnectionUrl, protocol, options); - - // Handle WebSocket message - this.wsConnection.on( - 'message', - this.onMessage.bind(this) as (this: WebSocket, data: RawData, isBinary: boolean) => void - ); - // Handle WebSocket error - this.wsConnection.on( - 'error', - this.onError.bind(this) as (this: WebSocket, error: Error) => void - ); - // Handle WebSocket close - this.wsConnection.on( - 'close', - this.onClose.bind(this) as (this: WebSocket, code: number, reason: Buffer) => void - ); - // Handle WebSocket open - this.wsConnection.on('open', this.onOpen.bind(this) as (this: WebSocket) => void); - // Handle WebSocket ping - this.wsConnection.on('ping', this.onPing.bind(this) as (this: WebSocket, data: Buffer) => void); - // Handle WebSocket pong - this.wsConnection.on('pong', this.onPong.bind(this) as (this: WebSocket, data: Buffer) => void); - } - - private closeWSConnection(): void { - if (this.isWebSocketConnectionOpened()) { - this.wsConnection.close(); - this.wsConnection = null; - } - } - private terminateWSConnection(): void { if (this.isWebSocketConnectionOpened()) { this.wsConnection.terminate(); diff --git a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts index 0d890687..18cbadef 100644 --- a/src/charging-station/ChargingStationWorkerBroadcastChannel.ts +++ b/src/charging-station/ChargingStationWorkerBroadcastChannel.ts @@ -17,7 +17,7 @@ import { } from '../types/WorkerBroadcastChannel'; import { ResponseStatus } from '../ui/web/src/type/UIProtocol'; import logger from '../utils/Logger'; -import ChargingStation from './ChargingStation'; +import type ChargingStation from './ChargingStation'; import WorkerBroadcastChannel from './WorkerBroadcastChannel'; const moduleName = 'ChargingStationWorkerBroadcastChannel'; @@ -109,6 +109,12 @@ export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadca case BroadcastChannelProcedureName.STOP_CHARGING_STATION: await this.chargingStation.stop(); break; + case BroadcastChannelProcedureName.OPEN_CONNECTION: + this.chargingStation.openWSConnection(); + break; + case BroadcastChannelProcedureName.CLOSE_CONNECTION: + this.chargingStation.closeWSConnection(); + break; default: // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new BaseError(`Unknown broadcast channel command: ${command}`); diff --git a/src/charging-station/UIServiceWorkerBroadcastChannel.ts b/src/charging-station/UIServiceWorkerBroadcastChannel.ts index e921972d..d50d514a 100644 --- a/src/charging-station/UIServiceWorkerBroadcastChannel.ts +++ b/src/charging-station/UIServiceWorkerBroadcastChannel.ts @@ -1,6 +1,6 @@ import { BroadcastChannelResponse, MessageEvent } from '../types/WorkerBroadcastChannel'; import logger from '../utils/Logger'; -import AbstractUIService from './ui-server/ui-services/AbstractUIService'; +import type AbstractUIService from './ui-server/ui-services/AbstractUIService'; import WorkerBroadcastChannel from './WorkerBroadcastChannel'; const moduleName = 'UIServiceWorkerBroadcastChannel'; diff --git a/src/charging-station/ui-server/AbstractUIServer.ts b/src/charging-station/ui-server/AbstractUIServer.ts index e0b12071..65847d85 100644 --- a/src/charging-station/ui-server/AbstractUIServer.ts +++ b/src/charging-station/ui-server/AbstractUIServer.ts @@ -4,7 +4,7 @@ import WebSocket from 'ws'; import { ChargingStationData } from '../../types/ChargingStationWorker'; import { ProtocolVersion } from '../../types/UIProtocol'; -import AbstractUIService from './ui-services/AbstractUIService'; +import type AbstractUIService from './ui-services/AbstractUIService'; export abstract class AbstractUIServer { public readonly chargingStations: Map; diff --git a/src/charging-station/ui-server/UIWebSocketServer.ts b/src/charging-station/ui-server/UIWebSocketServer.ts index 5f02f2ea..a68663d8 100644 --- a/src/charging-station/ui-server/UIWebSocketServer.ts +++ b/src/charging-station/ui-server/UIWebSocketServer.ts @@ -54,6 +54,7 @@ export default class UIWebSocketServer extends AbstractUIServer { } public sendResponse(response: string): void { + // TODO: send response only to the client that sent the request this.broadcastToClients(response); } diff --git a/src/charging-station/ui-server/ui-services/AbstractUIService.ts b/src/charging-station/ui-server/ui-services/AbstractUIService.ts index db9a036d..224f78a2 100644 --- a/src/charging-station/ui-server/ui-services/AbstractUIService.ts +++ b/src/charging-station/ui-server/ui-services/AbstractUIService.ts @@ -16,7 +16,7 @@ import logger from '../../../utils/Logger'; import Utils from '../../../utils/Utils'; import Bootstrap from '../../Bootstrap'; import UIServiceWorkerBroadcastChannel from '../../UIServiceWorkerBroadcastChannel'; -import { AbstractUIServer } from '../AbstractUIServer'; +import type { AbstractUIServer } from '../AbstractUIServer'; const moduleName = 'AbstractUIService'; diff --git a/src/charging-station/ui-server/ui-services/UIService001.ts b/src/charging-station/ui-server/ui-services/UIService001.ts index d4f59ebc..5061ed78 100644 --- a/src/charging-station/ui-server/ui-services/UIService001.ts +++ b/src/charging-station/ui-server/ui-services/UIService001.ts @@ -30,6 +30,14 @@ export default class UIService001 extends AbstractUIService { ProcedureName.STOP_CHARGING_STATION, this.handleStopChargingStation.bind(this) as ProtocolRequestHandler ); + this.requestHandlers.set( + ProcedureName.OPEN_CONNECTION, + this.handleOpenConnection.bind(this) as ProtocolRequestHandler + ); + this.requestHandlers.set( + ProcedureName.CLOSE_CONNECTION, + this.handleCloseConnection.bind(this) as ProtocolRequestHandler + ); } private handleStartTransaction(uuid: string, payload: RequestPayload): void { @@ -63,4 +71,20 @@ export default class UIService001 extends AbstractUIService { payload as BroadcastChannelRequestPayload, ]); } + + private handleOpenConnection(uuid: string, payload: RequestPayload): void { + this.uiServiceWorkerBroadcastChannel.sendRequest([ + uuid, + BroadcastChannelProcedureName.OPEN_CONNECTION, + payload as BroadcastChannelRequestPayload, + ]); + } + + private handleCloseConnection(uuid: string, payload: RequestPayload): void { + this.uiServiceWorkerBroadcastChannel.sendRequest([ + uuid, + BroadcastChannelProcedureName.CLOSE_CONNECTION, + payload as BroadcastChannelRequestPayload, + ]); + } } diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts index 27585b03..1e6917ae 100644 --- a/src/types/UIProtocol.ts +++ b/src/types/UIProtocol.ts @@ -29,6 +29,8 @@ export enum ProcedureName { STOP_TRANSACTION = 'stopTransaction', START_SIMULATOR = 'startSimulator', STOP_SIMULATOR = 'stopSimulator', + OPEN_CONNECTION = 'openConnection', + CLOSE_CONNECTION = 'closeConnection', } export interface RequestPayload extends JsonObject { hashId?: string; diff --git a/src/types/WorkerBroadcastChannel.ts b/src/types/WorkerBroadcastChannel.ts index 610d1c45..2a92f312 100644 --- a/src/types/WorkerBroadcastChannel.ts +++ b/src/types/WorkerBroadcastChannel.ts @@ -13,6 +13,8 @@ export enum BroadcastChannelProcedureName { STOP_CHARGING_STATION = 'stopChargingStation', START_TRANSACTION = 'startTransaction', STOP_TRANSACTION = 'stopTransaction', + OPEN_CONNECTION = 'openConnection', + CLOSE_CONNECTION = 'closeConnection', } export interface BroadcastChannelRequestPayload extends Omit { -- 2.34.1