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