UI Protocol: Expose ATG status and use array for all list
[e-mobility-charging-stations-simulator.git] / src / charging-station / ui-server / ui-services / AbstractUIService.ts
1 import BaseError from '../../../exception/BaseError';
2 import { Bootstrap } from '../../../internal';
3 import {
4 ProcedureName,
5 ProtocolRequest,
6 ProtocolRequestHandler,
7 ProtocolVersion,
8 RequestPayload,
9 ResponsePayload,
10 ResponseStatus,
11 } from '../../../types/UIProtocol';
12 import type {
13 BroadcastChannelProcedureName,
14 BroadcastChannelRequestPayload,
15 } from '../../../types/WorkerBroadcastChannel';
16 import logger from '../../../utils/Logger';
17 import Utils from '../../../utils/Utils';
18 import UIServiceWorkerBroadcastChannel from '../../UIServiceWorkerBroadcastChannel';
19 import type { AbstractUIServer } from '../AbstractUIServer';
20
21 const moduleName = 'AbstractUIService';
22
23 export default abstract class AbstractUIService {
24 protected readonly requestHandlers: Map<ProcedureName, ProtocolRequestHandler>;
25 private readonly version: ProtocolVersion;
26 private readonly uiServer: AbstractUIServer;
27 private readonly uiServiceWorkerBroadcastChannel: UIServiceWorkerBroadcastChannel;
28 private readonly broadcastChannelRequests: Map<string, number>;
29
30 constructor(uiServer: AbstractUIServer, version: ProtocolVersion) {
31 this.version = version;
32 this.uiServer = uiServer;
33 this.requestHandlers = new Map<ProcedureName, ProtocolRequestHandler>([
34 [ProcedureName.LIST_CHARGING_STATIONS, this.handleListChargingStations.bind(this)],
35 [ProcedureName.START_SIMULATOR, this.handleStartSimulator.bind(this)],
36 [ProcedureName.STOP_SIMULATOR, this.handleStopSimulator.bind(this)],
37 ]);
38 this.uiServiceWorkerBroadcastChannel = new UIServiceWorkerBroadcastChannel(this);
39 this.broadcastChannelRequests = new Map<string, number>();
40 }
41
42 public async requestHandler(request: ProtocolRequest): Promise<void> {
43 let messageId: string;
44 let command: ProcedureName;
45 let requestPayload: RequestPayload | undefined;
46 let responsePayload: ResponsePayload;
47 try {
48 [messageId, command, requestPayload] = request;
49
50 if (this.requestHandlers.has(command) === false) {
51 throw new BaseError(
52 `${command} is not implemented to handle message payload ${JSON.stringify(
53 requestPayload,
54 null,
55 2
56 )}`
57 );
58 }
59
60 // Call the request handler to build the response payload
61 responsePayload = await this.requestHandlers.get(command)(messageId, requestPayload);
62 } catch (error) {
63 // Log
64 logger.error(`${this.logPrefix(moduleName, 'messageHandler')} Handle request error:`, error);
65 responsePayload = {
66 status: ResponseStatus.FAILURE,
67 command,
68 requestPayload,
69 responsePayload,
70 errorMessage: (error as Error).message,
71 errorStack: (error as Error).stack,
72 };
73 }
74 // Send response for payload not forwarded to broadcast channel
75 if (responsePayload !== undefined) {
76 this.sendResponse(messageId ?? 'error', responsePayload);
77 }
78 }
79
80 public sendRequest(
81 messageId: string,
82 procedureName: ProcedureName,
83 requestPayload: RequestPayload
84 ): void {
85 this.uiServer.sendRequest(
86 this.uiServer.buildProtocolRequest(messageId, procedureName, requestPayload)
87 );
88 }
89
90 public sendResponse(messageId: string, responsePayload: ResponsePayload): void {
91 this.uiServer.sendResponse(this.uiServer.buildProtocolResponse(messageId, responsePayload));
92 }
93
94 public logPrefix(modName: string, methodName: string): string {
95 return this.uiServer.logPrefix(modName, methodName, this.version);
96 }
97
98 public deleteBroadcastChannelRequest(uuid: string): void {
99 this.broadcastChannelRequests.delete(uuid);
100 }
101
102 public getBroadcastChannelExpectedResponses(uuid: string): number {
103 return this.broadcastChannelRequests.get(uuid) ?? 0;
104 }
105
106 protected sendBroadcastChannelRequest(
107 uuid: string,
108 procedureName: BroadcastChannelProcedureName,
109 payload: BroadcastChannelRequestPayload
110 ): void {
111 if (!Utils.isEmptyArray(payload.hashIds)) {
112 payload.hashIds = payload.hashIds
113 .map((hashId) => {
114 if (this.uiServer.chargingStations.has(hashId) === true) {
115 return hashId;
116 }
117 logger.warn(
118 `${this.logPrefix(
119 moduleName,
120 'sendBroadcastChannelRequest'
121 )} Charging station with hashId '${hashId}' not found`
122 );
123 })
124 .filter((hashId) => hashId !== undefined);
125 }
126 const expectedNumberOfResponses = !Utils.isEmptyArray(payload.hashIds)
127 ? payload.hashIds.length
128 : this.uiServer.chargingStations.size;
129 this.uiServiceWorkerBroadcastChannel.sendRequest([uuid, procedureName, payload]);
130 this.broadcastChannelRequests.set(uuid, expectedNumberOfResponses);
131 }
132
133 private handleListChargingStations(): ResponsePayload {
134 return {
135 status: ResponseStatus.SUCCESS,
136 chargingStations: [...this.uiServer.chargingStations.values()],
137 } as ResponsePayload;
138 }
139
140 private async handleStartSimulator(): Promise<ResponsePayload> {
141 await Bootstrap.getInstance().start();
142 return { status: ResponseStatus.SUCCESS };
143 }
144
145 private async handleStopSimulator(): Promise<ResponsePayload> {
146 await Bootstrap.getInstance().stop();
147 return { status: ResponseStatus.SUCCESS };
148 }
149 }