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