+ public getReserveConnectorZeroSupported(): boolean {
+ return convertToBoolean(
+ getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value,
+ );
+ }
+
+ public async addReservation(reservation: Reservation): Promise<void> {
+ const reservationFound = this.getReservationBy('reservationId', reservation.reservationId);
+ if (!isUndefined(reservationFound)) {
+ await this.removeReservation(
+ reservationFound!,
+ ReservationTerminationReason.REPLACE_EXISTING,
+ );
+ }
+ this.getConnectorStatus(reservation.connectorId)!.reservation = reservation;
+ await OCPPServiceUtils.sendAndSetConnectorStatus(
+ this,
+ reservation.connectorId,
+ ConnectorStatusEnum.Reserved,
+ undefined,
+ { 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:
+ case ReservationTerminationReason.TRANSACTION_STARTED:
+ delete connector.reservation;
+ break;
+ case ReservationTerminationReason.RESERVATION_CANCELED:
+ case ReservationTerminationReason.REPLACE_EXISTING:
+ case ReservationTerminationReason.EXPIRED:
+ await OCPPServiceUtils.sendAndSetConnectorStatus(
+ this,
+ reservation.connectorId,
+ ConnectorStatusEnum.Available,
+ undefined,
+ { send: reservation.connectorId !== 0 },
+ );
+ delete connector.reservation;
+ break;
+ default:
+ // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+ throw new BaseError(`Unknown reservation termination reason '${reason}'`);
+ }
+ }
+
+ public getReservationBy(
+ filterKey: ReservationKey,
+ value: number | string,
+ ): Reservation | undefined {
+ if (this.hasEvses) {
+ for (const evseStatus of this.evses.values()) {
+ for (const connectorStatus of evseStatus.connectors.values()) {
+ if (connectorStatus?.reservation?.[filterKey] === value) {
+ return connectorStatus.reservation;
+ }
+ }
+ }
+ } else {
+ for (const connectorStatus of this.connectors.values()) {
+ if (connectorStatus?.reservation?.[filterKey] === value) {
+ return connectorStatus.reservation;
+ }
+ }
+ }
+ }
+
+ public isConnectorReservable(
+ reservationId: number,
+ idTag?: string,
+ connectorId?: number,
+ ): boolean {
+ const reservation = this.getReservationBy('reservationId', reservationId);
+ const reservationExists = !isUndefined(reservation) && !hasReservationExpired(reservation!);
+ if (arguments.length === 1) {
+ return !reservationExists;
+ } else if (arguments.length > 1) {
+ const userReservation = !isUndefined(idTag)
+ ? this.getReservationBy('idTag', idTag!)
+ : undefined;
+ const userReservationExists =
+ !isUndefined(userReservation) && !hasReservationExpired(userReservation!);
+ const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0;
+ const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
+ return (
+ !reservationExists && !userReservationExists && notConnectorZero && freeConnectorsAvailable
+ );
+ }
+ return false;
+ }
+
+ private startReservationExpirationSetInterval(customInterval?: number): void {
+ const interval =
+ customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_OBSERVATION_INTERVAL;
+ if (interval > 0) {
+ logger.info(
+ `${this.logPrefix()} Reservation expiration date checks started every ${formatDurationMilliSeconds(
+ interval,
+ )}`,
+ );
+ this.reservationExpirationSetInterval = setInterval((): void => {
+ removeExpiredReservations(this).catch(Constants.EMPTY_FUNCTION);
+ }, interval);
+ }
+ }
+
+ private stopReservationExpirationSetInterval(): void {
+ if (this.reservationExpirationSetInterval) {
+ clearInterval(this.reservationExpirationSetInterval);
+ }
+ }
+
+ // private restartReservationExpiryDateSetInterval(): void {
+ // this.stopReservationExpirationSetInterval();
+ // this.startReservationExpirationSetInterval();
+ // }
+
+ private getNumberOfReservableConnectors(): number {
+ let numberOfReservableConnectors = 0;
+ if (this.hasEvses) {
+ for (const evseStatus of this.evses.values()) {
+ numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors);
+ }
+ } else {
+ numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors);
+ }
+ return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero();
+ }
+
+ private getNumberOfReservationsOnConnectorZero(): number {
+ if (
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation) ||
+ (!this.hasEvses && this.connectors.get(0)?.reservation)
+ ) {
+ return 1;
+ }
+ return 0;
+ }
+