build(deps-dev): apply updates
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
2
3 import type { ValidateFunction } from 'ajv'
4 import { secondsToMilliseconds } from 'date-fns'
5
6 import {
7 addConfigurationKey,
8 type ChargingStation,
9 getConfigurationKey,
10 hasReservationExpired,
11 resetConnectorStatus
12 } from '../../../charging-station/index.js'
13 import { OCPPError } from '../../../exception/index.js'
14 import {
15 type ChangeConfigurationResponse,
16 ChargingStationEvents,
17 ErrorType,
18 type GenericResponse,
19 type GetConfigurationResponse,
20 type GetDiagnosticsResponse,
21 type JsonType,
22 OCPP16AuthorizationStatus,
23 type OCPP16AuthorizeRequest,
24 type OCPP16AuthorizeResponse,
25 type OCPP16BootNotificationResponse,
26 type OCPP16ChangeAvailabilityResponse,
27 OCPP16ChargePointStatus,
28 type OCPP16ClearChargingProfileResponse,
29 type OCPP16DataTransferResponse,
30 type OCPP16DiagnosticsStatusNotificationResponse,
31 type OCPP16FirmwareStatusNotificationResponse,
32 type OCPP16GetCompositeScheduleResponse,
33 type OCPP16HeartbeatResponse,
34 OCPP16IncomingRequestCommand,
35 type OCPP16MeterValuesRequest,
36 type OCPP16MeterValuesResponse,
37 OCPP16RequestCommand,
38 type OCPP16ReserveNowResponse,
39 OCPP16StandardParametersKey,
40 type OCPP16StartTransactionRequest,
41 type OCPP16StartTransactionResponse,
42 type OCPP16StatusNotificationResponse,
43 type OCPP16StopTransactionRequest,
44 type OCPP16StopTransactionResponse,
45 type OCPP16TriggerMessageResponse,
46 type OCPP16UpdateFirmwareResponse,
47 OCPPVersion,
48 RegistrationStatusEnumType,
49 ReservationTerminationReason,
50 type ResponseHandler,
51 type SetChargingProfileResponse,
52 type UnlockConnectorResponse
53 } from '../../../types/index.js'
54 import { Constants, convertToInt, isAsyncFunction, logger } from '../../../utils/index.js'
55 import { OCPPResponseService } from '../OCPPResponseService.js'
56 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
57
58 const moduleName = 'OCPP16ResponseService'
59
60 export class OCPP16ResponseService extends OCPPResponseService {
61 public incomingRequestResponsePayloadValidateFunctions: Map<
62 OCPP16IncomingRequestCommand,
63 ValidateFunction<JsonType>
64 >
65
66 protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
67 private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
68
69 public constructor () {
70 // if (new.target.name === moduleName) {
71 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
72 // }
73 super(OCPPVersion.VERSION_16)
74 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
75 [
76 OCPP16RequestCommand.BOOT_NOTIFICATION,
77 this.handleResponseBootNotification.bind(this) as ResponseHandler
78 ],
79 [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler],
80 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
81 [
82 OCPP16RequestCommand.START_TRANSACTION,
83 this.handleResponseStartTransaction.bind(this) as ResponseHandler
84 ],
85 [
86 OCPP16RequestCommand.STOP_TRANSACTION,
87 this.handleResponseStopTransaction.bind(this) as ResponseHandler
88 ],
89 [
90 OCPP16RequestCommand.STATUS_NOTIFICATION,
91 this.emptyResponseHandler.bind(this) as ResponseHandler
92 ],
93 [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler],
94 [
95 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
96 this.emptyResponseHandler.bind(this) as ResponseHandler
97 ],
98 [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler],
99 [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler]
100 ])
101 this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
102 [
103 OCPP16RequestCommand.BOOT_NOTIFICATION,
104 this.ajv
105 .compile(
106 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
107 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
108 moduleName,
109 'constructor'
110 )
111 )
112 .bind(this)
113 ],
114 [
115 OCPP16RequestCommand.HEARTBEAT,
116 this.ajv
117 .compile(
118 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
119 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
120 moduleName,
121 'constructor'
122 )
123 )
124 .bind(this)
125 ],
126 [
127 OCPP16RequestCommand.AUTHORIZE,
128 this.ajv
129 .compile(
130 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
131 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
132 moduleName,
133 'constructor'
134 )
135 )
136 .bind(this)
137 ],
138 [
139 OCPP16RequestCommand.START_TRANSACTION,
140 this.ajv
141 .compile(
142 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
143 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
144 moduleName,
145 'constructor'
146 )
147 )
148 .bind(this)
149 ],
150 [
151 OCPP16RequestCommand.STOP_TRANSACTION,
152 this.ajv
153 .compile(
154 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
155 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
156 moduleName,
157 'constructor'
158 )
159 )
160 .bind(this)
161 ],
162 [
163 OCPP16RequestCommand.STATUS_NOTIFICATION,
164 this.ajv
165 .compile(
166 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
167 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
168 moduleName,
169 'constructor'
170 )
171 )
172 .bind(this)
173 ],
174 [
175 OCPP16RequestCommand.METER_VALUES,
176 this.ajv
177 .compile(
178 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
179 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
180 moduleName,
181 'constructor'
182 )
183 )
184 .bind(this)
185 ],
186 [
187 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
188 this.ajv
189 .compile(
190 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
191 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
192 moduleName,
193 'constructor'
194 )
195 )
196 .bind(this)
197 ],
198 [
199 OCPP16RequestCommand.DATA_TRANSFER,
200 this.ajv
201 .compile(
202 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
203 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
204 moduleName,
205 'constructor'
206 )
207 )
208 .bind(this)
209 ],
210 [
211 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
212 this.ajv
213 .compile(
214 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
215 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
216 moduleName,
217 'constructor'
218 )
219 )
220 .bind(this)
221 ]
222 ])
223 this.incomingRequestResponsePayloadValidateFunctions = new Map<
224 OCPP16IncomingRequestCommand,
225 ValidateFunction<JsonType>
226 >([
227 [
228 OCPP16IncomingRequestCommand.RESET,
229 this.ajvIncomingRequest
230 .compile(
231 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
232 'assets/json-schemas/ocpp/1.6/ResetResponse.json',
233 moduleName,
234 'constructor'
235 )
236 )
237 .bind(this)
238 ],
239 [
240 OCPP16IncomingRequestCommand.CLEAR_CACHE,
241 this.ajvIncomingRequest
242 .compile(
243 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
244 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
245 moduleName,
246 'constructor'
247 )
248 )
249 .bind(this)
250 ],
251 [
252 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
253 this.ajvIncomingRequest
254 .compile(
255 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
256 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
257 moduleName,
258 'constructor'
259 )
260 )
261 .bind(this)
262 ],
263 [
264 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
265 this.ajvIncomingRequest
266 .compile(
267 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
268 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
269 moduleName,
270 'constructor'
271 )
272 )
273 .bind(this)
274 ],
275 [
276 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
277 this.ajvIncomingRequest
278 .compile(
279 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
280 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
281 moduleName,
282 'constructor'
283 )
284 )
285 .bind(this)
286 ],
287 [
288 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
289 this.ajvIncomingRequest
290 .compile(
291 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
292 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
293 moduleName,
294 'constructor'
295 )
296 )
297 .bind(this)
298 ],
299 [
300 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
301 this.ajvIncomingRequest
302 .compile(
303 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
304 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
305 moduleName,
306 'constructor'
307 )
308 )
309 .bind(this)
310 ],
311 [
312 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
313 this.ajvIncomingRequest
314 .compile(
315 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
316 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
317 moduleName,
318 'constructor'
319 )
320 )
321 .bind(this)
322 ],
323 [
324 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
325 this.ajvIncomingRequest
326 .compile(
327 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
328 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
329 moduleName,
330 'constructor'
331 )
332 )
333 .bind(this)
334 ],
335 [
336 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
337 this.ajvIncomingRequest
338 .compile(
339 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
340 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
341 moduleName,
342 'constructor'
343 )
344 )
345 .bind(this)
346 ],
347 [
348 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
349 this.ajvIncomingRequest
350 .compile(
351 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
352 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
353 moduleName,
354 'constructor'
355 )
356 )
357 .bind(this)
358 ],
359 [
360 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
361 this.ajvIncomingRequest
362 .compile(
363 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
364 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
365 moduleName,
366 'constructor'
367 )
368 )
369 .bind(this)
370 ],
371 [
372 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
373 this.ajvIncomingRequest
374 .compile(
375 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
376 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
377 moduleName,
378 'constructor'
379 )
380 )
381 .bind(this)
382 ],
383 [
384 OCPP16IncomingRequestCommand.DATA_TRANSFER,
385 this.ajvIncomingRequest
386 .compile(
387 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
388 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
389 moduleName,
390 'constructor'
391 )
392 )
393 .bind(this)
394 ],
395 [
396 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
397 this.ajvIncomingRequest
398 .compile(
399 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
400 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
401 moduleName,
402 'constructor'
403 )
404 )
405 .bind(this)
406 ],
407 [
408 OCPP16IncomingRequestCommand.RESERVE_NOW,
409 this.ajvIncomingRequest
410 .compile(
411 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
412 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
413 moduleName,
414 'constructor'
415 )
416 )
417 .bind(this)
418 ],
419 [
420 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
421 this.ajvIncomingRequest
422 .compile(
423 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
424 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
425 moduleName,
426 'constructor'
427 )
428 )
429 .bind(this)
430 ]
431 ])
432 this.validatePayload = this.validatePayload.bind(this)
433 }
434
435 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
436 chargingStation: ChargingStation,
437 commandName: OCPP16RequestCommand,
438 payload: ResType,
439 requestPayload: ReqType
440 ): Promise<void> {
441 if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
442 if (
443 this.responseHandlers.has(commandName) &&
444 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
445 ) {
446 try {
447 this.validatePayload(chargingStation, commandName, payload)
448 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
449 const responseHandler = this.responseHandlers.get(commandName)!
450 if (isAsyncFunction(responseHandler)) {
451 await responseHandler(chargingStation, payload, requestPayload)
452 } else {
453 (
454 responseHandler as (
455 chargingStation: ChargingStation,
456 payload: JsonType,
457 requestPayload?: JsonType
458 ) => void
459 )(chargingStation, payload, requestPayload)
460 }
461 } catch (error) {
462 logger.error(
463 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
464 error
465 )
466 throw error
467 }
468 } else {
469 // Throw exception
470 throw new OCPPError(
471 ErrorType.NOT_IMPLEMENTED,
472 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
473 payload,
474 undefined,
475 2
476 )}`,
477 commandName,
478 payload
479 )
480 }
481 } else {
482 throw new OCPPError(
483 ErrorType.SECURITY_ERROR,
484 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
485 payload,
486 undefined,
487 2
488 )} while the charging station is not registered on the central server`,
489 commandName,
490 payload
491 )
492 }
493 }
494
495 private validatePayload (
496 chargingStation: ChargingStation,
497 commandName: OCPP16RequestCommand,
498 payload: JsonType
499 ): boolean {
500 if (this.payloadValidateFunctions.has(commandName)) {
501 return this.validateResponsePayload(chargingStation, commandName, payload)
502 }
503 logger.warn(
504 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
505 )
506 return false
507 }
508
509 private handleResponseBootNotification (
510 chargingStation: ChargingStation,
511 payload: OCPP16BootNotificationResponse
512 ): void {
513 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
514 chargingStation.bootNotificationResponse = payload
515 if (chargingStation.isRegistered()) {
516 chargingStation.emit(ChargingStationEvents.registered)
517 if (chargingStation.inAcceptedState()) {
518 addConfigurationKey(
519 chargingStation,
520 OCPP16StandardParametersKey.HeartbeatInterval,
521 payload.interval.toString(),
522 {},
523 { overwrite: true, save: true }
524 )
525 addConfigurationKey(
526 chargingStation,
527 OCPP16StandardParametersKey.HeartBeatInterval,
528 payload.interval.toString(),
529 { visible: false },
530 { overwrite: true, save: true }
531 )
532 chargingStation.emit(ChargingStationEvents.accepted)
533 }
534 } else if (chargingStation.inRejectedState()) {
535 chargingStation.emit(ChargingStationEvents.rejected)
536 }
537 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
538 payload.status
539 }' state on the central server`
540 payload.status === RegistrationStatusEnumType.REJECTED
541 ? logger.warn(logMsg)
542 : logger.info(logMsg)
543 } else {
544 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 != null &&
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 != null &&
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 }