fix: send preparing connector status before `StartTransaction`
[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 ErrorType,
17 type GenericResponse,
18 type GetConfigurationResponse,
19 type GetDiagnosticsResponse,
20 type JsonType,
21 OCPP16AuthorizationStatus,
22 type OCPP16AuthorizeRequest,
23 type OCPP16AuthorizeResponse,
24 type OCPP16BootNotificationResponse,
25 type OCPP16ChangeAvailabilityResponse,
26 OCPP16ChargePointStatus,
27 type OCPP16ClearChargingProfileResponse,
28 type OCPP16DataTransferResponse,
29 type OCPP16DiagnosticsStatusNotificationResponse,
30 type OCPP16FirmwareStatusNotificationResponse,
31 type OCPP16GetCompositeScheduleResponse,
32 type OCPP16HeartbeatResponse,
33 OCPP16IncomingRequestCommand,
34 type OCPP16MeterValuesRequest,
35 type OCPP16MeterValuesResponse,
36 OCPP16RequestCommand,
37 type OCPP16ReserveNowResponse,
38 OCPP16StandardParametersKey,
39 type OCPP16StartTransactionRequest,
40 type OCPP16StartTransactionResponse,
41 type OCPP16StatusNotificationResponse,
42 type OCPP16StopTransactionRequest,
43 type OCPP16StopTransactionResponse,
44 type OCPP16TriggerMessageResponse,
45 type OCPP16UpdateFirmwareResponse,
46 OCPPVersion,
47 RegistrationStatusEnumType,
48 ReservationTerminationReason,
49 type ResponseHandler,
50 type SetChargingProfileResponse,
51 type UnlockConnectorResponse
52 } from '../../../types/index.js'
53 import { Constants, convertToInt, isAsyncFunction, logger } from '../../../utils/index.js'
54 import { OCPPResponseService } from '../OCPPResponseService.js'
55 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
56
57 const moduleName = 'OCPP16ResponseService'
58
59 export class OCPP16ResponseService extends OCPPResponseService {
60 public incomingRequestResponsePayloadValidateFunctions: Map<
61 OCPP16IncomingRequestCommand,
62 ValidateFunction<JsonType>
63 >
64
65 protected payloadValidateFunctions: 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.payloadValidateFunctions = 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.incomingRequestResponsePayloadValidateFunctions = 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 const responseHandler = this.responseHandlers.get(commandName)!
449 if (isAsyncFunction(responseHandler)) {
450 await responseHandler(chargingStation, payload, requestPayload)
451 } else {
452 (
453 responseHandler as (
454 chargingStation: ChargingStation,
455 payload: JsonType,
456 requestPayload?: JsonType
457 ) => void
458 )(chargingStation, payload, requestPayload)
459 }
460 } catch (error) {
461 logger.error(
462 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
463 error
464 )
465 throw error
466 }
467 } else {
468 // Throw exception
469 throw new OCPPError(
470 ErrorType.NOT_IMPLEMENTED,
471 `'${commandName}' is not implemented to handle response PDU ${JSON.stringify(
472 payload,
473 undefined,
474 2
475 )}`,
476 commandName,
477 payload
478 )
479 }
480 } else {
481 throw new OCPPError(
482 ErrorType.SECURITY_ERROR,
483 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
484 payload,
485 undefined,
486 2
487 )} while the charging station is not registered on the central server`,
488 commandName,
489 payload
490 )
491 }
492 }
493
494 private validatePayload (
495 chargingStation: ChargingStation,
496 commandName: OCPP16RequestCommand,
497 payload: JsonType
498 ): boolean {
499 if (this.payloadValidateFunctions.has(commandName)) {
500 return this.validateResponsePayload(chargingStation, commandName, payload)
501 }
502 logger.warn(
503 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
504 )
505 return false
506 }
507
508 private handleResponseBootNotification (
509 chargingStation: ChargingStation,
510 payload: OCPP16BootNotificationResponse
511 ): void {
512 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
513 addConfigurationKey(
514 chargingStation,
515 OCPP16StandardParametersKey.HeartbeatInterval,
516 payload.interval.toString(),
517 {},
518 { overwrite: true, save: true }
519 )
520 addConfigurationKey(
521 chargingStation,
522 OCPP16StandardParametersKey.HeartBeatInterval,
523 payload.interval.toString(),
524 { visible: false },
525 { overwrite: true, save: true }
526 )
527 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
528 }
529 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
530 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
531 payload.status
532 }' state on the central server`
533 payload.status === RegistrationStatusEnumType.REJECTED
534 ? logger.warn(logMsg)
535 : logger.info(logMsg)
536 } else {
537 logger.error(
538 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
539 payload
540 )
541 }
542 }
543
544 private handleResponseAuthorize (
545 chargingStation: ChargingStation,
546 payload: OCPP16AuthorizeResponse,
547 requestPayload: OCPP16AuthorizeRequest
548 ): void {
549 let authorizeConnectorId: number | undefined
550 if (chargingStation.hasEvses) {
551 for (const [evseId, evseStatus] of chargingStation.evses) {
552 if (evseId > 0) {
553 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
554 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
555 authorizeConnectorId = connectorId
556 break
557 }
558 }
559 }
560 }
561 } else {
562 for (const connectorId of chargingStation.connectors.keys()) {
563 if (
564 connectorId > 0 &&
565 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
566 ) {
567 authorizeConnectorId = connectorId
568 break
569 }
570 }
571 }
572 if (authorizeConnectorId != null) {
573 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
574 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
575 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
576 authorizeConnectorStatus.idTagAuthorized = true
577 logger.debug(
578 `${chargingStation.logPrefix()} idTag '${
579 requestPayload.idTag
580 }' accepted on connector id ${authorizeConnectorId}`
581 )
582 } else {
583 authorizeConnectorStatus.idTagAuthorized = false
584 delete authorizeConnectorStatus.authorizeIdTag
585 logger.debug(
586 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
587 payload.idTagInfo.status
588 }'`
589 )
590 }
591 } else {
592 logger.error(
593 `${chargingStation.logPrefix()} idTag '${
594 requestPayload.idTag
595 }' has no authorize request pending`
596 )
597 }
598 }
599
600 private async handleResponseStartTransaction (
601 chargingStation: ChargingStation,
602 payload: OCPP16StartTransactionResponse,
603 requestPayload: OCPP16StartTransactionRequest
604 ): Promise<void> {
605 const { connectorId } = requestPayload
606 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
607 logger.error(
608 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
609 )
610 return
611 }
612 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
613 if (
614 connectorStatus?.transactionRemoteStarted === true &&
615 chargingStation.getAuthorizeRemoteTxRequests() &&
616 chargingStation.getLocalAuthListEnabled() &&
617 chargingStation.hasIdTags() &&
618 connectorStatus.idTagLocalAuthorized === false
619 ) {
620 logger.error(
621 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
622 connectorStatus.localAuthorizeIdTag
623 } on connector id ${connectorId}`
624 )
625 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
626 return
627 }
628 if (
629 connectorStatus?.transactionRemoteStarted === true &&
630 chargingStation.getAuthorizeRemoteTxRequests() &&
631 chargingStation.stationInfo?.remoteAuthorization === true &&
632 connectorStatus.idTagLocalAuthorized === false &&
633 connectorStatus.idTagAuthorized === false
634 ) {
635 logger.error(
636 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
637 connectorStatus.authorizeIdTag
638 } on connector id ${connectorId}`
639 )
640 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
641 return
642 }
643 if (
644 connectorStatus?.idTagAuthorized === true &&
645 connectorStatus.authorizeIdTag !== requestPayload.idTag
646 ) {
647 logger.error(
648 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
649 requestPayload.idTag
650 } different from the authorize request one ${
651 connectorStatus.authorizeIdTag
652 } on connector id ${connectorId}`
653 )
654 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
655 return
656 }
657 if (
658 connectorStatus?.idTagLocalAuthorized === true &&
659 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
660 ) {
661 logger.error(
662 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
663 requestPayload.idTag
664 } different from the local authorized one ${
665 connectorStatus.localAuthorizeIdTag
666 } on connector id ${connectorId}`
667 )
668 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
669 return
670 }
671 if (connectorStatus?.transactionStarted === true) {
672 logger.error(
673 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
674 connectorStatus.transactionIdTag
675 }`
676 )
677 return
678 }
679 if (chargingStation.hasEvses) {
680 for (const [evseId, evseStatus] of chargingStation.evses) {
681 if (evseStatus.connectors.size > 1) {
682 for (const [id, status] of evseStatus.connectors) {
683 if (id !== connectorId && status.transactionStarted === true) {
684 logger.error(
685 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
686 status.transactionIdTag
687 }`
688 )
689 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
690 return
691 }
692 }
693 }
694 }
695 }
696 if (
697 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
698 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
699 ) {
700 logger.error(
701 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
702 )
703 return
704 }
705 if (!Number.isSafeInteger(payload.transactionId)) {
706 logger.warn(
707 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
708 payload.transactionId
709 }, converting to integer`
710 )
711 payload.transactionId = convertToInt(payload.transactionId)
712 }
713
714 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
715 connectorStatus.transactionStarted = true
716 connectorStatus.transactionStart = requestPayload.timestamp
717 connectorStatus.transactionId = payload.transactionId
718 connectorStatus.transactionIdTag = requestPayload.idTag
719 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
720 connectorStatus.transactionBeginMeterValue =
721 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
722 chargingStation,
723 connectorId,
724 requestPayload.meterStart
725 )
726 if (requestPayload.reservationId != null) {
727 const reservation = chargingStation.getReservationBy(
728 'reservationId',
729 requestPayload.reservationId
730 )
731 if (reservation != null) {
732 if (reservation.idTag !== requestPayload.idTag) {
733 logger.warn(
734 `${chargingStation.logPrefix()} Reserved transaction ${
735 payload.transactionId
736 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
737 reservation.idTag
738 }`
739 )
740 }
741 if (hasReservationExpired(reservation)) {
742 logger.warn(
743 `${chargingStation.logPrefix()} Reserved transaction ${
744 payload.transactionId
745 } started with expired reservation ${
746 requestPayload.reservationId
747 } (expiry date: ${reservation.expiryDate.toISOString()}))`
748 )
749 }
750 await chargingStation.removeReservation(
751 reservation,
752 ReservationTerminationReason.TRANSACTION_STARTED
753 )
754 } else {
755 logger.warn(
756 `${chargingStation.logPrefix()} Reserved transaction ${
757 payload.transactionId
758 } started with unknown reservation ${requestPayload.reservationId}`
759 )
760 }
761 }
762 chargingStation.stationInfo?.beginEndMeterValues === true &&
763 (await chargingStation.ocppRequestService.requestHandler<
764 OCPP16MeterValuesRequest,
765 OCPP16MeterValuesResponse
766 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
767 connectorId,
768 transactionId: payload.transactionId,
769 meterValue: [connectorStatus.transactionBeginMeterValue]
770 } satisfies OCPP16MeterValuesRequest))
771 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
772 chargingStation,
773 connectorId,
774 OCPP16ChargePointStatus.Charging
775 )
776 logger.info(
777 `${chargingStation.logPrefix()} Transaction with id ${
778 payload.transactionId
779 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
780 requestPayload.idTag
781 }'`
782 )
783 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
784 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
785 ++chargingStation.powerDivider!
786 }
787 const configuredMeterValueSampleInterval = getConfigurationKey(
788 chargingStation,
789 OCPP16StandardParametersKey.MeterValueSampleInterval
790 )
791 chargingStation.startMeterValues(
792 connectorId,
793 configuredMeterValueSampleInterval != null
794 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
795 : Constants.DEFAULT_METER_VALUES_INTERVAL
796 )
797 } else {
798 logger.warn(
799 `${chargingStation.logPrefix()} Starting transaction with id ${
800 payload.transactionId
801 } REJECTED on ${
802 chargingStation.stationInfo?.chargingStationId
803 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
804 requestPayload.idTag
805 }'${
806 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
807 ? `, reservationId '${requestPayload.reservationId}'`
808 : ''
809 }`
810 )
811 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
812 }
813 }
814
815 private async resetConnectorOnStartTransactionError (
816 chargingStation: ChargingStation,
817 connectorId: number
818 ): Promise<void> {
819 chargingStation.stopMeterValues(connectorId)
820 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
821 resetConnectorStatus(connectorStatus)
822 await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
823 }
824
825 private async handleResponseStopTransaction (
826 chargingStation: ChargingStation,
827 payload: OCPP16StopTransactionResponse,
828 requestPayload: OCPP16StopTransactionRequest
829 ): Promise<void> {
830 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
831 requestPayload.transactionId
832 )
833 if (transactionConnectorId == null) {
834 logger.error(
835 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
836 requestPayload.transactionId
837 }`
838 )
839 return
840 }
841 chargingStation.stationInfo?.beginEndMeterValues === true &&
842 chargingStation.stationInfo.ocppStrictCompliance === false &&
843 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
844 (await chargingStation.ocppRequestService.requestHandler<
845 OCPP16MeterValuesRequest,
846 OCPP16MeterValuesResponse
847 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
848 connectorId: transactionConnectorId,
849 transactionId: requestPayload.transactionId,
850 meterValue: [
851 OCPP16ServiceUtils.buildTransactionEndMeterValue(
852 chargingStation,
853 transactionConnectorId,
854 requestPayload.meterStop
855 )
856 ]
857 }))
858 if (
859 !chargingStation.isChargingStationAvailable() ||
860 !chargingStation.isConnectorAvailable(transactionConnectorId)
861 ) {
862 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
863 chargingStation,
864 transactionConnectorId,
865 OCPP16ChargePointStatus.Unavailable
866 )
867 } else {
868 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
869 chargingStation,
870 transactionConnectorId,
871 OCPP16ChargePointStatus.Available
872 )
873 }
874 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
875 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
876 chargingStation.powerDivider!--
877 }
878 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
879 chargingStation.stopMeterValues(transactionConnectorId)
880 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
881 requestPayload.transactionId
882 } STOPPED on ${
883 chargingStation.stationInfo?.chargingStationId
884 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
885 if (
886 payload.idTagInfo == null ||
887 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
888 ) {
889 logger.info(logMsg)
890 } else {
891 logger.warn(logMsg)
892 }
893 }
894 }