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