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