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