99b16e655923cfca003882e5e4003b0e1e45a327
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
1 import BaseError from '../exception/BaseError';
2 import type OCPPError from '../exception/OCPPError';
3 import {
4 HeartbeatRequest,
5 RequestCommand,
6 type StatusNotificationRequest,
7 } from '../types/ocpp/Requests';
8 import type { HeartbeatResponse, StatusNotificationResponse } from '../types/ocpp/Responses';
9 import {
10 AuthorizationStatus,
11 AuthorizeRequest,
12 AuthorizeResponse,
13 StartTransactionRequest,
14 StartTransactionResponse,
15 StopTransactionRequest,
16 StopTransactionResponse,
17 } from '../types/ocpp/Transaction';
18 import {
19 BroadcastChannelProcedureName,
20 BroadcastChannelRequest,
21 BroadcastChannelRequestPayload,
22 BroadcastChannelResponsePayload,
23 MessageEvent,
24 } from '../types/WorkerBroadcastChannel';
25 import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
26 import logger from '../utils/Logger';
27 import Utils from '../utils/Utils';
28 import type ChargingStation from './ChargingStation';
29 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
30
31 const moduleName = 'ChargingStationWorkerBroadcastChannel';
32
33 type CommandResponse =
34 | StartTransactionResponse
35 | StopTransactionResponse
36 | AuthorizeResponse
37 | StatusNotificationResponse
38 | HeartbeatResponse;
39
40 type CommandHandler = (
41 requestPayload?: BroadcastChannelRequestPayload
42 ) => Promise<CommandResponse | void> | void;
43
44 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
45 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
46
47 private readonly chargingStation: ChargingStation;
48
49 constructor(chargingStation: ChargingStation) {
50 super();
51 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
52 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
53 [
54 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
55 async () => this.chargingStation.stop(),
56 ],
57 [
58 BroadcastChannelProcedureName.OPEN_CONNECTION,
59 () => this.chargingStation.openWSConnection(),
60 ],
61 [
62 BroadcastChannelProcedureName.CLOSE_CONNECTION,
63 () => this.chargingStation.closeWSConnection(),
64 ],
65 [
66 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
67 (requestPayload?: BroadcastChannelRequestPayload) =>
68 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds),
69 ],
70 [
71 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
72 (requestPayload?: BroadcastChannelRequestPayload) =>
73 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds),
74 ],
75 [
76 BroadcastChannelProcedureName.START_TRANSACTION,
77 async (requestPayload?: BroadcastChannelRequestPayload) =>
78 this.chargingStation.ocppRequestService.requestHandler<
79 StartTransactionRequest,
80 StartTransactionResponse
81 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload),
82 ],
83 [
84 BroadcastChannelProcedureName.STOP_TRANSACTION,
85 async (requestPayload?: BroadcastChannelRequestPayload) =>
86 this.chargingStation.ocppRequestService.requestHandler<
87 StopTransactionRequest,
88 StartTransactionResponse
89 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
90 ...requestPayload,
91 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
92 requestPayload.transactionId,
93 true
94 ),
95 }),
96 ],
97 [
98 BroadcastChannelProcedureName.AUTHORIZE,
99 async (requestPayload?: BroadcastChannelRequestPayload) =>
100 this.chargingStation.ocppRequestService.requestHandler<
101 AuthorizeRequest,
102 AuthorizeResponse
103 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload),
104 ],
105 [
106 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
107 async (requestPayload?: BroadcastChannelRequestPayload) =>
108 this.chargingStation.ocppRequestService.requestHandler<
109 StatusNotificationRequest,
110 StatusNotificationResponse
111 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload),
112 ],
113 [
114 BroadcastChannelProcedureName.HEARTBEAT,
115 async (requestPayload?: BroadcastChannelRequestPayload) =>
116 this.chargingStation.ocppRequestService.requestHandler<
117 HeartbeatRequest,
118 HeartbeatResponse
119 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload),
120 ],
121 ]);
122 this.chargingStation = chargingStation;
123 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
124 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
125 }
126
127 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
128 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
129 if (validatedMessageEvent === false) {
130 return;
131 }
132 if (this.isResponse(validatedMessageEvent.data) === true) {
133 return;
134 }
135 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
136
137 if (
138 requestPayload?.hashIds !== undefined &&
139 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
140 ) {
141 return;
142 }
143 if (requestPayload?.hashId !== undefined) {
144 logger.error(
145 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
146 );
147 return;
148 }
149 let responsePayload: BroadcastChannelResponsePayload;
150 let commandResponse: CommandResponse | void;
151 try {
152 commandResponse = await this.commandHandler(command, requestPayload);
153 if (commandResponse === undefined || commandResponse === null) {
154 responsePayload = {
155 hashId: this.chargingStation.stationInfo.hashId,
156 status: ResponseStatus.SUCCESS,
157 };
158 } else {
159 const commandResponseStatus = this.commandResponseToResponseStatus(
160 command,
161 commandResponse as CommandResponse
162 );
163 if (commandResponseStatus === ResponseStatus.SUCCESS) {
164 responsePayload = {
165 hashId: this.chargingStation.stationInfo.hashId,
166 status: commandResponseStatus,
167 };
168 } else {
169 responsePayload = {
170 hashId: this.chargingStation.stationInfo.hashId,
171 status: commandResponseStatus,
172 command,
173 requestPayload,
174 commandResponse: commandResponse as CommandResponse,
175 };
176 }
177 }
178 } catch (error) {
179 logger.error(
180 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
181 error
182 );
183 responsePayload = {
184 hashId: this.chargingStation.stationInfo.hashId,
185 status: ResponseStatus.FAILURE,
186 command,
187 requestPayload,
188 commandResponse: commandResponse as CommandResponse,
189 errorMessage: (error as Error).message,
190 errorStack: (error as Error).stack,
191 errorDetails: (error as OCPPError).details,
192 };
193 } finally {
194 this.sendResponse([uuid, responsePayload]);
195 }
196 }
197
198 private messageErrorHandler(messageEvent: MessageEvent): void {
199 logger.error(
200 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
201 { messageEvent }
202 );
203 }
204
205 private async commandHandler(
206 command: BroadcastChannelProcedureName,
207 requestPayload: BroadcastChannelRequestPayload
208 ): Promise<CommandResponse | void> {
209 if (this.commandHandlers.has(command) === true) {
210 this.cleanRequestPayload(command, requestPayload);
211 return this.commandHandlers.get(command)(requestPayload);
212 }
213 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
214 }
215
216 private cleanRequestPayload(
217 command: BroadcastChannelProcedureName,
218 requestPayload: BroadcastChannelRequestPayload
219 ): void {
220 delete requestPayload.hashId;
221 delete requestPayload.hashIds;
222 [
223 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
224 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
225 ].includes(command) === false && delete requestPayload.connectorIds;
226 }
227
228 private commandResponseToResponseStatus(
229 command: BroadcastChannelProcedureName,
230 commandResponse: CommandResponse
231 ): ResponseStatus {
232 switch (command) {
233 case BroadcastChannelProcedureName.START_TRANSACTION:
234 case BroadcastChannelProcedureName.STOP_TRANSACTION:
235 case BroadcastChannelProcedureName.AUTHORIZE:
236 if (
237 (
238 commandResponse as
239 | StartTransactionResponse
240 | StopTransactionResponse
241 | AuthorizeResponse
242 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
243 ) {
244 return ResponseStatus.SUCCESS;
245 }
246 return ResponseStatus.FAILURE;
247 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
248 if (Utils.isEmptyObject(commandResponse) === true) {
249 return ResponseStatus.SUCCESS;
250 }
251 return ResponseStatus.FAILURE;
252 case BroadcastChannelProcedureName.HEARTBEAT:
253 if ('currentTime' in commandResponse) {
254 return ResponseStatus.SUCCESS;
255 }
256 return ResponseStatus.FAILURE;
257 default:
258 return ResponseStatus.FAILURE;
259 }
260 }
261 }