perf: lookup connector status once at OCPP responses handling
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
CommitLineData
edd13439 1// Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
c8eeb62b 2
04b1261c
JB
3import { parentPort } from 'node:worker_threads';
4
6c1761d4 5import type { JSONSchemaType } from 'ajv';
be4c6702 6import { secondsToMilliseconds } from 'date-fns';
844e496b 7
4c3c0d59 8import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
04b1261c
JB
9import {
10 type ChargingStation,
f2d5e3d9
JB
11 addConfigurationKey,
12 getConfigurationKey,
90aceaf6 13 hasReservationExpired,
fba11dc6 14 resetConnectorStatus,
04b1261c 15} from '../../../charging-station';
268a74bb 16import { OCPPError } from '../../../exception';
ef6fa3fb 17import {
268a74bb
JB
18 type ChangeConfigurationResponse,
19 type ClearChargingProfileResponse,
20 ErrorType,
21 type GenericResponse,
22 type GetConfigurationResponse,
23 type GetDiagnosticsResponse,
24 type JsonObject,
25 type JsonType,
8114d10e 26 OCPP16AuthorizationStatus,
27782dbc
JB
27 type OCPP16AuthorizeRequest,
28 type OCPP16AuthorizeResponse,
268a74bb 29 type OCPP16BootNotificationResponse,
366f75f6 30 type OCPP16ChangeAvailabilityResponse,
268a74bb
JB
31 OCPP16ChargePointStatus,
32 type OCPP16DataTransferResponse,
33 type OCPP16DiagnosticsStatusNotificationResponse,
34 type OCPP16FirmwareStatusNotificationResponse,
41189456 35 type OCPP16GetCompositeScheduleResponse,
268a74bb
JB
36 type OCPP16HeartbeatResponse,
37 OCPP16IncomingRequestCommand,
38 type OCPP16MeterValuesRequest,
39 type OCPP16MeterValuesResponse,
40 OCPP16RequestCommand,
66dd3447 41 type OCPP16ReserveNowResponse,
268a74bb 42 OCPP16StandardParametersKey,
27782dbc
JB
43 type OCPP16StartTransactionRequest,
44 type OCPP16StartTransactionResponse,
268a74bb 45 type OCPP16StatusNotificationResponse,
27782dbc
JB
46 type OCPP16StopTransactionRequest,
47 type OCPP16StopTransactionResponse,
268a74bb
JB
48 type OCPP16TriggerMessageResponse,
49 type OCPP16UpdateFirmwareResponse,
50 OCPPVersion,
02887891 51 RegistrationStatusEnumType,
d984c13f 52 ReservationTerminationReason,
02887891 53 type ResponseHandler,
268a74bb
JB
54 type SetChargingProfileResponse,
55 type UnlockConnectorResponse,
56} from '../../../types';
9bf0ef23
JB
57import {
58 Constants,
59 buildUpdatedMessage,
60 convertToInt,
61 isNullOrUndefined,
62 logger,
63} from '../../../utils';
4c3c0d59 64import { OCPPResponseService } from '../OCPPResponseService';
c0560973 65
909dcf2d
JB
66const moduleName = 'OCPP16ResponseService';
67
268a74bb 68export class OCPP16ResponseService extends OCPPResponseService {
b3fc3ff5
JB
69 public jsonIncomingRequestResponseSchemas: Map<
70 OCPP16IncomingRequestCommand,
71 JSONSchemaType<JsonObject>
72 >;
73
58144adb 74 private responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>;
b52c969d 75 private jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>;
58144adb 76
08f130a0 77 public constructor() {
b768993d
JB
78 // if (new.target?.name === moduleName) {
79 // throw new TypeError(`Cannot construct ${new.target?.name} instances directly`);
80 // }
d270cc87 81 super(OCPPVersion.VERSION_16);
58144adb 82 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
a37fc6dc
JB
83 [
84 OCPP16RequestCommand.BOOT_NOTIFICATION,
85 this.handleResponseBootNotification.bind(this) as ResponseHandler,
86 ],
87 [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this) as ResponseHandler],
88 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
89 [
90 OCPP16RequestCommand.START_TRANSACTION,
91 this.handleResponseStartTransaction.bind(this) as ResponseHandler,
92 ],
93 [
94 OCPP16RequestCommand.STOP_TRANSACTION,
95 this.handleResponseStopTransaction.bind(this) as ResponseHandler,
96 ],
97 [
98 OCPP16RequestCommand.STATUS_NOTIFICATION,
99 this.emptyResponseHandler.bind(this) as ResponseHandler,
100 ],
101 [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this) as ResponseHandler],
102 [
103 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
104 this.emptyResponseHandler.bind(this) as ResponseHandler,
105 ],
106 [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler.bind(this) as ResponseHandler],
107 [
108 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
109 this.emptyResponseHandler.bind(this) as ResponseHandler,
110 ],
b52c969d
JB
111 ]);
112 this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonObject>>([
113 [
114 OCPP16RequestCommand.BOOT_NOTIFICATION,
130783a7 115 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
51022aa0 116 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
1b271a54 117 moduleName,
5edd8ba0 118 'constructor',
e9a4164c 119 ),
b52c969d
JB
120 ],
121 [
122 OCPP16RequestCommand.HEARTBEAT,
130783a7 123 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
51022aa0 124 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
1b271a54 125 moduleName,
5edd8ba0 126 'constructor',
e9a4164c 127 ),
b52c969d
JB
128 ],
129 [
130 OCPP16RequestCommand.AUTHORIZE,
130783a7 131 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
51022aa0 132 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
1b271a54 133 moduleName,
5edd8ba0 134 'constructor',
e9a4164c 135 ),
b52c969d
JB
136 ],
137 [
138 OCPP16RequestCommand.START_TRANSACTION,
130783a7 139 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
51022aa0 140 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
1b271a54 141 moduleName,
5edd8ba0 142 'constructor',
e9a4164c 143 ),
b52c969d
JB
144 ],
145 [
146 OCPP16RequestCommand.STOP_TRANSACTION,
130783a7 147 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
51022aa0 148 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
1b271a54 149 moduleName,
5edd8ba0 150 'constructor',
e9a4164c 151 ),
b52c969d
JB
152 ],
153 [
154 OCPP16RequestCommand.STATUS_NOTIFICATION,
130783a7 155 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
51022aa0 156 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
1b271a54 157 moduleName,
5edd8ba0 158 'constructor',
e9a4164c 159 ),
b52c969d
JB
160 ],
161 [
162 OCPP16RequestCommand.METER_VALUES,
130783a7 163 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
51022aa0 164 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
1b271a54 165 moduleName,
5edd8ba0 166 'constructor',
e9a4164c 167 ),
b52c969d
JB
168 ],
169 [
170 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
130783a7 171 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
51022aa0 172 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
1b271a54 173 moduleName,
5edd8ba0 174 'constructor',
e9a4164c 175 ),
b52c969d 176 ],
91a7d3ea
JB
177 [
178 OCPP16RequestCommand.DATA_TRANSFER,
130783a7 179 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
51022aa0 180 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
1b271a54 181 moduleName,
5edd8ba0 182 'constructor',
e9a4164c 183 ),
91a7d3ea 184 ],
c9a4f9ea
JB
185 [
186 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
130783a7 187 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
51022aa0 188 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
1b271a54 189 moduleName,
5edd8ba0 190 'constructor',
e9a4164c 191 ),
c9a4f9ea 192 ],
58144adb 193 ]);
02887891
JB
194 this.jsonIncomingRequestResponseSchemas = new Map([
195 [
196 OCPP16IncomingRequestCommand.RESET,
130783a7 197 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 198 'assets/json-schemas/ocpp/1.6/ResetResponse.json',
1b271a54 199 moduleName,
5edd8ba0 200 'constructor',
e9a4164c 201 ),
02887891
JB
202 ],
203 [
204 OCPP16IncomingRequestCommand.CLEAR_CACHE,
130783a7 205 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 206 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
1b271a54 207 moduleName,
5edd8ba0 208 'constructor',
e9a4164c 209 ),
02887891
JB
210 ],
211 [
212 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
366f75f6 213 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
51022aa0 214 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
1b271a54 215 moduleName,
5edd8ba0 216 'constructor',
e9a4164c 217 ),
02887891
JB
218 ],
219 [
220 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
130783a7 221 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
51022aa0 222 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
1b271a54 223 moduleName,
5edd8ba0 224 'constructor',
e9a4164c 225 ),
02887891
JB
226 ],
227 [
228 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
130783a7 229 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
51022aa0 230 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
1b271a54 231 moduleName,
5edd8ba0 232 'constructor',
e9a4164c 233 ),
02887891
JB
234 ],
235 [
236 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
130783a7 237 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
51022aa0 238 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
1b271a54 239 moduleName,
5edd8ba0 240 'constructor',
e9a4164c 241 ),
02887891 242 ],
41189456
JB
243 [
244 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
245 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
51022aa0 246 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
41189456 247 moduleName,
5edd8ba0 248 'constructor',
41189456
JB
249 ),
250 ],
02887891
JB
251 [
252 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
130783a7 253 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
51022aa0 254 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
1b271a54 255 moduleName,
5edd8ba0 256 'constructor',
e9a4164c 257 ),
02887891
JB
258 ],
259 [
260 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
130783a7 261 OCPP16ServiceUtils.parseJsonSchemaFile<ClearChargingProfileResponse>(
51022aa0 262 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
1b271a54 263 moduleName,
5edd8ba0 264 'constructor',
e9a4164c 265 ),
02887891
JB
266 ],
267 [
268 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
130783a7 269 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 270 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
1b271a54 271 moduleName,
5edd8ba0 272 'constructor',
e9a4164c 273 ),
02887891
JB
274 ],
275 [
276 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
130783a7 277 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
51022aa0 278 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
1b271a54 279 moduleName,
5edd8ba0 280 'constructor',
e9a4164c 281 ),
02887891
JB
282 ],
283 [
284 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
130783a7 285 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
51022aa0 286 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
1b271a54 287 moduleName,
5edd8ba0 288 'constructor',
e9a4164c 289 ),
02887891
JB
290 ],
291 [
292 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
130783a7 293 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
51022aa0 294 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
1b271a54 295 moduleName,
5edd8ba0 296 'constructor',
e9a4164c 297 ),
02887891
JB
298 ],
299 [
300 OCPP16IncomingRequestCommand.DATA_TRANSFER,
130783a7 301 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
51022aa0 302 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
1b271a54 303 moduleName,
5edd8ba0 304 'constructor',
e9a4164c 305 ),
02887891
JB
306 ],
307 [
308 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
130783a7 309 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
51022aa0 310 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
1b271a54 311 moduleName,
5edd8ba0 312 'constructor',
e9a4164c 313 ),
02887891 314 ],
28fe900f
JB
315 [
316 OCPP16IncomingRequestCommand.RESERVE_NOW,
317 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
318 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
319 moduleName,
5edd8ba0 320 'constructor',
28fe900f
JB
321 ),
322 ],
323 [
324 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
b1f1b0f6 325 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
28fe900f
JB
326 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
327 moduleName,
5edd8ba0 328 'constructor',
28fe900f
JB
329 ),
330 ],
02887891 331 ]);
31f59c6d
JB
332 this.validatePayload = this.validatePayload.bind(this) as (
333 chargingStation: ChargingStation,
334 commandName: OCPP16RequestCommand,
5edd8ba0 335 payload: JsonType,
31f59c6d 336 ) => boolean;
58144adb
JB
337 }
338
9429aa42 339 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 340 chargingStation: ChargingStation,
e7aeea18 341 commandName: OCPP16RequestCommand,
9429aa42
JB
342 payload: ResType,
343 requestPayload: ReqType,
e7aeea18 344 ): Promise<void> {
ed6cfcff
JB
345 if (
346 chargingStation.isRegistered() === true ||
347 commandName === OCPP16RequestCommand.BOOT_NOTIFICATION
348 ) {
65554cc3 349 if (
ed6cfcff
JB
350 this.responseHandlers.has(commandName) === true &&
351 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName) === true
65554cc3 352 ) {
124f3553 353 try {
9c5c4195 354 this.validatePayload(chargingStation, commandName, payload);
e1d9a0f4 355 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload);
124f3553 356 } catch (error) {
6c8f5d90
JB
357 logger.error(
358 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
5edd8ba0 359 error,
6c8f5d90 360 );
124f3553
JB
361 throw error;
362 }
363 } else {
364 // Throw exception
e7aeea18
JB
365 throw new OCPPError(
366 ErrorType.NOT_IMPLEMENTED,
6c8f5d90 367 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
e7aeea18
JB
368 payload,
369 null,
5edd8ba0 370 2,
e7aeea18 371 )}`,
7369e417 372 commandName,
5edd8ba0 373 payload,
e7aeea18 374 );
887fef76 375 }
c0560973 376 } else {
e7aeea18
JB
377 throw new OCPPError(
378 ErrorType.SECURITY_ERROR,
6c8f5d90 379 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
e7aeea18
JB
380 payload,
381 null,
5edd8ba0 382 2,
439fc71b 383 )} while the charging station is not registered on the central server.`,
7369e417 384 commandName,
5edd8ba0 385 payload,
e7aeea18 386 );
c0560973
JB
387 }
388 }
389
9c5c4195
JB
390 private validatePayload(
391 chargingStation: ChargingStation,
392 commandName: OCPP16RequestCommand,
5edd8ba0 393 payload: JsonType,
9c5c4195 394 ): boolean {
45988780 395 if (this.jsonSchemas.has(commandName) === true) {
9c5c4195
JB
396 return this.validateResponsePayload(
397 chargingStation,
398 commandName,
e1d9a0f4 399 this.jsonSchemas.get(commandName)!,
5edd8ba0 400 payload,
9c5c4195
JB
401 );
402 }
403 logger.warn(
5edd8ba0 404 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`,
9c5c4195
JB
405 );
406 return false;
407 }
408
08f130a0
JB
409 private handleResponseBootNotification(
410 chargingStation: ChargingStation,
5edd8ba0 411 payload: OCPP16BootNotificationResponse,
08f130a0 412 ): void {
d270cc87 413 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
f2d5e3d9 414 addConfigurationKey(
17ac262c 415 chargingStation,
f0f65a62 416 OCPP16StandardParametersKey.HeartbeatInterval,
a95873d8 417 payload.interval.toString(),
abe9e9dd 418 {},
5edd8ba0 419 { overwrite: true, save: true },
e7aeea18 420 );
f2d5e3d9 421 addConfigurationKey(
17ac262c 422 chargingStation,
f0f65a62 423 OCPP16StandardParametersKey.HeartBeatInterval,
e7aeea18 424 payload.interval.toString(),
00db15b8 425 { visible: false },
5edd8ba0 426 { overwrite: true, save: true },
e7aeea18 427 );
8f953431 428 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval);
672fed6e 429 }
d270cc87 430 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
08f130a0 431 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
e7aeea18
JB
432 payload.status
433 }' state on the central server`;
d270cc87 434 payload.status === RegistrationStatusEnumType.REJECTED
e7aeea18
JB
435 ? logger.warn(logMsg)
436 : logger.info(logMsg);
c0560973 437 } else {
e7aeea18 438 logger.error(
44eb6026 439 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
5edd8ba0 440 payload,
e7aeea18 441 );
c0560973
JB
442 }
443 }
444
e7aeea18 445 private handleResponseAuthorize(
08f130a0 446 chargingStation: ChargingStation,
e7aeea18 447 payload: OCPP16AuthorizeResponse,
5edd8ba0 448 requestPayload: OCPP16AuthorizeRequest,
e7aeea18 449 ): void {
e1d9a0f4 450 let authorizeConnectorId: number | undefined;
ded57f02
JB
451 if (chargingStation.hasEvses) {
452 for (const [evseId, evseStatus] of chargingStation.evses) {
453 if (evseId > 0) {
454 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
455 if (connectorStatus?.authorizeIdTag === requestPayload.idTag) {
456 authorizeConnectorId = connectorId;
457 break;
458 }
459 }
460 }
461 }
462 } else {
463 for (const connectorId of chargingStation.connectors.keys()) {
464 if (
465 connectorId > 0 &&
466 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
467 ) {
468 authorizeConnectorId = connectorId;
469 break;
470 }
58144adb
JB
471 }
472 }
d929adcc 473 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId!);
9bf0ef23 474 const authorizeConnectorIdDefined = !isNullOrUndefined(authorizeConnectorId);
58144adb 475 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
d984c13f 476 if (authorizeConnectorIdDefined) {
d929adcc
JB
477 // authorizeConnectorStatus!.authorizeIdTag = requestPayload.idTag;
478 authorizeConnectorStatus!.idTagAuthorized = true;
d984c13f 479 }
e7aeea18 480 logger.debug(
2585c6e9 481 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' accepted${
54ebb82c 482 authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''
5edd8ba0 483 }`,
e7aeea18 484 );
58144adb 485 } else {
44eb6026 486 if (authorizeConnectorIdDefined) {
d929adcc
JB
487 authorizeConnectorStatus!.idTagAuthorized = false;
488 delete authorizeConnectorStatus?.authorizeIdTag;
1984f194 489 }
e7aeea18 490 logger.debug(
2585c6e9 491 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
e7aeea18 492 payload.idTagInfo.status
5edd8ba0 493 }'${authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''}`,
e7aeea18 494 );
58144adb
JB
495 }
496 }
497
e7aeea18 498 private async handleResponseStartTransaction(
08f130a0 499 chargingStation: ChargingStation,
e7aeea18 500 payload: OCPP16StartTransactionResponse,
5edd8ba0 501 requestPayload: OCPP16StartTransactionRequest,
e7aeea18 502 ): Promise<void> {
d929adcc
JB
503 const { connectorId } = requestPayload;
504 if (connectorId === 0 || chargingStation.hasConnector(connectorId) === false) {
e7aeea18 505 logger.error(
d929adcc 506 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`,
e7aeea18 507 );
c0560973
JB
508 return;
509 }
d929adcc 510 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
e7aeea18 511 if (
d929adcc 512 connectorStatus?.transactionRemoteStarted === true &&
3a13fc92
JB
513 chargingStation.getAuthorizeRemoteTxRequests() === true &&
514 chargingStation.getLocalAuthListEnabled() === true &&
d984c13f 515 chargingStation.hasIdTags() === true &&
d929adcc 516 connectorStatus?.idTagLocalAuthorized === false
e7aeea18
JB
517 ) {
518 logger.error(
d929adcc 519 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`,
e7aeea18 520 );
d929adcc 521 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
a2653482
JB
522 return;
523 }
e7aeea18 524 if (
d929adcc 525 connectorStatus?.transactionRemoteStarted === true &&
3a13fc92 526 chargingStation.getAuthorizeRemoteTxRequests() === true &&
cfdf901d 527 chargingStation.getRemoteAuthorization() === true &&
d929adcc
JB
528 connectorStatus?.idTagLocalAuthorized === false &&
529 connectorStatus?.idTagAuthorized === false
e7aeea18
JB
530 ) {
531 logger.error(
d929adcc 532 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`,
e7aeea18 533 );
d929adcc 534 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
a2653482
JB
535 return;
536 }
e7aeea18 537 if (
d929adcc
JB
538 connectorStatus?.idTagAuthorized &&
539 connectorStatus?.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
540 ) {
541 logger.error(
44eb6026
JB
542 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
543 requestPayload.idTag
d929adcc 544 } different from the authorize request one ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`,
e7aeea18 545 );
d929adcc 546 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
a2653482
JB
547 return;
548 }
e7aeea18 549 if (
d929adcc
JB
550 connectorStatus?.idTagLocalAuthorized &&
551 connectorStatus?.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
552 ) {
553 logger.error(
44eb6026
JB
554 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
555 requestPayload.idTag
d929adcc 556 } different from the local authorized one ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`,
e7aeea18 557 );
d929adcc 558 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
163547b1
JB
559 return;
560 }
d929adcc 561 if (connectorStatus?.transactionStarted === true) {
649287f8 562 logger.error(
d929adcc
JB
563 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId}:`,
564 connectorStatus,
e7aeea18 565 );
c0560973
JB
566 return;
567 }
649287f8
JB
568 if (chargingStation.hasEvses) {
569 for (const [evseId, evseStatus] of chargingStation.evses) {
570 if (evseStatus.connectors.size > 1) {
d929adcc
JB
571 for (const [id, status] of evseStatus.connectors) {
572 if (id !== connectorId && status?.transactionStarted === true) {
649287f8 573 logger.error(
d929adcc 574 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId}:`,
5edd8ba0 575 evseStatus,
649287f8 576 );
d929adcc 577 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
649287f8
JB
578 return;
579 }
580 }
581 }
582 }
583 }
e7aeea18 584 if (
d929adcc
JB
585 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
586 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
587 ) {
588 logger.error(
d929adcc 589 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`,
e7aeea18 590 );
290d006c
JB
591 return;
592 }
d1504492 593 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 594 logger.warn(
d929adcc 595 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 596 payload.transactionId
5edd8ba0 597 }, converting to integer`,
54ebb82c 598 );
9bf0ef23 599 payload.transactionId = convertToInt(payload.transactionId);
54ebb82c 600 }
c0560973 601
f0c6ed89 602 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
d929adcc
JB
603 connectorStatus.transactionStarted = true;
604 connectorStatus.transactionStart = requestPayload.timestamp;
605 connectorStatus.transactionId = payload.transactionId;
606 connectorStatus.transactionIdTag = requestPayload.idTag;
607 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
608 connectorStatus.transactionBeginMeterValue =
e7aeea18 609 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 610 chargingStation,
d929adcc 611 connectorId,
5edd8ba0 612 requestPayload.meterStart,
e7aeea18 613 );
90aceaf6
JB
614 if (requestPayload.reservationId) {
615 const reservation = chargingStation.getReservationBy(
616 'reservationId',
617 requestPayload.reservationId,
618 )!;
619 if (reservation.idTag !== requestPayload.idTag) {
620 logger.warn(
56563a3c 621 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
622 payload.transactionId
623 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
624 reservation.idTag
625 }`,
626 );
627 }
628 if (hasReservationExpired(reservation)) {
629 logger.warn(
56563a3c 630 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6
JB
631 payload.transactionId
632 } started with expired reservation ${
633 requestPayload.reservationId
634 } (expiry date: ${reservation.expiryDate.toISOString()}))`,
635 );
636 }
d984c13f 637 await chargingStation.removeReservation(
90aceaf6 638 reservation,
d984c13f
JB
639 ReservationTerminationReason.TRANSACTION_STARTED,
640 );
641 }
08f130a0
JB
642 chargingStation.getBeginEndMeterValues() &&
643 (await chargingStation.ocppRequestService.requestHandler<
ef6fa3fb
JB
644 OCPP16MeterValuesRequest,
645 OCPP16MeterValuesResponse
08f130a0 646 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 647 connectorId,
ef6fa3fb 648 transactionId: payload.transactionId,
d929adcc 649 meterValue: [connectorStatus.transactionBeginMeterValue],
e1d9a0f4 650 } as OCPP16MeterValuesRequest));
4ecff7ce
JB
651 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
652 chargingStation,
d929adcc 653 connectorId,
5edd8ba0 654 OCPP16ChargePointStatus.Charging,
4ecff7ce 655 );
e7aeea18 656 logger.info(
d929adcc 657 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
44eb6026 658 chargingStation.stationInfo.chargingStationId
d929adcc 659 }#${connectorId} for idTag '${requestPayload.idTag}'`,
e7aeea18 660 );
08f130a0 661 if (chargingStation.stationInfo.powerSharedByConnectors) {
1fe0632a 662 ++chargingStation.powerDivider;
c0560973 663 }
f2d5e3d9
JB
664 const configuredMeterValueSampleInterval = getConfigurationKey(
665 chargingStation,
666 OCPP16StandardParametersKey.MeterValueSampleInterval,
667 );
08f130a0 668 chargingStation.startMeterValues(
d929adcc 669 connectorId,
e7aeea18 670 configuredMeterValueSampleInterval
be4c6702 671 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
5edd8ba0 672 : Constants.DEFAULT_METER_VALUES_INTERVAL,
e7aeea18 673 );
c0560973 674 } else {
e7aeea18 675 logger.warn(
d929adcc
JB
676 `${chargingStation.logPrefix()} Starting transaction with id ${
677 payload.transactionId
678 } REJECTED on ${
56563a3c 679 chargingStation.stationInfo.chargingStationId
d929adcc 680 }#${connectorId} with status '${payload.idTagInfo?.status}', idTag '${
56563a3c
JB
681 requestPayload.idTag
682 }'${
d929adcc 683 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
684 ? `, reservationId '${requestPayload.reservationId}'`
685 : ''
686 }`,
e7aeea18 687 );
d929adcc 688 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
a2653482
JB
689 }
690 }
691
08f130a0
JB
692 private async resetConnectorOnStartTransactionError(
693 chargingStation: ChargingStation,
5edd8ba0 694 connectorId: number,
08f130a0 695 ): Promise<void> {
d929adcc
JB
696 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
697 resetConnectorStatus(connectorStatus!);
04b1261c 698 chargingStation.stopMeterValues(connectorId);
d929adcc 699 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
700 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
701 chargingStation,
ef6fa3fb 702 connectorId,
5edd8ba0 703 OCPP16ChargePointStatus.Available,
4ecff7ce 704 );
c0560973 705 }
4294bc6b 706 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
c0560973
JB
707 }
708
e7aeea18 709 private async handleResponseStopTransaction(
08f130a0 710 chargingStation: ChargingStation,
e7aeea18 711 payload: OCPP16StopTransactionResponse,
5edd8ba0 712 requestPayload: OCPP16StopTransactionRequest,
e7aeea18 713 ): Promise<void> {
08f130a0 714 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
5edd8ba0 715 requestPayload.transactionId,
f479a792 716 );
9bf0ef23 717 if (isNullOrUndefined(transactionConnectorId)) {
e7aeea18 718 logger.error(
d929adcc
JB
719 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
720 requestPayload.transactionId
721 }`,
e7aeea18 722 );
c0560973
JB
723 return;
724 }
2cace1a5
JB
725 chargingStation.getBeginEndMeterValues() === true &&
726 chargingStation.getOcppStrictCompliance() === false &&
727 chargingStation.getOutOfOrderEndMeterValues() === true &&
728 (await chargingStation.ocppRequestService.requestHandler<
729 OCPP16MeterValuesRequest,
730 OCPP16MeterValuesResponse
731 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
732 connectorId: transactionConnectorId,
733 transactionId: requestPayload.transactionId,
734 meterValue: [
735 OCPP16ServiceUtils.buildTransactionEndMeterValue(
736 chargingStation,
e1d9a0f4 737 transactionConnectorId!,
5edd8ba0 738 requestPayload.meterStop,
2cace1a5
JB
739 ),
740 ],
741 }));
742 if (
743 chargingStation.isChargingStationAvailable() === false ||
e1d9a0f4 744 chargingStation.isConnectorAvailable(transactionConnectorId!) === false
2cace1a5 745 ) {
4ecff7ce
JB
746 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
747 chargingStation,
e1d9a0f4 748 transactionConnectorId!,
5edd8ba0 749 OCPP16ChargePointStatus.Unavailable,
4ecff7ce 750 );
c0560973 751 } else {
4ecff7ce
JB
752 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
753 chargingStation,
e1d9a0f4 754 transactionConnectorId!,
5edd8ba0 755 OCPP16ChargePointStatus.Available,
4ecff7ce 756 );
2cace1a5
JB
757 }
758 if (chargingStation.stationInfo.powerSharedByConnectors) {
759 chargingStation.powerDivider--;
760 }
e1d9a0f4
JB
761 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!);
762 chargingStation.stopMeterValues(transactionConnectorId!);
c8faabc8 763 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
d929adcc
JB
764 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
765 requestPayload.transactionId
766 } STOPPED on ${
8ca6874c 767 chargingStation.stationInfo.chargingStationId
d929adcc 768 }#${transactionConnectorId} with status '${payload.idTagInfo?.status ?? 'undefined'}'`;
2cace1a5 769 if (
9bf0ef23 770 isNullOrUndefined(payload.idTagInfo) ||
2cace1a5
JB
771 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
772 ) {
773 logger.info(logMsg);
774 } else {
775 logger.warn(logMsg);
c0560973
JB
776 }
777 }
c0560973 778}