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