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