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