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