perf: lookup connector status once at OCPP responses handling
[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 authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId!);
474 const authorizeConnectorIdDefined = !isNullOrUndefined(authorizeConnectorId);
475 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
476 if (authorizeConnectorIdDefined) {
477 // authorizeConnectorStatus!.authorizeIdTag = requestPayload.idTag;
478 authorizeConnectorStatus!.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 authorizeConnectorStatus!.idTagAuthorized = false;
488 delete authorizeConnectorStatus?.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 { connectorId } = requestPayload;
504 if (connectorId === 0 || chargingStation.hasConnector(connectorId) === false) {
505 logger.error(
506 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`,
507 );
508 return;
509 }
510 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
511 if (
512 connectorStatus?.transactionRemoteStarted === true &&
513 chargingStation.getAuthorizeRemoteTxRequests() === true &&
514 chargingStation.getLocalAuthListEnabled() === true &&
515 chargingStation.hasIdTags() === true &&
516 connectorStatus?.idTagLocalAuthorized === false
517 ) {
518 logger.error(
519 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`,
520 );
521 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
522 return;
523 }
524 if (
525 connectorStatus?.transactionRemoteStarted === true &&
526 chargingStation.getAuthorizeRemoteTxRequests() === true &&
527 chargingStation.getRemoteAuthorization() === true &&
528 connectorStatus?.idTagLocalAuthorized === false &&
529 connectorStatus?.idTagAuthorized === false
530 ) {
531 logger.error(
532 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`,
533 );
534 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
535 return;
536 }
537 if (
538 connectorStatus?.idTagAuthorized &&
539 connectorStatus?.authorizeIdTag !== requestPayload.idTag
540 ) {
541 logger.error(
542 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
543 requestPayload.idTag
544 } different from the authorize request one ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`,
545 );
546 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
547 return;
548 }
549 if (
550 connectorStatus?.idTagLocalAuthorized &&
551 connectorStatus?.localAuthorizeIdTag !== requestPayload.idTag
552 ) {
553 logger.error(
554 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
555 requestPayload.idTag
556 } different from the local authorized one ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`,
557 );
558 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
559 return;
560 }
561 if (connectorStatus?.transactionStarted === true) {
562 logger.error(
563 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId}:`,
564 connectorStatus,
565 );
566 return;
567 }
568 if (chargingStation.hasEvses) {
569 for (const [evseId, evseStatus] of chargingStation.evses) {
570 if (evseStatus.connectors.size > 1) {
571 for (const [id, status] of evseStatus.connectors) {
572 if (id !== connectorId && status?.transactionStarted === true) {
573 logger.error(
574 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId}:`,
575 evseStatus,
576 );
577 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
578 return;
579 }
580 }
581 }
582 }
583 }
584 if (
585 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
586 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
587 ) {
588 logger.error(
589 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`,
590 );
591 return;
592 }
593 if (!Number.isSafeInteger(payload.transactionId)) {
594 logger.warn(
595 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
596 payload.transactionId
597 }, converting to integer`,
598 );
599 payload.transactionId = convertToInt(payload.transactionId);
600 }
601
602 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
603 connectorStatus.transactionStarted = true;
604 connectorStatus.transactionStart = requestPayload.timestamp;
605 connectorStatus.transactionId = payload.transactionId;
606 connectorStatus.transactionIdTag = requestPayload.idTag;
607 connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
608 connectorStatus.transactionBeginMeterValue =
609 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
610 chargingStation,
611 connectorId,
612 requestPayload.meterStart,
613 );
614 if (requestPayload.reservationId) {
615 const reservation = chargingStation.getReservationBy(
616 'reservationId',
617 requestPayload.reservationId,
618 )!;
619 if (reservation.idTag !== requestPayload.idTag) {
620 logger.warn(
621 `${chargingStation.logPrefix()} Reserved transaction ${
622 payload.transactionId
623 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
624 reservation.idTag
625 }`,
626 );
627 }
628 if (hasReservationExpired(reservation)) {
629 logger.warn(
630 `${chargingStation.logPrefix()} Reserved transaction ${
631 payload.transactionId
632 } started with expired reservation ${
633 requestPayload.reservationId
634 } (expiry date: ${reservation.expiryDate.toISOString()}))`,
635 );
636 }
637 await chargingStation.removeReservation(
638 reservation,
639 ReservationTerminationReason.TRANSACTION_STARTED,
640 );
641 }
642 chargingStation.getBeginEndMeterValues() &&
643 (await chargingStation.ocppRequestService.requestHandler<
644 OCPP16MeterValuesRequest,
645 OCPP16MeterValuesResponse
646 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
647 connectorId,
648 transactionId: payload.transactionId,
649 meterValue: [connectorStatus.transactionBeginMeterValue],
650 } as OCPP16MeterValuesRequest));
651 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
652 chargingStation,
653 connectorId,
654 OCPP16ChargePointStatus.Charging,
655 );
656 logger.info(
657 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
658 chargingStation.stationInfo.chargingStationId
659 }#${connectorId} for idTag '${requestPayload.idTag}'`,
660 );
661 if (chargingStation.stationInfo.powerSharedByConnectors) {
662 ++chargingStation.powerDivider;
663 }
664 const configuredMeterValueSampleInterval = getConfigurationKey(
665 chargingStation,
666 OCPP16StandardParametersKey.MeterValueSampleInterval,
667 );
668 chargingStation.startMeterValues(
669 connectorId,
670 configuredMeterValueSampleInterval
671 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
672 : Constants.DEFAULT_METER_VALUES_INTERVAL,
673 );
674 } else {
675 logger.warn(
676 `${chargingStation.logPrefix()} Starting transaction with id ${
677 payload.transactionId
678 } REJECTED on ${
679 chargingStation.stationInfo.chargingStationId
680 }#${connectorId} with status '${payload.idTagInfo?.status}', idTag '${
681 requestPayload.idTag
682 }'${
683 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
684 ? `, reservationId '${requestPayload.reservationId}'`
685 : ''
686 }`,
687 );
688 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId);
689 }
690 }
691
692 private async resetConnectorOnStartTransactionError(
693 chargingStation: ChargingStation,
694 connectorId: number,
695 ): Promise<void> {
696 const connectorStatus = chargingStation.getConnectorStatus(connectorId);
697 resetConnectorStatus(connectorStatus!);
698 chargingStation.stopMeterValues(connectorId);
699 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
700 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
701 chargingStation,
702 connectorId,
703 OCPP16ChargePointStatus.Available,
704 );
705 }
706 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
707 }
708
709 private async handleResponseStopTransaction(
710 chargingStation: ChargingStation,
711 payload: OCPP16StopTransactionResponse,
712 requestPayload: OCPP16StopTransactionRequest,
713 ): Promise<void> {
714 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
715 requestPayload.transactionId,
716 );
717 if (isNullOrUndefined(transactionConnectorId)) {
718 logger.error(
719 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
720 requestPayload.transactionId
721 }`,
722 );
723 return;
724 }
725 chargingStation.getBeginEndMeterValues() === true &&
726 chargingStation.getOcppStrictCompliance() === false &&
727 chargingStation.getOutOfOrderEndMeterValues() === true &&
728 (await chargingStation.ocppRequestService.requestHandler<
729 OCPP16MeterValuesRequest,
730 OCPP16MeterValuesResponse
731 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
732 connectorId: transactionConnectorId,
733 transactionId: requestPayload.transactionId,
734 meterValue: [
735 OCPP16ServiceUtils.buildTransactionEndMeterValue(
736 chargingStation,
737 transactionConnectorId!,
738 requestPayload.meterStop,
739 ),
740 ],
741 }));
742 if (
743 chargingStation.isChargingStationAvailable() === false ||
744 chargingStation.isConnectorAvailable(transactionConnectorId!) === false
745 ) {
746 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
747 chargingStation,
748 transactionConnectorId!,
749 OCPP16ChargePointStatus.Unavailable,
750 );
751 } else {
752 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
753 chargingStation,
754 transactionConnectorId!,
755 OCPP16ChargePointStatus.Available,
756 );
757 }
758 if (chargingStation.stationInfo.powerSharedByConnectors) {
759 chargingStation.powerDivider--;
760 }
761 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!);
762 chargingStation.stopMeterValues(transactionConnectorId!);
763 parentPort?.postMessage(buildUpdatedMessage(chargingStation));
764 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
765 requestPayload.transactionId
766 } STOPPED on ${
767 chargingStation.stationInfo.chargingStationId
768 }#${transactionConnectorId} with status '${payload.idTagInfo?.status ?? 'undefined'}'`;
769 if (
770 isNullOrUndefined(payload.idTagInfo) ||
771 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
772 ) {
773 logger.info(logMsg);
774 } else {
775 logger.warn(logMsg);
776 }
777 }
778 }