refactor: switch eslint configuration to strict type checking
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
1 // Partial Copyright Jerome Benoit. 2021-2023. 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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
464 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId!)
465 const authorizeConnectorIdDefined = authorizeConnectorId != null
466 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
467 if (authorizeConnectorIdDefined) {
468 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
469 authorizeConnectorStatus!.idTagAuthorized = true
470 }
471 logger.debug(
472 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' accepted${
473 authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''
474 }`
475 )
476 } else {
477 if (authorizeConnectorIdDefined) {
478 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
479 authorizeConnectorStatus!.idTagAuthorized = false
480 delete authorizeConnectorStatus?.authorizeIdTag
481 }
482 logger.debug(
483 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
484 payload.idTagInfo.status
485 }'${authorizeConnectorIdDefined ? ` on connector id ${authorizeConnectorId}` : ''}`
486 )
487 }
488 }
489
490 private async handleResponseStartTransaction (
491 chargingStation: ChargingStation,
492 payload: OCPP16StartTransactionResponse,
493 requestPayload: OCPP16StartTransactionRequest
494 ): Promise<void> {
495 const { connectorId } = requestPayload
496 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
497 logger.error(
498 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
499 )
500 return
501 }
502 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
503 if (
504 connectorStatus?.transactionRemoteStarted === true &&
505 chargingStation.getAuthorizeRemoteTxRequests() &&
506 chargingStation.getLocalAuthListEnabled() &&
507 chargingStation.hasIdTags() &&
508 connectorStatus.idTagLocalAuthorized === false
509 ) {
510 logger.error(
511 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
512 connectorStatus.localAuthorizeIdTag
513 } on connector id ${connectorId}`
514 )
515 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
516 return
517 }
518 if (
519 connectorStatus?.transactionRemoteStarted === true &&
520 chargingStation.getAuthorizeRemoteTxRequests() &&
521 chargingStation.stationInfo?.remoteAuthorization === true &&
522 connectorStatus.idTagLocalAuthorized === false &&
523 connectorStatus.idTagAuthorized === false
524 ) {
525 logger.error(
526 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
527 connectorStatus.authorizeIdTag
528 } on connector id ${connectorId}`
529 )
530 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
531 return
532 }
533 if (
534 connectorStatus?.idTagAuthorized === true &&
535 connectorStatus.authorizeIdTag !== requestPayload.idTag
536 ) {
537 logger.error(
538 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
539 requestPayload.idTag
540 } different from the authorize request one ${
541 connectorStatus.authorizeIdTag
542 } on connector id ${connectorId}`
543 )
544 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
545 return
546 }
547 if (
548 connectorStatus?.idTagLocalAuthorized === true &&
549 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
550 ) {
551 logger.error(
552 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
553 requestPayload.idTag
554 } different from the local authorized one ${
555 connectorStatus.localAuthorizeIdTag
556 } on connector id ${connectorId}`
557 )
558 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
559 return
560 }
561 if (connectorStatus?.transactionStarted === true) {
562 logger.error(
563 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
564 connectorStatus.transactionIdTag
565 }`
566 )
567 return
568 }
569 if (chargingStation.hasEvses) {
570 for (const [evseId, evseStatus] of chargingStation.evses) {
571 if (evseStatus.connectors.size > 1) {
572 for (const [id, status] of evseStatus.connectors) {
573 if (id !== connectorId && status.transactionStarted === true) {
574 logger.error(
575 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
576 status.transactionIdTag
577 }`
578 )
579 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
580 return
581 }
582 }
583 }
584 }
585 }
586 if (
587 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
588 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
589 ) {
590 logger.error(
591 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
592 )
593 return
594 }
595 if (!Number.isSafeInteger(payload.transactionId)) {
596 logger.warn(
597 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
598 payload.transactionId
599 }, converting to integer`
600 )
601 payload.transactionId = convertToInt(payload.transactionId)
602 }
603
604 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
605 connectorStatus.transactionStarted = true
606 connectorStatus.transactionStart = requestPayload.timestamp
607 connectorStatus.transactionId = payload.transactionId
608 connectorStatus.transactionIdTag = requestPayload.idTag
609 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
610 connectorStatus.transactionBeginMeterValue =
611 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
612 chargingStation,
613 connectorId,
614 requestPayload.meterStart
615 )
616 if (requestPayload.reservationId != null) {
617 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
618 const reservation = chargingStation.getReservationBy(
619 'reservationId',
620 requestPayload.reservationId
621 )!
622 if (reservation.idTag !== requestPayload.idTag) {
623 logger.warn(
624 `${chargingStation.logPrefix()} Reserved transaction ${
625 payload.transactionId
626 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
627 reservation.idTag
628 }`
629 )
630 }
631 if (hasReservationExpired(reservation)) {
632 logger.warn(
633 `${chargingStation.logPrefix()} Reserved transaction ${
634 payload.transactionId
635 } started with expired reservation ${
636 requestPayload.reservationId
637 } (expiry date: ${reservation.expiryDate.toISOString()}))`
638 )
639 }
640 await chargingStation.removeReservation(
641 reservation,
642 ReservationTerminationReason.TRANSACTION_STARTED
643 )
644 }
645 chargingStation.stationInfo?.beginEndMeterValues === true &&
646 (await chargingStation.ocppRequestService.requestHandler<
647 OCPP16MeterValuesRequest,
648 OCPP16MeterValuesResponse
649 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
650 connectorId,
651 transactionId: payload.transactionId,
652 meterValue: [connectorStatus.transactionBeginMeterValue]
653 } satisfies OCPP16MeterValuesRequest))
654 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
655 chargingStation,
656 connectorId,
657 OCPP16ChargePointStatus.Charging
658 )
659 logger.info(
660 `${chargingStation.logPrefix()} Transaction with id ${
661 payload.transactionId
662 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
663 requestPayload.idTag
664 }'`
665 )
666 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
667 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
668 ++chargingStation.powerDivider!
669 }
670 const configuredMeterValueSampleInterval = getConfigurationKey(
671 chargingStation,
672 OCPP16StandardParametersKey.MeterValueSampleInterval
673 )
674 chargingStation.startMeterValues(
675 connectorId,
676 configuredMeterValueSampleInterval != null
677 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
678 : Constants.DEFAULT_METER_VALUES_INTERVAL
679 )
680 } else {
681 logger.warn(
682 `${chargingStation.logPrefix()} Starting transaction with id ${
683 payload.transactionId
684 } REJECTED on ${chargingStation.stationInfo
685 ?.chargingStationId}#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
686 requestPayload.idTag
687 }'${
688 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
689 ? `, reservationId '${requestPayload.reservationId}'`
690 : ''
691 }`
692 )
693 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
694 }
695 }
696
697 private async resetConnectorOnStartTransactionError (
698 chargingStation: ChargingStation,
699 connectorId: number
700 ): Promise<void> {
701 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
702 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
703 resetConnectorStatus(connectorStatus!)
704 chargingStation.stopMeterValues(connectorId)
705 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
706 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
707 chargingStation,
708 connectorId,
709 OCPP16ChargePointStatus.Available
710 )
711 }
712 }
713
714 private async handleResponseStopTransaction (
715 chargingStation: ChargingStation,
716 payload: OCPP16StopTransactionResponse,
717 requestPayload: OCPP16StopTransactionRequest
718 ): Promise<void> {
719 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
720 requestPayload.transactionId
721 )
722 if (transactionConnectorId == null) {
723 logger.error(
724 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
725 requestPayload.transactionId
726 }`
727 )
728 return
729 }
730 chargingStation.stationInfo?.beginEndMeterValues === true &&
731 chargingStation.stationInfo.ocppStrictCompliance === false &&
732 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
733 (await chargingStation.ocppRequestService.requestHandler<
734 OCPP16MeterValuesRequest,
735 OCPP16MeterValuesResponse
736 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
737 connectorId: transactionConnectorId,
738 transactionId: requestPayload.transactionId,
739 meterValue: [
740 OCPP16ServiceUtils.buildTransactionEndMeterValue(
741 chargingStation,
742 transactionConnectorId,
743 requestPayload.meterStop
744 )
745 ]
746 }))
747 if (
748 !chargingStation.isChargingStationAvailable() ||
749 !chargingStation.isConnectorAvailable(transactionConnectorId)
750 ) {
751 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
752 chargingStation,
753 transactionConnectorId,
754 OCPP16ChargePointStatus.Unavailable
755 )
756 } else {
757 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
758 chargingStation,
759 transactionConnectorId,
760 OCPP16ChargePointStatus.Available
761 )
762 }
763 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
764 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
765 chargingStation.powerDivider!--
766 }
767 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
768 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId)!)
769 chargingStation.stopMeterValues(transactionConnectorId)
770 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
771 requestPayload.transactionId
772 } STOPPED on ${chargingStation.stationInfo
773 ?.chargingStationId}#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
774 if (
775 payload.idTagInfo == null ||
776 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
777 ) {
778 logger.info(logMsg)
779 } else {
780 logger.warn(logMsg)
781 }
782 }
783 }