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