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