import Configuration from '../utils/Configuration';
import { Storage } from '../performance/storage/Storage';
import { StorageFactory } from '../performance/storage/StorageFactory';
+import { UIServiceUtils } from './UIWebSocketServices/UIServiceUtils';
+import UIWebSocketServer from './UIWebSocketServer';
import Utils from '../utils/Utils';
-import WebSocketServer from './WebSocketServer';
import WorkerAbstract from '../worker/WorkerAbstract';
import WorkerFactory from '../worker/WorkerFactory';
import chalk from 'chalk';
export default class Bootstrap {
private static instance: Bootstrap | null = null;
private workerImplementation: WorkerAbstract | null = null;
- private readonly webSocketServer: WebSocketServer;
+ private readonly uiWebSocketServer: UIWebSocketServer;
private readonly storage: Storage;
private numberOfChargingStations: number;
private readonly version: string = version;
this.started = false;
this.workerScript = path.join(path.resolve(__dirname, '../'), 'charging-station', 'ChargingStationWorker.js');
this.initWorkerImplementation();
- this.webSocketServer = new WebSocketServer();
+ this.uiWebSocketServer = new UIWebSocketServer({ port: 80, handleProtocols: UIServiceUtils.handleProtocols });
this.storage = StorageFactory.getStorage(Configuration.getPerformanceStorage().type, Configuration.getPerformanceStorage().URI, this.logPrefix());
Configuration.setConfigurationChangeCallback(async () => Bootstrap.getInstance().restart());
}
this.numberOfChargingStations = 0;
await this.storage.open();
await this.workerImplementation.start();
- this.webSocketServer.start();
+ this.uiWebSocketServer.start();
// Start ChargingStation object in worker thread
if (Configuration.getStationTemplateURLs()) {
for (const stationURL of Configuration.getStationTemplateURLs()) {
public async stop(): Promise<void> {
if (isMainThread && this.started) {
await this.workerImplementation.stop();
- this.webSocketServer.stop();
+ this.uiWebSocketServer.stop();
await this.storage.close();
} else {
console.error(chalk.red('Trying to stop the charging stations simulator while not started'));
},
messageHandler: async (msg: ChargingStationWorkerMessage) => {
if (msg.id === ChargingStationWorkerMessageEvents.STARTED) {
- this.webSocketServer.webSocketServerService.chargingStations.add(msg.data.id);
+ this.uiWebSocketServer.uiService.chargingStations.add(msg.data.id);
} else if (msg.id === ChargingStationWorkerMessageEvents.STOPPED) {
- this.webSocketServer.webSocketServerService.chargingStations.delete(msg.data.id);
+ this.uiWebSocketServer.uiService.chargingStations.delete(msg.data.id);
} else if (msg.id === ChargingStationWorkerMessageEvents.PERFORMANCE_STATISTICS) {
await this.storage.storePerformanceStatistics(msg.data);
}
--- /dev/null
+import { Protocol, ProtocolCommand, ProtocolRequest, ProtocolVersion } from '../types/UIProtocol';
+
+import AbstractUIService from './UIWebSocketServices/AbstractUIService';
+import BaseError from '../exception/BaseError';
+import { IncomingMessage } from 'http';
+import UIServiceFactory from './UIWebSocketServices/UIServiceFactory';
+import Utils from '../utils/Utils';
+import WebSocket from 'ws';
+import logger from '../utils/Logger';
+
+export default class UIWebSocketServer extends WebSocket.Server {
+ public uiService: AbstractUIService;
+
+ public constructor(options?: WebSocket.ServerOptions, callback?: () => void) {
+ // Create the WebSocket Server
+ super(options ?? { port: 80 }, callback);
+ }
+
+ public broadcastToClients(message: string | Record<string, unknown>): void {
+ for (const client of this.clients) {
+ if (client?.readyState === WebSocket.OPEN) {
+ client.send(message);
+ }
+ }
+ }
+
+ public start(): void {
+ this.on('connection', (socket: WebSocket, request: IncomingMessage): void => {
+ const protocolIndex = socket.protocol.indexOf(Protocol.UI);
+ const version = socket.protocol.substring(protocolIndex + Protocol.UI.length) as ProtocolVersion;
+ this.uiService = UIServiceFactory.getUIServiceImplementation(version, this);
+ if (!this.uiService) {
+ throw new BaseError(`Could not find a UI service implementation for protocol version ${version}`);
+ }
+ // FIXME: check connection validity
+ socket.on('message', (messageData) => {
+ let [command, payload]: ProtocolRequest = [ProtocolCommand.UNKNOWN, {}];
+ const protocolRequest = JSON.parse(messageData.toString()) as ProtocolRequest;
+ if (Utils.isIterable(protocolRequest)) {
+ [command, payload] = protocolRequest;
+ } else {
+ throw new BaseError('Protocol request is not iterable');
+ }
+ this.uiService.handleMessage(command, payload).catch(() => {
+ logger.error(`${this.logPrefix()} Error while handling command %s message: %j`, command, payload);
+ });
+ });
+ socket.on('error', (error) => {
+ logger.error(`${this.logPrefix()} Error on WebSocket: %j`, error);
+ });
+ });
+ }
+
+ public stop(): void {
+ this.close();
+ }
+
+ public logPrefix(): string {
+ return Utils.logPrefix('WebSocket Server:');
+ }
+}
--- /dev/null
+import { ProtocolCommand, ProtocolRequestHandler } from '../../types/UIProtocol';
+
+import BaseError from '../../exception/BaseError';
+import UIWebSocketServer from '../UIWebSocketServer';
+import logger from '../../utils/Logger';
+
+export default abstract class AbstractUIService {
+ public readonly chargingStations: Set<string>;
+ protected readonly uiWebSocketServer: UIWebSocketServer;
+ protected readonly messageHandlers: Map<ProtocolCommand, ProtocolRequestHandler>;
+
+ constructor(uiWebSocketServer: UIWebSocketServer) {
+ this.chargingStations = new Set<string>();
+ this.uiWebSocketServer = uiWebSocketServer;
+ // TODO: Move the shared service code to AbstractUIService
+ this.messageHandlers = new Map<ProtocolCommand, ProtocolRequestHandler>([
+ [ProtocolCommand.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
+ ]);
+ }
+
+ public 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 message response
+ messageResponse = await this.messageHandlers.get(command)(payload) as Record<string, unknown>;
+ } catch (error) {
+ // Log
+ logger.error(this.uiWebSocketServer.logPrefix() + ' Handle message 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 message response
+ this.uiWebSocketServer.broadcastToClients(this.buildProtocolMessage(command, messageResponse));
+ }
+
+ protected buildProtocolMessage(
+ command: ProtocolCommand,
+ payload: Record<string, unknown>,
+ ): string {
+ return JSON.stringify([command, payload]);
+ }
+
+ protected handleListChargingStations(): Set<string> {
+ return this.chargingStations;
+ }
+}
--- /dev/null
+import AbstractUIService from './AbstractUIService';
+import { ProtocolVersion } from '../../types/UIProtocol';
+import UIService_0_0_1 from './UIService_0_0_1';
+import UIWebSocketServer from '../UIWebSocketServer';
+
+export default class UIServiceFactory {
+ private constructor() {
+ // This is intentional
+ }
+
+ public static getUIServiceImplementation(version: ProtocolVersion, uiWebSocketServer: UIWebSocketServer): AbstractUIService | null {
+ switch (version) {
+ case ProtocolVersion['0.0.1']:
+ return new UIService_0_0_1(uiWebSocketServer);
+ default:
+ return null;
+ }
+ }
+}
--- /dev/null
+import { Protocol, ProtocolVersion } from '../../types/UIProtocol';
+
+import { IncomingMessage } from 'http';
+import Utils from '../../utils/Utils';
+import logger from '../../utils/Logger';
+
+export class UIServiceUtils {
+ public static handleProtocols = (protocols: Set<string>, request: IncomingMessage): string | false => {
+ let protocolIndex: number;
+ let protocol: Protocol;
+ let version: ProtocolVersion;
+ for (const fullProtocol of protocols) {
+ protocolIndex = fullProtocol.indexOf(Protocol.UI);
+ protocol = fullProtocol.substring(protocolIndex, protocolIndex + Protocol.UI.length) as Protocol;
+ version = fullProtocol.substring(protocolIndex + Protocol.UI.length) as ProtocolVersion;
+ if (Object.values(Protocol).includes(protocol) && Object.values(ProtocolVersion).includes(version)) {
+ return fullProtocol;
+ }
+ }
+ logger.error(`${Utils.logPrefix('WebSocket Server:')} Unsupported protocol: ${protocol} or protocol version: ${version}`);
+ return false;
+ };
+}
--- /dev/null
+import AbstractUIService from './AbstractUIService';
+import { ProtocolCommand } from '../../types/UIProtocol';
+import UIWebSocketServer from '../UIWebSocketServer';
+
+export default class UIService_0_0_1 extends AbstractUIService {
+ constructor(uiWebSocketServer: UIWebSocketServer) {
+ super(uiWebSocketServer);
+ this.messageHandlers.set(ProtocolCommand.START_TRANSACTION, this.handleStartTransaction.bind(this));
+ this.messageHandlers.set(ProtocolCommand.STOP_TRANSACTION, this.handleStopTransaction.bind(this));
+ }
+
+ private handleStartTransaction(payload: Record<string, unknown>): void { }
+ private handleStopTransaction(payload: Record<string, unknown>): void { }
+}
+++ /dev/null
-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';
-
-export default class WebSocketServer extends WebSocket.Server {
- public webSocketServerService: AbstractUIService;
-
- public constructor(options?: WebSocket.ServerOptions, callback?: () => void) {
- // Create the WebSocket Server
- super(options ?? { port: 80 }, callback);
- // FIXME: version the instantiation
- this.webSocketServerService = new UIService(this);
- }
-
- public broadcastToClients(message: string | Record<string, unknown>): void {
- for (const client of this.clients) {
- if (client?.readyState === WebSocket.OPEN) {
- client.send(message);
- }
- }
- }
-
- public start(): void {
- this.on('connection', (socket: WebSocket, request: IncomingMessage): void => {
- // FIXME: check connection validity
- socket.on('message', (messageData) => {
- let [version, command, payload]: ProtocolRequest = [ProtocolVersion['0.0.1'], ProtocolCommand.UNKNOWN, {}];
- // FIXME: check for iterable object
- [version, command, payload] = JSON.parse(messageData.toString()) as ProtocolRequest;
- switch (version) {
- case ProtocolVersion['0.0.1']:
- this.webSocketServerService.handleMessage(version, command, payload).catch(() => {
- logger.error(`${this.logPrefix()} Error while handling command %s message: %j`, command, payload);
- });
- break;
- default:
- logger.error(`${this.logPrefix()} Unknown protocol version: ${version}`);
- }
- });
- socket.on('error', (error) => {
- logger.error(`${this.logPrefix()} Error on WebSocket: %j`, error);
- });
- });
- }
-
- public stop(): void {
- this.close();
- }
-
- public logPrefix(): string {
- return Utils.logPrefix('WebSocket Server:');
- }
-}
+++ /dev/null
-import { ProtocolCommand, ProtocolRequestHandler, ProtocolVersion } 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.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
- [ProtocolCommand.START_TRANSACTION, this.handleStartTransaction.bind(this)],
- [ProtocolCommand.STOP_TRANSACTION, this.handleStopTransaction.bind(this)],
- ]);
- }
-
- async handleMessage(version: ProtocolVersion, 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 message 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(this.buildProtocolMessage(version, command, messageResponse));
- }
-
- private handleListChargingStations(payload: Record<string, unknown>) {
- return this.chargingStations;
- }
-
- private handleStartTransaction(payload: Record<string, unknown>) { }
- private handleStopTransaction(payload: Record<string, unknown>) { }
-}
+++ /dev/null
-import { ProtocolCommand, ProtocolVersion } from '../../../types/UIProtocol';
-
-import WebSocketServer from '../../WebSocketServer';
-
-export default abstract class AbstractUIService {
- public readonly chargingStations: Set<string>;
- protected readonly webSocketServer: WebSocketServer;
-
- constructor(webSocketServer: WebSocketServer) {
- this.chargingStations = new Set<string>();
- this.webSocketServer = webSocketServer;
- }
-
- protected buildProtocolMessage(
- version: ProtocolVersion,
- command: ProtocolCommand,
- payload: Record<string, unknown>,
- ): string {
- return JSON.stringify([version, command, payload]);
- }
-
- abstract handleMessage(version: ProtocolVersion, command: ProtocolCommand, payload: Record<string, unknown>): Promise<void>;
-}
+export enum Protocol {
+ UI = 'ui',
+}
export enum ProtocolVersion {
'0.0.1' = '0.0.1',
UNKNOWN = 'unknown',
}
-export type ProtocolRequest = [ProtocolVersion, ProtocolCommand, Record<string, unknown>];
+export type ProtocolRequest = [ProtocolCommand, Record<string, unknown>];
-export type ProtocolRequestHandler = (payload: Record<string, unknown>) => Record<string, unknown> | Promise<Record<string, unknown>>;
+export type ProtocolRequestHandler = (payload: Record<string, unknown>) => void | Promise<void> | Record<string, unknown> | Promise<Record<string, unknown>>;