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