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