refactor: more coding style fixes
[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, isNullOrUndefined, 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 = !isNullOrUndefined(authorizeConnectorId)
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 ${connectorStatus?.localAuthorizeIdTag} 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 ${connectorStatus?.authorizeIdTag} on connector id ${connectorId}`
525 )
526 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
527 return
528 }
529 if (
530 connectorStatus?.idTagAuthorized === true &&
531 connectorStatus?.authorizeIdTag !== requestPayload.idTag
532 ) {
533 logger.error(
534 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
535 requestPayload.idTag
536 } different from the authorize request one ${connectorStatus?.authorizeIdTag} 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 ${connectorStatus?.localAuthorizeIdTag} on connector id ${connectorId}`
549 )
550 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
551 return
552 }
553 if (connectorStatus?.transactionStarted === true) {
554 logger.error(
555 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${connectorStatus?.transactionIdTag}`
556 )
557 return
558 }
559 if (chargingStation.hasEvses) {
560 for (const [evseId, evseStatus] of chargingStation.evses) {
561 if (evseStatus.connectors.size > 1) {
562 for (const [id, status] of evseStatus.connectors) {
563 if (id !== connectorId && status?.transactionStarted === true) {
564 logger.error(
565 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${status?.transactionIdTag}`
566 )
567 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
568 return
569 }
570 }
571 }
572 }
573 }
574 if (
575 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
576 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
577 ) {
578 logger.error(
579 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
580 )
581 return
582 }
583 if (!Number.isSafeInteger(payload.transactionId)) {
584 logger.warn(
585 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
586 payload.transactionId
587 }, converting to integer`
588 )
589 payload.transactionId = convertToInt(payload.transactionId)
590 }
591
592 if (payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
593 connectorStatus.transactionStarted = true
594 connectorStatus.transactionStart = requestPayload.timestamp
595 connectorStatus.transactionId = payload.transactionId
596 connectorStatus.transactionIdTag = requestPayload.idTag
597 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
598 connectorStatus.transactionBeginMeterValue =
599 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
600 chargingStation,
601 connectorId,
602 requestPayload.meterStart
603 )
604 if (requestPayload.reservationId != null) {
605 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
606 const reservation = chargingStation.getReservationBy(
607 'reservationId',
608 requestPayload.reservationId
609 )!
610 if (reservation.idTag !== requestPayload.idTag) {
611 logger.warn(
612 `${chargingStation.logPrefix()} Reserved transaction ${
613 payload.transactionId
614 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
615 reservation.idTag
616 }`
617 )
618 }
619 if (hasReservationExpired(reservation)) {
620 logger.warn(
621 `${chargingStation.logPrefix()} Reserved transaction ${
622 payload.transactionId
623 } started with expired reservation ${
624 requestPayload.reservationId
625 } (expiry date: ${reservation.expiryDate.toISOString()}))`
626 )
627 }
628 await chargingStation.removeReservation(
629 reservation,
630 ReservationTerminationReason.TRANSACTION_STARTED
631 )
632 }
633 chargingStation.stationInfo?.beginEndMeterValues === true &&
634 (await chargingStation.ocppRequestService.requestHandler<
635 OCPP16MeterValuesRequest,
636 OCPP16MeterValuesResponse
637 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
638 connectorId,
639 transactionId: payload.transactionId,
640 meterValue: [connectorStatus.transactionBeginMeterValue]
641 } satisfies OCPP16MeterValuesRequest))
642 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
643 chargingStation,
644 connectorId,
645 OCPP16ChargePointStatus.Charging
646 )
647 logger.info(
648 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
649 chargingStation.stationInfo.chargingStationId
650 }#${connectorId} for idTag '${requestPayload.idTag}'`
651 )
652 if (chargingStation.stationInfo.powerSharedByConnectors === true) {
653 ++chargingStation.powerDivider
654 }
655 const configuredMeterValueSampleInterval = getConfigurationKey(
656 chargingStation,
657 OCPP16StandardParametersKey.MeterValueSampleInterval
658 )
659 chargingStation.startMeterValues(
660 connectorId,
661 configuredMeterValueSampleInterval != null
662 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
663 : Constants.DEFAULT_METER_VALUES_INTERVAL
664 )
665 } else {
666 logger.warn(
667 `${chargingStation.logPrefix()} Starting transaction with id ${
668 payload.transactionId
669 } REJECTED on ${
670 chargingStation.stationInfo.chargingStationId
671 }#${connectorId} with status '${payload.idTagInfo?.status}', idTag '${
672 requestPayload.idTag
673 }'${
674 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
675 ? `, reservationId '${requestPayload.reservationId}'`
676 : ''
677 }`
678 )
679 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
680 }
681 }
682
683 private async resetConnectorOnStartTransactionError (
684 chargingStation: ChargingStation,
685 connectorId: number
686 ): Promise<void> {
687 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
688 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
689 resetConnectorStatus(connectorStatus!)
690 chargingStation.stopMeterValues(connectorId)
691 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
692 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
693 chargingStation,
694 connectorId,
695 OCPP16ChargePointStatus.Available
696 )
697 }
698 }
699
700 private async handleResponseStopTransaction (
701 chargingStation: ChargingStation,
702 payload: OCPP16StopTransactionResponse,
703 requestPayload: OCPP16StopTransactionRequest
704 ): Promise<void> {
705 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
706 requestPayload.transactionId
707 )
708 if (isNullOrUndefined(transactionConnectorId)) {
709 logger.error(
710 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
711 requestPayload.transactionId
712 }`
713 )
714 return
715 }
716 chargingStation.stationInfo?.beginEndMeterValues === true &&
717 chargingStation.stationInfo?.ocppStrictCompliance === false &&
718 chargingStation.stationInfo?.outOfOrderEndMeterValues === true &&
719 (await chargingStation.ocppRequestService.requestHandler<
720 OCPP16MeterValuesRequest,
721 OCPP16MeterValuesResponse
722 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
723 connectorId: transactionConnectorId,
724 transactionId: requestPayload.transactionId,
725 meterValue: [
726 OCPP16ServiceUtils.buildTransactionEndMeterValue(
727 chargingStation,
728 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
729 transactionConnectorId!,
730 requestPayload.meterStop
731 )
732 ]
733 }))
734 if (
735 !chargingStation.isChargingStationAvailable() ||
736 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
737 !chargingStation.isConnectorAvailable(transactionConnectorId!)
738 ) {
739 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
740 chargingStation,
741 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
742 transactionConnectorId!,
743 OCPP16ChargePointStatus.Unavailable
744 )
745 } else {
746 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
747 chargingStation,
748 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
749 transactionConnectorId!,
750 OCPP16ChargePointStatus.Available
751 )
752 }
753 if (chargingStation.stationInfo.powerSharedByConnectors === true) {
754 chargingStation.powerDivider--
755 }
756 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
757 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId!)!)
758 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
759 chargingStation.stopMeterValues(transactionConnectorId!)
760 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
761 requestPayload.transactionId
762 } STOPPED on ${
763 chargingStation.stationInfo.chargingStationId
764 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
765 if (
766 isNullOrUndefined(payload.idTagInfo) ||
767 payload.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED
768 ) {
769 logger.info(logMsg)
770 } else {
771 logger.warn(logMsg)
772 }
773 }
774 }