Merge pull request #1041 from syuusei3/issue39-ocpp2
[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 (
844 (keyToChange.key as OCPP16StandardParametersKey) ===
845 OCPP16StandardParametersKey.MeterValueSampleInterval &&
846 chargingStation.getNumberOfRunningTransactions() > 0 &&
847 valueChanged
848 ) {
849 for (
850 let connectorId = 1;
851 connectorId <= chargingStation.getNumberOfConnectors();
852 connectorId++
853 ) {
854 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
855 chargingStation.restartMeterValues(
856 connectorId,
857 secondsToMilliseconds(convertToInt(value))
858 )
859 }
860 }
861 }
862 if (keyToChange.reboot === true) {
863 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_REBOOT_REQUIRED
864 }
865 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_ACCEPTED
866 }
867 return OCPP16Constants.OCPP_CONFIGURATION_RESPONSE_NOT_SUPPORTED
868 }
869
870 private handleRequestSetChargingProfile (
871 chargingStation: ChargingStation,
872 commandPayload: SetChargingProfileRequest
873 ): SetChargingProfileResponse {
874 if (
875 !OCPP16ServiceUtils.checkFeatureProfile(
876 chargingStation,
877 OCPP16SupportedFeatureProfiles.SmartCharging,
878 OCPP16IncomingRequestCommand.SET_CHARGING_PROFILE
879 )
880 ) {
881 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_NOT_SUPPORTED
882 }
883 const { connectorId, csChargingProfiles } = commandPayload
884 if (!chargingStation.hasConnector(connectorId)) {
885 logger.error(
886 `${chargingStation.logPrefix()} Trying to set charging profile(s) to a non existing connector id ${connectorId}`
887 )
888 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
889 }
890 if (
891 csChargingProfiles.chargingProfilePurpose ===
892 OCPP16ChargingProfilePurposeType.CHARGE_POINT_MAX_PROFILE &&
893 connectorId !== 0
894 ) {
895 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
896 }
897 if (
898 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
899 connectorId === 0
900 ) {
901 logger.error(
902 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId}`
903 )
904 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
905 }
906 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
907 if (
908 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
909 connectorId > 0 &&
910 connectorStatus?.transactionStarted === false
911 ) {
912 logger.error(
913 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} without a started transaction`
914 )
915 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
916 }
917 if (
918 csChargingProfiles.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
919 connectorId > 0 &&
920 connectorStatus?.transactionStarted === true &&
921 csChargingProfiles.transactionId != null &&
922 csChargingProfiles.transactionId !== connectorStatus.transactionId
923 ) {
924 logger.error(
925 `${chargingStation.logPrefix()} Trying to set transaction charging profile(s) on connector ${connectorId} with a different transaction id ${
926 csChargingProfiles.transactionId
927 } than the started transaction id ${connectorStatus.transactionId}`
928 )
929 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_REJECTED
930 }
931 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, csChargingProfiles)
932 logger.debug(
933 `${chargingStation.logPrefix()} Charging profile(s) set on connector id ${connectorId}: %j`,
934 csChargingProfiles
935 )
936 return OCPP16Constants.OCPP_SET_CHARGING_PROFILE_RESPONSE_ACCEPTED
937 }
938
939 private handleRequestGetCompositeSchedule (
940 chargingStation: ChargingStation,
941 commandPayload: OCPP16GetCompositeScheduleRequest
942 ): OCPP16GetCompositeScheduleResponse {
943 if (
944 !OCPP16ServiceUtils.checkFeatureProfile(
945 chargingStation,
946 OCPP16SupportedFeatureProfiles.SmartCharging,
947 OCPP16IncomingRequestCommand.GET_COMPOSITE_SCHEDULE
948 )
949 ) {
950 return OCPP16Constants.OCPP_RESPONSE_REJECTED
951 }
952 const { connectorId, duration, chargingRateUnit } = commandPayload
953 if (!chargingStation.hasConnector(connectorId)) {
954 logger.error(
955 `${chargingStation.logPrefix()} Trying to get composite schedule to a non existing connector id ${connectorId}`
956 )
957 return OCPP16Constants.OCPP_RESPONSE_REJECTED
958 }
959 if (connectorId === 0) {
960 logger.error(
961 `${chargingStation.logPrefix()} Get composite schedule on connector id ${connectorId} is not yet supported`
962 )
963 return OCPP16Constants.OCPP_RESPONSE_REJECTED
964 }
965 if (chargingRateUnit != null) {
966 logger.warn(
967 `${chargingStation.logPrefix()} Get composite schedule with a specified rate unit is not yet supported, no conversion will be done`
968 )
969 }
970 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
971 if (
972 isEmpty(connectorStatus?.chargingProfiles) &&
973 isEmpty(chargingStation.getConnectorStatus(0)?.chargingProfiles)
974 ) {
975 return OCPP16Constants.OCPP_RESPONSE_REJECTED
976 }
977 const currentDate = new Date()
978 const compositeScheduleInterval: Interval = {
979 start: currentDate,
980 end: addSeconds(currentDate, duration)
981 }
982 // FIXME: add and handle charging station charging profiles
983 const chargingProfiles: OCPP16ChargingProfile[] = getConnectorChargingProfiles(
984 chargingStation,
985 connectorId
986 )
987 let previousCompositeSchedule: OCPP16ChargingSchedule | undefined
988 let compositeSchedule: OCPP16ChargingSchedule | undefined
989 for (const chargingProfile of chargingProfiles) {
990 if (chargingProfile.chargingSchedule.startSchedule == null) {
991 logger.debug(
992 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
993 chargingProfile.chargingProfileId
994 } has no startSchedule defined. Trying to set it to the connector current transaction start date`
995 )
996 // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
997 chargingProfile.chargingSchedule.startSchedule = connectorStatus?.transactionStart
998 }
999 if (!isDate(chargingProfile.chargingSchedule.startSchedule)) {
1000 logger.warn(
1001 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
1002 chargingProfile.chargingProfileId
1003 } startSchedule property is not a Date instance. Trying to convert it to a Date instance`
1004 )
1005 chargingProfile.chargingSchedule.startSchedule = convertToDate(
1006 chargingProfile.chargingSchedule.startSchedule
1007 )
1008 }
1009 if (chargingProfile.chargingSchedule.duration == null) {
1010 logger.debug(
1011 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetCompositeSchedule: Charging profile id ${
1012 chargingProfile.chargingProfileId
1013 } has no duration defined and will be set to the maximum time allowed`
1014 )
1015 // OCPP specifies that if duration is not defined, it should be infinite
1016 chargingProfile.chargingSchedule.duration = differenceInSeconds(
1017 maxTime,
1018 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1019 chargingProfile.chargingSchedule.startSchedule!
1020 )
1021 }
1022 if (
1023 !prepareChargingProfileKind(
1024 connectorStatus,
1025 chargingProfile,
1026 compositeScheduleInterval.start,
1027 chargingStation.logPrefix()
1028 )
1029 ) {
1030 continue
1031 }
1032 if (
1033 !canProceedChargingProfile(
1034 chargingProfile,
1035 compositeScheduleInterval.start,
1036 chargingStation.logPrefix()
1037 )
1038 ) {
1039 continue
1040 }
1041 compositeSchedule = OCPP16ServiceUtils.composeChargingSchedules(
1042 previousCompositeSchedule,
1043 chargingProfile.chargingSchedule,
1044 compositeScheduleInterval
1045 )
1046 previousCompositeSchedule = compositeSchedule
1047 }
1048 if (compositeSchedule != null) {
1049 return {
1050 status: GenericStatus.Accepted,
1051 scheduleStart: compositeSchedule.startSchedule,
1052 connectorId,
1053 chargingSchedule: compositeSchedule
1054 }
1055 }
1056 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1057 }
1058
1059 private handleRequestClearChargingProfile (
1060 chargingStation: ChargingStation,
1061 commandPayload: OCPP16ClearChargingProfileRequest
1062 ): OCPP16ClearChargingProfileResponse {
1063 if (
1064 !OCPP16ServiceUtils.checkFeatureProfile(
1065 chargingStation,
1066 OCPP16SupportedFeatureProfiles.SmartCharging,
1067 OCPP16IncomingRequestCommand.CLEAR_CHARGING_PROFILE
1068 )
1069 ) {
1070 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1071 }
1072 const { connectorId } = commandPayload
1073 if (connectorId != null) {
1074 if (!chargingStation.hasConnector(connectorId)) {
1075 logger.error(
1076 `${chargingStation.logPrefix()} Trying to clear a charging profile(s) to a non existing connector id ${connectorId}`
1077 )
1078 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1079 }
1080 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1081 if (isNotEmptyArray(connectorStatus?.chargingProfiles)) {
1082 connectorStatus.chargingProfiles = []
1083 logger.debug(
1084 `${chargingStation.logPrefix()} Charging profile(s) cleared on connector id ${connectorId}`
1085 )
1086 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1087 }
1088 } else {
1089 let clearedCP = false
1090 if (chargingStation.hasEvses) {
1091 for (const evseStatus of chargingStation.evses.values()) {
1092 for (const status of evseStatus.connectors.values()) {
1093 const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
1094 chargingStation,
1095 commandPayload,
1096 status.chargingProfiles
1097 )
1098 if (clearedConnectorCP && !clearedCP) {
1099 clearedCP = true
1100 }
1101 }
1102 }
1103 } else {
1104 for (const id of chargingStation.connectors.keys()) {
1105 const clearedConnectorCP = OCPP16ServiceUtils.clearChargingProfiles(
1106 chargingStation,
1107 commandPayload,
1108 chargingStation.getConnectorStatus(id)?.chargingProfiles
1109 )
1110 if (clearedConnectorCP && !clearedCP) {
1111 clearedCP = true
1112 }
1113 }
1114 }
1115 if (clearedCP) {
1116 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_ACCEPTED
1117 }
1118 }
1119 return OCPP16Constants.OCPP_CLEAR_CHARGING_PROFILE_RESPONSE_UNKNOWN
1120 }
1121
1122 private async handleRequestChangeAvailability (
1123 chargingStation: ChargingStation,
1124 commandPayload: OCPP16ChangeAvailabilityRequest
1125 ): Promise<OCPP16ChangeAvailabilityResponse> {
1126 const { connectorId, type } = commandPayload
1127 if (!chargingStation.hasConnector(connectorId)) {
1128 logger.error(
1129 `${chargingStation.logPrefix()} Trying to change the availability of a non existing connector id ${connectorId}`
1130 )
1131 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
1132 }
1133 const chargePointStatus: OCPP16ChargePointStatus =
1134 type === OCPP16AvailabilityType.Operative
1135 ? OCPP16ChargePointStatus.Available
1136 : OCPP16ChargePointStatus.Unavailable
1137 if (connectorId === 0) {
1138 let response: OCPP16ChangeAvailabilityResponse | undefined
1139 if (chargingStation.hasEvses) {
1140 for (const evseStatus of chargingStation.evses.values()) {
1141 response = await OCPP16ServiceUtils.changeAvailability(
1142 chargingStation,
1143 [...evseStatus.connectors.keys()],
1144 chargePointStatus,
1145 type
1146 )
1147 }
1148 } else {
1149 response = await OCPP16ServiceUtils.changeAvailability(
1150 chargingStation,
1151 [...chargingStation.connectors.keys()],
1152 chargePointStatus,
1153 type
1154 )
1155 }
1156 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1157 return response!
1158 } else if (
1159 connectorId > 0 &&
1160 (chargingStation.isChargingStationAvailable() ||
1161 (!chargingStation.isChargingStationAvailable() &&
1162 type === OCPP16AvailabilityType.Inoperative))
1163 ) {
1164 if (chargingStation.getConnectorStatus(connectorId)?.transactionStarted === true) {
1165 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1166 chargingStation.getConnectorStatus(connectorId)!.availability = type
1167 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_SCHEDULED
1168 }
1169 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1170 chargingStation.getConnectorStatus(connectorId)!.availability = type
1171 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1172 chargingStation,
1173 connectorId,
1174 chargePointStatus
1175 )
1176 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_ACCEPTED
1177 }
1178 return OCPP16Constants.OCPP_AVAILABILITY_RESPONSE_REJECTED
1179 }
1180
1181 private async handleRequestRemoteStartTransaction (
1182 chargingStation: ChargingStation,
1183 commandPayload: RemoteStartTransactionRequest
1184 ): Promise<GenericResponse> {
1185 if (commandPayload.connectorId == null) {
1186 for (
1187 let connectorId = 1;
1188 connectorId <= chargingStation.getNumberOfConnectors();
1189 connectorId++
1190 ) {
1191 if (
1192 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false &&
1193 !OCPP16ServiceUtils.hasReservation(chargingStation, connectorId, commandPayload.idTag)
1194 ) {
1195 commandPayload.connectorId = connectorId
1196 break
1197 }
1198 }
1199 if (commandPayload.connectorId == null) {
1200 logger.debug(
1201 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1202 chargingStation.stationInfo?.chargingStationId
1203 }, idTag '${commandPayload.idTag}': no available connector found`
1204 )
1205 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1206 }
1207 }
1208 const { connectorId: transactionConnectorId, idTag, chargingProfile } = commandPayload
1209 if (!chargingStation.hasConnector(transactionConnectorId)) {
1210 return this.notifyRemoteStartTransactionRejected(
1211 chargingStation,
1212 transactionConnectorId,
1213 idTag
1214 )
1215 }
1216 if (
1217 !chargingStation.isChargingStationAvailable() ||
1218 !chargingStation.isConnectorAvailable(transactionConnectorId)
1219 ) {
1220 return this.notifyRemoteStartTransactionRejected(
1221 chargingStation,
1222 transactionConnectorId,
1223 idTag
1224 )
1225 }
1226 // idTag authorization check required
1227 if (
1228 chargingStation.getAuthorizeRemoteTxRequests() &&
1229 !(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, transactionConnectorId, idTag))
1230 ) {
1231 return this.notifyRemoteStartTransactionRejected(
1232 chargingStation,
1233 transactionConnectorId,
1234 idTag
1235 )
1236 }
1237 if (
1238 chargingProfile != null &&
1239 !this.setRemoteStartTransactionChargingProfile(
1240 chargingStation,
1241 transactionConnectorId,
1242 chargingProfile
1243 )
1244 ) {
1245 return this.notifyRemoteStartTransactionRejected(
1246 chargingStation,
1247 transactionConnectorId,
1248 idTag
1249 )
1250 }
1251 logger.debug(
1252 `${chargingStation.logPrefix()} Remote start transaction ACCEPTED on ${
1253 chargingStation.stationInfo?.chargingStationId
1254 }#${transactionConnectorId}}, idTag '${idTag}'`
1255 )
1256 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1257 }
1258
1259 private notifyRemoteStartTransactionRejected (
1260 chargingStation: ChargingStation,
1261 connectorId: number,
1262 idTag: string
1263 ): GenericResponse {
1264 const connectorStatus = chargingStation.getConnectorStatus(connectorId)
1265 logger.debug(
1266 `${chargingStation.logPrefix()} Remote start transaction REJECTED on ${
1267 chargingStation.stationInfo?.chargingStationId
1268 }#${connectorId}, idTag '${idTag}', availability '${
1269 connectorStatus?.availability
1270 }', status '${connectorStatus?.status}'`
1271 )
1272 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1273 }
1274
1275 private setRemoteStartTransactionChargingProfile (
1276 chargingStation: ChargingStation,
1277 connectorId: number,
1278 chargingProfile: OCPP16ChargingProfile
1279 ): boolean {
1280 if (
1281 chargingProfile.chargingProfilePurpose === OCPP16ChargingProfilePurposeType.TX_PROFILE &&
1282 chargingProfile.transactionId == null
1283 ) {
1284 OCPP16ServiceUtils.setChargingProfile(chargingStation, connectorId, chargingProfile)
1285 logger.debug(
1286 `${chargingStation.logPrefix()} Charging profile(s) set at remote start transaction on ${
1287 chargingStation.stationInfo?.chargingStationId
1288 }#${connectorId}`,
1289 chargingProfile
1290 )
1291 return true
1292 }
1293 logger.debug(
1294 `${chargingStation.logPrefix()} Not allowed to set ${
1295 chargingProfile.chargingProfilePurpose
1296 } charging profile(s)${chargingProfile.transactionId != null ? ' with transactionId set' : ''} at remote start transaction`
1297 )
1298 return false
1299 }
1300
1301 private handleRequestRemoteStopTransaction (
1302 chargingStation: ChargingStation,
1303 commandPayload: RemoteStopTransactionRequest
1304 ): GenericResponse {
1305 const { transactionId } = commandPayload
1306 if (chargingStation.getConnectorIdByTransactionId(transactionId) != null) {
1307 logger.debug(
1308 `${chargingStation.logPrefix()} Remote stop transaction ACCEPTED for transactionId '${transactionId}'`
1309 )
1310 return OCPP16Constants.OCPP_RESPONSE_ACCEPTED
1311 }
1312 logger.debug(
1313 `${chargingStation.logPrefix()} Remote stop transaction REJECTED for transactionId '${transactionId}'`
1314 )
1315 return OCPP16Constants.OCPP_RESPONSE_REJECTED
1316 }
1317
1318 private handleRequestUpdateFirmware (
1319 chargingStation: ChargingStation,
1320 commandPayload: OCPP16UpdateFirmwareRequest
1321 ): OCPP16UpdateFirmwareResponse {
1322 if (
1323 !OCPP16ServiceUtils.checkFeatureProfile(
1324 chargingStation,
1325 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1326 OCPP16IncomingRequestCommand.UPDATE_FIRMWARE
1327 )
1328 ) {
1329 logger.warn(
1330 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: feature profile not supported`
1331 )
1332 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1333 }
1334 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1335 commandPayload.retrieveDate = convertToDate(commandPayload.retrieveDate)!
1336 const { retrieveDate } = commandPayload
1337 if (
1338 chargingStation.stationInfo?.firmwareStatus != null &&
1339 chargingStation.stationInfo.firmwareStatus !== OCPP16FirmwareStatus.Installed
1340 ) {
1341 logger.warn(
1342 `${chargingStation.logPrefix()} ${moduleName}.handleRequestUpdateFirmware: Cannot simulate firmware update: firmware update is already in progress`
1343 )
1344 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1345 }
1346 const now = Date.now()
1347 if (retrieveDate.getTime() <= now) {
1348 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1349 } else {
1350 setTimeout(() => {
1351 this.updateFirmwareSimulation(chargingStation).catch(Constants.EMPTY_FUNCTION)
1352 }, retrieveDate.getTime() - now)
1353 }
1354 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1355 }
1356
1357 private async updateFirmwareSimulation (
1358 chargingStation: ChargingStation,
1359 maxDelay = 30,
1360 minDelay = 15
1361 ): Promise<void> {
1362 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1363 return
1364 }
1365 if (chargingStation.hasEvses) {
1366 for (const [evseId, evseStatus] of chargingStation.evses) {
1367 if (evseId > 0) {
1368 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1369 if (connectorStatus.transactionStarted === false) {
1370 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1371 chargingStation,
1372 connectorId,
1373 OCPP16ChargePointStatus.Unavailable
1374 )
1375 }
1376 }
1377 }
1378 }
1379 } else {
1380 for (const connectorId of chargingStation.connectors.keys()) {
1381 if (
1382 connectorId > 0 &&
1383 chargingStation.getConnectorStatus(connectorId)?.transactionStarted === false
1384 ) {
1385 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1386 chargingStation,
1387 connectorId,
1388 OCPP16ChargePointStatus.Unavailable
1389 )
1390 }
1391 }
1392 }
1393 await chargingStation.ocppRequestService.requestHandler<
1394 OCPP16FirmwareStatusNotificationRequest,
1395 OCPP16FirmwareStatusNotificationResponse
1396 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1397 status: OCPP16FirmwareStatus.Downloading
1398 })
1399 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1400 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloading
1401 if (
1402 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1403 OCPP16FirmwareStatus.DownloadFailed
1404 ) {
1405 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1406 await chargingStation.ocppRequestService.requestHandler<
1407 OCPP16FirmwareStatusNotificationRequest,
1408 OCPP16FirmwareStatusNotificationResponse
1409 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1410 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1411 })
1412 chargingStation.stationInfo.firmwareStatus =
1413 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1414 return
1415 }
1416 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1417 await chargingStation.ocppRequestService.requestHandler<
1418 OCPP16FirmwareStatusNotificationRequest,
1419 OCPP16FirmwareStatusNotificationResponse
1420 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1421 status: OCPP16FirmwareStatus.Downloaded
1422 })
1423 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1424 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Downloaded
1425 let wasTransactionsStarted = false
1426 let transactionsStarted: boolean
1427 do {
1428 const runningTransactions = chargingStation.getNumberOfRunningTransactions()
1429 if (runningTransactions > 0) {
1430 const waitTime = secondsToMilliseconds(15)
1431 logger.debug(
1432 `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation: ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
1433 waitTime
1434 )} before continuing firmware update simulation`
1435 )
1436 await sleep(waitTime)
1437 transactionsStarted = true
1438 wasTransactionsStarted = true
1439 } else {
1440 if (chargingStation.hasEvses) {
1441 for (const [evseId, evseStatus] of chargingStation.evses) {
1442 if (evseId > 0) {
1443 for (const [connectorId, connectorStatus] of evseStatus.connectors) {
1444 if (connectorStatus.status !== OCPP16ChargePointStatus.Unavailable) {
1445 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1446 chargingStation,
1447 connectorId,
1448 OCPP16ChargePointStatus.Unavailable
1449 )
1450 }
1451 }
1452 }
1453 }
1454 } else {
1455 for (const connectorId of chargingStation.connectors.keys()) {
1456 if (
1457 connectorId > 0 &&
1458 chargingStation.getConnectorStatus(connectorId)?.status !==
1459 OCPP16ChargePointStatus.Unavailable
1460 ) {
1461 await OCPP16ServiceUtils.sendAndSetConnectorStatus(
1462 chargingStation,
1463 connectorId,
1464 OCPP16ChargePointStatus.Unavailable
1465 )
1466 }
1467 }
1468 }
1469 transactionsStarted = false
1470 }
1471 } while (transactionsStarted)
1472 !wasTransactionsStarted && (await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay))))
1473 if (!checkChargingStation(chargingStation, chargingStation.logPrefix())) {
1474 return
1475 }
1476 await chargingStation.ocppRequestService.requestHandler<
1477 OCPP16FirmwareStatusNotificationRequest,
1478 OCPP16FirmwareStatusNotificationResponse
1479 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1480 status: OCPP16FirmwareStatus.Installing
1481 })
1482 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1483 chargingStation.stationInfo!.firmwareStatus = OCPP16FirmwareStatus.Installing
1484 if (
1485 chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
1486 OCPP16FirmwareStatus.InstallationFailed
1487 ) {
1488 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1489 await chargingStation.ocppRequestService.requestHandler<
1490 OCPP16FirmwareStatusNotificationRequest,
1491 OCPP16FirmwareStatusNotificationResponse
1492 >(chargingStation, OCPP16RequestCommand.FIRMWARE_STATUS_NOTIFICATION, {
1493 status: chargingStation.stationInfo.firmwareUpgrade.failureStatus
1494 })
1495 chargingStation.stationInfo.firmwareStatus =
1496 chargingStation.stationInfo.firmwareUpgrade.failureStatus
1497 return
1498 }
1499 if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
1500 await sleep(secondsToMilliseconds(randomInt(minDelay, maxDelay)))
1501 await chargingStation.reset(OCPP16StopTransactionReason.REBOOT)
1502 }
1503 }
1504
1505 private async handleRequestGetDiagnostics (
1506 chargingStation: ChargingStation,
1507 commandPayload: GetDiagnosticsRequest
1508 ): Promise<GetDiagnosticsResponse> {
1509 if (
1510 !OCPP16ServiceUtils.checkFeatureProfile(
1511 chargingStation,
1512 OCPP16SupportedFeatureProfiles.FirmwareManagement,
1513 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1514 )
1515 ) {
1516 logger.warn(
1517 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Cannot get diagnostics: feature profile not supported`
1518 )
1519 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1520 }
1521 const { location } = commandPayload
1522 const uri = new URL(location)
1523 if (uri.protocol.startsWith('ftp:')) {
1524 let ftpClient: Client | undefined
1525 try {
1526 const logConfiguration = Configuration.getConfigurationSection<LogConfiguration>(
1527 ConfigurationSection.log
1528 )
1529 const logFiles = readdirSync(
1530 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1531 resolve((fileURLToPath(import.meta.url), '../', dirname(logConfiguration.file!)))
1532 )
1533 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1534 .filter(file => file.endsWith(extname(logConfiguration.file!)))
1535 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1536 .map(file => join(dirname(logConfiguration.file!), file))
1537 const diagnosticsArchive = `${chargingStation.stationInfo?.chargingStationId}_logs.tar.gz`
1538 create({ gzip: true }, logFiles).pipe(createWriteStream(diagnosticsArchive))
1539 ftpClient = new Client()
1540 const accessResponse = await ftpClient.access({
1541 host: uri.hostname,
1542 ...(isNotEmptyString(uri.port) && { port: convertToInt(uri.port) }),
1543 ...(isNotEmptyString(uri.username) && { user: uri.username }),
1544 ...(isNotEmptyString(uri.password) && { password: uri.password })
1545 })
1546 let uploadResponse: FTPResponse | undefined
1547 if (accessResponse.code === 220) {
1548 ftpClient.trackProgress(info => {
1549 logger.info(
1550 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: ${
1551 info.bytes / 1024
1552 } bytes transferred from diagnostics archive ${info.name}`
1553 )
1554 chargingStation.ocppRequestService
1555 .requestHandler<
1556 OCPP16DiagnosticsStatusNotificationRequest,
1557 OCPP16DiagnosticsStatusNotificationResponse
1558 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1559 status: OCPP16DiagnosticsStatus.Uploading
1560 })
1561 .catch((error: unknown) => {
1562 logger.error(
1563 `${chargingStation.logPrefix()} ${moduleName}.handleRequestGetDiagnostics: Error while sending '${
1564 OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION
1565 }'`,
1566 error
1567 )
1568 })
1569 })
1570 uploadResponse = await ftpClient.uploadFrom(
1571 join(resolve(dirname(fileURLToPath(import.meta.url)), '../'), diagnosticsArchive),
1572 `${uri.pathname}${diagnosticsArchive}`
1573 )
1574 if (uploadResponse.code === 226) {
1575 await chargingStation.ocppRequestService.requestHandler<
1576 OCPP16DiagnosticsStatusNotificationRequest,
1577 OCPP16DiagnosticsStatusNotificationResponse
1578 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1579 status: OCPP16DiagnosticsStatus.Uploaded
1580 })
1581 ftpClient.close()
1582 return { fileName: diagnosticsArchive }
1583 }
1584 throw new OCPPError(
1585 ErrorType.GENERIC_ERROR,
1586 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse.code}`,
1587 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1588 )
1589 }
1590 throw new OCPPError(
1591 ErrorType.GENERIC_ERROR,
1592 `Diagnostics transfer failed with error code ${accessResponse.code}|${uploadResponse?.code}`,
1593 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS
1594 )
1595 } catch (error) {
1596 await chargingStation.ocppRequestService.requestHandler<
1597 OCPP16DiagnosticsStatusNotificationRequest,
1598 OCPP16DiagnosticsStatusNotificationResponse
1599 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1600 status: OCPP16DiagnosticsStatus.UploadFailed
1601 })
1602 ftpClient?.close()
1603 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1604 return handleIncomingRequestError<GetDiagnosticsResponse>(
1605 chargingStation,
1606 OCPP16IncomingRequestCommand.GET_DIAGNOSTICS,
1607 error as Error,
1608 { errorResponse: OCPP16Constants.OCPP_RESPONSE_EMPTY }
1609 )!
1610 }
1611 } else {
1612 logger.error(
1613 `${chargingStation.logPrefix()} Unsupported protocol ${
1614 uri.protocol
1615 } to transfer the diagnostic logs archive`
1616 )
1617 await chargingStation.ocppRequestService.requestHandler<
1618 OCPP16DiagnosticsStatusNotificationRequest,
1619 OCPP16DiagnosticsStatusNotificationResponse
1620 >(chargingStation, OCPP16RequestCommand.DIAGNOSTICS_STATUS_NOTIFICATION, {
1621 status: OCPP16DiagnosticsStatus.UploadFailed
1622 })
1623 return OCPP16Constants.OCPP_RESPONSE_EMPTY
1624 }
1625 }
1626
1627 private handleRequestTriggerMessage (
1628 chargingStation: ChargingStation,
1629 commandPayload: OCPP16TriggerMessageRequest
1630 ): OCPP16TriggerMessageResponse {
1631 const { requestedMessage, connectorId } = commandPayload
1632 if (
1633 !OCPP16ServiceUtils.checkFeatureProfile(
1634 chargingStation,
1635 OCPP16SupportedFeatureProfiles.RemoteTrigger,
1636 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE
1637 ) ||
1638 !OCPP16ServiceUtils.isMessageTriggerSupported(chargingStation, requestedMessage)
1639 ) {
1640 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1641 }
1642 if (
1643 !OCPP16ServiceUtils.isConnectorIdValid(
1644 chargingStation,
1645 OCPP16IncomingRequestCommand.TRIGGER_MESSAGE,
1646 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1647 connectorId!
1648 )
1649 ) {
1650 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_REJECTED
1651 }
1652 switch (requestedMessage) {
1653 case OCPP16MessageTrigger.BootNotification:
1654 case OCPP16MessageTrigger.Heartbeat:
1655 case OCPP16MessageTrigger.StatusNotification:
1656 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_ACCEPTED
1657 default:
1658 return OCPP16Constants.OCPP_TRIGGER_MESSAGE_RESPONSE_NOT_IMPLEMENTED
1659 }
1660 }
1661
1662 private handleRequestDataTransfer (
1663 chargingStation: ChargingStation,
1664 commandPayload: OCPP16DataTransferRequest
1665 ): OCPP16DataTransferResponse {
1666 const { vendorId } = commandPayload
1667 try {
1668 if (Object.values(OCPP16DataTransferVendorId).includes(vendorId)) {
1669 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_ACCEPTED
1670 }
1671 return OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_UNKNOWN_VENDOR_ID
1672 } catch (error) {
1673 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1674 return handleIncomingRequestError<OCPP16DataTransferResponse>(
1675 chargingStation,
1676 OCPP16IncomingRequestCommand.DATA_TRANSFER,
1677 error as Error,
1678 { errorResponse: OCPP16Constants.OCPP_DATA_TRANSFER_RESPONSE_REJECTED }
1679 )!
1680 }
1681 }
1682
1683 private async handleRequestReserveNow (
1684 chargingStation: ChargingStation,
1685 commandPayload: OCPP16ReserveNowRequest
1686 ): Promise<OCPP16ReserveNowResponse> {
1687 if (
1688 !OCPP16ServiceUtils.checkFeatureProfile(
1689 chargingStation,
1690 OCPP16SupportedFeatureProfiles.Reservation,
1691 OCPP16IncomingRequestCommand.RESERVE_NOW
1692 )
1693 ) {
1694 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1695 }
1696 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1697 commandPayload.expiryDate = convertToDate(commandPayload.expiryDate)!
1698 const { reservationId, idTag, connectorId } = commandPayload
1699 if (!chargingStation.hasConnector(connectorId)) {
1700 logger.error(
1701 `${chargingStation.logPrefix()} Trying to reserve a non existing connector id ${connectorId}`
1702 )
1703 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1704 }
1705 if (connectorId > 0 && !chargingStation.isConnectorAvailable(connectorId)) {
1706 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1707 }
1708 if (connectorId === 0 && !chargingStation.getReserveConnectorZeroSupported()) {
1709 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1710 }
1711 if (!(await OCPP16ServiceUtils.isIdTagAuthorized(chargingStation, connectorId, idTag))) {
1712 return OCPP16Constants.OCPP_RESERVATION_RESPONSE_REJECTED
1713 }
1714 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1715 const connectorStatus = chargingStation.getConnectorStatus(connectorId)!
1716 resetAuthorizeConnectorStatus(connectorStatus)
1717 let response: OCPP16ReserveNowResponse
1718 try {
1719 await removeExpiredReservations(chargingStation)
1720 switch (connectorStatus.status) {
1721 case OCPP16ChargePointStatus.Faulted:
1722 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED
1723 break
1724 case OCPP16ChargePointStatus.Preparing:
1725 case OCPP16ChargePointStatus.Charging:
1726 case OCPP16ChargePointStatus.SuspendedEV:
1727 case OCPP16ChargePointStatus.SuspendedEVSE:
1728 case OCPP16ChargePointStatus.Finishing:
1729 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1730 break
1731 case OCPP16ChargePointStatus.Unavailable:
1732 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE
1733 break
1734 case OCPP16ChargePointStatus.Reserved:
1735 if (!chargingStation.isConnectorReservable(reservationId, idTag, connectorId)) {
1736 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1737 break
1738 }
1739 // eslint-disable-next-line no-fallthrough
1740 default:
1741 if (!chargingStation.isConnectorReservable(reservationId, idTag)) {
1742 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_OCCUPIED
1743 break
1744 }
1745 await chargingStation.addReservation({
1746 id: commandPayload.reservationId,
1747 ...commandPayload
1748 })
1749 response = OCPP16Constants.OCPP_RESERVATION_RESPONSE_ACCEPTED
1750 break
1751 }
1752 return response
1753 } catch (error) {
1754 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1755 chargingStation.getConnectorStatus(connectorId)!.status = OCPP16ChargePointStatus.Available
1756 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1757 return handleIncomingRequestError<OCPP16ReserveNowResponse>(
1758 chargingStation,
1759 OCPP16IncomingRequestCommand.RESERVE_NOW,
1760 error as Error,
1761 { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
1762 )!
1763 }
1764 }
1765
1766 private async handleRequestCancelReservation (
1767 chargingStation: ChargingStation,
1768 commandPayload: OCPP16CancelReservationRequest
1769 ): Promise<GenericResponse> {
1770 if (
1771 !OCPP16ServiceUtils.checkFeatureProfile(
1772 chargingStation,
1773 OCPP16SupportedFeatureProfiles.Reservation,
1774 OCPP16IncomingRequestCommand.CANCEL_RESERVATION
1775 )
1776 ) {
1777 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1778 }
1779 try {
1780 const { reservationId } = commandPayload
1781 const reservation = chargingStation.getReservationBy('reservationId', reservationId)
1782 if (reservation == null) {
1783 logger.debug(
1784 `${chargingStation.logPrefix()} Reservation with id ${reservationId} does not exist on charging station`
1785 )
1786 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1787 }
1788 await chargingStation.removeReservation(
1789 reservation,
1790 ReservationTerminationReason.RESERVATION_CANCELED
1791 )
1792 return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED
1793 } catch (error) {
1794 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1795 return handleIncomingRequestError<GenericResponse>(
1796 chargingStation,
1797 OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
1798 error as Error,
1799 {
1800 errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED
1801 }
1802 )!
1803 }
1804 }
1805 }