test: improve ErrorUtils coverage
[e-mobility-charging-stations-simulator.git] / src / charging-station / ocpp / 1.6 / OCPP16IncomingRequestService.ts
1 // Partial Copyright Jerome Benoit. 2021-2024. All Rights Reserved.
2
3 import { randomInt } from 'node:crypto'
4 import { createWriteStream, readdirSync } from 'node:fs'
5 import { dirname, extname, join, resolve } from 'node:path'
6 import { fileURLToPath, URL } from 'node:url'
7
8 import type { ValidateFunction } from 'ajv'
9 import { Client, type FTPResponse } from 'basic-ftp'
10 import {
11 addSeconds,
12 differenceInSeconds,
13 type Interval,
14 isDate,
15 secondsToMilliseconds
16 } from 'date-fns'
17 import { maxTime } from 'date-fns/constants'
18 import { isEmpty } from 'rambda'
19 import { create } from 'tar'
20
21 import {
22 canProceedChargingProfile,
23 type ChargingStation,
24 checkChargingStation,
25 getConfigurationKey,
26 getConnectorChargingProfiles,
27 prepareChargingProfileKind,
28 removeExpiredReservations,
29 resetAuthorizeConnectorStatus,
30 setConfigurationKeyValue
31 } from '../../../charging-station/index.js'
32 import { OCPPError } from '../../../exception/index.js'
33 import {
34 type ChangeConfigurationRequest,
35 type ChangeConfigurationResponse,
36 ConfigurationSection,
37 ErrorType,
38 type GenericResponse,
39 GenericStatus,
40 type GetConfigurationRequest,
41 type GetConfigurationResponse,
42 type GetDiagnosticsRequest,
43 type GetDiagnosticsResponse,
44 type IncomingRequestHandler,
45 type JsonType,
46 type LogConfiguration,
47 OCPP16AuthorizationStatus,
48 OCPP16AvailabilityType,
49 type OCPP16BootNotificationRequest,
50 type OCPP16BootNotificationResponse,
51 type OCPP16CancelReservationRequest,
52 type OCPP16ChangeAvailabilityRequest,
53 type OCPP16ChangeAvailabilityResponse,
54 OCPP16ChargePointErrorCode,
55 OCPP16ChargePointStatus,
56 type OCPP16ChargingProfile,
57 OCPP16ChargingProfilePurposeType,
58 type OCPP16ChargingSchedule,
59 type OCPP16ClearCacheRequest,
60 type OCPP16ClearChargingProfileRequest,
61 type OCPP16ClearChargingProfileResponse,
62 type OCPP16DataTransferRequest,
63 type OCPP16DataTransferResponse,
64 OCPP16DataTransferVendorId,
65 OCPP16DiagnosticsStatus,
66 type OCPP16DiagnosticsStatusNotificationRequest,
67 type OCPP16DiagnosticsStatusNotificationResponse,
68 OCPP16FirmwareStatus,
69 type OCPP16FirmwareStatusNotificationRequest,
70 type OCPP16FirmwareStatusNotificationResponse,
71 type OCPP16GetCompositeScheduleRequest,
72 type OCPP16GetCompositeScheduleResponse,
73 type OCPP16HeartbeatRequest,
74 type OCPP16HeartbeatResponse,
75 OCPP16IncomingRequestCommand,
76 OCPP16MessageTrigger,
77 OCPP16RequestCommand,
78 type OCPP16ReserveNowRequest,
79 type OCPP16ReserveNowResponse,
80 OCPP16StandardParametersKey,
81 type OCPP16StartTransactionRequest,
82 type OCPP16StartTransactionResponse,
83 type OCPP16StatusNotificationRequest,
84 type OCPP16StatusNotificationResponse,
85 OCPP16StopTransactionReason,
86 OCPP16SupportedFeatureProfiles,
87 type OCPP16TriggerMessageRequest,
88 type OCPP16TriggerMessageResponse,
89 OCPP16TriggerMessageStatus,
90 type OCPP16UpdateFirmwareRequest,
91 type OCPP16UpdateFirmwareResponse,
92 type OCPPConfigurationKey,
93 OCPPVersion,
94 type RemoteStartTransactionRequest,
95 type RemoteStopTransactionRequest,
96 ReservationTerminationReason,
97 type ResetRequest,
98 type SetChargingProfileRequest,
99 type SetChargingProfileResponse,
100 type UnlockConnectorRequest,
101 type UnlockConnectorResponse
102 } from '../../../types/index.js'
103 import {
104 Configuration,
105 Constants,
106 convertToDate,
107 convertToInt,
108 formatDurationMilliSeconds,
109 handleIncomingRequestError,
110 isAsyncFunction,
111 isNotEmptyArray,
112 isNotEmptyString,
113 logger,
114 sleep
115 } from '../../../utils/index.js'
116 import { OCPPIncomingRequestService } from '../OCPPIncomingRequestService.js'
117 import { OCPP16Constants } from './OCPP16Constants.js'
118 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils.js'
119
120 const moduleName = 'OCPP16IncomingRequestService'
121
122 export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
123 protected payloadValidateFunctions: Map<OCPP16IncomingRequestCommand, ValidateFunction<JsonType>>
124
125 private readonly incomingRequestHandlers: Map<
126 OCPP16IncomingRequestCommand,
127 IncomingRequestHandler
128 >
129
130 public constructor () {
131 // if (new.target.name === moduleName) {
132 // throw new TypeError(`Cannot construct ${new.target.name} instances directly`)
133 // }
134 super(OCPPVersion.VERSION_16)
135 this.incomingRequestHandlers = new Map<OCPP16IncomingRequestCommand, IncomingRequestHandler>([
136 [
137 OCPP16IncomingRequestCommand.RESET,
138 this.handleRequestReset.bind(this) as unknown as IncomingRequestHandler
139 ],
140 [
141 OCPP16IncomingRequestCommand.CLEAR_CACHE,
142 this.handleRequestClearCache.bind(this) as IncomingRequestHandler
143 ],
144 [
145 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
146 this.handleRequestUnlockConnector.bind(this) as unknown as IncomingRequestHandler
147 ],
148 [
149 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
150 this.handleRequestGetConfiguration.bind(this) as IncomingRequestHandler
151 ],
152 [
153 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
154 this.handleRequestChangeConfiguration.bind(this) as unknown as IncomingRequestHandler
155 ],
156 [
157 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
158 this.handleRequestGetCompositeSchedule.bind(this) as unknown as IncomingRequestHandler
159 ],
160 [
161 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
162 this.handleRequestSetChargingProfile.bind(this) as unknown as IncomingRequestHandler
163 ],
164 [
165 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
166 this.handleRequestClearChargingProfile.bind(this) as IncomingRequestHandler
167 ],
168 [
169 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
170 this.handleRequestChangeAvailability.bind(this) as unknown as IncomingRequestHandler
171 ],
172 [
173 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
174 this.handleRequestRemoteStartTransaction.bind(this) as unknown as IncomingRequestHandler
175 ],
176 [
177 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
178 this.handleRequestRemoteStopTransaction.bind(this) as unknown as IncomingRequestHandler
179 ],
180 [
181 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
182 this.handleRequestGetDiagnostics.bind(this) as IncomingRequestHandler
183 ],
184 [
185 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
186 this.handleRequestTriggerMessage.bind(this) as unknown as IncomingRequestHandler
187 ],
188 [
189 OCPP16IncomingRequestCommand.DATA_TRANSFER,
190 this.handleRequestDataTransfer.bind(this) as unknown as IncomingRequestHandler
191 ],
192 [
193 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
194 this.handleRequestUpdateFirmware.bind(this) as unknown as IncomingRequestHandler
195 ],
196 [
197 OCPP16IncomingRequestCommand.RESERVE_NOW,
198 this.handleRequestReserveNow.bind(this) as unknown as IncomingRequestHandler
199 ],
200 [
201 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
202 this.handleRequestCancelReservation.bind(this) as unknown as IncomingRequestHandler
203 ]
204 ])
205 this.payloadValidateFunctions = new Map<
206 OCPP16IncomingRequestCommand,
207 ValidateFunction<JsonType>
208 >([
209 [
210 OCPP16IncomingRequestCommand.RESET,
211 this.ajv
212 .compile(
213 OCPP16ServiceUtils.parseJsonSchemaFile<ResetRequest>(
214 'assets/json-schemas/ocpp/1.6/Reset.json',
215 moduleName,
216 'constructor'
217 )
218 )
219 .bind(this)
220 ],
221 [
222 OCPP16IncomingRequestCommand.CLEAR_CACHE,
223 this.ajv
224 .compile(
225 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearCacheRequest>(
226 'assets/json-schemas/ocpp/1.6/ClearCache.json',
227 moduleName,
228 'constructor'
229 )
230 )
231 .bind(this)
232 ],
233 [
234 OCPP16IncomingRequestCommand.UNLOCK_CONNECTOR,
235 this.ajv
236 .compile(
237 OCPP16ServiceUtils.parseJsonSchemaFile<UnlockConnectorRequest>(
238 'assets/json-schemas/ocpp/1.6/UnlockConnector.json',
239 moduleName,
240 'constructor'
241 )
242 )
243 .bind(this)
244 ],
245 [
246 OCPP16IncomingRequestCommand.GET_CONFIGURATION,
247 this.ajv
248 .compile(
249 OCPP16ServiceUtils.parseJsonSchemaFile<GetConfigurationRequest>(
250 'assets/json-schemas/ocpp/1.6/GetConfiguration.json',
251 moduleName,
252 'constructor'
253 )
254 )
255 .bind(this)
256 ],
257 [
258 OCPP16IncomingRequestCommand.CHANGE_CONFIGURATION,
259 this.ajv
260 .compile(
261 OCPP16ServiceUtils.parseJsonSchemaFile<ChangeConfigurationRequest>(
262 'assets/json-schemas/ocpp/1.6/ChangeConfiguration.json',
263 moduleName,
264 'constructor'
265 )
266 )
267 .bind(this)
268 ],
269 [
270 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
271 this.ajv
272 .compile(
273 OCPP16ServiceUtils.parseJsonSchemaFile<GetDiagnosticsRequest>(
274 'assets/json-schemas/ocpp/1.6/GetDiagnostics.json',
275 moduleName,
276 'constructor'
277 )
278 )
279 .bind(this)
280 ],
281 [
282 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE,
283 this.ajv
284 .compile(
285 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16GetCompositeScheduleRequest>(
286 'assets/json-schemas/ocpp/1.6/GetCompositeSchedule.json',
287 moduleName,
288 'constructor'
289 )
290 )
291 .bind(this)
292 ],
293 [
294 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE,
295 this.ajv
296 .compile(
297 OCPP16ServiceUtils.parseJsonSchemaFile<SetChargingProfileRequest>(
298 'assets/json-schemas/ocpp/1.6/SetChargingProfile.json',
299 moduleName,
300 'constructor'
301 )
302 )
303 .bind(this)
304 ],
305 [
306 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE,
307 this.ajv
308 .compile(
309 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ClearChargingProfileRequest>(
310 'assets/json-schemas/ocpp/1.6/ClearChargingProfile.json',
311 moduleName,
312 'constructor'
313 )
314 )
315 .bind(this)
316 ],
317 [
318 OCPP16IncomingRequestCommand.CHANGE_AVAILABILITY,
319 this.ajv
320 .compile(
321 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ChangeAvailabilityRequest>(
322 'assets/json-schemas/ocpp/1.6/ChangeAvailability.json',
323 moduleName,
324 'constructor'
325 )
326 )
327 .bind(this)
328 ],
329 [
330 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
331 this.ajv
332 .compile(
333 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStartTransactionRequest>(
334 'assets/json-schemas/ocpp/1.6/RemoteStartTransaction.json',
335 moduleName,
336 'constructor'
337 )
338 )
339 .bind(this)
340 ],
341 [
342 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
343 this.ajv
344 .compile(
345 OCPP16ServiceUtils.parseJsonSchemaFile<RemoteStopTransactionRequest>(
346 'assets/json-schemas/ocpp/1.6/RemoteStopTransaction.json',
347 moduleName,
348 'constructor'
349 )
350 )
351 .bind(this)
352 ],
353 [
354 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
355 this.ajv
356 .compile(
357 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16TriggerMessageRequest>(
358 'assets/json-schemas/ocpp/1.6/TriggerMessage.json',
359 moduleName,
360 'constructor'
361 )
362 )
363 .bind(this)
364 ],
365 [
366 OCPP16IncomingRequestCommand.DATA_TRANSFER,
367 this.ajv
368 .compile(
369 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16DataTransferRequest>(
370 'assets/json-schemas/ocpp/1.6/DataTransfer.json',
371 moduleName,
372 'constructor'
373 )
374 )
375 .bind(this)
376 ],
377 [
378 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE,
379 this.ajv
380 .compile(
381 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16UpdateFirmwareRequest>(
382 'assets/json-schemas/ocpp/1.6/UpdateFirmware.json',
383 moduleName,
384 'constructor'
385 )
386 )
387 .bind(this)
388 ],
389 [
390 OCPP16IncomingRequestCommand.RESERVE_NOW,
391 this.ajv
392 .compile(
393 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
394 'assets/json-schemas/ocpp/1.6/ReserveNow.json',
395 moduleName,
396 'constructor'
397 )
398 )
399 .bind(this)
400 ],
401 [
402 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
403 this.ajv
404 .compile(
405 OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
406 'assets/json-schemas/ocpp/1.6/CancelReservation.json',
407 moduleName,
408 'constructor'
409 )
410 )
411 .bind(this)
412 ]
413 ])
414 // Handle incoming request events
415 this.on(
416 OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION,
417 (
418 chargingStation: ChargingStation,
419 request: RemoteStartTransactionRequest,
420 response: GenericResponse
421 ) => {
422 if (response.status === GenericStatus.Accepted) {
423 const { connectorId, idTag } = request
424 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
425 chargingStation.getConnectorStatus(connectorId!)!.transactionRemoteStarted = true
426 chargingStation.ocppRequestService
427 .requestHandler<Partial<OCPP16StartTransactionRequest>, OCPP16StartTransactionResponse>(
428 chargingStation,
429 OCPP16RequestCommand.START_TRANSACTION,
430 {
431 connectorId,
432 idTag
433 }
434 )
435 .then(response => {
436 if (response.idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED) {
437 logger.debug(
438 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
439 chargingStation.stationInfo?.chargingStationId
440 }#${connectorId} for idTag '${idTag}'`
441 )
442 } else {
443 logger.debug(
444 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
445 chargingStation.stationInfo?.chargingStationId
446 }#${connectorId} for idTag '${idTag}'`
447 )
448 }
449 })
450 .catch((error: unknown) => {
451 logger.error(
452 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote start transaction error:`,
453 error
454 )
455 })
456 }
457 }
458 )
459 this.on(
460 OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION,
461 (
462 chargingStation: ChargingStation,
463 request: RemoteStopTransactionRequest,
464 response: GenericResponse
465 ) => {
466 if (response.status === GenericStatus.Accepted) {
467 const { transactionId } = request
468 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
469 const connectorId = chargingStation.getConnectorIdByTransactionId(transactionId)!
470 OCPP16ServiceUtils.remoteStopTransaction(chargingStation, connectorId)
471 .then(response => {
472 if (response.status === GenericStatus.Accepted) {
473 logger.debug(
474 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED on ${
475 chargingStation.stationInfo?.chargingStationId
476 }#${connectorId} for transaction '${transactionId}'`
477 )
478 } else {
479 logger.debug(
480 `${chargingStation.logPrefix()} Remote stop transaction REJECTED on ${
481 chargingStation.stationInfo?.chargingStationId
482 }#${connectorId} for transaction '${transactionId}'`
483 )
484 }
485 })
486 .catch((error: unknown) => {
487 logger.error(
488 `${chargingStation.logPrefix()} ${moduleName}.constructor: Remote stop transaction error:`,
489 error
490 )
491 })
492 }
493 }
494 )
495 this.on(
496 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
497 (
498 chargingStation: ChargingStation,
499 request: OCPP16TriggerMessageRequest,
500 response: OCPP16TriggerMessageResponse
501 ) => {
502 if (response.status !== OCPP16TriggerMessageStatus.ACCEPTED) {
503 return
504 }
505 const { requestedMessage, connectorId } = request
506 const errorHandler = (error: unknown): void => {
507 logger.error(
508 `${chargingStation.logPrefix()} ${moduleName}.constructor: Trigger ${requestedMessage} error:`,
509 error
510 )
511 }
512 switch (requestedMessage) {
513 case OCPP16MessageTrigger.BootNotification:
514 chargingStation.ocppRequestService
515 .requestHandler<
516 OCPP16BootNotificationRequest,
517 OCPP16BootNotificationResponse
518 >(chargingStation, OCPP16RequestCommand.BOOT_NOTIFICATION, chargingStation.bootNotificationRequest as OCPP16BootNotificationRequest, { skipBufferingOnError: true, triggerMessage: true })
519 .catch(errorHandler)
520 break
521 case OCPP16MessageTrigger.Heartbeat:
522 chargingStation.ocppRequestService
523 .requestHandler<OCPP16HeartbeatRequest, OCPP16HeartbeatResponse>(
524 chargingStation,
525 OCPP16RequestCommand.HEARTBEAT,
526 undefined,
527 {
528 triggerMessage: true
529 }
530 )
531 .catch(errorHandler)
532 break
533 case OCPP16MessageTrigger.StatusNotification:
534 if (connectorId != null) {
535 chargingStation.ocppRequestService
536 .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
537 chargingStation,
538 OCPP16RequestCommand.STATUS_NOTIFICATION,
539 {
540 connectorId,
541 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
542 status: chargingStation.getConnectorStatus(connectorId)
543 ?.status as OCPP16ChargePointStatus
544 },
545 {
546 triggerMessage: true
547 }
548 )
549 .catch(errorHandler)
550 } else if (chargingStation.hasEvses) {
551 for (const evseStatus of chargingStation.evses.values()) {
552 for (const [id, connectorStatus] of evseStatus.connectors) {
553 chargingStation.ocppRequestService
554 .requestHandler<
555 OCPP16StatusNotificationRequest,
556 OCPP16StatusNotificationResponse
557 >(
558 chargingStation,
559 OCPP16RequestCommand.STATUS_NOTIFICATION,
560 {
561 connectorId: id,
562 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
563 status: connectorStatus.status as OCPP16ChargePointStatus
564 },
565 {
566 triggerMessage: true
567 }
568 )
569 .catch(errorHandler)
570 }
571 }
572 } else {
573 for (const [id, connectorStatus] of chargingStation.connectors) {
574 chargingStation.ocppRequestService
575 .requestHandler<
576 OCPP16StatusNotificationRequest,
577 OCPP16StatusNotificationResponse
578 >(
579 chargingStation,
580 OCPP16RequestCommand.STATUS_NOTIFICATION,
581 {
582 connectorId: id,
583 errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
584 status: connectorStatus.status as OCPP16ChargePointStatus
585 },
586 {
587 triggerMessage: true
588 }
589 )
590 .catch(errorHandler)
591 }
592 }
593 break
594 }
595 }
596 )
597 this.validatePayload = this.validatePayload.bind(this)
598 }
599
600 public async incomingRequestHandler<ReqType extends JsonType, ResType extends JsonType>(
601 chargingStation: ChargingStation,
602 messageId: string,
603 commandName: OCPP16IncomingRequestCommand,
604 commandPayload: ReqType
605 ): Promise<void> {
606 let response: ResType
607 if (
608 chargingStation.stationInfo?.ocppStrictCompliance === true &&
609 chargingStation.inPendingState() &&
610 (commandName === OCPP16IncomingRequestCommand.REMOTE_START_TRANSACTION ||
611 commandName === OCPP16IncomingRequestCommand.REMOTE_STOP_TRANSACTION)
612 ) {
613 throw new OCPPError(
614 ErrorType.SECURITY_ERROR,
615 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
616 commandPayload,
617 undefined,
618 2
619 )} while the charging station is in pending state on the central server`,
620 commandName,
621 commandPayload
622 )
623 }
624 if (
625 chargingStation.isRegistered() ||
626 (chargingStation.stationInfo?.ocppStrictCompliance === false &&
627 chargingStation.inUnknownState())
628 ) {
629 if (
630 this.incomingRequestHandlers.has(commandName) &&
631 OCPP16ServiceUtils.isIncomingRequestCommandSupported(chargingStation, commandName)
632 ) {
633 try {
634 this.validatePayload(chargingStation, commandName, commandPayload)
635 // Call the method to build the response
636 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
637 const incomingRequestHandler = this.incomingRequestHandlers.get(commandName)!
638 if (isAsyncFunction(incomingRequestHandler)) {
639 response = (await incomingRequestHandler(chargingStation, commandPayload)) as ResType
640 } else {
641 response = incomingRequestHandler(chargingStation, commandPayload) as ResType
642 }
643 } catch (error) {
644 // Log
645 logger.error(
646 `${chargingStation.logPrefix()} ${moduleName}.incomingRequestHandler: Handle incoming request error:`,
647 error
648 )
649 throw error
650 }
651 } else {
652 // Throw exception
653 throw new OCPPError(
654 ErrorType.NOT_IMPLEMENTED,
655 `${commandName} is not implemented to handle request PDU ${JSON.stringify(
656 commandPayload,
657 undefined,
658 2
659 )}`,
660 commandName,
661 commandPayload
662 )
663 }
664 } else {
665 throw new OCPPError(
666 ErrorType.SECURITY_ERROR,
667 `${commandName} cannot be issued to handle request PDU ${JSON.stringify(
668 commandPayload,
669 undefined,
670 2
671 )} while the charging station is not registered on the central server`,
672 commandName,
673 commandPayload
674 )
675 }
676 // Send the built response
677 await chargingStation.ocppRequestService.sendResponse(
678 chargingStation,
679 messageId,
680 response,
681 commandName
682 )
683 // Emit command name event to allow delayed handling
684 this.emit(commandName, chargingStation, commandPayload, response)
685 }
686
687 private validatePayload (
688 chargingStation: ChargingStation,
689 commandName: OCPP16IncomingRequestCommand,
690 commandPayload: JsonType
691 ): boolean {
692 if (this.payloadValidateFunctions.has(commandName)) {
693 return this.validateIncomingRequestPayload(chargingStation, commandName, commandPayload)
694 }
695 logger.warn(
696 `${chargingStation.logPrefix()} ${moduleName}.validatePayload: No JSON schema validation function found for command '${commandName}' PDU validation`
697 )
698 return false
699 }
700
701 // Simulate charging station restart
702 private handleRequestReset (
703 chargingStation: ChargingStation,
704 commandPayload: ResetRequest
705 ): GenericResponse {
706 const { type } = commandPayload
707 chargingStation
708 .reset(`${type}Reset` as OCPP16StopTransactionReason)
709 .catch(Constants.EMPTY_FUNCTION)
710 logger.info(
711 `${chargingStation.logPrefix()} ${type} reset command received, simulating it. The station will be back online in ${formatDurationMilliSeconds(
712 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
713 chargingStation.stationInfo!.resetTime!
714 )}`
715 )
716 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
717 }
718
719 private async handleRequestUnlockConnector (
720 chargingStation: ChargingStation,
721 commandPayload: UnlockConnectorRequest
722 ): Promise<UnlockConnectorResponse> {
723 const { connectorId } = commandPayload
724 if (!chargingStation.hasConnector(connectorId)) {
725 logger.error(
726 `${chargingStation.logPrefix()} Trying to unlock a non existing connector id ${connectorId}`
727 )
728 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
729 }
730 if (connectorId === 0) {
731 logger.error(`${chargingStation.logPrefix()} Trying to unlock connector id ${connectorId}`)
732 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_NOT_SUPPORTED
733 }
734 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
735 const stopResponse = await chargingStation.stopTransactionOnConnector(
736 connectorId,
737 OCPP16StopTransactionReason.UNLOCK_COMMAND
738 )
739 if (stopResponse.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
740 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
741 }
742 return OCPP16Constants.OCPP_RESPONSE_UNLOCK_FAILED
743 }
744 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
745 chargingStation,
746 connectorId,
747 OCPP16ChargePointStatus.Available
748 )
749 return OCPP16Constants.OCPP_RESPONSE_UNLOCKED
750 }
751
752 private handleRequestGetConfiguration (
753 chargingStation: ChargingStation,
754 commandPayload: GetConfigurationRequest
755 ): GetConfigurationResponse {
756 const { key } = commandPayload
757 const configurationKey: OCPPConfigurationKey[] = []
758 const unknownKey: string[] = []
759 if (key == null) {
760 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
761 for (const configKey of chargingStation.ocppConfiguration!.configurationKey!) {
762 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(configKey)) {
763 continue
764 }
765 configurationKey.push({
766 key: configKey.key,
767 readonly: configKey.readonly,
768 value: configKey.value
769 })
770 }
771 } else if (isNotEmptyArray(key)) {
772 for (const k of key) {
773 const keyFound = getConfigurationKey(chargingStation, k, true)
774 if (keyFound != null) {
775 if (!OCPP16ServiceUtils.isConfigurationKeyVisible(keyFound)) {
776 continue
777 }
778 configurationKey.push({
779 key: keyFound.key,
780 readonly: keyFound.readonly,
781 value: keyFound.value
782 })
783 } else {
784 unknownKey.push(k)
785 }
786 }
787 }
788 return {
789 configurationKey,
790 unknownKey
791 }
792 }
793
794 private handleRequestChangeConfiguration (
795 chargingStation: ChargingStation,
796 commandPayload: ChangeConfigurationRequest
797 ): ChangeConfigurationResponse {
798 const { key, value } = commandPayload
799 const keyToChange = getConfigurationKey(chargingStation, key, true)
800 if (keyToChange?.readonly === true) {
801 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REJECTED
802 } else if (keyToChange?.readonly === false) {
803 let valueChanged = false
804 if (keyToChange.value !== value) {
805 setConfigurationKeyValue(chargingStation, key, value, true)
806 valueChanged = true
807 }
808 let triggerHeartbeatRestart = false
809 if (
810 (keyToChange.key as OCPP16StandardParametersKey) ===
811 OCPP16StandardParametersKey.HeartBeatInterval &&
812 valueChanged
813 ) {
814 setConfigurationKeyValue(
815 chargingStation,
816 OCPP16StandardParametersKey.HeartbeatInterval,
817 value
818 )
819 triggerHeartbeatRestart = true
820 }
821 if (
822 (keyToChange.key as OCPP16StandardParametersKey) ===
823 OCPP16StandardParametersKey.HeartbeatInterval &&
824 valueChanged
825 ) {
826 setConfigurationKeyValue(
827 chargingStation,
828 OCPP16StandardParametersKey.HeartBeatInterval,
829 value
830 )
831 triggerHeartbeatRestart = true
832 }
833 if (triggerHeartbeatRestart) {
834 chargingStation.restartHeartbeat()
835 }
836 if (
837 (keyToChange.key as OCPP16StandardParametersKey) ===
838 OCPP16StandardParametersKey.WebSocketPingInterval &&
839 valueChanged
840 ) {
841 chargingStation.restartWebSocketPing()
842 }
843 if (keyToChange.reboot === true) {
844 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
845 }
846 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
847 }
848 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
849 }
850
851 private handleRequestSetChargingProfile (
852 chargingStation: ChargingStation,
853 commandPayload: SetChargingProfileRequest
854 ): SetChargingProfileResponse {
855 if (
856 !OCPP16ServiceUtils.checkFeatureProfile(
857 chargingStation,
858 OCPP16SupportedFeatureProfiles.SmartCharging,
859 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
860 )
861 ) {
862 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
863 }
864 const { connectorId, csChargingProfiles } = commandPayload
865 if (!chargingStation.hasConnector(connectorId)) {
866 logger.error(
867 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
868 )
869 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
870 }
871 if (
872 csChargingProfiles.chargingProfilePurpose ===
873 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
874 connectorId !== 0
875 ) {
876 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
877 }
878 if (
879 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
880 connectorId === 0
881 ) {
882 logger.error(
883 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
884 )
885 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
886 }
887 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
888 if (
889 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
890 connectorId > 0 &&
891 connectorStatus?.transactionStarted === false
892 ) {
893 logger.error(
894 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
895 )
896 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
897 }
898 if (
899 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
900 connectorId > 0 &&
901 connectorStatus?.transactionStarted === true &&
902 csChargingProfiles.transactionId !== connectorStatus.transactionId
903 ) {
904 logger.error(
905 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
906 csChargingProfiles.transactionId
907 } than the started transaction id ${connectorStatus.transactionId}`
908 )
909 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
910 }
911 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
912 logger.debug(
913 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
914 csChargingProfiles
915 )
916 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
917 }
918
919 private handleRequestGetCompositeSchedule (
920 chargingStation: ChargingStation,
921 commandPayload: OCPP16GetCompositeScheduleRequest
922 ): OCPP16GetCompositeScheduleResponse {
923 if (
924 !OCPP16ServiceUtils.checkFeatureProfile(
925 chargingStation,
926 OCPP16SupportedFeatureProfiles.SmartCharging,
927 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
928 )
929 ) {
930 return OCPP16Constants.OCPP_RESPONSE_REJECTED
931 }
932 const { connectorId, duration, chargingRateUnit } = commandPayload
933 if (!chargingStation.hasConnector(connectorId)) {
934 logger.error(
935 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
936 )
937 return OCPP16Constants.OCPP_RESPONSE_REJECTED
938 }
939 if (connectorId === 0) {
940 logger.error(
941 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
942 )
943 return OCPP16Constants.OCPP_RESPONSE_REJECTED
944 }
945 if (chargingRateUnit != null) {
946 logger.warn(
947 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
948 )
949 }
950 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
951 if (
952 isEmpty(connectorStatus?.chargingProfiles) &&
953 isEmpty(chargingStation.getConnectorStatus(0)?.chargingProfiles)
954 ) {
955 return OCPP16Constants.OCPP_RESPONSE_REJECTED
956 }
957 const currentDate = new Date()
958 const compositeScheduleInterval: Interval = {
959 start: currentDate,
960 end: addSeconds(currentDate, duration)
961 }
962 // Get charging profiles sorted by connector id then stack level
963 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
964 chargingStation,
965 connectorId
966 )
967 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
968 let compositeSchedule: OCPP16ChargingSchedule | undefined
969 for (const chargingProfile of chargingProfiles) {
970 if (chargingProfile.chargingSchedule.startSchedule == null) {
971 logger.debug(
972 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
973 chargingProfile.chargingProfileId
974 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
975 )
976 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
977 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
978 }
979 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
980 logger.warn(
981 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
982 chargingProfile.chargingProfileId
983 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
984 )
985 chargingProfile.chargingSchedule.startSchedule = convertToDate(
986 chargingProfile.chargingSchedule.startSchedule
987 )
988 }
989 if (chargingProfile.chargingSchedule.duration == null) {
990 logger.debug(
991 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
992 chargingProfile.chargingProfileId
993 } has no duration defined and will be set to the maximum time allowed`
994 )
995 // OCPP specifies that if duration is not defined, it should be infinite
996 chargingProfile.chargingSchedule.duration = differenceInSeconds(
997 maxTime,
998 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
999 chargingProfile.chargingSchedule.startSchedule!
1000 )
1001 }
1002 if (
1003 !prepareChargingProfileKind(
1004 connectorStatus,
1005 chargingProfile,
1006 compositeScheduleInterval.start,
1007 chargingStation.logPrefix()
1008 )
1009 ) {
1010 continue
1011 }
1012 if (
1013 !canProceedChargingProfile(
1014 chargingProfile,
1015 compositeScheduleInterval.start,
1016 chargingStation.logPrefix()
1017 )
1018 ) {
1019 continue
1020 }
1021 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
1022 previousCompositeSchedule,
1023 chargingProfile.chargingSchedule,
1024 compositeScheduleInterval
1025 )
1026 previousCompositeSchedule = compositeSchedule
1027 }
1028 if (compositeSchedule != null) {
1029 return {
1030 status: GenericStatus.Accepted,
1031 scheduleStart: compositeSchedule.startSchedule,
1032 connectorId,
1033 chargingSchedule: compositeSchedule
1034 }
1035 }
1036 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1037 }
1038
1039 private handleRequestClearChargingProfile (
1040 chargingStation: ChargingStation,
1041 commandPayload: OCPP16ClearChargingProfileRequest
1042 ): OCPP16ClearChargingProfileResponse {
1043 if (
1044 !OCPP16ServiceUtils.checkFeatureProfile(
1045 chargingStation,
1046 OCPP16SupportedFeatureProfiles.SmartCharging,
1047 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
1048 )
1049 ) {
1050 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1051 }
1052 const { connectorId } = commandPayload
1053 if (connectorId != null) {
1054 if (!chargingStation.hasConnector(connectorId)) {
1055 logger.error(
1056 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
1057 )
1058 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1059 }
1060 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1061 if (isNotEmptyArray(connectorStatus?.chargingProfiles)) {
1062 connectorStatus.chargingProfiles = []
1063 logger.debug(
1064 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
1065 )
1066 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1067 }
1068 } else {
1069 let clearedCP = false
1070 if (chargingStation.hasEvses) {
1071 for (const evseStatus of chargingStation.evses.values()) {
1072 for (const status of evseStatus.connectors.values()) {
1073 const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
1074 chargingStation,
1075 commandPayload,
1076 status.chargingProfiles
1077 )
1078 if (clearedConnectorCP && !clearedCP) {
1079 clearedCP = true
1080 }
1081 }
1082 }
1083 } else {
1084 for (const id of chargingStation.connectors.keys()) {
1085 const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
1086 chargingStation,
1087 commandPayload,
1088 chargingStation.getConnectorStatus(id)?.chargingProfiles
1089 )
1090 if (clearedConnectorCP && !clearedCP) {
1091 clearedCP = true
1092 }
1093 }
1094 }
1095 if (clearedCP) {
1096 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1097 }
1098 }
1099 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1100 }
1101
1102 private async handleRequestChangeAvailability (
1103 chargingStation: ChargingStation,
1104 commandPayload: OCPP16ChangeAvailabilityRequest
1105 ): Promise<OCPP16ChangeAvailabilityResponse> {
1106 const { connectorId, type } = commandPayload
1107 if (!chargingStation.hasConnector(connectorId)) {
1108 logger.error(
1109 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
1110 )
1111 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
1112 }
1113 const chargePointStatus: OCPP16ChargePointStatus =
1114 type === OCPP16AvailabilityType.Operative
1115 ? OCPP16ChargePointStatus.Available
1116 : OCPP16ChargePointStatus.Unavailable
1117 if (connectorId === 0) {
1118 let response: OCPP16ChangeAvailabilityResponse | undefined
1119 if (chargingStation.hasEvses) {
1120 for (const evseStatus of chargingStation.evses.values()) {
1121 response = await OCPP16ServiceUtils.changeAvailability(
1122 chargingStation,
1123 [...evseStatus.connectors.keys()],
1124 chargePointStatus,
1125 type
1126 )
1127 }
1128 } else {
1129 response = await OCPP16ServiceUtils.changeAvailability(
1130 chargingStation,
1131 [...chargingStation.connectors.keys()],
1132 chargePointStatus,
1133 type
1134 )
1135 }
1136 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1137 return response!
1138 } else if (
1139 connectorId > 0 &&
1140 (chargingStation.isChargingStationAvailable() ||
1141 (!chargingStation.isChargingStationAvailable() &&
1142 type === OCPP16AvailabilityType.Inoperative))
1143 ) {
1144 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
1145 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1146 chargingStation.getConnectorStatus(connectorId)!.availability = type
1147 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
1148 }
1149 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1150 chargingStation.getConnectorStatus(connectorId)!.availability = type
1151 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1152 chargingStation,
1153 connectorId,
1154 chargePointStatus
1155 )
1156 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
1157 }
1158 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
1159 }
1160
1161 private async handleRequestRemoteStartTransaction (
1162 chargingStation: ChargingStation,
1163 commandPayload: RemoteStartTransactionRequest
1164 ): Promise<GenericResponse> {
1165 if (commandPayload.connectorId == null) {
1166 for (
1167 let connectorId = 1;
1168 connectorId <= chargingStation.getNumberOfConnectors();
1169 connectorId++
1170 ) {
1171 if (
1172 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false &&
1173 !OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, commandPayload.idTag)
1174 ) {
1175 commandPayload.connectorId = connectorId
1176 break
1177 }
1178 }
1179 if (commandPayload.connectorId == null) {
1180 logger.debug(
1181 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1182 chargingStation.stationInfo?.chargingStationId
1183 }, idTag '${commandPayload.idTag}': no available connector found`
1184 )
1185 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1186 }
1187 }
1188 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1189 if (!chargingStation.hasConnector(transactionConnectorId)) {
1190 return this.notifyRemoteStartTransactionRejected(
1191 chargingStation,
1192 transactionConnectorId,
1193 idTag
1194 )
1195 }
1196 if (
1197 !chargingStation.isChargingStationAvailable() ||
1198 !chargingStation.isConnectorAvailable(transactionConnectorId)
1199 ) {
1200 return this.notifyRemoteStartTransactionRejected(
1201 chargingStation,
1202 transactionConnectorId,
1203 idTag
1204 )
1205 }
1206 // idTag authorization check required
1207 if (
1208 chargingStation.getAuthorizeRemoteTxRequests() &&
1209 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
1210 ) {
1211 return this.notifyRemoteStartTransactionRejected(
1212 chargingStation,
1213 transactionConnectorId,
1214 idTag
1215 )
1216 }
1217 if (
1218 chargingProfile != null &&
1219 !this.setRemoteStartTransactionChargingProfile(
1220 chargingStation,
1221 transactionConnectorId,
1222 chargingProfile
1223 )
1224 ) {
1225 return this.notifyRemoteStartTransactionRejected(
1226 chargingStation,
1227 transactionConnectorId,
1228 idTag
1229 )
1230 }
1231 logger.debug(
1232 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
1233 chargingStation.stationInfo?.chargingStationId
1234 }#${transactionConnectorId}}, idTag '${idTag}'`
1235 )
1236 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1237 }
1238
1239 private notifyRemoteStartTransactionRejected (
1240 chargingStation: ChargingStation,
1241 connectorId: number,
1242 idTag: string
1243 ): GenericResponse {
1244 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1245 logger.debug(
1246 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1247 chargingStation.stationInfo?.chargingStationId
1248 }#${connectorId}, idTag '${idTag}', availability '${
1249 connectorStatus?.availability
1250 }', status '${connectorStatus?.status}'`
1251 )
1252 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1253 }
1254
1255 private setRemoteStartTransactionChargingProfile (
1256 chargingStation: ChargingStation,
1257 connectorId: number,
1258 chargingProfile: OCPP16ChargingProfile
1259 ): boolean {
1260 if (chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE) {
1261 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1262 logger.debug(
1263 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${
1264 chargingStation.stationInfo?.chargingStationId
1265 }#${connectorId}`,
1266 chargingProfile
1267 )
1268 return true
1269 }
1270 logger.debug(
1271 `${chargingStation.logPrefix()} Not allowed to set ${
1272 chargingProfile.chargingProfilePurpose
1273 } charging profile(s) at remote start transaction`
1274 )
1275 return false
1276 }
1277
1278 private handleRequestRemoteStopTransaction (
1279 chargingStation: ChargingStation,
1280 commandPayload: RemoteStopTransactionRequest
1281 ): GenericResponse {
1282 const { transactionId } = commandPayload
1283 if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
1284 logger.debug(
1285 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
1286 )
1287 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1288 }
1289 logger.debug(
1290 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
1291 )
1292 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1293 }
1294
1295 private handleRequestUpdateFirmware (
1296 chargingStation: ChargingStation,
1297 commandPayload: OCPP16UpdateFirmwareRequest
1298 ): OCPP16UpdateFirmwareResponse {
1299 if (
1300 !OCPP16ServiceUtils.checkFeatureProfile(
1301 chargingStation,
1302 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1303 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1304 )
1305 ) {
1306 logger.warn(
1307 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1308 )
1309 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1310 }
1311 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1312 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1313 const { retrieveDate } = commandPayload
1314 if (
1315 chargingStation.stationInfo?.firmwareStatus != null &&
1316 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1317 ) {
1318 logger.warn(
1319 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1320 )
1321 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1322 }
1323 const now = Date.now()
1324 if (retrieveDate.getTime() <= now) {
1325 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1326 } else {
1327 setTimeout(() => {
1328 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1329 }, retrieveDate.getTime() - now)
1330 }
1331 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1332 }
1333
1334 private async updateFirmwareSimulation (
1335 chargingStation: ChargingStation,
1336 maxDelay = 30,
1337 minDelay = 15
1338 ): Promise<void> {
1339 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1340 return
1341 }
1342 if (chargingStation.hasEvses) {
1343 for (const [evseId, evseStatus] of chargingStation.evses) {
1344 if (evseId > 0) {
1345 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1346 if (connectorStatus.transactionStarted === false) {
1347 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1348 chargingStation,
1349 connectorId,
1350 OCPP16ChargePointStatus.Unavailable
1351 )
1352 }
1353 }
1354 }
1355 }
1356 } else {
1357 for (const connectorId of chargingStation.connectors.keys()) {
1358 if (
1359 connectorId > 0 &&
1360 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1361 ) {
1362 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1363 chargingStation,
1364 connectorId,
1365 OCPP16ChargePointStatus.Unavailable
1366 )
1367 }
1368 }
1369 }
1370 await chargingStation.ocppRequestService.requestHandler<
1371 OCPP16FirmwareStatusNotificationRequest,
1372 OCPP16FirmwareStatusNotificationResponse
1373 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1374 status: OCPP16FirmwareStatus.Downloading
1375 })
1376 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1377 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1378 if (
1379 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1380 OCPP16FirmwareStatus.DownloadFailed
1381 ) {
1382 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1383 await chargingStation.ocppRequestService.requestHandler<
1384 OCPP16FirmwareStatusNotificationRequest,
1385 OCPP16FirmwareStatusNotificationResponse
1386 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1387 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1388 })
1389 chargingStation.stationInfo.firmwareStatus =
1390 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1391 return
1392 }
1393 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1394 await chargingStation.ocppRequestService.requestHandler<
1395 OCPP16FirmwareStatusNotificationRequest,
1396 OCPP16FirmwareStatusNotificationResponse
1397 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1398 status: OCPP16FirmwareStatus.Downloaded
1399 })
1400 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1401 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1402 let wasTransactionsStarted = false
1403 let transactionsStarted: boolean
1404 do {
1405 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1406 if (runningTransactions > 0) {
1407 const waitTime = secondsToMilliseconds(15)
1408 logger.debug(
1409 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1410 waitTime
1411 )} before continuing firmware update simulation`
1412 )
1413 await sleep(waitTime)
1414 transactionsStarted = true
1415 wasTransactionsStarted = true
1416 } else {
1417 if (chargingStation.hasEvses) {
1418 for (const [evseId, evseStatus] of chargingStation.evses) {
1419 if (evseId > 0) {
1420 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1421 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1422 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1423 chargingStation,
1424 connectorId,
1425 OCPP16ChargePointStatus.Unavailable
1426 )
1427 }
1428 }
1429 }
1430 }
1431 } else {
1432 for (const connectorId of chargingStation.connectors.keys()) {
1433 if (
1434 connectorId > 0 &&
1435 chargingStation.getConnectorStatus(connectorId)?.status !==
1436 OCPP16ChargePointStatus.Unavailable
1437 ) {
1438 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1439 chargingStation,
1440 connectorId,
1441 OCPP16ChargePointStatus.Unavailable
1442 )
1443 }
1444 }
1445 }
1446 transactionsStarted = false
1447 }
1448 } while (transactionsStarted)
1449 !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
1450 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1451 return
1452 }
1453 await chargingStation.ocppRequestService.requestHandler<
1454 OCPP16FirmwareStatusNotificationRequest,
1455 OCPP16FirmwareStatusNotificationResponse
1456 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1457 status: OCPP16FirmwareStatus.Installing
1458 })
1459 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1460 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1461 if (
1462 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1463 OCPP16FirmwareStatus.InstallationFailed
1464 ) {
1465 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1466 await chargingStation.ocppRequestService.requestHandler<
1467 OCPP16FirmwareStatusNotificationRequest,
1468 OCPP16FirmwareStatusNotificationResponse
1469 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1470 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1471 })
1472 chargingStation.stationInfo.firmwareStatus =
1473 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1474 return
1475 }
1476 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1477 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1478 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1479 }
1480 }
1481
1482 private async handleRequestGetDiagnostics (
1483 chargingStation: ChargingStation,
1484 commandPayload: GetDiagnosticsRequest
1485 ): Promise<GetDiagnosticsResponse> {
1486 if (
1487 !OCPP16ServiceUtils.checkFeatureProfile(
1488 chargingStation,
1489 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1490 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1491 )
1492 ) {
1493 logger.warn(
1494 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1495 )
1496 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1497 }
1498 const { location } = commandPayload
1499 const uri = new URL(location)
1500 if (uri.protocol.startsWith('ftp:')) {
1501 let ftpClient: Client | undefined
1502 try {
1503 const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
1504 ConfigurationSection.log
1505 )
1506 const logFiles = readdirSync(
1507 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1508 resolve((fileURLToPath(import.meta.url), '../', dirname(logConfiguration.file!)))
1509 )
1510 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1511 .filter(file => file.endsWith(extname(logConfiguration.file!)))
1512 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1513 .map(file => join(dirname(logConfiguration.file!), file))
1514 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1515 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1516 ftpClient = new Client()
1517 const accessResponse = await ftpClient.access({
1518 host: uri.hostname,
1519 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1520 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1521 ...(isNotEmptyString(uri.password) && { password: uri.password })
1522 })
1523 let uploadResponse: FTPResponse | undefined
1524 if (accessResponse.code === 220) {
1525 ftpClient.trackProgress(info => {
1526 logger.info(
1527 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1528 info.bytes / 1024
1529 } bytes transferred from diagnostics archive ${info.name}`
1530 )
1531 chargingStation.ocppRequestService
1532 .requestHandler<
1533 OCPP16DiagnosticsStatusNotificationRequest,
1534 OCPP16DiagnosticsStatusNotificationResponse
1535 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1536 status: OCPP16DiagnosticsStatus.Uploading
1537 })
1538 .catch((error: unknown) => {
1539 logger.error(
1540 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1541 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1542 }'`,
1543 error
1544 )
1545 })
1546 })
1547 uploadResponse = await ftpClient.uploadFrom(
1548 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1549 `${uri.pathname}${diagnosticsArchive}`
1550 )
1551 if (uploadResponse.code === 226) {
1552 await chargingStation.ocppRequestService.requestHandler<
1553 OCPP16DiagnosticsStatusNotificationRequest,
1554 OCPP16DiagnosticsStatusNotificationResponse
1555 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1556 status: OCPP16DiagnosticsStatus.Uploaded
1557 })
1558 ftpClient.close()
1559 return { fileName: diagnosticsArchive }
1560 }
1561 throw new OCPPError(
1562 ErrorType.GENERIC_ERROR,
1563 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1564 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1565 )
1566 }
1567 throw new OCPPError(
1568 ErrorType.GENERIC_ERROR,
1569 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1570 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1571 )
1572 } catch (error) {
1573 await chargingStation.ocppRequestService.requestHandler<
1574 OCPP16DiagnosticsStatusNotificationRequest,
1575 OCPP16DiagnosticsStatusNotificationResponse
1576 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1577 status: OCPP16DiagnosticsStatus.UploadFailed
1578 })
1579 ftpClient?.close()
1580 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1581 return handleIncomingRequestError<GetDiagnosticsResponse>(
1582 chargingStation,
1583 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1584 error as Error,
1585 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1586 )!
1587 }
1588 } else {
1589 logger.error(
1590 `${chargingStation.logPrefix()} Unsupported protocol ${
1591 uri.protocol
1592 } to transfer the diagnostic logs archive`
1593 )
1594 await chargingStation.ocppRequestService.requestHandler<
1595 OCPP16DiagnosticsStatusNotificationRequest,
1596 OCPP16DiagnosticsStatusNotificationResponse
1597 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1598 status: OCPP16DiagnosticsStatus.UploadFailed
1599 })
1600 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1601 }
1602 }
1603
1604 private handleRequestTriggerMessage (
1605 chargingStation: ChargingStation,
1606 commandPayload: OCPP16TriggerMessageRequest
1607 ): OCPP16TriggerMessageResponse {
1608 const { requestedMessage, connectorId } = commandPayload
1609 if (
1610 !OCPP16ServiceUtils.checkFeatureProfile(
1611 chargingStation,
1612 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1613 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1614 ) ||
1615 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1616 ) {
1617 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1618 }
1619 if (
1620 !OCPP16ServiceUtils.isConnectorIdValid(
1621 chargingStation,
1622 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1623 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1624 connectorId!
1625 )
1626 ) {
1627 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1628 }
1629 switch (requestedMessage) {
1630 case OCPP16MessageTrigger.BootNotification:
1631 case OCPP16MessageTrigger.Heartbeat:
1632 case OCPP16MessageTrigger.StatusNotification:
1633 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1634 default:
1635 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1636 }
1637 }
1638
1639 private handleRequestDataTransfer (
1640 chargingStation: ChargingStation,
1641 commandPayload: OCPP16DataTransferRequest
1642 ): OCPP16DataTransferResponse {
1643 const { vendorId } = commandPayload
1644 try {
1645 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1646 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1647 }
1648 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1649 } catch (error) {
1650 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1651 return handleIncomingRequestError<OCPP16DataTransferResponse>(
1652 chargingStation,
1653 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1654 error as Error,
1655 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1656 )!
1657 }
1658 }
1659
1660 private async handleRequestReserveNow (
1661 chargingStation: ChargingStation,
1662 commandPayload: OCPP16ReserveNowRequest
1663 ): Promise<OCPP16ReserveNowResponse> {
1664 if (
1665 !OCPP16ServiceUtils.checkFeatureProfile(
1666 chargingStation,
1667 OCPP16SupportedFeatureProfiles.Reservation,
1668 OCPP16IncomingRequestCommand.RESERVE_NOW
1669 )
1670 ) {
1671 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1672 }
1673 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1674 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
1675 const { reservationId, idTag, connectorId } = commandPayload
1676 if (!chargingStation.hasConnector(connectorId)) {
1677 logger.error(
1678 `${chargingStation.logPrefix()} Trying to reserve a non existing connector id ${connectorId}`
1679 )
1680 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1681 }
1682 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1683 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1684 }
1685 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1686 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1687 }
1688 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1689 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1690 }
1691 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1692 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
1693 resetAuthorizeConnectorStatus(connectorStatus)
1694 let response: OCPP16ReserveNowResponse
1695 try {
1696 await removeExpiredReservations(chargingStation)
1697 switch (connectorStatus.status) {
1698 case OCPP16ChargePointStatus.Faulted:
1699 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1700 break
1701 case OCPP16ChargePointStatus.Preparing:
1702 case OCPP16ChargePointStatus.Charging:
1703 case OCPP16ChargePointStatus.SuspendedEV:
1704 case OCPP16ChargePointStatus.SuspendedEVSE:
1705 case OCPP16ChargePointStatus.Finishing:
1706 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1707 break
1708 case OCPP16ChargePointStatus.Unavailable:
1709 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1710 break
1711 case OCPP16ChargePointStatus.Reserved:
1712 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1713 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1714 break
1715 }
1716 // eslint-disable-next-line no-fallthrough
1717 default:
1718 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1719 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1720 break
1721 }
1722 await chargingStation.addReservation({
1723 id: commandPayload.reservationId,
1724 ...commandPayload
1725 })
1726 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1727 break
1728 }
1729 return response
1730 } catch (error) {
1731 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1732 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1733 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1734 return handleIncomingRequestError<OCPP16ReserveNowResponse>(
1735 chargingStation,
1736 OCPP16IncomingRequestCommand.RESERVE_NOW,
1737 error as Error,
1738 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1739 )!
1740 }
1741 }
1742
1743 private async handleRequestCancelReservation (
1744 chargingStation: ChargingStation,
1745 commandPayload: OCPP16CancelReservationRequest
1746 ): Promise<GenericResponse> {
1747 if (
1748 !OCPP16ServiceUtils.checkFeatureProfile(
1749 chargingStation,
1750 OCPP16SupportedFeatureProfiles.Reservation,
1751 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1752 )
1753 ) {
1754 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1755 }
1756 try {
1757 const { reservationId } = commandPayload
1758 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1759 if (reservation == null) {
1760 logger.debug(
1761 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1762 )
1763 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1764 }
1765 await chargingStation.removeReservation(
1766 reservation,
1767 ReservationTerminationReason.RESERVATION_CANCELED
1768 )
1769 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1770 } catch (error) {
1771 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1772 return handleIncomingRequestError<GenericResponse>(
1773 chargingStation,
1774 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1775 error as Error,
1776 {
1777 errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1778 }
1779 )!
1780 }
1781 }
1782 }