refactor: rewriting functionalities and added additional helper functions
authorJulian Buecher <julian.buecher@gmx.de>
Wed, 24 May 2023 21:32:17 +0000 (23:32 +0200)
committerJulian Buecher <julian.buecher@gmx.de>
Wed, 24 May 2023 21:32:17 +0000 (23:32 +0200)
src/charging-station/ChargingStation.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16RequestService.ts
src/types/ocpp/1.6/Requests.ts
src/types/ocpp/1.6/Reservation.ts
src/types/ocpp/Reservation.ts
src/utils/Configuration.ts
src/utils/Constants.ts

index ace7aff1cfb67c29f6c291cdee78bbc7b659a977..6f786eab1d1af2c1c1cbb459cb7bdd26e60f9c7a 100644 (file)
@@ -63,8 +63,11 @@ import {
   MeterValueMeasurand,
   type MeterValuesRequest,
   type MeterValuesResponse,
+  OCPP16AuthorizationStatus,
+  type OCPP16AuthorizeRequest,
+  type OCPP16AuthorizeResponse,
+  OCPP16RequestCommand,
   OCPP16SupportedFeatureProfiles,
-  OCPP20ConnectorStatusEnumType,
   OCPPVersion,
   type OutgoingRequest,
   PowerUnits,
@@ -84,6 +87,7 @@ import {
   WebSocketCloseEventStatusCode,
   type WsOptions,
 } from '../types';
+import { ReservationTerminationReason } from '../types/ocpp/1.6/Reservation';
 import type { Reservation } from '../types/ocpp/Reservation';
 import {
   ACElectricUtils,
@@ -137,6 +141,7 @@ export class ChargingStation {
   private webSocketPingSetInterval!: NodeJS.Timeout;
   private readonly chargingStationWorkerBroadcastChannel: ChargingStationWorkerBroadcastChannel;
   private reservations?: Reservation[];
+  private reservationExpiryDateSetInterval?: NodeJS.Timeout;
 
   constructor(index: number, templateFile: string) {
     this.started = false;
@@ -647,6 +652,9 @@ export class ChargingStation {
         if (this.getEnableStatistics() === true) {
           this.performanceStatistics?.start();
         }
+        if (this.supportsReservations()) {
+          this.startReservationExpiryDateSetInterval();
+        }
         this.openWSConnection();
         // Monitor charging station template file
         this.templateFileWatcher = FileUtils.watchJsonFile(
@@ -903,7 +911,7 @@ export class ChargingStation {
 
   public supportsReservationsOnConnectorId0(): boolean {
     logger.info(
-      `Check for reservation support on connector 0 in charging station (CS): ${this.logPrefix()}`
+      ` ${this.logPrefix()} Check for reservation support on connector 0 in charging station (CS)`
     );
     return (
       this.supportsReservations() &&
@@ -914,57 +922,188 @@ export class ChargingStation {
     );
   }
 
-  public addReservation(newReservation: Reservation): void {
+  public async addReservation(reservation: Reservation): Promise<void> {
     if (Utils.isNullOrUndefined(this.reservations)) {
       this.reservations = [];
     }
-    const [exists, foundReservation] = this.doesReservationExist(newReservation.reservationId);
+    const [exists, reservationFound] = this.doesReservationExists(reservation);
     if (exists) {
-      this.replaceExistingReservation(foundReservation, newReservation);
-    } else {
-      this.reservations.push(newReservation);
+      await this.removeReservation(reservationFound);
+    }
+    this.reservations.push(reservation);
+    if (reservation.connectorId === 0) {
+      return;
     }
+    this.getConnectorStatus(reservation.connectorId).status = ConnectorStatusEnum.Reserved;
+    await this.ocppRequestService.requestHandler<
+      StatusNotificationRequest,
+      StatusNotificationResponse
+    >(
+      this,
+      RequestCommand.STATUS_NOTIFICATION,
+      OCPPServiceUtils.buildStatusNotificationRequest(
+        this,
+        reservation.connectorId,
+        ConnectorStatusEnum.Reserved
+      )
+    );
   }
 
-  public removeReservation(existingReservationId: number): void {
-    const index = this.reservations.findIndex((res) => res.reservationId === existingReservationId);
+  public async removeReservation(
+    reservation: Reservation,
+    reason?: ReservationTerminationReason
+  ): Promise<void> {
+    const sameReservation = (r: Reservation) => r.id === reservation.id;
+    const index = this.reservations?.findIndex(sameReservation);
     this.reservations.splice(index, 1);
+    switch (reason) {
+      case ReservationTerminationReason.TRANSACTION_STARTED:
+        // No action needed
+        break;
+      case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
+        // No action needed
+        break;
+      default: // ReservationTerminationReason.EXPIRED, ReservationTerminationReason.CANCELED
+        this.getConnectorStatus(reservation.connectorId).status = ConnectorStatusEnum.Available;
+        await this.ocppRequestService.requestHandler<
+          StatusNotificationRequest,
+          StatusNotificationResponse
+        >(
+          this,
+          RequestCommand.STATUS_NOTIFICATION,
+          OCPPServiceUtils.buildStatusNotificationRequest(
+            this,
+            reservation.connectorId,
+            ConnectorStatusEnum.Available
+          )
+        );
+        break;
+    }
+  }
+
+  public getReservationById(id: number): Reservation {
+    return this.reservations?.find((reservation) => reservation.id === id);
+  }
+
+  public getReservationByIdTag(id: string): Reservation {
+    return this.reservations?.find((reservation) => reservation.idTag === id);
+  }
+
+  public getReservationByConnectorId(id: number): Reservation {
+    return this.reservations?.find((reservation) => reservation.connectorId === id);
+  }
+
+  public doesReservationExists(reservation: Partial<Reservation>): [boolean, Reservation] {
+    const sameReservation = (r: Reservation) => r.id === reservation.id;
+    const foundReservation = this.reservations?.find(sameReservation);
+    return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation];
   }
 
-  public getReservation(reservationId: number, reservationIndex?: number): Reservation {
-    if (!Utils.isNullOrUndefined(reservationIndex)) {
-      return this.reservations[reservationIndex];
+  public async isAuthorized(
+    connectorId: number,
+    idTag: string,
+    parentIdTag?: string
+  ): Promise<boolean> {
+    let authorized = false;
+    const connectorStatus = this.getConnectorStatus(connectorId);
+    if (
+      this.getLocalAuthListEnabled() === true &&
+      this.hasIdTags() === true &&
+      Utils.isNotEmptyString(
+        this.idTagsCache
+          .getIdTags(ChargingStationUtils.getIdTagsFile(this.stationInfo))
+          ?.find((tag) => tag === idTag)
+      )
+    ) {
+      connectorStatus.localAuthorizeIdTag = idTag;
+      connectorStatus.idTagLocalAuthorized = true;
+      authorized = true;
+    } else if (this.getMustAuthorizeAtRemoteStart() === true) {
+      connectorStatus.authorizeIdTag = idTag;
+      const authorizeResponse: OCPP16AuthorizeResponse =
+        await this.ocppRequestService.requestHandler<
+          OCPP16AuthorizeRequest,
+          OCPP16AuthorizeResponse
+        >(this, OCPP16RequestCommand.AUTHORIZE, {
+          idTag: idTag,
+        });
+      if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
+        authorized = true;
+      }
+    } else {
+      logger.warn(
+        `${this.logPrefix()} The charging station configuration expects authorize at
+          remote start transaction but local authorization or authorize isn't enabled`
+      );
     }
-    return this.reservations.find((r) => r.reservationId === reservationId);
+    return authorized;
   }
 
-  public doesReservationExist(
-    reservationId: number,
-    reservation?: Reservation
-  ): [boolean, Reservation] {
-    const foundReservation = this.reservations.find(
-      (r) => r.reservationId === reservationId || r.reservationId === reservation.reservationId
+  public startReservationExpiryDateSetInterval(customInterval?: number): void {
+    const interval =
+      customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL;
+    logger.info(
+      `${this.logPrefix()} Reservation expiration date interval is set to ${interval}
+        and starts on CS now`
     );
-    return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation];
+    // eslint-disable-next-line @typescript-eslint/no-misused-promises
+    this.reservationExpiryDateSetInterval = setInterval(async (): Promise<void> => {
+      if (!Utils.isNullOrUndefined(this.reservations) && !Utils.isEmptyArray(this.reservations)) {
+        for (const reservation of this.reservations) {
+          if (reservation.expiryDate.toString() < new Date().toISOString()) {
+            await this.removeReservation(reservation);
+            logger.info(
+              `${this.logPrefix()} Reservation with ID ${
+                reservation.id
+              } reached expiration date and was removed from CS`
+            );
+          }
+        }
+      }
+    }, interval);
   }
 
-  public getReservationByConnectorId(connectorId: number): Reservation {
-    return this.reservations.find((r) => r.connectorId === connectorId);
+  public restartReservationExpiryDateSetInterval(): void {
+    this.stopReservationExpiryDateSetInterval();
+    this.startReservationExpiryDateSetInterval();
   }
 
-  public getAvailableConnector(): Map<number, ConnectorStatus> {
-    for (const connectorId in this.connectors) {
-      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
-      const connector = this.connectors[Utils.convertToInt(connectorId)];
-      if (
-        this.isConnectorAvailable(Utils.convertToInt(connectorId)) &&
-        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
-        connector.status === OCPP20ConnectorStatusEnumType.Available
-      ) {
-        // eslint-disable-next-line @typescript-eslint/no-unsafe-return
-        return connector;
+  public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean {
+    const reservation = this.getReservationByConnectorId(connectorId);
+    return Utils.isUndefined(reservation) || reservation.idTag !== idTag;
+  }
+
+  public isConnectorReservable(
+    reservationId: number,
+    connectorId?: number,
+    idTag?: string
+  ): boolean {
+    const [alreadyExists, _] = this.doesReservationExists({ id: reservationId });
+    if (alreadyExists) {
+      return alreadyExists;
+    }
+    const userReservedAlready = Utils.isUndefined(this.getReservationByIdTag(idTag)) ? false : true;
+    const notConnectorZero = Utils.isUndefined(connectorId) ? true : connectorId > 0;
+    const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
+    return !alreadyExists && !userReservedAlready && notConnectorZero && freeConnectorsAvailable;
+  }
+
+  private getNumberOfReservableConnectors(): number {
+    let reservableConnectors = 0;
+    this.connectors.forEach((connector, id) => {
+      if (id === 0) {
+        return;
       }
-    }
+      if (connector.status === ConnectorStatusEnum.Available) {
+        reservableConnectors++;
+      }
+    });
+    return reservableConnectors - this.getNumberOfReservationsOnConnectorZero();
+  }
+
+  private getNumberOfReservationsOnConnectorZero(): number {
+    const reservations = this.reservations?.filter((reservation) => reservation.connectorId === 0);
+    return Utils.isNullOrUndefined(reservations) ? 0 : reservations.length;
   }
 
   private flushMessageBuffer(): void {
@@ -994,14 +1133,10 @@ export class ChargingStation {
     return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
   }
 
-  private replaceExistingReservation(
-    existingReservation: Reservation,
-    newReservation: Reservation
-  ): void {
-    const existingReservationIndex = this.reservations.findIndex(
-      (r) => r.reservationId === existingReservation.reservationId
-    );
-    this.reservations.splice(existingReservationIndex, 1, newReservation);
+  private stopReservationExpiryDateSetInterval(): void {
+    if (this.reservationExpiryDateSetInterval) {
+      clearInterval(this.reservationExpiryDateSetInterval);
+    }
   }
 
   private getSupervisionUrlOcppKey(): string {
@@ -1040,6 +1175,7 @@ export class ChargingStation {
 
   private getStationInfoFromTemplate(): ChargingStationInfo {
     const stationTemplate: ChargingStationTemplate | undefined = this.getTemplateFromFile();
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-call
     ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
     ChargingStationUtils.warnTemplateKeysDeprecation(
       stationTemplate,
@@ -1155,6 +1291,7 @@ export class ChargingStation {
 
   private initialize(): void {
     const stationTemplate = this.getTemplateFromFile();
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-call
     ChargingStationUtils.checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
     this.configurationFile = path.join(
       path.dirname(this.templateFile.replace('station-templates', 'configurations')),
index 46c1201872280760d6972836d4f8c1e80ecf6695..599735973fb69da61a92cb7312e0082b5470bda3 100644 (file)
@@ -37,8 +37,6 @@ import {
   type JsonObject,
   type JsonType,
   OCPP16AuthorizationStatus,
-  type OCPP16AuthorizeRequest,
-  type OCPP16AuthorizeResponse,
   OCPP16AvailabilityType,
   type OCPP16BootNotificationRequest,
   type OCPP16BootNotificationResponse,
@@ -90,6 +88,7 @@ import type {
   OCPP16CancelReservationRequest,
   OCPP16ReserveNowRequest,
 } from '../../../types/ocpp/1.6/Requests';
+import { ReservationTerminationReason } from '../../../types/ocpp/1.6/Reservation';
 import type {
   OCPP16CancelReservationResponse,
   OCPP16ReserveNowResponse,
@@ -149,6 +148,11 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       [OCPP16IncomingRequestCommand.TRIGGER_MESSAGE, this.handleRequestTriggerMessage.bind(this)],
       [OCPP16IncomingRequestCommand.DATA_TRANSFER, this.handleRequestDataTransfer.bind(this)],
       [OCPP16IncomingRequestCommand.UPDATE_FIRMWARE, this.handleRequestUpdateFirmware.bind(this)],
+      [OCPP16IncomingRequestCommand.RESERVE_NOW, this.handleRequestReserveNow.bind(this)],
+      [
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+        this.handleRequestCancelReservation.bind(this),
+      ],
     ]);
     this.jsonSchemas = new Map<OCPP16IncomingRequestCommand, JSONSchemaType<JsonObject>>([
       [
@@ -271,6 +275,22 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
           'constructor'
         ),
       ],
+      [
+        OCPP16IncomingRequestCommand.RESERVE_NOW,
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
+          'assets/json-schemas/ocpp/1.6/ReserveNow.json',
+          moduleName,
+          'constructor'
+        ),
+      ],
+      [
+        OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
+          'assets/json-schemas/ocpp/1.6/CancelReservation.json',
+          moduleName,
+          'constructor'
+        ),
+      ],
     ]);
     this.validatePayload = this.validatePayload.bind(this) as (
       chargingStation: ChargingStation,
@@ -810,9 +830,18 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     commandPayload: RemoteStartTransactionRequest
   ): Promise<GenericResponse> {
     const transactionConnectorId = commandPayload.connectorId;
-    const reserved: boolean =
+    const reserved =
       chargingStation.getConnectorStatus(transactionConnectorId).status ===
       OCPP16ChargePointStatus.Reserved;
+    if (
+      reserved &&
+      chargingStation.validateIncomingRequestWithReservation(
+        transactionConnectorId,
+        commandPayload.idTag
+      )
+    ) {
+      return OCPP16Constants.OCPP_RESPONSE_REJECTED;
+    }
     if (chargingStation.hasConnector(transactionConnectorId) === false) {
       return this.notifyRemoteStartTransactionRejected(
         chargingStation,
@@ -821,9 +850,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       );
     }
     if (
-      chargingStation.isChargingStationAvailable() === false ||
-      chargingStation.isConnectorAvailable(transactionConnectorId) === false ||
-      reserved
+      !chargingStation.isChargingStationAvailable() ||
+      !chargingStation.isConnectorAvailable(transactionConnectorId)
     ) {
       return this.notifyRemoteStartTransactionRejected(
         chargingStation,
@@ -842,8 +870,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     const connectorStatus = chargingStation.getConnectorStatus(transactionConnectorId);
     // Check if authorized
     if (chargingStation.getAuthorizeRemoteTxRequests() === true) {
-      const authorized = await this.isAuthorized(
-        chargingStation,
+      const authorized = await chargingStation.isAuthorized(
         transactionConnectorId,
         commandPayload.idTag
       );
@@ -862,8 +889,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
             idTag: commandPayload.idTag,
           };
           if (reserved) {
-            startTransactionData['reservationId'] =
-              chargingStation.getReservationByConnectorId(transactionConnectorId).reservationId;
+            const reservation = chargingStation.getReservationByConnectorId(transactionConnectorId);
+            startTransactionData.reservationId = reservation.id;
+            await chargingStation.removeReservation(
+              reservation,
+              ReservationTerminationReason.TRANSACTION_STARTED
+            );
           }
           if (
             (
@@ -894,13 +925,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         commandPayload.idTag
       );
     }
-    if (reserved) {
-      await this.handleReservedRemoteStartTransaction(
-        chargingStation,
-        transactionConnectorId,
-        commandPayload
-      );
-    }
     // No authorization check required, start transaction
     if (
       this.setRemoteStartTransactionChargingProfile(
@@ -1512,7 +1536,6 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     commandPayload: OCPP16ReserveNowRequest
   ): Promise<OCPP16ReserveNowResponse> {
     const { reservationId, idTag, connectorId } = commandPayload;
-    let connector: Map<number, ConnectorStatus>;
     let response: OCPP16ReserveNowResponse;
     try {
       if (
@@ -1524,7 +1547,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       if (connectorId === 0 && !chargingStation.supportsReservationsOnConnectorId0()) {
         return OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED;
       }
-      if (!(await this.isAuthorized(chargingStation, connectorId, commandPayload.idTag))) {
+      if (!(await chargingStation.isAuthorized(connectorId, idTag))) {
         return OCPPConstants.OCPP_RESERVATION_RESPONSE_REJECTED;
       }
       switch (chargingStation.getConnectorStatus(connectorId).status) {
@@ -1538,43 +1561,31 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
           response = OCPPConstants.OCPP_RESERVATION_RESPONSE_UNAVAILABLE;
           break;
         case ConnectorStatusEnum.Reserved:
-          if (Utils.isUndefined(chargingStation.getReservation(commandPayload.reservationId))) {
+          if (!chargingStation.isConnectorReservable(reservationId, connectorId, idTag)) {
             response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
             break;
           }
         // eslint-disable-next-line no-fallthrough
         default:
-          logger.info(
-            `${chargingStation.logPrefix()} on connector ${connectorId} is now reserved for ${
-              commandPayload.idTag
-            }`
-          );
-          chargingStation.getConnectorStatus(connectorId).status = ConnectorStatusEnum.Reserved;
-          chargingStation.addReservation({ ...commandPayload });
-          await chargingStation.ocppRequestService
-            .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
-              chargingStation,
-              OCPP16RequestCommand.STATUS_NOTIFICATION,
-              {
-                connectorId,
-                errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
-                status: chargingStation.getConnectorStatus(connectorId).status,
-              },
-              {
-                triggerMessage: true,
-              }
-            )
-            .catch(Constants.EMPTY_FUNCTION);
+          if (!chargingStation.isConnectorReservable(reservationId)) {
+            response = OCPPConstants.OCPP_RESERVATION_RESPONSE_OCCUPIED;
+            break;
+          }
+          await chargingStation.addReservation({
+            id: commandPayload.reservationId,
+            ...commandPayload,
+          });
           response = OCPPConstants.OCPP_RESERVATION_RESPONSE_ACCEPTED;
           break;
       }
       return response;
     } catch (error) {
+      chargingStation.getConnectorStatus(connectorId).status = ConnectorStatusEnum.Available;
       return this.handleIncomingRequestError(
         chargingStation,
         OCPP16IncomingRequestCommand.RESERVE_NOW,
         error as Error,
-        { errorResponse: OCPP16Constants.OCPP_RESERVATION_RESPONSE_FAULTED }
+        { errorResponse: OCPPConstants.OCPP_RESERVATION_RESPONSE_FAULTED }
       );
     }
   }
@@ -1584,133 +1595,23 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     commandPayload: OCPP16CancelReservationRequest
   ): Promise<OCPP16CancelReservationResponse> {
     try {
-      const reservationId = commandPayload.reservationId;
-      const [exists, reservation] = chargingStation.doesReservationExist(reservationId);
+      const { reservationId } = commandPayload;
+      const [exists, reservation] = chargingStation.doesReservationExists({ id: reservationId });
       if (!exists) {
         logger.error(
           `${chargingStation.logPrefix()} Reservation with ID ${reservationId} does not exist on charging station`
         );
-        return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
+        return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED;
       }
-      chargingStation.getConnectorStatus(reservation.connectorId).status =
-        ConnectorStatusEnum.Available;
-      chargingStation.removeReservation(reservation.reservationId);
-      await chargingStation.ocppRequestService
-        .requestHandler<OCPP16StatusNotificationRequest, OCPP16StatusNotificationResponse>(
-          chargingStation,
-          OCPP16RequestCommand.STATUS_NOTIFICATION,
-          {
-            connectorId: reservation.connectorId,
-            errorCode: OCPP16ChargePointErrorCode.NO_ERROR,
-            status: chargingStation.getConnectorStatus(reservation.connectorId).status,
-          },
-          {
-            triggerMessage: true,
-          }
-        )
-        .catch(Constants.EMPTY_FUNCTION);
-      return OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
+      await chargingStation.removeReservation(reservation);
+      return OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_ACCEPTED;
     } catch (error) {
       return this.handleIncomingRequestError(
         chargingStation,
         OCPP16IncomingRequestCommand.CANCEL_RESERVATION,
         error as Error,
-        { errorResponse: OCPP16Constants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
-      );
-    }
-  }
-
-  /**
-   * Check for authorized access on a connector with given ConnectorId and idTag for the user
-   * @param {ChargingStation} chargingStation - Charging Station working on incoming request
-   * @param {number} ConnectorId - Identifier of the connector at the charging station
-   * @param {string} idTag - Identifier of the user
-   * @param {string} parentIdTag - Identifier for a group of idTags, which is optional
-   * @returns {Promise<boolean>} - 'true' if user is authorized, 'false' otherwise
-   */
-  private async isAuthorized(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    idTag: string,
-    parentIdTag?: string
-  ): Promise<boolean> {
-    let authorized = false;
-    const connectorStatus = chargingStation.getConnectorStatus(connectorId);
-    if (
-      chargingStation.getLocalAuthListEnabled() === true &&
-      chargingStation.hasIdTags() === true &&
-      Utils.isNotEmptyString(
-        chargingStation.idTagsCache
-          .getIdTags(ChargingStationUtils.getIdTagsFile(chargingStation.stationInfo))
-          ?.find((tag) => tag === idTag)
-      )
-    ) {
-      connectorStatus.localAuthorizeIdTag = idTag;
-      connectorStatus.idTagLocalAuthorized = true;
-      authorized = true;
-    } else if (chargingStation.getMustAuthorizeAtRemoteStart() === true) {
-      connectorStatus.authorizeIdTag = idTag;
-      const authorizeResponse: OCPP16AuthorizeResponse =
-        await chargingStation.ocppRequestService.requestHandler<
-          OCPP16AuthorizeRequest,
-          OCPP16AuthorizeResponse
-        >(chargingStation, OCPP16RequestCommand.AUTHORIZE, {
-          idTag: idTag,
-        });
-      if (authorizeResponse?.idTagInfo?.status === OCPP16AuthorizationStatus.ACCEPTED) {
-        authorized = true;
-      }
-    } else {
-      logger.warn(
-        `${chargingStation.logPrefix()} The charging station configuration expects authorize at remote start transaction but local authorization or authorize isn't enabled`
-      );
-    }
-    return authorized;
-  }
-
-  private async handleReservedRemoteStartTransaction(
-    chargingStation: ChargingStation,
-    connectorId: number,
-    commandPayload: RemoteStartTransactionRequest
-  ): Promise<GenericResponse> {
-    const reservation = chargingStation.getReservationByConnectorId(connectorId);
-    if (
-      !Utils.isUndefined(reservation) &&
-      (await this.isAuthorized(chargingStation, connectorId, commandPayload.idTag)) &&
-      reservation.idTag === commandPayload.idTag
-    ) {
-      const remoteStartTransactionLogMsg = `${chargingStation.logPrefix()} Transaction remotely STARTED on ${
-        chargingStation.stationInfo.chargingStationId
-      }#${connectorId.toString()} for idTag '${commandPayload.idTag}'`;
-      await OCPP16ServiceUtils.sendAndSetConnectorStatus(
-        chargingStation,
-        connectorId,
-        OCPP16ChargePointStatus.Preparing
+        { errorResponse: OCPPConstants.OCPP_CANCEL_RESERVATION_RESPONSE_REJECTED }
       );
-      if (
-        this.setRemoteStartTransactionChargingProfile(
-          chargingStation,
-          connectorId,
-          commandPayload.chargingProfile
-        ) === true
-      ) {
-        chargingStation.getConnectorStatus(connectorId).transactionRemoteStarted = true;
-        if (
-          (
-            await chargingStation.ocppRequestService.requestHandler<
-              OCPP16StartTransactionRequest,
-              OCPP16StartTransactionResponse
-            >(chargingStation, OCPP16RequestCommand.START_TRANSACTION, {
-              connectorId: connectorId,
-              idTag: commandPayload.idTag,
-              reservationId: reservation.reservationId,
-            })
-          ).idTagInfo.status === OCPP16AuthorizationStatus.ACCEPTED
-        ) {
-          logger.debug(remoteStartTransactionLogMsg);
-          return OCPP16Constants.OCPP_RESPONSE_ACCEPTED;
-        }
-      }
     }
   }
 }
index 7ff1fdb8d43b27da48147a789a1612c87644d8a4..b82599b768e08ed13cfff1586ef20d1b12190637 100644 (file)
@@ -24,6 +24,10 @@ import {
   OCPPVersion,
   type RequestParams,
 } from '../../../types';
+import type {
+  OCPP16CancelReservationRequest,
+  OCPP16ReserveNowRequest,
+} from '../../../types/ocpp/1.6/Requests';
 import { Constants, Utils } from '../../../utils';
 import { OCPPRequestService } from '../OCPPRequestService';
 import type { OCPPResponseService } from '../OCPPResponseService';
@@ -119,6 +123,22 @@ export class OCPP16RequestService extends OCPPRequestService {
           'constructor'
         ),
       ],
+      [
+        OCPP16RequestCommand.RESERVE_NOW,
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16ReserveNowRequest>(
+          'assets/json-schemas/ocpp/1.6/ReserveNow.json',
+          moduleName,
+          'constructor'
+        ),
+      ],
+      [
+        OCPP16RequestCommand.CANCEL_RESERVATION,
+        OCPP16ServiceUtils.parseJsonSchemaFile<OCPP16CancelReservationRequest>(
+          'assets/json-schemas/ocpp/1.6/CancelReservation.json',
+          moduleName,
+          'constructor'
+        ),
+      ],
     ]);
     this.buildRequestPayload = this.buildRequestPayload.bind(this) as <Request extends JsonType>(
       chargingStation: ChargingStation,
index a04a3f65ba61f5321e16e2936f54729bd018bcc8..80128a271c9fd6edd3cb6ec9609ad6b2ed898da7 100644 (file)
@@ -188,7 +188,7 @@ export interface OCPP16DataTransferRequest extends JsonObject {
   data?: string;
 }
 
-export interface OCPP16ReserveNowRequest {
+export interface OCPP16ReserveNowRequest extends JsonObject {
   connectorId: number;
   expiryDate: Date;
   idTag: string;
@@ -196,6 +196,6 @@ export interface OCPP16ReserveNowRequest {
   reservationId: number;
 }
 
-export interface OCPP16CancelReservationRequest {
+export interface OCPP16CancelReservationRequest extends JsonObject {
   reservationId: number;
 }
index 7a78225d28cca73f4c9c8317640987124e21fb36..49bebd14049c5b7c55a841f12d8a1a015ad4df64 100644 (file)
@@ -1,7 +1,14 @@
 export interface OCPP16Reservation {
+  id: number;
   connectorId: number;
   expiryDate: Date;
   idTag: string;
   parentIdTag?: string;
-  reservationId: number;
+}
+
+export enum ReservationTerminationReason {
+  EXPIRED = 'Expired',
+  TRANSACTION_STARTED = 'TransactionStarted',
+  CONNECTOR_STATE_CHANGED = 'ConnectorStateChanged',
+  CANCELED = 'ReservationCanceled',
 }
index dd5d8190fc3d6c46949898080b268b1733e1ea48..b185b8744f1a3d9597867b9ff0e9033679e1fc42 100644 (file)
@@ -1,3 +1,3 @@
-import { OCPP16Reservation } from './1.6/Reservation';
+import { type OCPP16Reservation } from './1.6/Reservation';
 
 export type Reservation = OCPP16Reservation;
index e4215f94c973e3be09c6283d9773e489067105d7..6f53ef3bd92e796d7f9be4fd049df4b88f8be6d9 100644 (file)
@@ -55,7 +55,9 @@ export class Configuration {
   public static getUIServer(): UIServerConfiguration {
     if (Utils.hasOwnProp(Configuration.getConfig(), 'uiWebSocketServer')) {
       console.error(
-        chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead}`
+        `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+          "Deprecated configuration section 'uiWebSocketServer' usage. Use 'uiServer' instead"
+        )}`
       );
     }
     let uiServerConfiguration: UIServerConfiguration = {
@@ -137,9 +139,9 @@ export class Configuration {
       (stationTemplateUrl: StationTemplateUrl) => {
         if (!Utils.isUndefined(stationTemplateUrl['numberOfStation'])) {
           console.error(
-            chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key 'numberOfStation' usage for template file '${
-              stationTemplateUrl.file
-            }' in 'stationTemplateUrls'. Use 'numberOfStations' instead}`
+            `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+              `Deprecated configuration key 'numberOfStation' usage for template file '${stationTemplateUrl.file}' in 'stationTemplateUrls'. Use 'numberOfStations' instead`
+            )}`
           );
         }
       }
@@ -337,15 +339,19 @@ export class Configuration {
       !Utils.isUndefined((Configuration.getConfig()[sectionName] as Record<string, unknown>)[key])
     ) {
       console.error(
-        chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage in section '${sectionName}'${
-          logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
-        }}`
+        `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+          `Deprecated configuration key '${key}' usage in section '${sectionName}'${
+            logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
+          }`
+        )}`
       );
     } else if (!Utils.isUndefined(Configuration.getConfig()[key])) {
       console.error(
-        chalk`{green ${Configuration.logPrefix()}} {red Deprecated configuration key '${key}' usage${
-          logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
-        }}`
+        `${chalk.green(Configuration.logPrefix())} ${chalk.red(
+          `Deprecated configuration key '${key}' usage${
+            logMsgToAppend.trim().length > 0 ? `. ${logMsgToAppend}` : ''
+          }`
+        )}`
       );
     }
   }
index 8608e480f7d7b4a3a1ff938e0c487c216da8912c..6c641cf62da79416498fd2149380d1d17223b4f7 100644 (file)
@@ -39,6 +39,8 @@ export class Constants {
     /* This is intentional */
   });
 
+  static readonly DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL = 5000; // Ms
+
   private constructor() {
     // This is intentional
   }