fix: warn about reserved transaction started not found
[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 const reservation = chargingStation.getReservationBy(
619 'reservationId',
620 requestPayload.reservationId
621 )
622 if (reservation != null) {
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 } else {
646 logger.warn(
647 `${chargingStation.logPrefix()} Reserved transaction ${
648 payload.transactionId
649 } started with unknown reservation ${requestPayload.reservationId}`
650 )
651 }
652 }
653 chargingStation.stationInfo?.beginEndMeterValues === true &&
654 (await chargingStation.ocppRequestService.requestHandler<
655 OCPP16MeterValuesRequest,
656 OCPP16MeterValuesResponse
657 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
658 connectorId,
659 transactionId: payload.transactionId,
660 meterValue: [connectorStatus.transactionBeginMeterValue]
661 } satisfies OCPP16MeterValuesRequest))
662 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
663 chargingStation,
664 connectorId,
665 OCPP16ChargePointStatus.Charging
666 )
667 logger.info(
668 `${chargingStation.logPrefix()} Transaction with id ${
669 payload.transactionId
670 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
671 requestPayload.idTag
672 }'`
673 )
674 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
675 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
676 ++chargingStation.powerDivider!
677 }
678 const configuredMeterValueSampleInterval = getConfigurationKey(
679 chargingStation,
680 OCPP16StandardParametersKey.MeterValueSampleInterval
681 )
682 chargingStation.startMeterValues(
683 connectorId,
684 configuredMeterValueSampleInterval != null
685 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
686 : Constants.DEFAULT_METER_VALUES_INTERVAL
687 )
688 } else {
689 logger.warn(
690 `${chargingStation.logPrefix()} Starting transaction with id ${
691 payload.transactionId
692 } REJECTED on ${
693 chargingStation.stationInfo?.chargingStationId
694 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
695 requestPayload.idTag
696 }'${
697 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
698 ? `, reservationId '${requestPayload.reservationId}'`
699 : ''
700 }`
701 )
702 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
703 }
704 }
705
706 private async resetConnectorOnStartTransactionError (
707 chargingStation: ChargingStation,
708 connectorId: number
709 ): Promise<void> {
710 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
711 resetConnectorStatus(connectorStatus)
712 chargingStation.stopMeterValues(connectorId)
713 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
714 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
715 chargingStation,
716 connectorId,
717 OCPP16ChargePointStatus.Available
718 )
719 }
720 }
721
722 private async handleResponseStopTransaction (
723 chargingStation: ChargingStation,
724 payload: OCPP16StopTransactionResponse,
725 requestPayload: OCPP16StopTransactionRequest
726 ): Promise<void> {
727 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
728 requestPayload.transactionId
729 )
730 if (transactionConnectorId == null) {
731 logger.error(
732 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
733 requestPayload.transactionId
734 }`
735 )
736 return
737 }
738 chargingStation.stationInfo?.beginEndMeterValues === true &&
739 chargingStation.stationInfo.ocppStrictCompliance === false &&
740 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
741 (await chargingStation.ocppRequestService.requestHandler<
742 OCPP16MeterValuesRequest,
743 OCPP16MeterValuesResponse
744 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
745 connectorId: transactionConnectorId,
746 transactionId: requestPayload.transactionId,
747 meterValue: [
748 OCPP16ServiceUtils.buildTransactionEndMeterValue(
749 chargingStation,
750 transactionConnectorId,
751 requestPayload.meterStop
752 )
753 ]
754 }))
755 if (
756 !chargingStation.isChargingStationAvailable() ||
757 !chargingStation.isConnectorAvailable(transactionConnectorId)
758 ) {
759 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
760 chargingStation,
761 transactionConnectorId,
762 OCPP16ChargePointStatus.Unavailable
763 )
764 } else {
765 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
766 chargingStation,
767 transactionConnectorId,
768 OCPP16ChargePointStatus.Available
769 )
770 }
771 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
772 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
773 chargingStation.powerDivider!--
774 }
775 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
776 chargingStation.stopMeterValues(transactionConnectorId)
777 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
778 requestPayload.transactionId
779 } STOPPED on ${
780 chargingStation.stationInfo?.chargingStationId
781 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
782 if (
783 payload.idTagInfo == null ||
784 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
785 ) {
786 logger.info(logMsg)
787 } else {
788 logger.warn(logMsg)
789 }
790 }
791 }