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