UI Services: add status notification command
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
CommitLineData
6c8f5d90 1import BaseError from '../exception/BaseError';
a9ed42b2
JB
2import type OCPPError from '../exception/OCPPError';
3import { RequestCommand, type StatusNotificationRequest } from '../types/ocpp/Requests';
4import type { StatusNotificationResponse } from '../types/ocpp/Responses';
89b7a234 5import {
6c8f5d90 6 AuthorizationStatus,
89b7a234
JB
7 StartTransactionRequest,
8 StartTransactionResponse,
89b7a234
JB
9 StopTransactionRequest,
10 StopTransactionResponse,
11} from '../types/ocpp/Transaction';
12import {
13 BroadcastChannelProcedureName,
14 BroadcastChannelRequest,
6c8f5d90
JB
15 BroadcastChannelRequestPayload,
16 BroadcastChannelResponsePayload,
17 MessageEvent,
89b7a234 18} from '../types/WorkerBroadcastChannel';
f27eb751 19import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
6c8f5d90 20import logger from '../utils/Logger';
a9ed42b2 21import Utils from '../utils/Utils';
db2336d9 22import type ChargingStation from './ChargingStation';
1598b27c 23import WorkerBroadcastChannel from './WorkerBroadcastChannel';
89b7a234 24
4e3ff94d
JB
25const moduleName = 'ChargingStationWorkerBroadcastChannel';
26
a9ed42b2
JB
27type CommandResponse =
28 | StartTransactionResponse
29 | StopTransactionResponse
30 | StatusNotificationResponse;
89b7a234 31
1598b27c 32export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
89b7a234
JB
33 private readonly chargingStation: ChargingStation;
34
35 constructor(chargingStation: ChargingStation) {
1598b27c 36 super();
89b7a234 37 this.chargingStation = chargingStation;
02a6943a 38 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 39 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
40 }
41
02a6943a 42 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
3880dbcf 43 if (this.isResponse(messageEvent.data) === true) {
6c8f5d90
JB
44 return;
45 }
5e3cb728
JB
46 const [uuid, command, requestPayload] = this.validateMessageEvent(messageEvent)
47 .data as BroadcastChannelRequest;
6c8f5d90 48
18057587
JB
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 }
4eca248c 67 }
89b7a234 68
6c8f5d90
JB
69 let responsePayload: BroadcastChannelResponsePayload;
70 let commandResponse: CommandResponse;
71 try {
72 commandResponse = await this.commandHandler(command, requestPayload);
73 if (commandResponse === undefined) {
10d244c0 74 responsePayload = {
51c83d6f 75 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
76 status: ResponseStatus.SUCCESS,
77 };
6c8f5d90 78 } else {
10d244c0 79 responsePayload = {
51c83d6f 80 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
81 status: this.commandResponseToResponseStatus(commandResponse),
82 };
6c8f5d90
JB
83 }
84 } catch (error) {
85 logger.error(
86 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
87 error
88 );
89 responsePayload = {
51c83d6f 90 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
91 status: ResponseStatus.FAILURE,
92 command,
93 requestPayload,
94 commandResponse,
95 errorMessage: (error as Error).message,
96 errorStack: (error as Error).stack,
a9ed42b2 97 errorDetails: (error as OCPPError).details,
6c8f5d90
JB
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:`,
5e3cb728 106 { messageEvent }
6c8f5d90
JB
107 );
108 }
109
110 private async commandHandler(
111 command: BroadcastChannelProcedureName,
112 requestPayload: BroadcastChannelRequestPayload
113 ): Promise<CommandResponse | undefined> {
89b7a234 114 switch (command) {
4f69be04
JB
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;
89b7a234 127 case BroadcastChannelProcedureName.START_TRANSACTION:
6c8f5d90 128 return this.chargingStation.ocppRequestService.requestHandler<
89b7a234
JB
129 StartTransactionRequest,
130 StartTransactionResponse
131 >(this.chargingStation, RequestCommand.START_TRANSACTION, {
6c8f5d90
JB
132 connectorId: requestPayload.connectorId,
133 idTag: requestPayload.idTag,
89b7a234 134 });
89b7a234 135 case BroadcastChannelProcedureName.STOP_TRANSACTION:
6c8f5d90 136 return this.chargingStation.ocppRequestService.requestHandler<
89b7a234
JB
137 StopTransactionRequest,
138 StopTransactionResponse
139 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
6c8f5d90 140 transactionId: requestPayload.transactionId,
89b7a234 141 meterStop: this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
07989fad
JB
142 requestPayload.transactionId,
143 true
89b7a234 144 ),
6c8f5d90 145 idTag: this.chargingStation.getTransactionIdTag(requestPayload.transactionId),
4f317101 146 ...(requestPayload.reason && { reason: requestPayload.reason }),
89b7a234 147 });
4f69be04 148 case BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR:
a5e9befc 149 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds);
89b7a234 150 break;
4f69be04 151 case BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR:
a5e9befc 152 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds);
db2336d9 153 break;
a9ed42b2
JB
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 });
6c8f5d90
JB
169 default:
170 // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
ce7a4fc3 171 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
172 }
173 }
174
175 private commandResponseToResponseStatus(commandResponse: CommandResponse): ResponseStatus {
a9ed42b2
JB
176 if (
177 Utils.isEmptyObject(commandResponse) ||
178 commandResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
179 ) {
6c8f5d90 180 return ResponseStatus.SUCCESS;
89b7a234 181 }
6c8f5d90 182 return ResponseStatus.FAILURE;
89b7a234
JB
183 }
184}