refactor: cleanup unneeded type casting
[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)
321 }
322
323 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
324 chargingStation: ChargingStation,
325 commandName: OCPP16RequestCommand,
326 payload: ResType,
327 requestPayload: ReqType
328 ): Promise<void> {
329 if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
330 if (
331 this.responseHandlers.has(commandName) &&
332 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
333 ) {
334 try {
335 this.validatePayload(chargingStation, commandName, payload)
336 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
337 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
338 } catch (error) {
339 logger.error(
340 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
341 error
342 )
343 throw error
344 }
345 } else {
346 // Throw exception
347 throw new OCPPError(
348 ErrorType.NOT_IMPLEMENTED,
349 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
350 payload,
351 undefined,
352 2
353 )}`,
354 commandName,
355 payload
356 )
357 }
358 } else {
359 throw new OCPPError(
360 ErrorType.SECURITY_ERROR,
361 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
362 payload,
363 undefined,
364 2
365 )} while the charging station is not registered on the central server.`,
366 commandName,
367 payload
368 )
369 }
370 }
371
372 private validatePayload (
373 chargingStation: ChargingStation,
374 commandName: OCPP16RequestCommand,
375 payload: JsonType
376 ): boolean {
377 if (this.jsonSchemas.has(commandName)) {
378 return this.validateResponsePayload(
379 chargingStation,
380 commandName,
381 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
382 this.jsonSchemas.get(commandName)!,
383 payload
384 )
385 }
386 logger.warn(
387 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema found for command '${commandName}' PDU validation`
388 )
389 return false
390 }
391
392 private handleResponseBootNotification (
393 chargingStation: ChargingStation,
394 payload: OCPP16BootNotificationResponse
395 ): void {
396 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
397 addConfigurationKey(
398 chargingStation,
399 OCPP16StandardParametersKey.HeartbeatInterval,
400 payload.interval.toString(),
401 {},
402 { overwrite: true, save: true }
403 )
404 addConfigurationKey(
405 chargingStation,
406 OCPP16StandardParametersKey.HeartBeatInterval,
407 payload.interval.toString(),
408 { visible: false },
409 { overwrite: true, save: true }
410 )
411 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
412 }
413 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
414 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
415 payload.status
416 }' state on the central server`
417 payload.status === RegistrationStatusEnumType.REJECTED
418 ? logger.warn(logMsg)
419 : logger.info(logMsg)
420 } else {
421 logger.error(
422 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
423 payload
424 )
425 }
426 }
427
428 private handleResponseAuthorize (
429 chargingStation: ChargingStation,
430 payload: OCPP16AuthorizeResponse,
431 requestPayload: OCPP16AuthorizeRequest
432 ): void {
433 let authorizeConnectorId: number | undefined
434 if (chargingStation.hasEvses) {
435 for (const [evseId, evseStatus] of chargingStation.evses) {
436 if (evseId > 0) {
437 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
438 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
439 authorizeConnectorId = connectorId
440 break
441 }
442 }
443 }
444 }
445 } else {
446 for (const connectorId of chargingStation.connectors.keys()) {
447 if (
448 connectorId > 0 &&
449 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
450 ) {
451 authorizeConnectorId = connectorId
452 break
453 }
454 }
455 }
456 if (authorizeConnectorId != null) {
457 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
458 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
459 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
460 authorizeConnectorStatus.idTagAuthorized = true
461 logger.debug(
462 `${chargingStation.logPrefix()} idTag '${
463 requestPayload.idTag
464 }' accepted on connector id ${authorizeConnectorId}`
465 )
466 } else {
467 authorizeConnectorStatus.idTagAuthorized = false
468 delete authorizeConnectorStatus.authorizeIdTag
469 logger.debug(
470 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
471 payload.idTagInfo.status
472 }`
473 )
474 }
475 } else {
476 logger.error(
477 `${chargingStation.logPrefix()} idTag '${
478 requestPayload.idTag
479 }' has no authorize request pending`
480 )
481 }
482 }
483
484 private async handleResponseStartTransaction (
485 chargingStation: ChargingStation,
486 payload: OCPP16StartTransactionResponse,
487 requestPayload: OCPP16StartTransactionRequest
488 ): Promise<void> {
489 const { connectorId } = requestPayload
490 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
491 logger.error(
492 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
493 )
494 return
495 }
496 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
497 if (
498 connectorStatus?.transactionRemoteStarted === true &&
499 chargingStation.getAuthorizeRemoteTxRequests() &&
500 chargingStation.getLocalAuthListEnabled() &&
501 chargingStation.hasIdTags() &&
502 connectorStatus.idTagLocalAuthorized === false
503 ) {
504 logger.error(
505 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
506 connectorStatus.localAuthorizeIdTag
507 } on connector id ${connectorId}`
508 )
509 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
510 return
511 }
512 if (
513 connectorStatus?.transactionRemoteStarted === true &&
514 chargingStation.getAuthorizeRemoteTxRequests() &&
515 chargingStation.stationInfo?.remoteAuthorization === true &&
516 connectorStatus.idTagLocalAuthorized === false &&
517 connectorStatus.idTagAuthorized === false
518 ) {
519 logger.error(
520 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
521 connectorStatus.authorizeIdTag
522 } on connector id ${connectorId}`
523 )
524 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
525 return
526 }
527 if (
528 connectorStatus?.idTagAuthorized === true &&
529 connectorStatus.authorizeIdTag !== requestPayload.idTag
530 ) {
531 logger.error(
532 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
533 requestPayload.idTag
534 } different from the authorize request one ${
535 connectorStatus.authorizeIdTag
536 } on connector id ${connectorId}`
537 )
538 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
539 return
540 }
541 if (
542 connectorStatus?.idTagLocalAuthorized === true &&
543 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
544 ) {
545 logger.error(
546 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
547 requestPayload.idTag
548 } different from the local authorized one ${
549 connectorStatus.localAuthorizeIdTag
550 } on connector id ${connectorId}`
551 )
552 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
553 return
554 }
555 if (connectorStatus?.transactionStarted === true) {
556 logger.error(
557 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
558 connectorStatus.transactionIdTag
559 }`
560 )
561 return
562 }
563 if (chargingStation.hasEvses) {
564 for (const [evseId, evseStatus] of chargingStation.evses) {
565 if (evseStatus.connectors.size > 1) {
566 for (const [id, status] of evseStatus.connectors) {
567 if (id !== connectorId && status.transactionStarted === true) {
568 logger.error(
569 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
570 status.transactionIdTag
571 }`
572 )
573 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
574 return
575 }
576 }
577 }
578 }
579 }
580 if (
581 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
582 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
583 ) {
584 logger.error(
585 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
586 )
587 return
588 }
589 if (!Number.isSafeInteger(payload.transactionId)) {
590 logger.warn(
591 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
592 payload.transactionId
593 }, converting to integer`
594 )
595 payload.transactionId = convertToInt(payload.transactionId)
596 }
597
598 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
599 connectorStatus.transactionStarted = true
600 connectorStatus.transactionStart = requestPayload.timestamp
601 connectorStatus.transactionId = payload.transactionId
602 connectorStatus.transactionIdTag = requestPayload.idTag
603 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
604 connectorStatus.transactionBeginMeterValue =
605 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
606 chargingStation,
607 connectorId,
608 requestPayload.meterStart
609 )
610 if (requestPayload.reservationId != null) {
611 const reservation = chargingStation.getReservationBy(
612 'reservationId',
613 requestPayload.reservationId
614 )
615 if (reservation != null) {
616 if (reservation.idTag !== requestPayload.idTag) {
617 logger.warn(
618 `${chargingStation.logPrefix()} Reserved transaction ${
619 payload.transactionId
620 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
621 reservation.idTag
622 }`
623 )
624 }
625 if (hasReservationExpired(reservation)) {
626 logger.warn(
627 `${chargingStation.logPrefix()} Reserved transaction ${
628 payload.transactionId
629 } started with expired reservation ${
630 requestPayload.reservationId
631 } (expiry date: ${reservation.expiryDate.toISOString()}))`
632 )
633 }
634 await chargingStation.removeReservation(
635 reservation,
636 ReservationTerminationReason.TRANSACTION_STARTED
637 )
638 } else {
639 logger.warn(
640 `${chargingStation.logPrefix()} Reserved transaction ${
641 payload.transactionId
642 } started with unknown reservation ${requestPayload.reservationId}`
643 )
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 }