42448275432337772fe80a691a5364840680f068
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
1 import BaseError from '../exception/BaseError';
2 import { RequestCommand } from '../types/ocpp/Requests';
3 import {
4 AuthorizationStatus,
5 StartTransactionRequest,
6 StartTransactionResponse,
7 StopTransactionReason,
8 StopTransactionRequest,
9 StopTransactionResponse,
10 } from '../types/ocpp/Transaction';
11 import {
12 BroadcastChannelProcedureName,
13 BroadcastChannelRequest,
14 BroadcastChannelRequestPayload,
15 BroadcastChannelResponsePayload,
16 MessageEvent,
17 } from '../types/WorkerBroadcastChannel';
18 import { ResponseStatus } from '../ui/web/src/type/UIProtocol';
19 import logger from '../utils/Logger';
20 import type ChargingStation from './ChargingStation';
21 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
22
23 const moduleName = 'ChargingStationWorkerBroadcastChannel';
24
25 type CommandResponse = StartTransactionResponse | StopTransactionResponse;
26
27 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
28 private readonly chargingStation: ChargingStation;
29
30 constructor(chargingStation: ChargingStation) {
31 super();
32 this.chargingStation = chargingStation;
33 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
34 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
35 }
36
37 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
38 if (this.isResponse(messageEvent.data)) {
39 return;
40 }
41 this.validateMessageEvent(messageEvent);
42
43 const [uuid, command, requestPayload] = messageEvent.data as BroadcastChannelRequest;
44
45 if (
46 requestPayload?.hashId === undefined &&
47 (requestPayload?.hashIds as string[])?.includes(this.chargingStation.hashId) === false
48 ) {
49 return;
50 }
51 if (
52 requestPayload?.hashIds === undefined &&
53 requestPayload?.hashId !== this.chargingStation.hashId
54 ) {
55 return;
56 }
57 if (requestPayload?.hashId !== undefined) {
58 logger.warn(
59 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
60 );
61 }
62
63 let responsePayload: BroadcastChannelResponsePayload;
64 let commandResponse: CommandResponse;
65 try {
66 commandResponse = await this.commandHandler(command, requestPayload);
67 if (commandResponse === undefined) {
68 responsePayload = { status: ResponseStatus.SUCCESS };
69 } else {
70 responsePayload = { status: this.commandResponseToResponseStatus(commandResponse) };
71 }
72 } catch (error) {
73 logger.error(
74 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
75 error
76 );
77 responsePayload = {
78 status: ResponseStatus.FAILURE,
79 command,
80 requestPayload,
81 commandResponse,
82 errorMessage: (error as Error).message,
83 errorStack: (error as Error).stack,
84 };
85 }
86 this.sendResponse([uuid, responsePayload]);
87 }
88
89 private messageErrorHandler(messageEvent: MessageEvent): void {
90 logger.error(
91 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
92 { messageEvent, messageEventData: messageEvent.data }
93 );
94 }
95
96 private async commandHandler(
97 command: BroadcastChannelProcedureName,
98 requestPayload: BroadcastChannelRequestPayload
99 ): Promise<CommandResponse | undefined> {
100 switch (command) {
101 case BroadcastChannelProcedureName.START_TRANSACTION:
102 return this.chargingStation.ocppRequestService.requestHandler<
103 StartTransactionRequest,
104 StartTransactionResponse
105 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
106 connectorId: requestPayload.connectorId,
107 idTag: requestPayload.idTag,
108 });
109 case BroadcastChannelProcedureName.STOP_TRANSACTION:
110 return this.chargingStation.ocppRequestService.requestHandler<
111 StopTransactionRequest,
112 StopTransactionResponse
113 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
114 transactionId: requestPayload.transactionId,
115 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
116 requestPayload.transactionId
117 ),
118 idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
119 reason: StopTransactionReason.NONE,
120 });
121 case BroadcastChannelProcedureName.START_CHARGING_STATION:
122 this.chargingStation.start();
123 break;
124 case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
125 await this.chargingStation.stop();
126 break;
127 case BroadcastChannelProcedureName.OPEN_CONNECTION:
128 this.chargingStation.openWSConnection();
129 break;
130 case BroadcastChannelProcedureName.CLOSE_CONNECTION:
131 this.chargingStation.closeWSConnection();
132 break;
133 default:
134 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
135 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
136 }
137 }
138
139 private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
140 if (commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
141 return ResponseStatus.SUCCESS;
142 }
143 return ResponseStatus.FAILURE;
144 }
145 }