fix: ensure inflight requests id cannot be duplicated
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16ResponseService.ts
CommitLineData
a19b897d 1// Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
c8eeb62b 2
24d15716 3import type { ValidateFunction } from 'ajv'
66a7748d 4import { secondsToMilliseconds } from 'date-fns'
844e496b 5
04b1261c 6import {
f2d5e3d9 7 addConfigurationKey,
4c3f6c20 8 type ChargingStation,
f2d5e3d9 9 getConfigurationKey,
90aceaf6 10 hasReservationExpired,
66a7748d
JB
11 resetConnectorStatus
12} from '../../../charging-station/index.js'
13import { OCPPError } from '../../../exception/index.js'
ef6fa3fb 14import {
268a74bb 15 type ChangeConfigurationResponse,
50539084 16 ChargingStationEvents,
268a74bb
JB
17 ErrorType,
18 type GenericResponse,
19 type GetConfigurationResponse,
20 type GetDiagnosticsResponse,
268a74bb 21 type JsonType,
8114d10e 22 OCPP16AuthorizationStatus,
27782dbc
JB
23 type OCPP16AuthorizeRequest,
24 type OCPP16AuthorizeResponse,
268a74bb 25 type OCPP16BootNotificationResponse,
366f75f6 26 type OCPP16ChangeAvailabilityResponse,
268a74bb 27 OCPP16ChargePointStatus,
41f3983a 28 type OCPP16ClearChargingProfileResponse,
268a74bb
JB
29 type OCPP16DataTransferResponse,
30 type OCPP16DiagnosticsStatusNotificationResponse,
31 type OCPP16FirmwareStatusNotificationResponse,
41189456 32 type OCPP16GetCompositeScheduleResponse,
268a74bb
JB
33 type OCPP16HeartbeatResponse,
34 OCPP16IncomingRequestCommand,
35 type OCPP16MeterValuesRequest,
36 type OCPP16MeterValuesResponse,
37 OCPP16RequestCommand,
66dd3447 38 type OCPP16ReserveNowResponse,
268a74bb 39 OCPP16StandardParametersKey,
27782dbc
JB
40 type OCPP16StartTransactionRequest,
41 type OCPP16StartTransactionResponse,
268a74bb 42 type OCPP16StatusNotificationResponse,
27782dbc
JB
43 type OCPP16StopTransactionRequest,
44 type OCPP16StopTransactionResponse,
268a74bb
JB
45 type OCPP16TriggerMessageResponse,
46 type OCPP16UpdateFirmwareResponse,
47 OCPPVersion,
02887891 48 RegistrationStatusEnumType,
d984c13f 49 ReservationTerminationReason,
02887891 50 type ResponseHandler,
268a74bb 51 type SetChargingProfileResponse,
66a7748d
JB
52 type UnlockConnectorResponse
53} from '../../../types/index.js'
bcf95df1 54import { Constants, convertToInt, isAsyncFunction, logger } from '../../../utils/index.js'
66a7748d 55import { OCPPResponseService } from '../OCPPResponseService.js'
4c3f6c20 56import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
c0560973 57
66a7748d 58const moduleName = 'OCPP16ResponseService'
909dcf2d 59
268a74bb 60export class OCPP16ResponseService extends OCPPResponseService {
d5490a13 61 public incomingRequestResponsePayloadValidateFunctions: Map<
66a7748d 62 OCPP16IncomingRequestCommand,
24d15716 63 ValidateFunction<JsonType>
66a7748d 64 >
b3fc3ff5 65
d5490a13 66 protected payloadValidateFunctions: Map<OCPP16RequestCommand, ValidateFunction<JsonType>>
66a7748d 67 private readonly responseHandlers: Map<OCPP16RequestCommand, ResponseHandler>
58144adb 68
66a7748d 69 public constructor () {
5199f9fd
JB
70 // if (new.target.name === moduleName) {
71 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
b768993d 72 // }
66a7748d 73 super(OCPPVersion.VERSION_16)
58144adb 74 this.responseHandlers = new Map<OCPP16RequestCommand, ResponseHandler>([
a37fc6dc
JB
75 [
76 OCPP16RequestCommand.BOOT_NOTIFICATION,
66a7748d 77 this.handleResponseBootNotification.bind(this) as ResponseHandler
a37fc6dc 78 ],
65b5177e 79 [OCPP16RequestCommand.HEARTBEAT, this.emptyResponseHandler],
a37fc6dc
JB
80 [OCPP16RequestCommand.AUTHORIZE, this.handleResponseAuthorize.bind(this) as ResponseHandler],
81 [
82 OCPP16RequestCommand.START_TRANSACTION,
66a7748d 83 this.handleResponseStartTransaction.bind(this) as ResponseHandler
a37fc6dc
JB
84 ],
85 [
86 OCPP16RequestCommand.STOP_TRANSACTION,
66a7748d 87 this.handleResponseStopTransaction.bind(this) as ResponseHandler
a37fc6dc
JB
88 ],
89 [
90 OCPP16RequestCommand.STATUS_NOTIFICATION,
66a7748d 91 this.emptyResponseHandler.bind(this) as ResponseHandler
a37fc6dc 92 ],
65b5177e 93 [OCPP16RequestCommand.METER_VALUES, this.emptyResponseHandler],
a37fc6dc
JB
94 [
95 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
66a7748d 96 this.emptyResponseHandler.bind(this) as ResponseHandler
a37fc6dc 97 ],
65b5177e
JB
98 [OCPP16RequestCommand.DATA_TRANSFER, this.emptyResponseHandler],
99 [OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, this.emptyResponseHandler]
66a7748d 100 ])
d5490a13 101 this.payloadValidateFunctions = new Map<OCPP16RequestCommand, ValidateFunction<JsonType>>([
b52c969d
JB
102 [
103 OCPP16RequestCommand.BOOT_NOTIFICATION,
24d15716
JB
104 this.ajv
105 .compile(
106 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16BootNotificationResponse>(
107 'assets/json-schemas/ocpp/1.6/BootNotificationResponse.json',
108 moduleName,
109 'constructor'
110 )
111 )
112 .bind(this)
b52c969d
JB
113 ],
114 [
115 OCPP16RequestCommand.HEARTBEAT,
24d15716
JB
116 this.ajv
117 .compile(
118 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16HeartbeatResponse>(
119 'assets/json-schemas/ocpp/1.6/HeartbeatResponse.json',
120 moduleName,
121 'constructor'
122 )
123 )
124 .bind(this)
b52c969d
JB
125 ],
126 [
127 OCPP16RequestCommand.AUTHORIZE,
24d15716
JB
128 this.ajv
129 .compile(
130 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16AuthorizeResponse>(
131 'assets/json-schemas/ocpp/1.6/AuthorizeResponse.json',
132 moduleName,
133 'constructor'
134 )
135 )
136 .bind(this)
b52c969d
JB
137 ],
138 [
139 OCPP16RequestCommand.START_TRANSACTION,
24d15716
JB
140 this.ajv
141 .compile(
142 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StartTransactionResponse>(
143 'assets/json-schemas/ocpp/1.6/StartTransactionResponse.json',
144 moduleName,
145 'constructor'
146 )
147 )
148 .bind(this)
b52c969d
JB
149 ],
150 [
151 OCPP16RequestCommand.STOP_TRANSACTION,
24d15716
JB
152 this.ajv
153 .compile(
154 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StopTransactionResponse>(
155 'assets/json-schemas/ocpp/1.6/StopTransactionResponse.json',
156 moduleName,
157 'constructor'
158 )
159 )
160 .bind(this)
b52c969d
JB
161 ],
162 [
163 OCPP16RequestCommand.STATUS_NOTIFICATION,
24d15716
JB
164 this.ajv
165 .compile(
166 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16StatusNotificationResponse>(
167 'assets/json-schemas/ocpp/1.6/StatusNotificationResponse.json',
168 moduleName,
169 'constructor'
170 )
171 )
172 .bind(this)
b52c969d
JB
173 ],
174 [
175 OCPP16RequestCommand.METER_VALUES,
24d15716
JB
176 this.ajv
177 .compile(
178 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16MeterValuesResponse>(
179 'assets/json-schemas/ocpp/1.6/MeterValuesResponse.json',
180 moduleName,
181 'constructor'
182 )
183 )
184 .bind(this)
b52c969d
JB
185 ],
186 [
187 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION,
24d15716
JB
188 this.ajv
189 .compile(
190 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DiagnosticsStatusNotificationResponse>(
191 'assets/json-schemas/ocpp/1.6/DiagnosticsStatusNotificationResponse.json',
192 moduleName,
193 'constructor'
194 )
195 )
196 .bind(this)
b52c969d 197 ],
91a7d3ea
JB
198 [
199 OCPP16RequestCommand.DATA_TRANSFER,
24d15716
JB
200 this.ajv
201 .compile(
202 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
203 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
204 moduleName,
205 'constructor'
206 )
207 )
208 .bind(this)
91a7d3ea 209 ],
c9a4f9ea
JB
210 [
211 OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION,
24d15716
JB
212 this.ajv
213 .compile(
214 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16FirmwareStatusNotificationResponse>(
215 'assets/json-schemas/ocpp/1.6/FirmwareStatusNotificationResponse.json',
216 moduleName,
217 'constructor'
218 )
219 )
220 .bind(this)
66a7748d
JB
221 ]
222 ])
d5490a13 223 this.incomingRequestResponsePayloadValidateFunctions = new Map<
24d15716
JB
224 OCPP16IncomingRequestCommand,
225 ValidateFunction<JsonType>
226 >([
02887891
JB
227 [
228 OCPP16IncomingRequestCommand.RESET,
298be10c 229 this.ajvIncomingRequest
24d15716
JB
230 .compile(
231 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
232 'assets/json-schemas/ocpp/1.6/ResetResponse.json',
233 moduleName,
234 'constructor'
235 )
236 )
237 .bind(this)
02887891
JB
238 ],
239 [
240 OCPP16IncomingRequestCommand.CLEAR_CACHE,
298be10c 241 this.ajvIncomingRequest
24d15716
JB
242 .compile(
243 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
244 'assets/json-schemas/ocpp/1.6/ClearCacheResponse.json',
245 moduleName,
246 'constructor'
247 )
248 )
249 .bind(this)
02887891
JB
250 ],
251 [
252 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
298be10c 253 this.ajvIncomingRequest
24d15716
JB
254 .compile(
255 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityResponse>(
256 'assets/json-schemas/ocpp/1.6/ChangeAvailabilityResponse.json',
257 moduleName,
258 'constructor'
259 )
260 )
261 .bind(this)
02887891
JB
262 ],
263 [
264 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
298be10c 265 this.ajvIncomingRequest
24d15716
JB
266 .compile(
267 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorResponse>(
268 'assets/json-schemas/ocpp/1.6/UnlockConnectorResponse.json',
269 moduleName,
270 'constructor'
271 )
272 )
273 .bind(this)
02887891
JB
274 ],
275 [
276 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
298be10c 277 this.ajvIncomingRequest
24d15716
JB
278 .compile(
279 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationResponse>(
280 'assets/json-schemas/ocpp/1.6/GetConfigurationResponse.json',
281 moduleName,
282 'constructor'
283 )
284 )
285 .bind(this)
02887891
JB
286 ],
287 [
288 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
298be10c 289 this.ajvIncomingRequest
24d15716
JB
290 .compile(
291 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationResponse>(
292 'assets/json-schemas/ocpp/1.6/ChangeConfigurationResponse.json',
293 moduleName,
294 'constructor'
295 )
296 )
297 .bind(this)
02887891 298 ],
41189456
JB
299 [
300 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
298be10c 301 this.ajvIncomingRequest
24d15716
JB
302 .compile(
303 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleResponse>(
304 'assets/json-schemas/ocpp/1.6/GetCompositeScheduleResponse.json',
305 moduleName,
306 'constructor'
307 )
308 )
309 .bind(this)
41189456 310 ],
02887891
JB
311 [
312 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
298be10c 313 this.ajvIncomingRequest
24d15716
JB
314 .compile(
315 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileResponse>(
316 'assets/json-schemas/ocpp/1.6/SetChargingProfileResponse.json',
317 moduleName,
318 'constructor'
319 )
320 )
321 .bind(this)
02887891
JB
322 ],
323 [
324 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
298be10c 325 this.ajvIncomingRequest
24d15716
JB
326 .compile(
327 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileResponse>(
328 'assets/json-schemas/ocpp/1.6/ClearChargingProfileResponse.json',
329 moduleName,
330 'constructor'
331 )
332 )
333 .bind(this)
02887891
JB
334 ],
335 [
336 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
298be10c 337 this.ajvIncomingRequest
24d15716
JB
338 .compile(
339 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
340 'assets/json-schemas/ocpp/1.6/RemoteStartTransactionResponse.json',
341 moduleName,
342 'constructor'
343 )
344 )
345 .bind(this)
02887891
JB
346 ],
347 [
348 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
298be10c 349 this.ajvIncomingRequest
24d15716
JB
350 .compile(
351 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
352 'assets/json-schemas/ocpp/1.6/RemoteStopTransactionResponse.json',
353 moduleName,
354 'constructor'
355 )
356 )
357 .bind(this)
02887891
JB
358 ],
359 [
360 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
298be10c 361 this.ajvIncomingRequest
24d15716
JB
362 .compile(
363 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsResponse>(
364 'assets/json-schemas/ocpp/1.6/GetDiagnosticsResponse.json',
365 moduleName,
366 'constructor'
367 )
368 )
369 .bind(this)
02887891
JB
370 ],
371 [
372 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
298be10c 373 this.ajvIncomingRequest
24d15716
JB
374 .compile(
375 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageResponse>(
376 'assets/json-schemas/ocpp/1.6/TriggerMessageResponse.json',
377 moduleName,
378 'constructor'
379 )
380 )
381 .bind(this)
02887891
JB
382 ],
383 [
384 OCPP16IncomingRequestCommand.DATA_TRANSFER,
298be10c 385 this.ajvIncomingRequest
24d15716
JB
386 .compile(
387 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferResponse>(
388 'assets/json-schemas/ocpp/1.6/DataTransferResponse.json',
389 moduleName,
390 'constructor'
391 )
392 )
393 .bind(this)
02887891
JB
394 ],
395 [
396 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
298be10c 397 this.ajvIncomingRequest
24d15716
JB
398 .compile(
399 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareResponse>(
400 'assets/json-schemas/ocpp/1.6/UpdateFirmwareResponse.json',
401 moduleName,
402 'constructor'
403 )
404 )
405 .bind(this)
02887891 406 ],
28fe900f
JB
407 [
408 OCPP16IncomingRequestCommand.RESERVE_NOW,
298be10c 409 this.ajvIncomingRequest
24d15716
JB
410 .compile(
411 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowResponse>(
412 'assets/json-schemas/ocpp/1.6/ReserveNowResponse.json',
413 moduleName,
414 'constructor'
415 )
416 )
417 .bind(this)
28fe900f
JB
418 ],
419 [
420 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
298be10c 421 this.ajvIncomingRequest
24d15716
JB
422 .compile(
423 OCPP16ServiceUtils.parseJsonSchemaFile<GenericResponse>(
424 'assets/json-schemas/ocpp/1.6/CancelReservationResponse.json',
425 moduleName,
426 'constructor'
427 )
428 )
429 .bind(this)
66a7748d
JB
430 ]
431 ])
ba9a56a6 432 this.validatePayload = this.validatePayload.bind(this)
58144adb
JB
433 }
434
9429aa42 435 public async responseHandler<ReqType extends JsonType, ResType extends JsonType>(
08f130a0 436 chargingStation: ChargingStation,
e7aeea18 437 commandName: OCPP16RequestCommand,
9429aa42 438 payload: ResType,
66a7748d 439 requestPayload: ReqType
e7aeea18 440 ): Promise<void> {
66a7748d 441 if (chargingStation.isRegistered() || commandName === OCPP16RequestCommand.BOOT_NOTIFICATION) {
65554cc3 442 if (
66a7748d
JB
443 this.responseHandlers.has(commandName) &&
444 OCPP16ServiceUtils.isRequestCommandSupported(chargingStation, commandName)
65554cc3 445 ) {
124f3553 446 try {
66a7748d
JB
447 this.validatePayload(chargingStation, commandName, payload)
448 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
bcf95df1
JB
449 const responseHandler = this.responseHandlers.get(commandName)!
450 if (isAsyncFunction(responseHandler)) {
451 await responseHandler(chargingStation, payload, requestPayload)
452 } else {
453 (
454 responseHandler as (
455 chargingStation: ChargingStation,
456 payload: JsonType,
457 requestPayload?: JsonType
458 ) => void
459 )(chargingStation, payload, requestPayload)
460 }
124f3553 461 } catch (error) {
6c8f5d90
JB
462 logger.error(
463 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
66a7748d
JB
464 error
465 )
466 throw error
124f3553
JB
467 }
468 } else {
469 // Throw exception
e7aeea18
JB
470 throw new OCPPError(
471 ErrorType.NOT_IMPLEMENTED,
3024d5b2 472 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
e7aeea18 473 payload,
4ed03b6e 474 undefined,
66a7748d 475 2
e7aeea18 476 )}`,
7369e417 477 commandName,
66a7748d
JB
478 payload
479 )
887fef76 480 }
c0560973 481 } else {
e7aeea18
JB
482 throw new OCPPError(
483 ErrorType.SECURITY_ERROR,
6c8f5d90 484 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
e7aeea18 485 payload,
4ed03b6e 486 undefined,
66a7748d 487 2
412cece8 488 )} while the charging station is not registered on the central server`,
7369e417 489 commandName,
66a7748d
JB
490 payload
491 )
c0560973
JB
492 }
493 }
494
66a7748d 495 private validatePayload (
9c5c4195
JB
496 chargingStation: ChargingStation,
497 commandName: OCPP16RequestCommand,
66a7748d 498 payload: JsonType
9c5c4195 499 ): boolean {
d5490a13 500 if (this.payloadValidateFunctions.has(commandName)) {
24d15716 501 return this.validateResponsePayload(chargingStation, commandName, payload)
9c5c4195
JB
502 }
503 logger.warn(
24d15716 504 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
505 )
506 return false
9c5c4195
JB
507 }
508
66a7748d 509 private handleResponseBootNotification (
08f130a0 510 chargingStation: ChargingStation,
66a7748d 511 payload: OCPP16BootNotificationResponse
08f130a0 512 ): void {
d270cc87 513 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
0320e2bb 514 chargingStation.bootNotificationResponse = payload
50539084
JB
515 if (chargingStation.isRegistered()) {
516 chargingStation.emit(ChargingStationEvents.registered)
517 if (chargingStation.inAcceptedState()) {
d627f8ef
JB
518 addConfigurationKey(
519 chargingStation,
520 OCPP16StandardParametersKey.HeartbeatInterval,
521 payload.interval.toString(),
522 {},
523 { overwrite: true, save: true }
524 )
525 addConfigurationKey(
526 chargingStation,
527 OCPP16StandardParametersKey.HeartBeatInterval,
528 payload.interval.toString(),
529 { visible: false },
530 { overwrite: true, save: true }
531 )
99100f9c 532 chargingStation.emit(ChargingStationEvents.accepted)
50539084
JB
533 }
534 } else if (chargingStation.inRejectedState()) {
535 chargingStation.emit(ChargingStationEvents.rejected)
536 }
08f130a0 537 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
e7aeea18 538 payload.status
66a7748d 539 }' state on the central server`
d270cc87 540 payload.status === RegistrationStatusEnumType.REJECTED
e7aeea18 541 ? logger.warn(logMsg)
66a7748d 542 : logger.info(logMsg)
c0560973 543 } else {
18c6df2b 544 delete chargingStation.bootNotificationResponse
e7aeea18 545 logger.error(
44eb6026 546 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
547 payload
548 )
c0560973
JB
549 }
550 }
551
66a7748d 552 private handleResponseAuthorize (
08f130a0 553 chargingStation: ChargingStation,
e7aeea18 554 payload: OCPP16AuthorizeResponse,
66a7748d 555 requestPayload: OCPP16AuthorizeRequest
e7aeea18 556 ): void {
66a7748d 557 let authorizeConnectorId: number | undefined
ded57f02
JB
558 if (chargingStation.hasEvses) {
559 for (const [evseId, evseStatus] of chargingStation.evses) {
560 if (evseId > 0) {
561 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 562 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
66a7748d
JB
563 authorizeConnectorId = connectorId
564 break
ded57f02
JB
565 }
566 }
567 }
568 }
569 } else {
570 for (const connectorId of chargingStation.connectors.keys()) {
571 if (
572 connectorId > 0 &&
573 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
574 ) {
66a7748d
JB
575 authorizeConnectorId = connectorId
576 break
ded57f02 577 }
58144adb
JB
578 }
579 }
f938317f
JB
580 if (authorizeConnectorId != null) {
581 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
582 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
583 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
584 authorizeConnectorStatus.idTagAuthorized = true
585 logger.debug(
586 `${chargingStation.logPrefix()} idTag '${
587 requestPayload.idTag
588 }' accepted on connector id ${authorizeConnectorId}`
589 )
590 } else {
591 authorizeConnectorStatus.idTagAuthorized = false
592 delete authorizeConnectorStatus.authorizeIdTag
593 logger.debug(
48847bc0
JB
594 `${chargingStation.logPrefix()} idTag '${
595 requestPayload.idTag
596 }' rejected with status '${payload.idTagInfo.status}'`
f938317f 597 )
d984c13f 598 }
58144adb 599 } else {
f938317f
JB
600 logger.error(
601 `${chargingStation.logPrefix()} idTag '${
602 requestPayload.idTag
603 }' has no authorize request pending`
66a7748d 604 )
58144adb
JB
605 }
606 }
607
66a7748d 608 private async handleResponseStartTransaction (
08f130a0 609 chargingStation: ChargingStation,
e7aeea18 610 payload: OCPP16StartTransactionResponse,
66a7748d 611 requestPayload: OCPP16StartTransactionRequest
e7aeea18 612 ): Promise<void> {
66a7748d
JB
613 const { connectorId } = requestPayload
614 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
e7aeea18 615 logger.error(
66a7748d
JB
616 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
617 )
618 return
c0560973 619 }
66a7748d 620 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
e7aeea18 621 if (
d929adcc 622 connectorStatus?.transactionRemoteStarted === true &&
66a7748d
JB
623 chargingStation.getAuthorizeRemoteTxRequests() &&
624 chargingStation.getLocalAuthListEnabled() &&
625 chargingStation.hasIdTags() &&
5199f9fd 626 connectorStatus.idTagLocalAuthorized === false
e7aeea18
JB
627 ) {
628 logger.error(
5199f9fd
JB
629 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
630 connectorStatus.localAuthorizeIdTag
631 } on connector id ${connectorId}`
66a7748d
JB
632 )
633 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
634 return
a2653482 635 }
e7aeea18 636 if (
d929adcc 637 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 638 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 639 chargingStation.stationInfo?.remoteAuthorization === true &&
5199f9fd
JB
640 connectorStatus.idTagLocalAuthorized === false &&
641 connectorStatus.idTagAuthorized === false
e7aeea18
JB
642 ) {
643 logger.error(
5199f9fd
JB
644 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
645 connectorStatus.authorizeIdTag
646 } on connector id ${connectorId}`
66a7748d
JB
647 )
648 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
649 return
a2653482 650 }
e7aeea18 651 if (
66a7748d 652 connectorStatus?.idTagAuthorized === true &&
5199f9fd 653 connectorStatus.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
654 ) {
655 logger.error(
44eb6026
JB
656 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
657 requestPayload.idTag
5199f9fd
JB
658 } different from the authorize request one ${
659 connectorStatus.authorizeIdTag
660 } on connector id ${connectorId}`
66a7748d
JB
661 )
662 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
663 return
a2653482 664 }
e7aeea18 665 if (
66a7748d 666 connectorStatus?.idTagLocalAuthorized === true &&
5199f9fd 667 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
668 ) {
669 logger.error(
44eb6026
JB
670 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
671 requestPayload.idTag
5199f9fd
JB
672 } different from the local authorized one ${
673 connectorStatus.localAuthorizeIdTag
674 } on connector id ${connectorId}`
66a7748d
JB
675 )
676 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
677 return
163547b1 678 }
d929adcc 679 if (connectorStatus?.transactionStarted === true) {
649287f8 680 logger.error(
5199f9fd
JB
681 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
682 connectorStatus.transactionIdTag
683 }`
66a7748d
JB
684 )
685 return
c0560973 686 }
649287f8
JB
687 if (chargingStation.hasEvses) {
688 for (const [evseId, evseStatus] of chargingStation.evses) {
689 if (evseStatus.connectors.size > 1) {
d929adcc 690 for (const [id, status] of evseStatus.connectors) {
5199f9fd 691 if (id !== connectorId && status.transactionStarted === true) {
649287f8 692 logger.error(
5199f9fd
JB
693 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
694 status.transactionIdTag
695 }`
66a7748d
JB
696 )
697 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
698 return
649287f8
JB
699 }
700 }
701 }
702 }
703 }
e7aeea18 704 if (
d929adcc
JB
705 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
706 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
707 ) {
708 logger.error(
48847bc0
JB
709 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${
710 connectorStatus?.status
711 }`
66a7748d
JB
712 )
713 return
290d006c 714 }
d1504492 715 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 716 logger.warn(
d929adcc 717 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 718 payload.transactionId
66a7748d
JB
719 }, converting to integer`
720 )
721 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 722 }
c0560973 723
5199f9fd 724 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
725 connectorStatus.transactionStarted = true
726 connectorStatus.transactionStart = requestPayload.timestamp
727 connectorStatus.transactionId = payload.transactionId
728 connectorStatus.transactionIdTag = requestPayload.idTag
729 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 730 connectorStatus.transactionBeginMeterValue =
e7aeea18 731 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 732 chargingStation,
d929adcc 733 connectorId,
66a7748d
JB
734 requestPayload.meterStart
735 )
736 if (requestPayload.reservationId != null) {
90aceaf6
JB
737 const reservation = chargingStation.getReservationBy(
738 'reservationId',
66a7748d 739 requestPayload.reservationId
a095d7d7
JB
740 )
741 if (reservation != null) {
742 if (reservation.idTag !== requestPayload.idTag) {
743 logger.warn(
744 `${chargingStation.logPrefix()} Reserved transaction ${
745 payload.transactionId
48847bc0
JB
746 } started with a different idTag ${
747 requestPayload.idTag
748 } than the reservation one ${reservation.idTag}`
a095d7d7
JB
749 )
750 }
751 if (hasReservationExpired(reservation)) {
752 logger.warn(
753 `${chargingStation.logPrefix()} Reserved transaction ${
754 payload.transactionId
755 } started with expired reservation ${
756 requestPayload.reservationId
757 } (expiry date: ${reservation.expiryDate.toISOString()}))`
758 )
759 }
760 await chargingStation.removeReservation(
761 reservation,
762 ReservationTerminationReason.TRANSACTION_STARTED
66a7748d 763 )
a095d7d7 764 } else {
90aceaf6 765 logger.warn(
56563a3c 766 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6 767 payload.transactionId
a095d7d7 768 } started with unknown reservation ${requestPayload.reservationId}`
66a7748d 769 )
90aceaf6 770 }
d984c13f 771 }
66a7748d 772 chargingStation.stationInfo?.beginEndMeterValues === true &&
08f130a0 773 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
774 OCPP16MeterValuesRequest,
775 OCPP16MeterValuesResponse
08f130a0 776 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 777 connectorId,
ef6fa3fb 778 transactionId: payload.transactionId,
66a7748d
JB
779 meterValue: [connectorStatus.transactionBeginMeterValue]
780 } satisfies OCPP16MeterValuesRequest))
4ecff7ce
JB
781 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
782 chargingStation,
d929adcc 783 connectorId,
66a7748d
JB
784 OCPP16ChargePointStatus.Charging
785 )
e7aeea18 786 logger.info(
48847bc0
JB
787 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
788 chargingStation.stationInfo?.chargingStationId
789 }#${connectorId} for idTag '${requestPayload.idTag}'`
66a7748d 790 )
5199f9fd
JB
791 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
792 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
793 ++chargingStation.powerDivider!
c0560973 794 }
f2d5e3d9
JB
795 const configuredMeterValueSampleInterval = getConfigurationKey(
796 chargingStation,
66a7748d
JB
797 OCPP16StandardParametersKey.MeterValueSampleInterval
798 )
08f130a0 799 chargingStation.startMeterValues(
d929adcc 800 connectorId,
a807045b 801 configuredMeterValueSampleInterval != null
be4c6702 802 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
803 : Constants.DEFAULT_METER_VALUES_INTERVAL
804 )
c0560973 805 } else {
e7aeea18 806 logger.warn(
d929adcc
JB
807 `${chargingStation.logPrefix()} Starting transaction with id ${
808 payload.transactionId
a223d9be
JB
809 } REJECTED on ${
810 chargingStation.stationInfo?.chargingStationId
811 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
56563a3c
JB
812 requestPayload.idTag
813 }'${
d929adcc 814 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
815 ? `, reservationId '${requestPayload.reservationId}'`
816 : ''
66a7748d
JB
817 }`
818 )
819 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
820 }
821 }
822
66a7748d 823 private async resetConnectorOnStartTransactionError (
08f130a0 824 chargingStation: ChargingStation,
66a7748d 825 connectorId: number
08f130a0 826 ): Promise<void> {
a9671b9e 827 chargingStation.stopMeterValues(connectorId)
66a7748d 828 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f938317f 829 resetConnectorStatus(connectorStatus)
a9671b9e 830 await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
c0560973
JB
831 }
832
66a7748d 833 private async handleResponseStopTransaction (
08f130a0 834 chargingStation: ChargingStation,
e7aeea18 835 payload: OCPP16StopTransactionResponse,
66a7748d 836 requestPayload: OCPP16StopTransactionRequest
e7aeea18 837 ): Promise<void> {
08f130a0 838 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
66a7748d
JB
839 requestPayload.transactionId
840 )
aa63c9b7 841 if (transactionConnectorId == null) {
e7aeea18 842 logger.error(
d929adcc
JB
843 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
844 requestPayload.transactionId
66a7748d
JB
845 }`
846 )
847 return
c0560973 848 }
5398cecf 849 chargingStation.stationInfo?.beginEndMeterValues === true &&
5199f9fd
JB
850 chargingStation.stationInfo.ocppStrictCompliance === false &&
851 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
2cace1a5 852 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
853 OCPP16MeterValuesRequest,
854 OCPP16MeterValuesResponse
2cace1a5
JB
855 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
856 connectorId: transactionConnectorId,
857 transactionId: requestPayload.transactionId,
858 meterValue: [
859 OCPP16ServiceUtils.buildTransactionEndMeterValue(
860 chargingStation,
aa63c9b7 861 transactionConnectorId,
66a7748d
JB
862 requestPayload.meterStop
863 )
864 ]
865 }))
2cace1a5 866 if (
66a7748d 867 !chargingStation.isChargingStationAvailable() ||
aa63c9b7 868 !chargingStation.isConnectorAvailable(transactionConnectorId)
2cace1a5 869 ) {
4ecff7ce
JB
870 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
871 chargingStation,
aa63c9b7 872 transactionConnectorId,
66a7748d
JB
873 OCPP16ChargePointStatus.Unavailable
874 )
c0560973 875 } else {
4ecff7ce
JB
876 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
877 chargingStation,
aa63c9b7 878 transactionConnectorId,
66a7748d
JB
879 OCPP16ChargePointStatus.Available
880 )
2cace1a5 881 }
5199f9fd
JB
882 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
883 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
884 chargingStation.powerDivider!--
2cace1a5 885 }
f938317f 886 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
aa63c9b7 887 chargingStation.stopMeterValues(transactionConnectorId)
d929adcc
JB
888 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
889 requestPayload.transactionId
a223d9be
JB
890 } STOPPED on ${
891 chargingStation.stationInfo?.chargingStationId
892 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 893 if (
aa63c9b7 894 payload.idTagInfo == null ||
5199f9fd 895 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
2cace1a5 896 ) {
66a7748d 897 logger.info(logMsg)
2cace1a5 898 } else {
66a7748d 899 logger.warn(logMsg)
c0560973
JB
900 }
901 }
c0560973 902}