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