fix: avoid worker-threads restart at error during startup
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index bf5bc189d4d3ad22eacd853fa621229b44013e74..f92ea2b6e96863c01ecca86ecaa91891592073be 100644 (file)
@@ -26,26 +26,12 @@ import {
   getConfigurationKey,
   setConfigurationKeyValue,
 } from './ConfigurationKeyUtils';
-import { IdTagsCache } from './IdTagsCache';
-import {
-  OCPP16IncomingRequestService,
-  OCPP16RequestService,
-  OCPP16ResponseService,
-  OCPP16ServiceUtils,
-  OCPP20IncomingRequestService,
-  OCPP20RequestService,
-  OCPP20ResponseService,
-  type OCPPIncomingRequestService,
-  type OCPPRequestService,
-  OCPPServiceUtils,
-} from './ocpp';
-import { SharedLRUCache } from './SharedLRUCache';
 import {
   buildConnectorsMap,
+  checkChargingStation,
   checkConnectorsConfiguration,
   checkStationInfoConnectorStatus,
   checkTemplate,
-  countReservableConnectors,
   createBootNotificationRequest,
   createSerialNumber,
   getAmperageLimitationUnitDivider,
@@ -56,13 +42,30 @@ import {
   getHashId,
   getIdTagsFile,
   getMaxNumberOfEvses,
+  getNumberOfReservableConnectors,
   getPhaseRotationValue,
   hasFeatureProfile,
+  hasReservationExpired,
   initializeConnectorsMapStatus,
   propagateSerialNumber,
+  removeExpiredReservations,
   stationTemplateToStationInfo,
   warnTemplateKeysDeprecation,
-} from './Utils';
+} from './Helpers';
+import { IdTagsCache } from './IdTagsCache';
+import {
+  OCPP16IncomingRequestService,
+  OCPP16RequestService,
+  OCPP16ResponseService,
+  OCPP16ServiceUtils,
+  OCPP20IncomingRequestService,
+  OCPP20RequestService,
+  OCPP20ResponseService,
+  type OCPPIncomingRequestService,
+  type OCPPRequestService,
+  OCPPServiceUtils,
+} from './ocpp';
+import { SharedLRUCache } from './SharedLRUCache';
 import { BaseError, OCPPError } from '../exception';
 import { PerformanceStatistics } from '../performance';
 import {
@@ -104,7 +107,7 @@ import {
   RegistrationStatusEnumType,
   RequestCommand,
   type Reservation,
-  type ReservationFilterKey,
+  type ReservationKey,
   ReservationTerminationReason,
   type Response,
   StandardParametersKey,
@@ -246,8 +249,8 @@ export class ChargingStation {
     return this.stationInfo.enableStatistics ?? false;
   }
 
-  public getMustAuthorizeAtRemoteStart(): boolean {
-    return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
+  public getRemoteAuthorization(): boolean {
+    return this.stationInfo.remoteAuthorization ?? true;
   }
 
   public getNumberOfPhases(stationInfo?: ChargingStationInfo): number {
@@ -417,7 +420,7 @@ export class ChargingStation {
   }
 
   public getNumberOfRunningTransactions(): number {
-    let trxCount = 0;
+    let numberOfRunningTransactions = 0;
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
         if (evseId === 0) {
@@ -425,18 +428,18 @@ export class ChargingStation {
         }
         for (const connectorStatus of evseStatus.connectors.values()) {
           if (connectorStatus.transactionStarted === true) {
-            ++trxCount;
+            ++numberOfRunningTransactions;
           }
         }
       }
     } else {
       for (const connectorId of this.connectors.keys()) {
         if (connectorId > 0 && this.getConnectorStatus(connectorId)?.transactionStarted === true) {
-          ++trxCount;
+          ++numberOfRunningTransactions;
         }
       }
     }
-    return trxCount;
+    return numberOfRunningTransactions;
   }
 
   public getOutOfOrderEndMeterValues(): boolean {
@@ -769,19 +772,16 @@ export class ChargingStation {
   }
 
   public openWSConnection(
-    options: WsOptions = this.stationInfo?.wsOptions ?? {},
-    params: { closeOpened?: boolean; terminateOpened?: boolean } = {
-      closeOpened: false,
-      terminateOpened: false,
-    },
+    options?: WsOptions,
+    params?: { closeOpened?: boolean; terminateOpened?: boolean },
   ): void {
-    options = { handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()), ...options };
+    options = {
+      handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()),
+      ...this.stationInfo?.wsOptions,
+      ...options,
+    };
     params = { ...{ closeOpened: false, terminateOpened: false }, ...params };
-    if (this.started === false && this.starting === false) {
-      logger.warn(
-        `${this.logPrefix()} Cannot open OCPP connection to URL ${this.wsConnectionUrl.toString()}
-          on stopped charging station`,
-      );
+    if (!checkChargingStation(this, this.logPrefix())) {
       return;
     }
     if (
@@ -936,7 +936,7 @@ export class ChargingStation {
     );
   }
 
-  public getReservationOnConnectorId0Enabled(): boolean {
+  public getReserveConnectorZeroSupported(): boolean {
     return convertToBoolean(
       getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value,
     );
@@ -962,7 +962,7 @@ export class ChargingStation {
 
   public async removeReservation(
     reservation: Reservation,
-    reason?: ReservationTerminationReason,
+    reason: ReservationTerminationReason,
   ): Promise<void> {
     const connector = this.getConnectorStatus(reservation.connectorId)!;
     switch (reason) {
@@ -983,12 +983,13 @@ export class ChargingStation {
         delete connector.reservation;
         break;
       default:
-        break;
+        // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
+        throw new BaseError(`Unknown reservation termination reason '${reason}'`);
     }
   }
 
   public getReservationBy(
-    filterKey: ReservationFilterKey,
+    filterKey: ReservationKey,
     value: number | string,
   ): Reservation | undefined {
     if (this.hasEvses) {
@@ -1008,68 +1009,21 @@ export class ChargingStation {
     }
   }
 
-  public 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 => {
-        const currentDate = new Date();
-        if (this.hasEvses) {
-          for (const evseStatus of this.evses.values()) {
-            for (const connectorStatus of evseStatus.connectors.values()) {
-              if (
-                connectorStatus.reservation &&
-                connectorStatus.reservation.expiryDate < currentDate
-              ) {
-                this.removeReservation(
-                  connectorStatus.reservation,
-                  ReservationTerminationReason.EXPIRED,
-                ).catch(Constants.EMPTY_FUNCTION);
-              }
-            }
-          }
-        } else {
-          for (const connectorStatus of this.connectors.values()) {
-            if (
-              connectorStatus.reservation &&
-              connectorStatus.reservation.expiryDate < currentDate
-            ) {
-              this.removeReservation(
-                connectorStatus.reservation,
-                ReservationTerminationReason.EXPIRED,
-              ).catch(Constants.EMPTY_FUNCTION);
-            }
-          }
-        }
-      }, interval);
-    }
-  }
-
-  public restartReservationExpiryDateSetInterval(): void {
-    this.stopReservationExpirationSetInterval();
-    this.startReservationExpirationSetInterval();
-  }
-
-  public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean {
-    return this.getReservationBy('connectorId', connectorId)?.idTag === idTag;
-  }
-
   public isConnectorReservable(
     reservationId: number,
     idTag?: string,
     connectorId?: number,
   ): boolean {
-    const reservationExists = !isUndefined(this.getReservationBy('reservationId', reservationId));
+    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(idTag) && isUndefined(this.getReservationBy('idTag', idTag!)) ? false : true;
+        !isUndefined(userReservation) && !hasReservationExpired(userReservation!);
       const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0;
       const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
       return (
@@ -1079,28 +1033,53 @@ export class ChargingStation {
     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 reservableConnectors = 0;
+    let numberOfReservableConnectors = 0;
     if (this.hasEvses) {
       for (const evseStatus of this.evses.values()) {
-        reservableConnectors += countReservableConnectors(evseStatus.connectors);
+        numberOfReservableConnectors += getNumberOfReservableConnectors(evseStatus.connectors);
       }
     } else {
-      reservableConnectors = countReservableConnectors(this.connectors);
+      numberOfReservableConnectors = getNumberOfReservableConnectors(this.connectors);
     }
-    return reservableConnectors - this.getNumberOfReservationsOnConnectorZero();
+    return numberOfReservableConnectors - this.getNumberOfReservationsOnConnectorZero();
   }
 
   private getNumberOfReservationsOnConnectorZero(): number {
-    let numberOfReservations = 0;
     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)
     ) {
-      ++numberOfReservations;
+      return 1;
     }
-    return numberOfReservations;
+    return 0;
   }
 
   private flushMessageBuffer(): void {
@@ -1130,12 +1109,6 @@ export class ChargingStation {
     return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
   }
 
-  private stopReservationExpirationSetInterval(): void {
-    if (this.reservationExpirationSetInterval) {
-      clearInterval(this.reservationExpirationSetInterval);
-    }
-  }
-
   private getSupervisionUrlOcppKey(): string {
     return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl;
   }
@@ -1594,6 +1567,13 @@ export class ChargingStation {
         } with evse id 0 with no connector id 0 configuration`,
       );
     }
+    if (Object.keys(stationTemplate?.Evses?.[0]?.Connectors as object).length > 1) {
+      logger.warn(
+        `${this.logPrefix()} Charging station information from template ${
+          this.templateFile
+        } with evse id 0 with more than one connector configuration, only connector id 0 configuration will be used`,
+      );
+    }
     if (stationTemplate?.Evses) {
       const evsesConfigHash = createHash(Constants.DEFAULT_HASH_ALGORITHM)
         .update(JSON.stringify(stationTemplate?.Evses))
@@ -2385,7 +2365,6 @@ export class ChargingStation {
       );
       this.openWSConnection(
         {
-          ...(this.stationInfo?.wsOptions ?? {}),
           handshakeTimeout: reconnectTimeout,
         },
         { closeOpened: true },