build(deps-dev): apply updates
[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,
240fa4da 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 {
e7aeea18 544 logger.error(
44eb6026 545 `${chargingStation.logPrefix()} Charging station boot notification response received: %j with undefined registration status`,
66a7748d
JB
546 payload
547 )
c0560973
JB
548 }
549 }
550
66a7748d 551 private handleResponseAuthorize (
08f130a0 552 chargingStation: ChargingStation,
e7aeea18 553 payload: OCPP16AuthorizeResponse,
66a7748d 554 requestPayload: OCPP16AuthorizeRequest
e7aeea18 555 ): void {
66a7748d 556 let authorizeConnectorId: number | undefined
ded57f02
JB
557 if (chargingStation.hasEvses) {
558 for (const [evseId, evseStatus] of chargingStation.evses) {
559 if (evseId > 0) {
560 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
5199f9fd 561 if (connectorStatus.authorizeIdTag === requestPayload.idTag) {
66a7748d
JB
562 authorizeConnectorId = connectorId
563 break
ded57f02
JB
564 }
565 }
566 }
567 }
568 } else {
569 for (const connectorId of chargingStation.connectors.keys()) {
570 if (
571 connectorId > 0 &&
572 chargingStation.getConnectorStatus(connectorId)?.authorizeIdTag === requestPayload.idTag
573 ) {
66a7748d
JB
574 authorizeConnectorId = connectorId
575 break
ded57f02 576 }
58144adb
JB
577 }
578 }
f938317f
JB
579 if (authorizeConnectorId != null) {
580 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
581 const authorizeConnectorStatus = chargingStation.getConnectorStatus(authorizeConnectorId)!
582 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
583 authorizeConnectorStatus.idTagAuthorized = true
584 logger.debug(
585 `${chargingStation.logPrefix()} idTag '${
586 requestPayload.idTag
587 }' accepted on connector id ${authorizeConnectorId}`
588 )
589 } else {
590 authorizeConnectorStatus.idTagAuthorized = false
591 delete authorizeConnectorStatus.authorizeIdTag
592 logger.debug(
48847bc0
JB
593 `${chargingStation.logPrefix()} idTag '${
594 requestPayload.idTag
595 }' rejected with status '${payload.idTagInfo.status}'`
f938317f 596 )
d984c13f 597 }
58144adb 598 } else {
f938317f
JB
599 logger.error(
600 `${chargingStation.logPrefix()} idTag '${
601 requestPayload.idTag
602 }' has no authorize request pending`
66a7748d 603 )
58144adb
JB
604 }
605 }
606
66a7748d 607 private async handleResponseStartTransaction (
08f130a0 608 chargingStation: ChargingStation,
e7aeea18 609 payload: OCPP16StartTransactionResponse,
66a7748d 610 requestPayload: OCPP16StartTransactionRequest
e7aeea18 611 ): Promise<void> {
66a7748d
JB
612 const { connectorId } = requestPayload
613 if (connectorId === 0 || !chargingStation.hasConnector(connectorId)) {
e7aeea18 614 logger.error(
66a7748d
JB
615 `${chargingStation.logPrefix()} Trying to start a transaction on a non existing connector id ${connectorId}`
616 )
617 return
c0560973 618 }
66a7748d 619 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
e7aeea18 620 if (
d929adcc 621 connectorStatus?.transactionRemoteStarted === true &&
66a7748d
JB
622 chargingStation.getAuthorizeRemoteTxRequests() &&
623 chargingStation.getLocalAuthListEnabled() &&
624 chargingStation.hasIdTags() &&
5199f9fd 625 connectorStatus.idTagLocalAuthorized === false
e7aeea18
JB
626 ) {
627 logger.error(
5199f9fd
JB
628 `${chargingStation.logPrefix()} Trying to start a transaction with a not local authorized idTag ${
629 connectorStatus.localAuthorizeIdTag
630 } on connector id ${connectorId}`
66a7748d
JB
631 )
632 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
633 return
a2653482 634 }
e7aeea18 635 if (
d929adcc 636 connectorStatus?.transactionRemoteStarted === true &&
66a7748d 637 chargingStation.getAuthorizeRemoteTxRequests() &&
5398cecf 638 chargingStation.stationInfo?.remoteAuthorization === true &&
5199f9fd
JB
639 connectorStatus.idTagLocalAuthorized === false &&
640 connectorStatus.idTagAuthorized === false
e7aeea18
JB
641 ) {
642 logger.error(
5199f9fd
JB
643 `${chargingStation.logPrefix()} Trying to start a transaction with a not authorized idTag ${
644 connectorStatus.authorizeIdTag
645 } on connector id ${connectorId}`
66a7748d
JB
646 )
647 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
648 return
a2653482 649 }
e7aeea18 650 if (
66a7748d 651 connectorStatus?.idTagAuthorized === true &&
5199f9fd 652 connectorStatus.authorizeIdTag !== requestPayload.idTag
e7aeea18
JB
653 ) {
654 logger.error(
44eb6026
JB
655 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
656 requestPayload.idTag
5199f9fd
JB
657 } different from the authorize request one ${
658 connectorStatus.authorizeIdTag
659 } on connector id ${connectorId}`
66a7748d
JB
660 )
661 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
662 return
a2653482 663 }
e7aeea18 664 if (
66a7748d 665 connectorStatus?.idTagLocalAuthorized === true &&
5199f9fd 666 connectorStatus.localAuthorizeIdTag !== requestPayload.idTag
e7aeea18
JB
667 ) {
668 logger.error(
44eb6026
JB
669 `${chargingStation.logPrefix()} Trying to start a transaction with an idTag ${
670 requestPayload.idTag
5199f9fd
JB
671 } different from the local authorized one ${
672 connectorStatus.localAuthorizeIdTag
673 } on connector id ${connectorId}`
66a7748d
JB
674 )
675 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
676 return
163547b1 677 }
d929adcc 678 if (connectorStatus?.transactionStarted === true) {
649287f8 679 logger.error(
5199f9fd
JB
680 `${chargingStation.logPrefix()} Trying to start a transaction on an already used connector id ${connectorId} by idTag ${
681 connectorStatus.transactionIdTag
682 }`
66a7748d
JB
683 )
684 return
c0560973 685 }
649287f8
JB
686 if (chargingStation.hasEvses) {
687 for (const [evseId, evseStatus] of chargingStation.evses) {
688 if (evseStatus.connectors.size > 1) {
d929adcc 689 for (const [id, status] of evseStatus.connectors) {
5199f9fd 690 if (id !== connectorId && status.transactionStarted === true) {
649287f8 691 logger.error(
5199f9fd
JB
692 `${chargingStation.logPrefix()} Trying to start a transaction on an already used evse id ${evseId} by connector id ${id} with idTag ${
693 status.transactionIdTag
694 }`
66a7748d
JB
695 )
696 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
697 return
649287f8
JB
698 }
699 }
700 }
701 }
702 }
e7aeea18 703 if (
d929adcc
JB
704 connectorStatus?.status !== OCPP16ChargePointStatus.Available &&
705 connectorStatus?.status !== OCPP16ChargePointStatus.Preparing
e7aeea18
JB
706 ) {
707 logger.error(
48847bc0
JB
708 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with status ${
709 connectorStatus?.status
710 }`
66a7748d
JB
711 )
712 return
290d006c 713 }
d1504492 714 if (!Number.isSafeInteger(payload.transactionId)) {
54ebb82c 715 logger.warn(
d929adcc 716 `${chargingStation.logPrefix()} Trying to start a transaction on connector id ${connectorId} with a non integer transaction id ${
54ebb82c 717 payload.transactionId
66a7748d
JB
718 }, converting to integer`
719 )
720 payload.transactionId = convertToInt(payload.transactionId)
54ebb82c 721 }
c0560973 722
5199f9fd 723 if (payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
66a7748d
JB
724 connectorStatus.transactionStarted = true
725 connectorStatus.transactionStart = requestPayload.timestamp
726 connectorStatus.transactionId = payload.transactionId
727 connectorStatus.transactionIdTag = requestPayload.idTag
728 connectorStatus.transactionEnergyActiveImportRegisterValue = 0
d929adcc 729 connectorStatus.transactionBeginMeterValue =
e7aeea18 730 OCPP16ServiceUtils.buildTransactionBeginMeterValue(
08f130a0 731 chargingStation,
d929adcc 732 connectorId,
66a7748d
JB
733 requestPayload.meterStart
734 )
735 if (requestPayload.reservationId != null) {
90aceaf6
JB
736 const reservation = chargingStation.getReservationBy(
737 'reservationId',
66a7748d 738 requestPayload.reservationId
a095d7d7
JB
739 )
740 if (reservation != null) {
741 if (reservation.idTag !== requestPayload.idTag) {
742 logger.warn(
743 `${chargingStation.logPrefix()} Reserved transaction ${
744 payload.transactionId
48847bc0
JB
745 } started with a different idTag ${
746 requestPayload.idTag
747 } than the reservation one ${reservation.idTag}`
a095d7d7
JB
748 )
749 }
750 if (hasReservationExpired(reservation)) {
751 logger.warn(
752 `${chargingStation.logPrefix()} Reserved transaction ${
753 payload.transactionId
754 } started with expired reservation ${
755 requestPayload.reservationId
756 } (expiry date: ${reservation.expiryDate.toISOString()}))`
757 )
758 }
759 await chargingStation.removeReservation(
760 reservation,
761 ReservationTerminationReason.TRANSACTION_STARTED
66a7748d 762 )
a095d7d7 763 } else {
90aceaf6 764 logger.warn(
56563a3c 765 `${chargingStation.logPrefix()} Reserved transaction ${
90aceaf6 766 payload.transactionId
a095d7d7 767 } started with unknown reservation ${requestPayload.reservationId}`
66a7748d 768 )
90aceaf6 769 }
d984c13f 770 }
66a7748d 771 chargingStation.stationInfo?.beginEndMeterValues === true &&
08f130a0 772 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
773 OCPP16MeterValuesRequest,
774 OCPP16MeterValuesResponse
08f130a0 775 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
d929adcc 776 connectorId,
ef6fa3fb 777 transactionId: payload.transactionId,
66a7748d
JB
778 meterValue: [connectorStatus.transactionBeginMeterValue]
779 } satisfies OCPP16MeterValuesRequest))
4ecff7ce
JB
780 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
781 chargingStation,
d929adcc 782 connectorId,
66a7748d
JB
783 OCPP16ChargePointStatus.Charging
784 )
e7aeea18 785 logger.info(
48847bc0
JB
786 `${chargingStation.logPrefix()} Transaction with id ${payload.transactionId} STARTED on ${
787 chargingStation.stationInfo?.chargingStationId
788 }#${connectorId} for idTag '${requestPayload.idTag}'`
66a7748d 789 )
5199f9fd
JB
790 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
791 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
792 ++chargingStation.powerDivider!
c0560973 793 }
f2d5e3d9
JB
794 const configuredMeterValueSampleInterval = getConfigurationKey(
795 chargingStation,
66a7748d
JB
796 OCPP16StandardParametersKey.MeterValueSampleInterval
797 )
08f130a0 798 chargingStation.startMeterValues(
d929adcc 799 connectorId,
a807045b 800 configuredMeterValueSampleInterval != null
be4c6702 801 ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
66a7748d
JB
802 : Constants.DEFAULT_METER_VALUES_INTERVAL
803 )
c0560973 804 } else {
e7aeea18 805 logger.warn(
d929adcc
JB
806 `${chargingStation.logPrefix()} Starting transaction with id ${
807 payload.transactionId
a223d9be
JB
808 } REJECTED on ${
809 chargingStation.stationInfo?.chargingStationId
810 }#${connectorId} with status '${payload.idTagInfo.status}', idTag '${
56563a3c
JB
811 requestPayload.idTag
812 }'${
d929adcc 813 OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, requestPayload.idTag)
56563a3c
JB
814 ? `, reservationId '${requestPayload.reservationId}'`
815 : ''
66a7748d
JB
816 }`
817 )
818 await this.resetConnectorOnStartTransactionError(chargingStation, connectorId)
a2653482
JB
819 }
820 }
821
66a7748d 822 private async resetConnectorOnStartTransactionError (
08f130a0 823 chargingStation: ChargingStation,
66a7748d 824 connectorId: number
08f130a0 825 ): Promise<void> {
a9671b9e 826 chargingStation.stopMeterValues(connectorId)
66a7748d 827 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
f938317f 828 resetConnectorStatus(connectorStatus)
a9671b9e 829 await OCPP16ServiceUtils.restoreConnectorStatus(chargingStation, connectorId, connectorStatus)
c0560973
JB
830 }
831
66a7748d 832 private async handleResponseStopTransaction (
08f130a0 833 chargingStation: ChargingStation,
e7aeea18 834 payload: OCPP16StopTransactionResponse,
66a7748d 835 requestPayload: OCPP16StopTransactionRequest
e7aeea18 836 ): Promise<void> {
08f130a0 837 const transactionConnectorId = chargingStation.getConnectorIdByTransactionId(
66a7748d
JB
838 requestPayload.transactionId
839 )
aa63c9b7 840 if (transactionConnectorId == null) {
e7aeea18 841 logger.error(
d929adcc
JB
842 `${chargingStation.logPrefix()} Trying to stop a non existing transaction with id ${
843 requestPayload.transactionId
66a7748d
JB
844 }`
845 )
846 return
c0560973 847 }
5398cecf 848 chargingStation.stationInfo?.beginEndMeterValues === true &&
5199f9fd
JB
849 chargingStation.stationInfo.ocppStrictCompliance === false &&
850 chargingStation.stationInfo.outOfOrderEndMeterValues === true &&
2cace1a5 851 (await chargingStation.ocppRequestService.requestHandler<
66a7748d
JB
852 OCPP16MeterValuesRequest,
853 OCPP16MeterValuesResponse
2cace1a5
JB
854 >(chargingStation, OCPP16RequestCommand.METER_VALUES, {
855 connectorId: transactionConnectorId,
856 transactionId: requestPayload.transactionId,
857 meterValue: [
858 OCPP16ServiceUtils.buildTransactionEndMeterValue(
859 chargingStation,
aa63c9b7 860 transactionConnectorId,
66a7748d
JB
861 requestPayload.meterStop
862 )
863 ]
864 }))
2cace1a5 865 if (
66a7748d 866 !chargingStation.isChargingStationAvailable() ||
aa63c9b7 867 !chargingStation.isConnectorAvailable(transactionConnectorId)
2cace1a5 868 ) {
4ecff7ce
JB
869 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
870 chargingStation,
aa63c9b7 871 transactionConnectorId,
66a7748d
JB
872 OCPP16ChargePointStatus.Unavailable
873 )
c0560973 874 } else {
4ecff7ce
JB
875 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
876 chargingStation,
aa63c9b7 877 transactionConnectorId,
66a7748d
JB
878 OCPP16ChargePointStatus.Available
879 )
2cace1a5 880 }
5199f9fd
JB
881 if (chargingStation.stationInfo?.powerSharedByConnectors === true) {
882 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
883 chargingStation.powerDivider!--
2cace1a5 884 }
f938317f 885 resetConnectorStatus(chargingStation.getConnectorStatus(transactionConnectorId))
aa63c9b7 886 chargingStation.stopMeterValues(transactionConnectorId)
d929adcc
JB
887 const logMsg = `${chargingStation.logPrefix()} Transaction with id ${
888 requestPayload.transactionId
a223d9be
JB
889 } STOPPED on ${
890 chargingStation.stationInfo?.chargingStationId
891 }#${transactionConnectorId} with status '${payload.idTagInfo?.status}'`
2cace1a5 892 if (
aa63c9b7 893 payload.idTagInfo == null ||
5199f9fd 894 payload.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
2cace1a5 895 ) {
66a7748d 896 logger.info(logMsg)
2cace1a5 897 } else {
66a7748d 898 logger.warn(logMsg)
c0560973
JB
899 }
900 }
c0560973 901}