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