UI protocol: add meter values command
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationWorkerBroadcastChannel.ts
CommitLineData
6c8f5d90 1import BaseError from '../exception/BaseError';
a9ed42b2 2import type OCPPError from '../exception/OCPPError';
d3195f0a 3import { StandardParametersKey } from '../types/ocpp/Configuration';
10db00b2
JB
4import {
5 HeartbeatRequest,
d3195f0a 6 MeterValuesRequest,
10db00b2
JB
7 RequestCommand,
8 type StatusNotificationRequest,
9} from '../types/ocpp/Requests';
d3195f0a
JB
10import type {
11 HeartbeatResponse,
12 MeterValuesResponse,
13 StatusNotificationResponse,
14} from '../types/ocpp/Responses';
89b7a234 15import {
6c8f5d90 16 AuthorizationStatus,
1984f194
JB
17 AuthorizeRequest,
18 AuthorizeResponse,
89b7a234
JB
19 StartTransactionRequest,
20 StartTransactionResponse,
89b7a234
JB
21 StopTransactionRequest,
22 StopTransactionResponse,
23} from '../types/ocpp/Transaction';
24import {
25 BroadcastChannelProcedureName,
26 BroadcastChannelRequest,
6c8f5d90
JB
27 BroadcastChannelRequestPayload,
28 BroadcastChannelResponsePayload,
29 MessageEvent,
89b7a234 30} from '../types/WorkerBroadcastChannel';
f27eb751 31import { ResponseStatus } from '../ui/web/src/types/UIProtocol';
d3195f0a 32import Constants from '../utils/Constants';
6c8f5d90 33import logger from '../utils/Logger';
a9ed42b2 34import Utils from '../utils/Utils';
db2336d9 35import type ChargingStation from './ChargingStation';
d3195f0a
JB
36import { ChargingStationConfigurationUtils } from './ChargingStationConfigurationUtils';
37import { OCPP16ServiceUtils } from './ocpp/1.6/OCPP16ServiceUtils';
1598b27c 38import WorkerBroadcastChannel from './WorkerBroadcastChannel';
89b7a234 39
4e3ff94d
JB
40const moduleName = 'ChargingStationWorkerBroadcastChannel';
41
a9ed42b2
JB
42type CommandResponse =
43 | StartTransactionResponse
44 | StopTransactionResponse
1984f194 45 | AuthorizeResponse
10db00b2 46 | StatusNotificationResponse
d3195f0a
JB
47 | HeartbeatResponse
48 | MeterValuesResponse;
89b7a234 49
d273692c
JB
50type CommandHandler = (
51 requestPayload?: BroadcastChannelRequestPayload
52) => Promise<CommandResponse | void> | void;
53
1598b27c 54export default class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChannel {
d273692c 55 private readonly commandHandlers: Map<BroadcastChannelProcedureName, CommandHandler>;
9d73266c 56
89b7a234
JB
57 private readonly chargingStation: ChargingStation;
58
59 constructor(chargingStation: ChargingStation) {
1598b27c 60 super();
d273692c 61 this.commandHandlers = new Map<BroadcastChannelProcedureName, CommandHandler>([
9d73266c
JB
62 [BroadcastChannelProcedureName.START_CHARGING_STATION, () => this.chargingStation.start()],
63 [
64 BroadcastChannelProcedureName.STOP_CHARGING_STATION,
d273692c 65 async () => this.chargingStation.stop(),
9d73266c
JB
66 ],
67 [
68 BroadcastChannelProcedureName.OPEN_CONNECTION,
69 () => this.chargingStation.openWSConnection(),
70 ],
71 [
72 BroadcastChannelProcedureName.CLOSE_CONNECTION,
73 () => this.chargingStation.closeWSConnection(),
74 ],
623b39b5
JB
75 [
76 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
77 (requestPayload?: BroadcastChannelRequestPayload) =>
78 this.chargingStation.startAutomaticTransactionGenerator(requestPayload.connectorIds),
79 ],
80 [
81 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
82 (requestPayload?: BroadcastChannelRequestPayload) =>
83 this.chargingStation.stopAutomaticTransactionGenerator(requestPayload.connectorIds),
84 ],
9d73266c
JB
85 [
86 BroadcastChannelProcedureName.START_TRANSACTION,
87 async (requestPayload?: BroadcastChannelRequestPayload) =>
88 this.chargingStation.ocppRequestService.requestHandler<
89 StartTransactionRequest,
90 StartTransactionResponse
1984f194 91 >(this.chargingStation, RequestCommand.START_TRANSACTION, requestPayload),
9d73266c
JB
92 ],
93 [
94 BroadcastChannelProcedureName.STOP_TRANSACTION,
95 async (requestPayload?: BroadcastChannelRequestPayload) =>
96 this.chargingStation.ocppRequestService.requestHandler<
97 StopTransactionRequest,
98 StartTransactionResponse
99 >(this.chargingStation, RequestCommand.STOP_TRANSACTION, {
1984f194 100 ...requestPayload,
d3195f0a
JB
101 meterStop:
102 requestPayload.meterStop ??
103 this.chargingStation.getEnergyActiveImportRegisterByTransactionId(
104 requestPayload.transactionId,
105 true
106 ),
9d73266c
JB
107 }),
108 ],
1984f194
JB
109 [
110 BroadcastChannelProcedureName.AUTHORIZE,
111 async (requestPayload?: BroadcastChannelRequestPayload) =>
112 this.chargingStation.ocppRequestService.requestHandler<
113 AuthorizeRequest,
114 AuthorizeResponse
115 >(this.chargingStation, RequestCommand.AUTHORIZE, requestPayload),
116 ],
9d73266c
JB
117 [
118 BroadcastChannelProcedureName.STATUS_NOTIFICATION,
119 async (requestPayload?: BroadcastChannelRequestPayload) =>
120 this.chargingStation.ocppRequestService.requestHandler<
121 StatusNotificationRequest,
122 StatusNotificationResponse
1984f194 123 >(this.chargingStation, RequestCommand.STATUS_NOTIFICATION, requestPayload),
9d73266c
JB
124 ],
125 [
126 BroadcastChannelProcedureName.HEARTBEAT,
1984f194
JB
127 async (requestPayload?: BroadcastChannelRequestPayload) =>
128 this.chargingStation.ocppRequestService.requestHandler<
9d73266c
JB
129 HeartbeatRequest,
130 HeartbeatResponse
1984f194 131 >(this.chargingStation, RequestCommand.HEARTBEAT, requestPayload),
9d73266c 132 ],
d3195f0a
JB
133 [
134 BroadcastChannelProcedureName.METER_VALUES,
135 async (requestPayload?: BroadcastChannelRequestPayload) => {
136 const configuredMeterValueSampleInterval =
137 ChargingStationConfigurationUtils.getConfigurationKey(
138 chargingStation,
139 StandardParametersKey.MeterValueSampleInterval
140 );
141 return this.chargingStation.ocppRequestService.requestHandler<
142 MeterValuesRequest,
143 MeterValuesResponse
144 >(this.chargingStation, RequestCommand.METER_VALUES, {
145 ...requestPayload,
146 meterValue: requestPayload.meterValue ?? [
147 OCPP16ServiceUtils.buildMeterValue(
148 this.chargingStation,
149 requestPayload.connectorId,
150 this.chargingStation.getConnectorStatus(requestPayload.connectorId)?.transactionId,
151 configuredMeterValueSampleInterval
152 ? Utils.convertToInt(configuredMeterValueSampleInterval.value) * 1000
153 : Constants.DEFAULT_METER_VALUES_INTERVAL
154 ),
155 ],
156 });
157 },
158 ],
9d73266c 159 ]);
89b7a234 160 this.chargingStation = chargingStation;
02a6943a 161 this.onmessage = this.requestHandler.bind(this) as (message: MessageEvent) => void;
6c8f5d90 162 this.onmessageerror = this.messageErrorHandler.bind(this) as (message: MessageEvent) => void;
89b7a234
JB
163 }
164
02a6943a 165 private async requestHandler(messageEvent: MessageEvent): Promise<void> {
5dea4c94
JB
166 const validatedMessageEvent = this.validateMessageEvent(messageEvent);
167 if (validatedMessageEvent === false) {
6c8f5d90
JB
168 return;
169 }
5dea4c94
JB
170 if (this.isResponse(validatedMessageEvent.data) === true) {
171 return;
172 }
173 const [uuid, command, requestPayload] = validatedMessageEvent.data as BroadcastChannelRequest;
6c8f5d90 174
2afb4d15
JB
175 if (
176 requestPayload?.hashIds !== undefined &&
177 requestPayload?.hashIds?.includes(this.chargingStation.stationInfo.hashId) === false
178 ) {
179 return;
180 }
181 if (requestPayload?.hashId !== undefined) {
182 logger.error(
183 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: 'hashId' field usage in PDU is deprecated, use 'hashIds' instead`
184 );
185 return;
4eca248c 186 }
6c8f5d90 187 let responsePayload: BroadcastChannelResponsePayload;
9d73266c 188 let commandResponse: CommandResponse | void;
6c8f5d90
JB
189 try {
190 commandResponse = await this.commandHandler(command, requestPayload);
9d73266c 191 if (commandResponse === undefined || commandResponse === null) {
10d244c0 192 responsePayload = {
51c83d6f 193 hashId: this.chargingStation.stationInfo.hashId,
10d244c0
JB
194 status: ResponseStatus.SUCCESS,
195 };
6c8f5d90 196 } else {
d3195f0a 197 responsePayload = this.commandResponseToResponsePayload(
1984f194 198 command,
d3195f0a 199 requestPayload,
1984f194
JB
200 commandResponse as CommandResponse
201 );
6c8f5d90
JB
202 }
203 } catch (error) {
204 logger.error(
205 `${this.chargingStation.logPrefix()} ${moduleName}.requestHandler: Handle request error:`,
206 error
207 );
208 responsePayload = {
51c83d6f 209 hashId: this.chargingStation.stationInfo.hashId,
6c8f5d90
JB
210 status: ResponseStatus.FAILURE,
211 command,
212 requestPayload,
9d73266c 213 commandResponse: commandResponse as CommandResponse,
6c8f5d90
JB
214 errorMessage: (error as Error).message,
215 errorStack: (error as Error).stack,
a9ed42b2 216 errorDetails: (error as OCPPError).details,
6c8f5d90 217 };
623b39b5
JB
218 } finally {
219 this.sendResponse([uuid, responsePayload]);
6c8f5d90 220 }
6c8f5d90
JB
221 }
222
223 private messageErrorHandler(messageEvent: MessageEvent): void {
224 logger.error(
225 `${this.chargingStation.logPrefix()} ${moduleName}.messageErrorHandler: Error at handling message:`,
5e3cb728 226 { messageEvent }
6c8f5d90
JB
227 );
228 }
229
230 private async commandHandler(
231 command: BroadcastChannelProcedureName,
232 requestPayload: BroadcastChannelRequestPayload
9d73266c
JB
233 ): Promise<CommandResponse | void> {
234 if (this.commandHandlers.has(command) === true) {
1984f194 235 this.cleanRequestPayload(command, requestPayload);
9d73266c 236 return this.commandHandlers.get(command)(requestPayload);
6c8f5d90 237 }
9d73266c 238 throw new BaseError(`Unknown worker broadcast channel command: ${command}`);
6c8f5d90
JB
239 }
240
1984f194
JB
241 private cleanRequestPayload(
242 command: BroadcastChannelProcedureName,
243 requestPayload: BroadcastChannelRequestPayload
244 ): void {
245 delete requestPayload.hashId;
246 delete requestPayload.hashIds;
247 [
248 BroadcastChannelProcedureName.START_AUTOMATIC_TRANSACTION_GENERATOR,
249 BroadcastChannelProcedureName.STOP_AUTOMATIC_TRANSACTION_GENERATOR,
250 ].includes(command) === false && delete requestPayload.connectorIds;
251 }
252
d3195f0a
JB
253 private commandResponseToResponsePayload(
254 command: BroadcastChannelProcedureName,
255 requestPayload: BroadcastChannelRequestPayload,
256 commandResponse: CommandResponse
257 ): BroadcastChannelResponsePayload {
258 const commandResponseStatus = this.commandResponseStatusToResponseStatus(
259 command,
260 commandResponse
261 );
262 if (commandResponseStatus === ResponseStatus.SUCCESS) {
263 return {
264 hashId: this.chargingStation.stationInfo.hashId,
265 status: commandResponseStatus,
266 };
267 }
268 return {
269 hashId: this.chargingStation.stationInfo.hashId,
270 status: commandResponseStatus,
271 command,
272 requestPayload,
273 commandResponse,
274 };
275 }
276
277 private commandResponseStatusToResponseStatus(
10db00b2
JB
278 command: BroadcastChannelProcedureName,
279 commandResponse: CommandResponse
280 ): ResponseStatus {
281 switch (command) {
282 case BroadcastChannelProcedureName.START_TRANSACTION:
283 case BroadcastChannelProcedureName.STOP_TRANSACTION:
1984f194 284 case BroadcastChannelProcedureName.AUTHORIZE:
10db00b2 285 if (
1984f194
JB
286 (
287 commandResponse as
288 | StartTransactionResponse
289 | StopTransactionResponse
290 | AuthorizeResponse
291 )?.idTagInfo?.status === AuthorizationStatus.ACCEPTED
10db00b2
JB
292 ) {
293 return ResponseStatus.SUCCESS;
294 }
295 return ResponseStatus.FAILURE;
296 case BroadcastChannelProcedureName.STATUS_NOTIFICATION:
d3195f0a 297 case BroadcastChannelProcedureName.METER_VALUES:
10db00b2
JB
298 if (Utils.isEmptyObject(commandResponse) === true) {
299 return ResponseStatus.SUCCESS;
300 }
301 return ResponseStatus.FAILURE;
302 case BroadcastChannelProcedureName.HEARTBEAT:
303 if ('currentTime' in commandResponse) {
304 return ResponseStatus.SUCCESS;
305 }
306 return ResponseStatus.FAILURE;
307 default:
308 return ResponseStatus.FAILURE;
89b7a234
JB
309 }
310 }
311}