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