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