From 8244f5f080133602e63ce6d985f87782a8af9fbc Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Mon, 29 Nov 2021 19:06:31 +0100 Subject: [PATCH] Version the WebSocket server 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/Bootstrap.ts | 5 ++ src/charging-station/WebSocketServer.ts | 46 +++++++++++-------- .../WebSocketServices/ui/0.0.1/UIService.ts | 40 ++++++++++++++++ .../WebSocketServices/ui/AbstractUIService.ts | 12 +++++ .../ocpp/1.6/OCPP16IncomingRequestService.ts | 1 - src/types/UIProtocol.ts | 15 ++++++ 6 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 src/charging-station/WebSocketServices/ui/0.0.1/UIService.ts create mode 100644 src/charging-station/WebSocketServices/ui/AbstractUIService.ts create mode 100644 src/types/UIProtocol.ts diff --git a/src/charging-station/Bootstrap.ts b/src/charging-station/Bootstrap.ts index 4f49a3d9..06bc50e3 100644 --- a/src/charging-station/Bootstrap.ts +++ b/src/charging-station/Bootstrap.ts @@ -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 { 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')); diff --git a/src/charging-station/WebSocketServer.ts b/src/charging-station/WebSocketServer.ts index b0660bd8..3fe7bbb2 100644 --- a/src/charging-station/WebSocketServer.ts +++ b/src/charging-station/WebSocketServer.ts @@ -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]; - 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): 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 index 00000000..fb2986a9 --- /dev/null +++ b/src/charging-station/WebSocketServices/ui/0.0.1/UIService.ts @@ -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; + + constructor(webSocketServer: WebSocketServer) { + super(webSocketServer); + this.messageHandlers = new Map([ + [ProtocolCommand.START_TRANSACTION, this.handleStartTransaction.bind(this)], + [ProtocolCommand.STOP_TRANSACTION, this.handleStopTransaction.bind(this)], + ]); + } + + async handleMessage(command: ProtocolCommand, payload: Record): Promise { + let messageResponse: Record; + 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) { } + private handleStopTransaction(payload: Record) { } +} diff --git a/src/charging-station/WebSocketServices/ui/AbstractUIService.ts b/src/charging-station/WebSocketServices/ui/AbstractUIService.ts new file mode 100644 index 00000000..7a078bc2 --- /dev/null +++ b/src/charging-station/WebSocketServices/ui/AbstractUIService.ts @@ -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): Promise; +} diff --git a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts index 33f08255..1136c93b 100644 --- a/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts +++ b/src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts @@ -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 index 00000000..a7dfa334 --- /dev/null +++ b/src/types/UIProtocol.ts @@ -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]; + +export type ProtocolRequestHandler = (payload: Record) => Record | Promise>; -- 2.34.1