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