fix: avoid duplicate payload validation function
[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
66a7748d 6import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
04b1261c
JB
7import {
8 type ChargingStation,
f2d5e3d9
JB
9 addConfigurationKey,
10 getConfigurationKey,
90aceaf6 11 hasReservationExpired,
66a7748d
JB
12 resetConnectorStatus
13} from '../../../charging-station/index.js'
14import { OCPPError } from '../../../exception/index.js'
ef6fa3fb 15import {
268a74bb 16 type ChangeConfigurationResponse,
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'
aa63c9b7 54import { Constants, convertToInt, logger } from '../../../utils/index.js'
66a7748d 55import { OCPPResponseService } from '../OCPPResponseService.js'
c0560973 56
66a7748d 57const moduleName = 'OCPP16ResponseService'
909dcf2d 58
268a74bb 59export class OCPP16ResponseService extends OCPPResponseService {
24d15716 60 public jsonSchemasIncomingRequestResponseValidateFunction: Map<
66a7748d 61 OCPP16IncomingRequestCommand,
24d15716 62 ValidateFunction<JsonType>
66a7748d 63 >
b3fc3ff5 64
24d15716 65 protected jsonSchemasValidateFunction: 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 ])
24d15716 100 this.jsonSchemasValidateFunction = 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 ])
24d15716
JB
222 this.jsonSchemasIncomingRequestResponseValidateFunction = new Map<
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
448 await this.responseHandlers.get(commandName)!(chargingStation, payload, requestPayload)
124f3553 449 } catch (error) {
6c8f5d90
JB
450 logger.error(
451 `${chargingStation.logPrefix()} ${moduleName}.responseHandler: Handle response error:`,
66a7748d
JB
452 error
453 )
454 throw error
124f3553
JB
455 }
456 } else {
457 // Throw exception
e7aeea18
JB
458 throw new OCPPError(
459 ErrorType.NOT_IMPLEMENTED,
6c8f5d90 460 `${commandName} is not implemented to handle response PDU ${JSON.stringify(
e7aeea18 461 payload,
4ed03b6e 462 undefined,
66a7748d 463 2
e7aeea18 464 )}`,
7369e417 465 commandName,
66a7748d
JB
466 payload
467 )
887fef76 468 }
c0560973 469 } else {
e7aeea18
JB
470 throw new OCPPError(
471 ErrorType.SECURITY_ERROR,
6c8f5d90 472 `${commandName} cannot be issued to handle response PDU ${JSON.stringify(
e7aeea18 473 payload,
4ed03b6e 474 undefined,
66a7748d 475 2
439fc71b 476 )} while the charging station is not registered on the central server.`,
7369e417 477 commandName,
66a7748d
JB
478 payload
479 )
c0560973
JB
480 }
481 }
482
66a7748d 483 private validatePayload (
9c5c4195
JB
484 chargingStation: ChargingStation,
485 commandName: OCPP16RequestCommand,
66a7748d 486 payload: JsonType
9c5c4195 487 ): boolean {
24d15716
JB
488 if (this.jsonSchemasValidateFunction.has(commandName)) {
489 return this.validateResponsePayload(chargingStation, commandName, payload)
9c5c4195
JB
490 }
491 logger.warn(
24d15716 492 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
66a7748d
JB
493 )
494 return false
9c5c4195
JB
495 }
496
66a7748d 497 private handleResponseBootNotification (
08f130a0 498 chargingStation: ChargingStation,
66a7748d 499 payload: OCPP16BootNotificationResponse
08f130a0 500 ): void {
d270cc87 501 if (payload.status === RegistrationStatusEnumType.ACCEPTED) {
f2d5e3d9 502 addConfigurationKey(
17ac262c 503 chargingStation,
f0f65a62 504 OCPP16StandardParametersKey.HeartbeatInterval,
a95873d8 505 payload.interval.toString(),
abe9e9dd 506 {},
66a7748d
JB
507 { overwrite: true, save: true }
508 )
f2d5e3d9 509 addConfigurationKey(
17ac262c 510 chargingStation,
f0f65a62 511 OCPP16StandardParametersKey.HeartBeatInterval,
e7aeea18 512 payload.interval.toString(),
00db15b8 513 { visible: false },
66a7748d
JB
514 { overwrite: true, save: true }
515 )
516 OCPP16ServiceUtils.startHeartbeatInterval(chargingStation, payload.interval)
672fed6e 517 }
d270cc87 518 if (Object.values(RegistrationStatusEnumType).includes(payload.status)) {
08f130a0 519 const logMsg = `${chargingStation.logPrefix()} Charging station in '${
e7aeea18 520 payload.status
66a7748d 521 }' state on the central server`
d270cc87 522 payload.status === RegistrationStatusEnumType.REJECTED
e7aeea18 523 ? logger.warn(logMsg)
66a7748d 524 : logger.info(logMsg)
c0560973 525 } else {
e7aeea18 526 logger.error(
44eb6026 527 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
528 payload
529 )
c0560973
JB
530 }
531 }
532
66a7748d 533 private handleResponseAuthorize (
08f130a0 534 chargingStation: ChargingStation,
e7aeea18 535 payload: OCPP16AuthorizeResponse,
66a7748d 536 requestPayload: OCPP16AuthorizeRequest
e7aeea18 537 ): void {
66a7748d 538 let authorizeConnectorId: number | undefined
ded57f02
JB
539 if (chargingStation.hasEvses) {
540 for (const [evseId, evseStatus] of chargingStation.evses) {
541 if (evseId > 0) {
542 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 543 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
66a7748d
JB
544 authorizeConnectorId = connectorId
545 break
ded57f02
JB
546 }
547 }
548 }
549 }
550 } else {
551 for (const connectorId of chargingStation.connectors.keys()) {
552 if (
553 connectorId > 0 &&
554 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
555 ) {
66a7748d
JB
556 authorizeConnectorId = connectorId
557 break
ded57f02 558 }
58144adb
JB
559 }
560 }
f938317f
JB
561 if (authorizeConnectorId != null) {
562 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
563 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
564 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
565 authorizeConnectorStatus.idTagAuthorized = true
566 logger.debug(
567 `${chargingStation.logPrefix()} idTag '${
568 requestPayload.idTag
569 }' accepted on connector id ${authorizeConnectorId}`
570 )
571 } else {
572 authorizeConnectorStatus.idTagAuthorized = false
573 delete authorizeConnectorStatus.authorizeIdTag
574 logger.debug(
575 `${chargingStation.logPrefix()} idTag '${requestPayload.idTag}' rejected with status '${
576 payload.idTagInfo.status
577 }`
578 )
d984c13f 579 }
58144adb 580 } else {
f938317f
JB
581 logger.error(
582 `${chargingStation.logPrefix()} idTag '${
583 requestPayload.idTag
584 }' has no authorize request pending`
66a7748d 585 )
58144adb
JB
586 }
587 }
588
66a7748d 589 private async handleResponseStartTransaction (
08f130a0 590 chargingStation: ChargingStation,
e7aeea18 591 payload: OCPP16StartTransactionResponse,
66a7748d 592 requestPayload: OCPP16StartTransactionRequest
e7aeea18 593 ): Promise<void> {
66a7748d
JB
594 const { connectorId } = requestPayload
595 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
e7aeea18 596 logger.error(
66a7748d
JB
597 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
598 )
599 return
c0560973 600 }
66a7748d 601 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
e7aeea18 602 if (
d929adcc 603 connectorStatus?.transactionRemoteStarted === true &&
66a7748d
JB
604 chargingStation.getAuthorizeRemoteTxRequests() &&
605 chargingStation.getLocalAuthListEnabled() &&
606 chargingStation.hasIdTags() &&
5199f9fd 607 connectorStatus.idTagLocalAuthorized === false
e7aeea18
JB
608 ) {
609 logger.error(
5199f9fd
JB
610 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
611 connectorStatus.localAuthorizeIdTag
612 } on connector id ${connectorId}`
66a7748d
JB
613 )
614 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
615 return
a2653482 616 }
e7aeea18 617 if (
d929adcc 618 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 619 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 620 chargingStation.stationInfo?.remoteAuthorization === true &&
5199f9fd
JB
621 connectorStatus.idTagLocalAuthorized === false &&
622 connectorStatus.idTagAuthorized === false
e7aeea18
JB
623 ) {
624 logger.error(
5199f9fd
JB
625 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
626 connectorStatus.authorizeIdTag
627 } on connector id ${connectorId}`
66a7748d
JB
628 )
629 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
630 return
a2653482 631 }
e7aeea18 632 if (
66a7748d 633 connectorStatus?.idTagAuthorized === true &&
5199f9fd 634 connectorStatus.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
635 ) {
636 logger.error(
44eb6026
JB
637 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
638 requestPayload.idTag
5199f9fd
JB
639 } different from the authorize request one ${
640 connectorStatus.authorizeIdTag
641 } on connector id ${connectorId}`
66a7748d
JB
642 )
643 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
644 return
a2653482 645 }
e7aeea18 646 if (
66a7748d 647 connectorStatus?.idTagLocalAuthorized === true &&
5199f9fd 648 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
649 ) {
650 logger.error(
44eb6026
JB
651 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
652 requestPayload.idTag
5199f9fd
JB
653 } different from the local authorized one ${
654 connectorStatus.localAuthorizeIdTag
655 } on connector id ${connectorId}`
66a7748d
JB
656 )
657 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
658 return
163547b1 659 }
d929adcc 660 if (connectorStatus?.transactionStarted === true) {
649287f8 661 logger.error(
5199f9fd
JB
662 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
663 connectorStatus.transactionIdTag
664 }`
66a7748d
JB
665 )
666 return
c0560973 667 }
649287f8
JB
668 if (chargingStation.hasEvses) {
669 for (const [evseId, evseStatus] of chargingStation.evses) {
670 if (evseStatus.connectors.size > 1) {
d929adcc 671 for (const [id, status] of evseStatus.connectors) {
5199f9fd 672 if (id !== connectorId && status.transactionStarted === true) {
649287f8 673 logger.error(
5199f9fd
JB
674 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
675 status.transactionIdTag
676 }`
66a7748d
JB
677 )
678 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
679 return
649287f8
JB
680 }
681 }
682 }
683 }
684 }
e7aeea18 685 if (
d929adcc
JB
686 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
687 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
688 ) {
689 logger.error(
66a7748d
JB
690 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${connectorStatus?.status}`
691 )
692 return
290d006c 693 }
d1504492 694 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 695 logger.warn(
d929adcc 696 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 697 payload.transactionId
66a7748d
JB
698 }, converting to integer`
699 )
700 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 701 }
c0560973 702
5199f9fd 703 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
704 connectorStatus.transactionStarted = true
705 connectorStatus.transactionStart = requestPayload.timestamp
706 connectorStatus.transactionId = payload.transactionId
707 connectorStatus.transactionIdTag = requestPayload.idTag
708 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 709 connectorStatus.transactionBeginMeterValue =
e7aeea18 710 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 711 chargingStation,
d929adcc 712 connectorId,
66a7748d
JB
713 requestPayload.meterStart
714 )
715 if (requestPayload.reservationId != null) {
90aceaf6
JB
716 const reservation = chargingStation.getReservationBy(
717 'reservationId',
66a7748d 718 requestPayload.reservationId
a095d7d7
JB
719 )
720 if (reservation != null) {
721 if (reservation.idTag !== requestPayload.idTag) {
722 logger.warn(
723 `${chargingStation.logPrefix()} Reserved transaction ${
724 payload.transactionId
725 } started with a different idTag ${requestPayload.idTag} than the reservation one ${
726 reservation.idTag
727 }`
728 )
729 }
730 if (hasReservationExpired(reservation)) {
731 logger.warn(
732 `${chargingStation.logPrefix()} Reserved transaction ${
733 payload.transactionId
734 } started with expired reservation ${
735 requestPayload.reservationId
736 } (expiry date: ${reservation.expiryDate.toISOString()}))`
737 )
738 }
739 await chargingStation.removeReservation(
740 reservation,
741 ReservationTerminationReason.TRANSACTION_STARTED
66a7748d 742 )
a095d7d7 743 } else {
90aceaf6 744 logger.warn(
56563a3c 745 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6 746 payload.transactionId
a095d7d7 747 } started with unknown reservation ${requestPayload.reservationId}`
66a7748d 748 )
90aceaf6 749 }
d984c13f 750 }
66a7748d 751 chargingStation.stationInfo?.beginEndMeterValues === true &&
08f130a0 752 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
753 OCPP16MeterValuesRequest,
754 OCPP16MeterValuesResponse
08f130a0 755 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 756 connectorId,
ef6fa3fb 757 transactionId: payload.transactionId,
66a7748d
JB
758 meterValue: [connectorStatus.transactionBeginMeterValue]
759 } satisfies OCPP16MeterValuesRequest))
4ecff7ce
JB
760 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
761 chargingStation,
d929adcc 762 connectorId,
66a7748d
JB
763 OCPP16ChargePointStatus.Charging
764 )
e7aeea18 765 logger.info(
5199f9fd
JB
766 `${chargingStation.logPrefix()} Transaction with id ${
767 payload.transactionId
768 } STARTED on ${chargingStation.stationInfo?.chargingStationId}#${connectorId} for idTag '${
769 requestPayload.idTag
770 }'`
66a7748d 771 )
5199f9fd
JB
772 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
773 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
774 ++chargingStation.powerDivider!
c0560973 775 }
f2d5e3d9
JB
776 const configuredMeterValueSampleInterval = getConfigurationKey(
777 chargingStation,
66a7748d
JB
778 OCPP16StandardParametersKey.MeterValueSampleInterval
779 )
08f130a0 780 chargingStation.startMeterValues(
d929adcc 781 connectorId,
a807045b 782 configuredMeterValueSampleInterval != null
be4c6702 783 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
784 : Constants.DEFAULT_METER_VALUES_INTERVAL
785 )
c0560973 786 } else {
e7aeea18 787 logger.warn(
d929adcc
JB
788 `${chargingStation.logPrefix()} Starting transaction with id ${
789 payload.transactionId
a223d9be
JB
790 } REJECTED on ${
791 chargingStation.stationInfo?.chargingStationId
792 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
56563a3c
JB
793 requestPayload.idTag
794 }'${
d929adcc 795 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
796 ? `, reservationId '${requestPayload.reservationId}'`
797 : ''
66a7748d
JB
798 }`
799 )
800 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
801 }
802 }
803
66a7748d 804 private async resetConnectorOnStartTransactionError (
08f130a0 805 chargingStation: ChargingStation,
66a7748d 806 connectorId: number
08f130a0 807 ): Promise<void> {
66a7748d 808 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f938317f 809 resetConnectorStatus(connectorStatus)
66a7748d 810 chargingStation.stopMeterValues(connectorId)
d929adcc 811 if (connectorStatus?.status !== OCPP16ChargePointStatus.Available) {
4ecff7ce
JB
812 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
813 chargingStation,
ef6fa3fb 814 connectorId,
66a7748d
JB
815 OCPP16ChargePointStatus.Available
816 )
c0560973
JB
817 }
818 }
819
66a7748d 820 private async handleResponseStopTransaction (
08f130a0 821 chargingStation: ChargingStation,
e7aeea18 822 payload: OCPP16StopTransactionResponse,
66a7748d 823 requestPayload: OCPP16StopTransactionRequest
e7aeea18 824 ): Promise<void> {
08f130a0 825 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
66a7748d
JB
826 requestPayload.transactionId
827 )
aa63c9b7 828 if (transactionConnectorId == null) {
e7aeea18 829 logger.error(
d929adcc
JB
830 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
831 requestPayload.transactionId
66a7748d
JB
832 }`
833 )
834 return
c0560973 835 }
5398cecf 836 chargingStation.stationInfo?.beginEndMeterValues === true &&
5199f9fd
JB
837 chargingStation.stationInfo.ocppStrictCompliance === false &&
838 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
2cace1a5 839 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
840 OCPP16MeterValuesRequest,
841 OCPP16MeterValuesResponse
2cace1a5
JB
842 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
843 connectorId: transactionConnectorId,
844 transactionId: requestPayload.transactionId,
845 meterValue: [
846 OCPP16ServiceUtils.buildTransactionEndMeterValue(
847 chargingStation,
aa63c9b7 848 transactionConnectorId,
66a7748d
JB
849 requestPayload.meterStop
850 )
851 ]
852 }))
2cace1a5 853 if (
66a7748d 854 !chargingStation.isChargingStationAvailable() ||
aa63c9b7 855 !chargingStation.isConnectorAvailable(transactionConnectorId)
2cace1a5 856 ) {
4ecff7ce
JB
857 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
858 chargingStation,
aa63c9b7 859 transactionConnectorId,
66a7748d
JB
860 OCPP16ChargePointStatus.Unavailable
861 )
c0560973 862 } else {
4ecff7ce
JB
863 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
864 chargingStation,
aa63c9b7 865 transactionConnectorId,
66a7748d
JB
866 OCPP16ChargePointStatus.Available
867 )
2cace1a5 868 }
5199f9fd
JB
869 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
870 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
871 chargingStation.powerDivider!--
2cace1a5 872 }
f938317f 873 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
aa63c9b7 874 chargingStation.stopMeterValues(transactionConnectorId)
d929adcc
JB
875 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
876 requestPayload.transactionId
a223d9be
JB
877 } STOPPED on ${
878 chargingStation.stationInfo?.chargingStationId
879 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 880 if (
aa63c9b7 881 payload.idTagInfo == null ||
5199f9fd 882 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
2cace1a5 883 ) {
66a7748d 884 logger.info(logMsg)
2cace1a5 885 } else {
66a7748d 886 logger.warn(logMsg)
c0560973
JB
887 }
888 }
c0560973 889}