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