Add WebSocket connection close and open support to the UI protocol
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 24 Aug 2022 16:39:50 +0000 (18:39 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 24 Aug 2022 16:39:50 +0000 (18:39 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/ChargingStation.ts
src/charging-station/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/UIServiceWorkerBroadcastChannel.ts
src/charging-station/ui-server/AbstractUIServer.ts
src/charging-station/ui-server/UIWebSocketServer.ts
src/charging-station/ui-server/ui-services/AbstractUIService.ts
src/charging-station/ui-server/ui-services/UIService001.ts
src/types/UIProtocol.ts
src/types/WorkerBroadcastChannel.ts

index 0c00bf20dd2936519e7415b5c9cfa96f9b43591e..25d9733b0e67063939d7174bae430e39bb058841 100644 (file)
@@ -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();
index 0d890687828cb6b86ac450c48a3610f5e283c893..18cbadefdaf63d4915e6bb497abe0628072949c2 100644 (file)
@@ -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}`);
index e921972d4c183d7dd5d86fc611de8d5a656ab8ab..d50d514a469496159b5aad15f752bde3c3a0d031 100644 (file)
@@ -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';
index e0b1207106e34ab1755a68cf9d3974d27a8d3088..65847d85d4f313a92880c99fc6bfcb2f98a3cfbf 100644 (file)
@@ -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<string, ChargingStationData>;
index 5f02f2ea47f9de93276970d53672e078a7fbc9b8..a68663d83f9e37ad78d1b9e902253dccf65b5114 100644 (file)
@@ -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);
   }
 
index db9a036d183054b7fd77cb98dda7ac0e3eb7626f..224f78a2e03dd827f82f9b33c97325752003857e 100644 (file)
@@ -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';
 
index d4f59ebc5e71746f609492e27d2042ca9be16101..5061ed78d854b066e05e813a5d6972ad2f8c0853 100644 (file)
@@ -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,
+    ]);
+  }
 }
index 27585b03dad549cd58fcd0500b9346f88803314c..1e6917aee4871d5a964e6d22c3f26ab4da732468 100644 (file)
@@ -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;
index 610d1c4517e9540505f53d565947657e8f647fab..2a92f312323abccfc95c6b25dc389cdaaa84f35c 100644 (file)
@@ -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<RequestPayload, 'hashId'> {