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