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