+ public getReservationOnConnectorId0Enabled(): boolean {
+ return Utils.convertToBoolean(
+ ChargingStationConfigurationUtils.getConfigurationKey(
+ this,
+ StandardParametersKey.ReserveConnectorZeroSupported
+ ).value
+ );
+ }
+
+ public async addReservation(reservation: Reservation): Promise<void> {
+ const [exists, reservationFound] = this.doesReservationExists(reservation);
+ if (exists) {
+ await this.removeReservation(reservationFound, ReservationTerminationReason.REPLACE_EXISTING);
+ }
+ this.getConnectorStatus(reservation.connectorId).reservation = reservation;
+ await OCPPServiceUtils.sendAndSetConnectorStatus(
+ this,
+ reservation.connectorId,
+ ConnectorStatusEnum.Reserved,
+ null,
+ { send: reservation.connectorId !== 0 }
+ );
+ }
+
+ public async removeReservation(
+ reservation: Reservation,
+ reason?: ReservationTerminationReason
+ ): Promise<void> {
+ const connector = this.getConnectorStatus(reservation.connectorId);
+ switch (reason) {
+ case ReservationTerminationReason.CONNECTOR_STATE_CHANGED:
+ delete connector.reservation;
+ break;
+ case ReservationTerminationReason.TRANSACTION_STARTED:
+ delete connector.reservation;
+ break;
+ case ReservationTerminationReason.RESERVATION_CANCELED ||
+ ReservationTerminationReason.REPLACE_EXISTING ||
+ ReservationTerminationReason.EXPIRED:
+ await OCPPServiceUtils.sendAndSetConnectorStatus(
+ this,
+ reservation.connectorId,
+ ConnectorStatusEnum.Available,
+ null,
+ { send: reservation.connectorId !== 0 }
+ );
+ delete connector.reservation;
+ break;
+ default:
+ break;
+ }
+ }
+
+ public getReservationBy(filterKey: ReservationFilterKey, value: number | string): Reservation {
+ if (this.hasEvses) {
+ for (const evse of this.evses.values()) {
+ for (const connector of evse.connectors.values()) {
+ if (connector?.reservation?.[filterKey] === value) {
+ return connector.reservation;
+ }
+ }
+ }
+ } else {
+ for (const connector of this.connectors.values()) {
+ if (connector?.reservation?.[filterKey] === value) {
+ return connector.reservation;
+ }
+ }
+ }
+ }
+
+ public doesReservationExists(reservation: Partial<Reservation>): [boolean, Reservation] {
+ const foundReservation = this.getReservationBy(
+ ReservationFilterKey.RESERVATION_ID,
+ reservation?.id
+ );
+ return Utils.isUndefined(foundReservation) ? [false, null] : [true, foundReservation];
+ }
+
+ public startReservationExpirationSetInterval(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 charging station now`
+ );
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
+ this.reservationExpirationSetInterval = setInterval(async (): Promise<void> => {
+ if (this.hasEvses) {
+ for (const evse of this.evses.values()) {
+ for (const connector of evse.connectors.values()) {
+ if (connector?.reservation?.expiryDate.toString() < new Date().toISOString()) {
+ await this.removeReservation(
+ connector.reservation,
+ ReservationTerminationReason.EXPIRED
+ );
+ }
+ }
+ }
+ } else {
+ for (const connector of this.connectors.values()) {
+ if (connector?.reservation?.expiryDate.toString() < new Date().toISOString()) {
+ await this.removeReservation(
+ connector.reservation,
+ ReservationTerminationReason.EXPIRED
+ );
+ }
+ }
+ }
+ }, interval);
+ }
+
+ public restartReservationExpiryDateSetInterval(): void {
+ this.stopReservationExpirationSetInterval();
+ this.startReservationExpirationSetInterval();
+ }
+
+ public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean {
+ const reservation = this.getReservationBy(ReservationFilterKey.CONNECTOR_ID, connectorId);
+ return !Utils.isUndefined(reservation) && reservation.idTag === idTag;
+ }
+
+ public isConnectorReservable(
+ reservationId: number,
+ idTag?: string,
+ connectorId?: number
+ ): boolean {
+ const [alreadyExists] = this.doesReservationExists({ id: reservationId });
+ if (alreadyExists) {
+ return alreadyExists;
+ }
+ const userReservedAlready = Utils.isUndefined(
+ this.getReservationBy(ReservationFilterKey.ID_TAG, 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;
+ if (this.hasEvses) {
+ for (const evse of this.evses.values()) {
+ reservableConnectors = this.countReservableConnectors(evse.connectors);
+ }
+ } else {
+ reservableConnectors = this.countReservableConnectors(this.connectors);
+ }
+ return reservableConnectors - this.getNumberOfReservationsOnConnectorZero();
+ }
+
+ private countReservableConnectors(connectors: Map<number, ConnectorStatus>) {
+ let reservableConnectors = 0;
+ for (const [connectorId, connector] of connectors) {
+ if (connectorId === 0) {
+ continue;
+ }
+ if (connector.status === ConnectorStatusEnum.Available) {
+ ++reservableConnectors;
+ }
+ }
+ return reservableConnectors;
+ }
+
+ private getNumberOfReservationsOnConnectorZero(): number {
+ let numberOfReservations = 0;
+ if (this.hasEvses) {
+ for (const evse of this.evses.values()) {
+ if (evse.connectors.get(0)?.reservation) {
+ ++numberOfReservations;
+ }
+ }
+ } else if (this.connectors.get(0)?.reservation) {
+ ++numberOfReservations;
+ }
+ return numberOfReservations;
+ }
+