6ab170e5ba3a49c125236518f46577b3300dc0da
[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 StartTransactionRequest,
12 StartTransactionResponse,
13 StopTransactionRequest,
14 StopTransactionResponse,
15 } from '../types/ocpp/Transaction';
16 import {
17 BroadcastChannelProcedureName,
18 BroadcastChannelRequest,
19 BroadcastChannelRequestPayload,
20 BroadcastChannelResponsePayload,
21 MessageEvent,
22 } from '../types/WorkerBroadcastChannel';
23 import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
24 import logger from '../utils/Logger';
25 import Utils from '../utils/Utils';
26 import type ChargingStation from './ChargingStation';
27 import WorkerBroadcastChannel from './WorkerBroadcastChannel';
28
29 const moduleName = 'ChargingStationWorkerBroadcastChannel';
30
31 type CommandResponse =
32 | StartTransactionResponse
33 | StopTransactionResponse
34 | StatusNotificationResponse
35 | HeartbeatResponse;
36
37 export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
38 private readonly chargingStation: ChargingStation;
39
40 constructor(chargingStation: ChargingStation) {
41 super();
42 this.chargingStation = chargingStation;
43 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
44 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
45 }
46
47 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
48 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
49 if (validatedMessageEvent === false) {
50 return;
51 }
52 if (this.isResponse(validatedMessageEvent.data) === true) {
53 return;
54 }
55 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
56
57 if (
58 requestPayload?.hashIds !== undefined &&
59 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
60 ) {
61 return;
62 }
63 if (requestPayload?.hashId !== undefined) {
64 logger.error(
65 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
66 );
67 return;
68 }
69
70 let responsePayload: BroadcastChannelResponsePayload;
71 let commandResponse: CommandResponse;
72 try {
73 commandResponse = await this.commandHandler(command, requestPayload);
74 if (commandResponse === undefined) {
75 responsePayload = {
76 hashId: this.chargingStation.stationInfo.hashId,
77 status: ResponseStatus.SUCCESS,
78 };
79 } else {
80 responsePayload = {
81 hashId: this.chargingStation.stationInfo.hashId,
82 status: this.commandResponseToResponseStatus(command, commandResponse),
83 };
84 }
85 } catch (error) {
86 logger.error(
87 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
88 error
89 );
90 responsePayload = {
91 hashId: this.chargingStation.stationInfo.hashId,
92 status: ResponseStatus.FAILURE,
93 command,
94 requestPayload,
95 commandResponse,
96 errorMessage: (error as Error).message,
97 errorStack: (error as Error).stack,
98 errorDetails: (error as OCPPError).details,
99 };
100 }
101 this.sendResponse([uuid, responsePayload]);
102 }
103
104 private messageErrorHandler(messageEvent: MessageEvent): void {
105 logger.error(
106 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
107 { messageEvent }
108 );
109 }
110
111 private async commandHandler(
112 command: BroadcastChannelProcedureName,
113 requestPayload: BroadcastChannelRequestPayload
114 ): Promise<CommandResponse | undefined> {
115 switch (command) {
116 case BroadcastChannelProcedureName.START_CHARGING_STATION:
117 this.chargingStation.start();
118 break;
119 case BroadcastChannelProcedureName.STOP_CHARGING_STATION:
120 await this.chargingStation.stop();
121 break;
122 case BroadcastChannelProcedureName.OPEN_CONNECTION:
123 this.chargingStation.openWSConnection();
124 break;
125 case BroadcastChannelProcedureName.CLOSE_CONNECTION:
126 this.chargingStation.closeWSConnection();
127 break;
128 case BroadcastChannelProcedureName.START_TRANSACTION:
129 return this.chargingStation.ocppRequestService.requestHandler<
130 StartTransactionRequest,
131 StartTransactionResponse
132 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
133 connectorId: requestPayload.connectorId,
134 idTag: requestPayload.idTag,
135 });
136 case BroadcastChannelProcedureName.STOP_TRANSACTION:
137 return this.chargingStation.ocppRequestService.requestHandler<
138 StopTransactionRequest,
139 StopTransactionResponse
140 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
141 transactionId: requestPayload.transactionId,
142 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
143 requestPayload.transactionId,
144 true
145 ),
146 idTag: requestPayload.idTag,
147 reason: requestPayload.reason,
148 });
149 case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
150 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds);
151 break;
152 case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR:
153 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds);
154 break;
155 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
156 return this.chargingStation.ocppRequestService.requestHandler<
157 StatusNotificationRequest,
158 StatusNotificationResponse
159 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, {
160 connectorId: requestPayload.connectorId,
161 errorCode: requestPayload.errorCode,
162 status: requestPayload.status,
163 ...(requestPayload.info && { info: requestPayload.info }),
164 ...(requestPayload.timestamp && { timestamp: requestPayload.timestamp }),
165 ...(requestPayload.vendorId && { vendorId: requestPayload.vendorId }),
166 ...(requestPayload.vendorErrorCode && {
167 vendorErrorCode: requestPayload.vendorErrorCode,
168 }),
169 });
170 case BroadcastChannelProcedureName.HEARTBEAT:
171 delete requestPayload.hashId;
172 delete requestPayload.hashIds;
173 delete requestPayload.connectorIds;
174 return this.chargingStation.ocppRequestService.requestHandler<
175 HeartbeatRequest,
176 HeartbeatResponse
177 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload);
178 default:
179 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
180 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
181 }
182 }
183
184 private commandResponseToResponseStatus(
185 command: BroadcastChannelProcedureName,
186 commandResponse: CommandResponse
187 ): ResponseStatus {
188 switch (command) {
189 case BroadcastChannelProcedureName.START_TRANSACTION:
190 case BroadcastChannelProcedureName.STOP_TRANSACTION:
191 if (
192 (commandResponse as StartTransactionResponse | StopTransactionResponse)?.idTagInfo
193 ?.status === AuthorizationStatus.ACCEPTED
194 ) {
195 return ResponseStatus.SUCCESS;
196 }
197 return ResponseStatus.FAILURE;
198 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
199 if (Utils.isEmptyObject(commandResponse) === true) {
200 return ResponseStatus.SUCCESS;
201 }
202 return ResponseStatus.FAILURE;
203 case BroadcastChannelProcedureName.HEARTBEAT:
204 if ('currentTime' in commandResponse) {
205 return ResponseStatus.SUCCESS;
206 }
207 return ResponseStatus.FAILURE;
208 default:
209 return ResponseStatus.FAILURE;
210 }
211 }
212 }