Version the WebSocket server UI protocol
authorJérôme Benoit <jerome.benoit@sap.com>
Mon, 29 Nov 2021 18:06:31 +0000 (19:06 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Mon, 29 Nov 2021 18:06:31 +0000 (19:06 +0100)
Signed-off-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
src/charging-station/Bootstrap.ts
src/charging-station/WebSocketServer.ts
src/charging-station/WebSocketServices/ui/0.0.1/UIService.ts [new file with mode: 0644]
src/charging-station/WebSocketServices/ui/AbstractUIService.ts [new file with mode: 0644]
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/types/UIProtocol.ts [new file with mode: 0644]

index 4f49a3d9ab06565fb21e9c89ad67e1456ac880e3..06bc50e391695090eb25d6e469a98c5e5be7ce3d 100644 (file)
@@ -6,6 +6,7 @@ import Configuration from '../utils/Configuration';
 import { Storage } from '../performance/storage/Storage';
 import { StorageFactory } from '../performance/storage/StorageFactory';
 import Utils from '../utils/Utils';
+import WebSocketServer from './WebSocketServer';
 import WorkerAbstract from '../worker/WorkerAbstract';
 import WorkerFactory from '../worker/WorkerFactory';
 import chalk from 'chalk';
@@ -16,6 +17,7 @@ import { version } from '../../package.json';
 export default class Bootstrap {
   private static instance: Bootstrap | null = null;
   private workerImplementation: WorkerAbstract | null = null;
+  private readonly webSocketServer: WebSocketServer;
   private readonly storage: Storage;
   private numberOfChargingStations: number;
   private readonly version: string = version;
@@ -26,6 +28,7 @@ export default class Bootstrap {
     this.started = false;
     this.workerScript = path.join(path.resolve(__dirname, '../'), 'charging-station', 'ChargingStationWorker.js');
     this.initWorkerImplementation();
+    this.webSocketServer = new WebSocketServer();
     this.storage = StorageFactory.getStorage(Configuration.getPerformanceStorage().type, Configuration.getPerformanceStorage().URI, this.logPrefix());
     Configuration.setConfigurationChangeCallback(async () => Bootstrap.getInstance().restart());
   }
@@ -43,6 +46,7 @@ export default class Bootstrap {
         this.numberOfChargingStations = 0;
         await this.storage.open();
         await this.workerImplementation.start();
+        this.webSocketServer.start();
         // Start ChargingStation object in worker thread
         if (Configuration.getStationTemplateURLs()) {
           for (const stationURL of Configuration.getStationTemplateURLs()) {
@@ -80,6 +84,7 @@ export default class Bootstrap {
   public async stop(): Promise<void> {
     if (isMainThread && this.started) {
       await this.workerImplementation.stop();
+      this.webSocketServer.stop();
       await this.storage.close();
     } else {
       console.error(chalk.red('Trying to stop the charging stations simulator while not started'));
index b0660bd8318b9947a69dabef1d00d28266257608..3fe7bbb2da06b9a30e8251ba4c6b80968bc45d61 100644 (file)
@@ -1,19 +1,20 @@
+import { ProtocolCommand, ProtocolRequest, ProtocolVersion } from '../types/UIProtocol';
+
+import AbstractUIService from './WebSocketServices/ui/AbstractUIService';
 import { IncomingMessage } from 'http';
+import UIService from './WebSocketServices/ui/0.0.1/UIService';
+import Utils from '../utils/Utils';
 import WebSocket from 'ws';
 import logger from '../utils/Logger';
 
-enum WebSocketServerCommand {
-  START_TRANSACTION = 'startTransaction',
-  STOP_TRANSACTION = 'stopTransaction',
-  UNKNOWN = 'unknown',
-}
-
-type WebSocketServerRequest = [WebSocketServerCommand, Record<string, unknown>];
-
 export default class WebSocketServer extends WebSocket.Server {
+  private webSocketServerService: AbstractUIService;
+
   public constructor(options?: WebSocket.ServerOptions, callback?: () => void) {
     // Create the WebSocket Server
     super(options, callback);
+    // FIXME: version the instantiation
+    this.webSocketServerService = new UIService(this);
   }
 
   public broadcastToClients(message: Record<string, unknown>): void {
@@ -25,21 +26,30 @@ export default class WebSocketServer extends WebSocket.Server {
   }
 
   public start(): void {
-    // this.on('connection', (socket: WebSocket, request: IncomingMessage): void => {
-    //   // Check connection validity
-    // });
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    const self = this;
+    this.on('connection', (socket: WebSocket, request: IncomingMessage): void => {
+      // Check connection validity
+    });
     this.on('message', (messageData) => {
-      let [command, payload]: WebSocketServerRequest = [WebSocketServerCommand.UNKNOWN, {}];
+      let [version, command, payload]: ProtocolRequest = [ProtocolVersion['0.0.1'], ProtocolCommand.UNKNOWN, {}];
       // FIXME: check for iterable object
-      [command, payload] = JSON.parse(messageData.toString()) as WebSocketServerRequest;
-      switch (command) {
-        case WebSocketServerCommand.START_TRANSACTION:
-          break;
-        case WebSocketServerCommand.STOP_TRANSACTION:
+      [version, command, payload] = JSON.parse(messageData.toString()) as ProtocolRequest;
+      switch (version) {
+        case ProtocolVersion['0.0.1']:
+          self.webSocketServerService.handleMessage(command, payload).catch(() => { });
           break;
         default:
-          logger.warn(`Unknown command: ${command}`);
+          logger.error(`${this.logPrefix()} Unknown protocol version: ${version}`);
       }
     });
   }
+
+  public stop(): void {
+    this.close();
+  }
+
+  public logPrefix(): string {
+    return Utils.logPrefix('WebSocket Server:');
+  }
 }
diff --git a/src/charging-station/WebSocketServices/ui/0.0.1/UIService.ts b/src/charging-station/WebSocketServices/ui/0.0.1/UIService.ts
new file mode 100644 (file)
index 0000000..fb2986a
--- /dev/null
@@ -0,0 +1,40 @@
+import { ProtocolCommand, ProtocolRequestHandler } from '../../../../types/UIProtocol';
+
+import AbstractUIService from '../AbstractUIService';
+import BaseError from '../../../../exception/BaseError';
+import WebSocketServer from '../../../WebSocketServer';
+import logger from '../../../../utils/Logger';
+
+export default class UIService extends AbstractUIService {
+  private readonly messageHandlers: Map<ProtocolCommand, ProtocolRequestHandler>;
+
+  constructor(webSocketServer: WebSocketServer) {
+    super(webSocketServer);
+    this.messageHandlers = new Map<ProtocolCommand, ProtocolRequestHandler>([
+      [ProtocolCommand.START_TRANSACTION, this.handleStartTransaction.bind(this)],
+      [ProtocolCommand.STOP_TRANSACTION, this.handleStopTransaction.bind(this)],
+    ]);
+  }
+
+  async handleMessage(command: ProtocolCommand, payload: Record<string, unknown>): Promise<void> {
+    let messageResponse: Record<string, unknown>;
+    if (this.messageHandlers.has(command)) {
+      try {
+        // Call the method to build the response
+        messageResponse = await this.messageHandlers.get(command)(payload);
+      } catch (error) {
+        // Log
+        logger.error(this.webSocketServer.logPrefix() + ' Handle request error: %j', error);
+        throw error;
+      }
+    } else {
+      // Throw exception
+      throw new BaseError(`${command} is not implemented to handle message payload ${JSON.stringify(payload, null, 2)}`);
+    }
+    // Send the built response
+    this.webSocketServer.broadcastToClients(messageResponse);
+  }
+
+  private handleStartTransaction(payload: Record<string, unknown>) { }
+  private handleStopTransaction(payload: Record<string, unknown>) { }
+}
diff --git a/src/charging-station/WebSocketServices/ui/AbstractUIService.ts b/src/charging-station/WebSocketServices/ui/AbstractUIService.ts
new file mode 100644 (file)
index 0000000..7a078bc
--- /dev/null
@@ -0,0 +1,12 @@
+import { ProtocolCommand } from '../../../types/UIProtocol';
+import WebSocketServer from '../../WebSocketServer';
+
+export default abstract class AbstractUIService {
+  protected readonly webSocketServer: WebSocketServer;
+
+  constructor(webSocketServer: WebSocketServer) {
+    this.webSocketServer = webSocketServer;
+  }
+
+  abstract handleMessage(command: ProtocolCommand, payload: Record<string, unknown>): Promise<void>;
+}
index 33f0825515402eab4c51994a32a3063e36fa37f4..1136c93b568721fbc2cac483f29232e36cb347ad 100644 (file)
@@ -11,7 +11,6 @@ import Constants from '../../../utils/Constants';
 import { DefaultResponse } from '../../../types/ocpp/Responses';
 import { ErrorType } from '../../../types/ocpp/ErrorType';
 import { IncomingRequestHandler } from '../../../types/ocpp/Requests';
-import { MessageType } from '../../../types/ocpp/MessageType';
 import { OCPP16ChargePointStatus } from '../../../types/ocpp/1.6/ChargePointStatus';
 import { OCPP16DiagnosticsStatus } from '../../../types/ocpp/1.6/DiagnosticsStatus';
 import { OCPP16StandardParametersKey } from '../../../types/ocpp/1.6/Configuration';
diff --git a/src/types/UIProtocol.ts b/src/types/UIProtocol.ts
new file mode 100644 (file)
index 0000000..a7dfa33
--- /dev/null
@@ -0,0 +1,15 @@
+
+export enum ProtocolVersion {
+  '0.0.1' = '0.0.1',
+  '0.0.2' = '0.0.2',
+}
+
+export enum ProtocolCommand {
+  START_TRANSACTION = 'startTransaction',
+  STOP_TRANSACTION = 'stopTransaction',
+  UNKNOWN = 'unknown',
+}
+
+export type ProtocolRequest = [ProtocolVersion, ProtocolCommand, Record<string, unknown>];
+
+export type ProtocolRequestHandler = (payload: Record<string, unknown>) => Record<string, unknown> | Promise<Record<string, unknown>>;