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