fix: ensure boot notification response is assigned before querying
[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 (payload.status === RegistrationStatusEnumType.ACCEPTED) {
514 addConfigurationKey(
515 chargingStation,
516 OCPP16StandardParametersKey.HeartbeatInterval,
517 payload.interval.toString(),
518 {},
519 { overwrite: true, save: true }
520 )
521 addConfigurationKey(
522 chargingStation,
523 OCPP16StandardParametersKey.HeartBeatInterval,
524 payload.interval.toString(),
525 { visible: false },
526 { overwrite: true, save: true }
527 )
528 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
529 }
530 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
531 chargingStation.bootNotificationResponse = payload
532 if (chargingStation.isRegistered()) {
533 chargingStation.emit(ChargingStationEvents.registered)
534 if (chargingStation.inAcceptedState()) {
535 chargingStation.emit(ChargingStationEvents.accepted)
536 }
537 } else if (chargingStation.inRejectedState()) {
538 chargingStation.emit(ChargingStationEvents.rejected)
539 }
540 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
541 payload.status
542 }' state on the central server`
543 payload.status === RegistrationStatusEnumType.REJECTED
544 ? logger.warn(logMsg)
545 : logger.info(logMsg)
546 } else {
547 logger.error(
548 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
549 payload
550 )
551 }
552 }
553
554 private handleResponseAuthorize (
555 chargingStation: ChargingStation,
556 payload: OCPP16AuthorizeResponse,
557 requestPayload: OCPP16AuthorizeRequest
558 ): void {
559 let authorizeConnectorId: number | undefined
560 if (chargingStation.hasEvses) {
561 for (const [evseId, evseStatus] of chargingStation.evses) {
562 if (evseId > 0) {
563 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
564 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
565 authorizeConnectorId = connectorId
566 break
567 }
568 }
569 }
570 }
571 } else {
572 for (const connectorId of chargingStation.connectors.keys()) {
573 if (
574 connectorId > 0 &&
575 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
576 ) {
577 authorizeConnectorId = connectorId
578 break
579 }
580 }
581 }
582 if (authorizeConnectorId != null) {
583 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
584 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
585 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
586 authorizeConnectorStatus.idTagAuthorized = true
587 logger.debug(
588 `${chargingStation.logPrefix()} idTag '${
589 requestPayload.idTag
590 }' accepted on connector id ${authorizeConnectorId}`
591 )
592 } else {
593 authorizeConnectorStatus.idTagAuthorized = false
594 delete authorizeConnectorStatus.authorizeIdTag
595 logger.debug(
596 `${chargingStation.logPrefix()} idTag '${
597 requestPayload.idTag
598 }' rejected with status '${payload.idTagInfo.status}'`
599 )
600 }
601 } else {
602 logger.error(
603 `${chargingStation.logPrefix()} idTag '${
604 requestPayload.idTag
605 }' has no authorize request pending`
606 )
607 }
608 }
609
610 private async handleResponseStartTransaction (
611 chargingStation: ChargingStation,
612 payload: OCPP16StartTransactionResponse,
613 requestPayload: OCPP16StartTransactionRequest
614 ): Promise<void> {
615 const { connectorId } = requestPayload
616 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
617 logger.error(
618 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
619 )
620 return
621 }
622 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
623 if (
624 connectorStatus?.transactionRemoteStarted === true &&
625 chargingStation.getAuthorizeRemoteTxRequests() &&
626 chargingStation.getLocalAuthListEnabled() &&
627 chargingStation.hasIdTags() &&
628 connectorStatus.idTagLocalAuthorized === false
629 ) {
630 logger.error(
631 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
632 connectorStatus.localAuthorizeIdTag
633 } on connector id ${connectorId}`
634 )
635 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
636 return
637 }
638 if (
639 connectorStatus?.transactionRemoteStarted === true &&
640 chargingStation.getAuthorizeRemoteTxRequests() &&
641 chargingStation.stationInfo?.remoteAuthorization === true &&
642 connectorStatus.idTagLocalAuthorized === false &&
643 connectorStatus.idTagAuthorized === false
644 ) {
645 logger.error(
646 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
647 connectorStatus.authorizeIdTag
648 } on connector id ${connectorId}`
649 )
650 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
651 return
652 }
653 if (
654 connectorStatus?.idTagAuthorized === true &&
655 connectorStatus.authorizeIdTag !== requestPayload.idTag
656 ) {
657 logger.error(
658 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
659 requestPayload.idTag
660 } different from the authorize request one ${
661 connectorStatus.authorizeIdTag
662 } on connector id ${connectorId}`
663 )
664 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
665 return
666 }
667 if (
668 connectorStatus?.idTagLocalAuthorized === true &&
669 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
670 ) {
671 logger.error(
672 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
673 requestPayload.idTag
674 } different from the local authorized one ${
675 connectorStatus.localAuthorizeIdTag
676 } on connector id ${connectorId}`
677 )
678 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
679 return
680 }
681 if (connectorStatus?.transactionStarted === true) {
682 logger.error(
683 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
684 connectorStatus.transactionIdTag
685 }`
686 )
687 return
688 }
689 if (chargingStation.hasEvses) {
690 for (const [evseId, evseStatus] of chargingStation.evses) {
691 if (evseStatus.connectors.size > 1) {
692 for (const [id, status] of evseStatus.connectors) {
693 if (id !== connectorId && status.transactionStarted === true) {
694 logger.error(
695 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
696 status.transactionIdTag
697 }`
698 )
699 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
700 return
701 }
702 }
703 }
704 }
705 }
706 if (
707 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
708 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
709 ) {
710 logger.error(
711 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${
712 connectorStatus?.status
713 }`
714 )
715 return
716 }
717 if (!Number.isSafeInteger(payload.transactionId)) {
718 logger.warn(
719 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
720 payload.transactionId
721 }, converting to integer`
722 )
723 payload.transactionId = convertToInt(payload.transactionId)
724 }
725
726 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
727 connectorStatus.transactionStarted = true
728 connectorStatus.transactionStart = requestPayload.timestamp
729 connectorStatus.transactionId = payload.transactionId
730 connectorStatus.transactionIdTag = requestPayload.idTag
731 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
732 connectorStatus.transactionBeginMeterValue =
733 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
734 chargingStation,
735 connectorId,
736 requestPayload.meterStart
737 )
738 if (requestPayload.reservationId != null) {
739 const reservation = chargingStation.getReservationBy(
740 'reservationId',
741 requestPayload.reservationId
742 )
743 if (reservation != null) {
744 if (reservation.idTag !== requestPayload.idTag) {
745 logger.warn(
746 `${chargingStation.logPrefix()} Reserved transaction ${
747 payload.transactionId
748 } started with a different idTag ${
749 requestPayload.idTag
750 } than the reservation one ${reservation.idTag}`
751 )
752 }
753 if (hasReservationExpired(reservation)) {
754 logger.warn(
755 `${chargingStation.logPrefix()} Reserved transaction ${
756 payload.transactionId
757 } started with expired reservation ${
758 requestPayload.reservationId
759 } (expiry date: ${reservation.expiryDate.toISOString()}))`
760 )
761 }
762 await chargingStation.removeReservation(
763 reservation,
764 ReservationTerminationReason.TRANSACTION_STARTED
765 )
766 } else {
767 logger.warn(
768 `${chargingStation.logPrefix()} Reserved transaction ${
769 payload.transactionId
770 } started with unknown reservation ${requestPayload.reservationId}`
771 )
772 }
773 }
774 chargingStation.stationInfo?.beginEndMeterValues === true &&
775 (await chargingStation.ocppRequestService.requestHandler<
776 OCPP16MeterValuesRequest,
777 OCPP16MeterValuesResponse
778 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
779 connectorId,
780 transactionId: payload.transactionId,
781 meterValue: [connectorStatus.transactionBeginMeterValue]
782 } satisfies OCPP16MeterValuesRequest))
783 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
784 chargingStation,
785 connectorId,
786 OCPP16ChargePointStatus.Charging
787 )
788 logger.info(
789 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
790 chargingStation.stationInfo?.chargingStationId
791 }#${connectorId} for idTag '${requestPayload.idTag}'`
792 )
793 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
794 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
795 ++chargingStation.powerDivider!
796 }
797 const configuredMeterValueSampleInterval = getConfigurationKey(
798 chargingStation,
799 OCPP16StandardParametersKey.MeterValueSampleInterval
800 )
801 chargingStation.startMeterValues(
802 connectorId,
803 configuredMeterValueSampleInterval != null
804 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
805 : Constants.DEFAULT_METER_VALUES_INTERVAL
806 )
807 } else {
808 logger.warn(
809 `${chargingStation.logPrefix()} Starting transaction with id ${
810 payload.transactionId
811 } REJECTED on ${
812 chargingStation.stationInfo?.chargingStationId
813 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
814 requestPayload.idTag
815 }'${
816 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
817 ? `, reservationId '${requestPayload.reservationId}'`
818 : ''
819 }`
820 )
821 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
822 }
823 }
824
825 private async resetConnectorOnStartTransactionError (
826 chargingStation: ChargingStation,
827 connectorId: number
828 ): Promise<void> {
829 chargingStation.stopMeterValues(connectorId)
830 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
831 resetConnectorStatus(connectorStatus)
832 await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
833 }
834
835 private async handleResponseStopTransaction (
836 chargingStation: ChargingStation,
837 payload: OCPP16StopTransactionResponse,
838 requestPayload: OCPP16StopTransactionRequest
839 ): Promise<void> {
840 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
841 requestPayload.transactionId
842 )
843 if (transactionConnectorId == null) {
844 logger.error(
845 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
846 requestPayload.transactionId
847 }`
848 )
849 return
850 }
851 chargingStation.stationInfo?.beginEndMeterValues === true &&
852 chargingStation.stationInfo.ocppStrictCompliance === false &&
853 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
854 (await chargingStation.ocppRequestService.requestHandler<
855 OCPP16MeterValuesRequest,
856 OCPP16MeterValuesResponse
857 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
858 connectorId: transactionConnectorId,
859 transactionId: requestPayload.transactionId,
860 meterValue: [
861 OCPP16ServiceUtils.buildTransactionEndMeterValue(
862 chargingStation,
863 transactionConnectorId,
864 requestPayload.meterStop
865 )
866 ]
867 }))
868 if (
869 !chargingStation.isChargingStationAvailable() ||
870 !chargingStation.isConnectorAvailable(transactionConnectorId)
871 ) {
872 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
873 chargingStation,
874 transactionConnectorId,
875 OCPP16ChargePointStatus.Unavailable
876 )
877 } else {
878 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
879 chargingStation,
880 transactionConnectorId,
881 OCPP16ChargePointStatus.Available
882 )
883 }
884 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
885 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
886 chargingStation.powerDivider!--
887 }
888 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
889 chargingStation.stopMeterValues(transactionConnectorId)
890 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
891 requestPayload.transactionId
892 } STOPPED on ${
893 chargingStation.stationInfo?.chargingStationId
894 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
895 if (
896 payload.idTagInfo == null ||
897 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
898 ) {
899 logger.info(logMsg)
900 } else {
901 logger.warn(logMsg)
902 }
903 }
904 }