chore: update copyright years
[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 { JSONSchemaType } from 'ajv'
4 import { secondsToMilliseconds } from 'date-fns'
5
6 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
7 import {
8 type ChargingStation,
9 addConfigurationKey,
10 getConfigurationKey,
11 hasReservationExpired,
12 resetConnectorStatus
13 } from '../../../charging-station/index.js'
14 import { OCPPError } from '../../../exception/index.js'
15 import {
16 type ChangeConfigurationResponse,
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, logger } from '../../../utils/index.js'
55 import { OCPPResponseService } from '../OCPPResponseService.js'
56
57 const moduleName = 'OCPP16ResponseService'
58
59 export class OCPP16ResponseService extends OCPPResponseService {
60 public jsonIncomingRequestResponseSchemas: Map<
61 OCPP16IncomingRequestCommand,
62 JSONSchemaType<JsonType>
63 >
64
65 private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
66 private readonly jsonSchemas: Map<OCPP16RequestCommand, JSONSchemaType<JsonType>>
67
68 public constructor () {
69 // if (new.target.name === moduleName) {
70 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
71 // }
72 super(OCPPVersion.VERSION_16)
73 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
74 [
75 OCPP16RequestCommand.BOOT_NOTIFICATION,
76 this.handleResponseBootNotification.bind(this) as ResponseHandler
77 ],
78 [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler.bind(this) as ResponseHandler],
79 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
80 [
81 OCPP16RequestCommand.START_TRANSACTION,
82 this.handleResponseStartTransaction.bind(this) as ResponseHandler
83 ],
84 [
85 OCPP16RequestCommand.STOP_TRANSACTION,
86 this.handleResponseStopTransaction.bind(this) as ResponseHandler
87 ],
88 [
89 OCPP16RequestCommand.STATUS_NOTIFICATION,
90 this.emptyResponseHandler.bind(this) as ResponseHandler
91 ],
92 [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler.bind(this) as ResponseHandler],
93 [
94 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
95 this.emptyResponseHandler.bind(this) as ResponseHandler
96 ],
97 [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler.bind(this) as ResponseHandler],
98 [
99 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
100 this.emptyResponseHandler.bind(this) as ResponseHandler
101 ]
102 ])
103 this.jsonSchemas = new Map<OCPP16RequestCommand, JSONSchemaType<JsonType>>([
104 [
105 OCPP16RequestCommand.BOOT_NOTIFICATION,
106 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
107 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
108 moduleName,
109 'constructor'
110 )
111 ],
112 [
113 OCPP16RequestCommand.HEARTBEAT,
114 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
115 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
116 moduleName,
117 'constructor'
118 )
119 ],
120 [
121 OCPP16RequestCommand.AUTHORIZE,
122 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
123 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
124 moduleName,
125 'constructor'
126 )
127 ],
128 [
129 OCPP16RequestCommand.START_TRANSACTION,
130 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
131 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
132 moduleName,
133 'constructor'
134 )
135 ],
136 [
137 OCPP16RequestCommand.STOP_TRANSACTION,
138 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
139 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
140 moduleName,
141 'constructor'
142 )
143 ],
144 [
145 OCPP16RequestCommand.STATUS_NOTIFICATION,
146 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
147 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
148 moduleName,
149 'constructor'
150 )
151 ],
152 [
153 OCPP16RequestCommand.METER_VALUES,
154 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
155 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
156 moduleName,
157 'constructor'
158 )
159 ],
160 [
161 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
162 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
163 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
164 moduleName,
165 'constructor'
166 )
167 ],
168 [
169 OCPP16RequestCommand.DATA_TRANSFER,
170 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
171 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
172 moduleName,
173 'constructor'
174 )
175 ],
176 [
177 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
178 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
179 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
180 moduleName,
181 'constructor'
182 )
183 ]
184 ])
185 this.jsonIncomingRequestResponseSchemas = new Map([
186 [
187 OCPP16IncomingRequestCommand.RESET,
188 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
189 'assets/json-schemas/ocpp/1.6/ResetResponse.json',
190 moduleName,
191 'constructor'
192 )
193 ],
194 [
195 OCPP16IncomingRequestCommand.CLEAR_CACHE,
196 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
197 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
198 moduleName,
199 'constructor'
200 )
201 ],
202 [
203 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
204 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
205 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
206 moduleName,
207 'constructor'
208 )
209 ],
210 [
211 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
212 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
213 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
214 moduleName,
215 'constructor'
216 )
217 ],
218 [
219 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
220 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
221 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
222 moduleName,
223 'constructor'
224 )
225 ],
226 [
227 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
228 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
229 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
230 moduleName,
231 'constructor'
232 )
233 ],
234 [
235 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
236 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
237 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
238 moduleName,
239 'constructor'
240 )
241 ],
242 [
243 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
244 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
245 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
246 moduleName,
247 'constructor'
248 )
249 ],
250 [
251 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
252 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
253 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
254 moduleName,
255 'constructor'
256 )
257 ],
258 [
259 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
260 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
261 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
262 moduleName,
263 'constructor'
264 )
265 ],
266 [
267 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
268 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
269 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
270 moduleName,
271 'constructor'
272 )
273 ],
274 [
275 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
276 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
277 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
278 moduleName,
279 'constructor'
280 )
281 ],
282 [
283 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
284 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
285 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
286 moduleName,
287 'constructor'
288 )
289 ],
290 [
291 OCPP16IncomingRequestCommand.DATA_TRANSFER,
292 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
293 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
294 moduleName,
295 'constructor'
296 )
297 ],
298 [
299 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
300 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
301 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
302 moduleName,
303 'constructor'
304 )
305 ],
306 [
307 OCPP16IncomingRequestCommand.RESERVE_NOW,
308 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
309 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
310 moduleName,
311 'constructor'
312 )
313 ],
314 [
315 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
316 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
317 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
318 moduleName,
319 'constructor'
320 )
321 ]
322 ])
323 this.validatePayload = this.validatePayload.bind(this) as (
324 chargingStation: ChargingStation,
325 commandName: OCPP16RequestCommand,
326 payload: JsonType
327 ) => boolean
328 }
329
330 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
331 chargingStation: ChargingStation,
332 commandName: OCPP16RequestCommand,
333 payload: ResType,
334 requestPayload: ReqType
335 ): Promise<void> {
336 if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
337 if (
338 this.responseHandlers.has(commandName) &&
339 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
340 ) {
341 try {
342 this.validatePayload(chargingStation, commandName, payload)
343 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
344 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
345 } catch (error) {
346 logger.error(
347 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
348 error
349 )
350 throw error
351 }
352 } else {
353 // Throw exception
354 throw new OCPPError(
355 ErrorType.NOT_IMPLEMENTED,
356 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
357 payload,
358 undefined,
359 2
360 )}`,
361 commandName,
362 payload
363 )
364 }
365 } else {
366 throw new OCPPError(
367 ErrorType.SECURITY_ERROR,
368 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
369 payload,
370 undefined,
371 2
372 )} while the charging station is not registered on the central server.`,
373 commandName,
374 payload
375 )
376 }
377 }
378
379 private validatePayload (
380 chargingStation: ChargingStation,
381 commandName: OCPP16RequestCommand,
382 payload: JsonType
383 ): boolean {
384 if (this.jsonSchemas.has(commandName)) {
385 return this.validateResponsePayload(
386 chargingStation,
387 commandName,
388 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
389 this.jsonSchemas.get(commandName)!,
390 payload
391 )
392 }
393 logger.warn(
394 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
395 )
396 return false
397 }
398
399 private handleResponseBootNotification (
400 chargingStation: ChargingStation,
401 payload: OCPP16BootNotificationResponse
402 ): void {
403 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
404 addConfigurationKey(
405 chargingStation,
406 OCPP16StandardParametersKey.HeartbeatInterval,
407 payload.interval.toString(),
408 {},
409 { overwrite: true, save: true }
410 )
411 addConfigurationKey(
412 chargingStation,
413 OCPP16StandardParametersKey.HeartBeatInterval,
414 payload.interval.toString(),
415 { visible: false },
416 { overwrite: true, save: true }
417 )
418 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
419 }
420 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
421 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
422 payload.status
423 }' state on the central server`
424 payload.status === RegistrationStatusEnumType.REJECTED
425 ? logger.warn(logMsg)
426 : logger.info(logMsg)
427 } else {
428 logger.error(
429 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
430 payload
431 )
432 }
433 }
434
435 private handleResponseAuthorize (
436 chargingStation: ChargingStation,
437 payload: OCPP16AuthorizeResponse,
438 requestPayload: OCPP16AuthorizeRequest
439 ): void {
440 let authorizeConnectorId: number | undefined
441 if (chargingStation.hasEvses) {
442 for (const [evseId, evseStatus] of chargingStation.evses) {
443 if (evseId > 0) {
444 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
445 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
446 authorizeConnectorId = connectorId
447 break
448 }
449 }
450 }
451 }
452 } else {
453 for (const connectorId of chargingStation.connectors.keys()) {
454 if (
455 connectorId > 0 &&
456 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
457 ) {
458 authorizeConnectorId = connectorId
459 break
460 }
461 }
462 }
463 if (authorizeConnectorId != null) {
464 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
465 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
466 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
467 authorizeConnectorStatus.idTagAuthorized = true
468 logger.debug(
469 `${chargingStation.logPrefix()} idTag '${
470 requestPayload.idTag
471 }' accepted on connector id ${authorizeConnectorId}`
472 )
473 } else {
474 authorizeConnectorStatus.idTagAuthorized = false
475 delete authorizeConnectorStatus.authorizeIdTag
476 logger.debug(
477 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
478 payload.idTagInfo.status
479 }`
480 )
481 }
482 } else {
483 logger.error(
484 `${chargingStation.logPrefix()} idTag '${
485 requestPayload.idTag
486 }' has no authorize request pending`
487 )
488 }
489 }
490
491 private async handleResponseStartTransaction (
492 chargingStation: ChargingStation,
493 payload: OCPP16StartTransactionResponse,
494 requestPayload: OCPP16StartTransactionRequest
495 ): Promise<void> {
496 const { connectorId } = requestPayload
497 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
498 logger.error(
499 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
500 )
501 return
502 }
503 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
504 if (
505 connectorStatus?.transactionRemoteStarted === true &&
506 chargingStation.getAuthorizeRemoteTxRequests() &&
507 chargingStation.getLocalAuthListEnabled() &&
508 chargingStation.hasIdTags() &&
509 connectorStatus.idTagLocalAuthorized === false
510 ) {
511 logger.error(
512 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
513 connectorStatus.localAuthorizeIdTag
514 } on connector id ${connectorId}`
515 )
516 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
517 return
518 }
519 if (
520 connectorStatus?.transactionRemoteStarted === true &&
521 chargingStation.getAuthorizeRemoteTxRequests() &&
522 chargingStation.stationInfo?.remoteAuthorization === true &&
523 connectorStatus.idTagLocalAuthorized === false &&
524 connectorStatus.idTagAuthorized === false
525 ) {
526 logger.error(
527 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
528 connectorStatus.authorizeIdTag
529 } on connector id ${connectorId}`
530 )
531 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
532 return
533 }
534 if (
535 connectorStatus?.idTagAuthorized === true &&
536 connectorStatus.authorizeIdTag !== requestPayload.idTag
537 ) {
538 logger.error(
539 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
540 requestPayload.idTag
541 } different from the authorize request one ${
542 connectorStatus.authorizeIdTag
543 } on connector id ${connectorId}`
544 )
545 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
546 return
547 }
548 if (
549 connectorStatus?.idTagLocalAuthorized === true &&
550 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
551 ) {
552 logger.error(
553 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
554 requestPayload.idTag
555 } different from the local authorized one ${
556 connectorStatus.localAuthorizeIdTag
557 } on connector id ${connectorId}`
558 )
559 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
560 return
561 }
562 if (connectorStatus?.transactionStarted === true) {
563 logger.error(
564 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
565 connectorStatus.transactionIdTag
566 }`
567 )
568 return
569 }
570 if (chargingStation.hasEvses) {
571 for (const [evseId, evseStatus] of chargingStation.evses) {
572 if (evseStatus.connectors.size > 1) {
573 for (const [id, status] of evseStatus.connectors) {
574 if (id !== connectorId && status.transactionStarted === true) {
575 logger.error(
576 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
577 status.transactionIdTag
578 }`
579 )
580 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
581 return
582 }
583 }
584 }
585 }
586 }
587 if (
588 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
589 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
590 ) {
591 logger.error(
592 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
593 )
594 return
595 }
596 if (!Number.isSafeInteger(payload.transactionId)) {
597 logger.warn(
598 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
599 payload.transactionId
600 }, converting to integer`
601 )
602 payload.transactionId = convertToInt(payload.transactionId)
603 }
604
605 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
606 connectorStatus.transactionStarted = true
607 connectorStatus.transactionStart = requestPayload.timestamp
608 connectorStatus.transactionId = payload.transactionId
609 connectorStatus.transactionIdTag = requestPayload.idTag
610 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
611 connectorStatus.transactionBeginMeterValue =
612 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
613 chargingStation,
614 connectorId,
615 requestPayload.meterStart
616 )
617 if (requestPayload.reservationId != null) {
618 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
619 const reservation = chargingStation.getReservationBy(
620 'reservationId',
621 requestPayload.reservationId
622 )!
623 if (reservation.idTag !== requestPayload.idTag) {
624 logger.warn(
625 `${chargingStation.logPrefix()} Reserved transaction ${
626 payload.transactionId
627 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
628 reservation.idTag
629 }`
630 )
631 }
632 if (hasReservationExpired(reservation)) {
633 logger.warn(
634 `${chargingStation.logPrefix()} Reserved transaction ${
635 payload.transactionId
636 } started with expired reservation ${
637 requestPayload.reservationId
638 } (expiry date: ${reservation.expiryDate.toISOString()}))`
639 )
640 }
641 await chargingStation.removeReservation(
642 reservation,
643 ReservationTerminationReason.TRANSACTION_STARTED
644 )
645 }
646 chargingStation.stationInfo?.beginEndMeterValues === true &&
647 (await chargingStation.ocppRequestService.requestHandler<
648 OCPP16MeterValuesRequest,
649 OCPP16MeterValuesResponse
650 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
651 connectorId,
652 transactionId: payload.transactionId,
653 meterValue: [connectorStatus.transactionBeginMeterValue]
654 } satisfies OCPP16MeterValuesRequest))
655 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
656 chargingStation,
657 connectorId,
658 OCPP16ChargePointStatus.Charging
659 )
660 logger.info(
661 `${chargingStation.logPrefix()} Transaction with id ${
662 payload.transactionId
663 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
664 requestPayload.idTag
665 }'`
666 )
667 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
668 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
669 ++chargingStation.powerDivider!
670 }
671 const configuredMeterValueSampleInterval = getConfigurationKey(
672 chargingStation,
673 OCPP16StandardParametersKey.MeterValueSampleInterval
674 )
675 chargingStation.startMeterValues(
676 connectorId,
677 configuredMeterValueSampleInterval != null
678 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
679 : Constants.DEFAULT_METER_VALUES_INTERVAL
680 )
681 } else {
682 logger.warn(
683 `${chargingStation.logPrefix()} Starting transaction with id ${
684 payload.transactionId
685 } REJECTED on ${
686 chargingStation.stationInfo?.chargingStationId
687 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
688 requestPayload.idTag
689 }'${
690 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
691 ? `, reservationId '${requestPayload.reservationId}'`
692 : ''
693 }`
694 )
695 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
696 }
697 }
698
699 private async resetConnectorOnStartTransactionError (
700 chargingStation: ChargingStation,
701 connectorId: number
702 ): Promise<void> {
703 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
704 resetConnectorStatus(connectorStatus)
705 chargingStation.stopMeterValues(connectorId)
706 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
707 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
708 chargingStation,
709 connectorId,
710 OCPP16ChargePointStatus.Available
711 )
712 }
713 }
714
715 private async handleResponseStopTransaction (
716 chargingStation: ChargingStation,
717 payload: OCPP16StopTransactionResponse,
718 requestPayload: OCPP16StopTransactionRequest
719 ): Promise<void> {
720 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
721 requestPayload.transactionId
722 )
723 if (transactionConnectorId == null) {
724 logger.error(
725 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
726 requestPayload.transactionId
727 }`
728 )
729 return
730 }
731 chargingStation.stationInfo?.beginEndMeterValues === true &&
732 chargingStation.stationInfo.ocppStrictCompliance === false &&
733 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
734 (await chargingStation.ocppRequestService.requestHandler<
735 OCPP16MeterValuesRequest,
736 OCPP16MeterValuesResponse
737 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
738 connectorId: transactionConnectorId,
739 transactionId: requestPayload.transactionId,
740 meterValue: [
741 OCPP16ServiceUtils.buildTransactionEndMeterValue(
742 chargingStation,
743 transactionConnectorId,
744 requestPayload.meterStop
745 )
746 ]
747 }))
748 if (
749 !chargingStation.isChargingStationAvailable() ||
750 !chargingStation.isConnectorAvailable(transactionConnectorId)
751 ) {
752 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
753 chargingStation,
754 transactionConnectorId,
755 OCPP16ChargePointStatus.Unavailable
756 )
757 } else {
758 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
759 chargingStation,
760 transactionConnectorId,
761 OCPP16ChargePointStatus.Available
762 )
763 }
764 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
765 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
766 chargingStation.powerDivider!--
767 }
768 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
769 chargingStation.stopMeterValues(transactionConnectorId)
770 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
771 requestPayload.transactionId
772 } STOPPED on ${
773 chargingStation.stationInfo?.chargingStationId
774 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
775 if (
776 payload.idTagInfo == null ||
777 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
778 ) {
779 logger.info(logMsg)
780 } else {
781 logger.warn(logMsg)
782 }
783 }
784 }