refactor: remove getter on stationInfo properties
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStation.ts
index 0f2f5217956100920f6c1377e21ad5b6105017dd..ae5f0fd1a965a6ea8b58660adce1dbf5873ec440 100644 (file)
@@ -1,19 +1,13 @@
 // Partial Copyright Jerome Benoit. 2021-2023. All Rights Reserved.
 
 import { createHash } from 'node:crypto';
-import {
-  type FSWatcher,
-  closeSync,
-  existsSync,
-  mkdirSync,
-  openSync,
-  readFileSync,
-  writeFileSync,
-} from 'node:fs';
+import { EventEmitter } from 'node:events';
+import { type FSWatcher, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
 import { dirname, join } from 'node:path';
 import { URL } from 'node:url';
 import { parentPort } from 'node:worker_threads';
 
+import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns';
 import merge from 'just-merge';
 import { type RawData, WebSocket } from 'ws';
 
@@ -24,13 +18,13 @@ import {
   deleteConfigurationKey,
   getConfigurationKey,
   setConfigurationKeyValue,
-} from './ChargingStationConfigurationUtils';
+} from './ConfigurationKeyUtils';
 import {
   buildConnectorsMap,
+  checkChargingStation,
   checkConnectorsConfiguration,
   checkStationInfoConnectorStatus,
   checkTemplate,
-  countReservableConnectors,
   createBootNotificationRequest,
   createSerialNumber,
   getAmperageLimitationUnitDivider,
@@ -41,12 +35,16 @@ import {
   getHashId,
   getIdTagsFile,
   getMaxNumberOfEvses,
+  getNumberOfReservableConnectors,
   getPhaseRotationValue,
+  hasFeatureProfile,
+  hasReservationExpired,
   initializeConnectorsMapStatus,
   propagateSerialNumber,
+  removeExpiredReservations,
   stationTemplateToStationInfo,
   warnTemplateKeysDeprecation,
-} from './ChargingStationUtils';
+} from './Helpers';
 import { IdTagsCache } from './IdTagsCache';
 import {
   OCPP16IncomingRequestService,
@@ -70,6 +68,7 @@ import {
   type BootNotificationResponse,
   type CachedRequest,
   type ChargingStationConfiguration,
+  ChargingStationEvents,
   type ChargingStationInfo,
   type ChargingStationOcppConfiguration,
   type ChargingStationTemplate,
@@ -102,7 +101,7 @@ import {
   RegistrationStatusEnumType,
   RequestCommand,
   type Reservation,
-  ReservationFilterKey,
+  type ReservationKey,
   ReservationTerminationReason,
   type Response,
   StandardParametersKey,
@@ -147,22 +146,23 @@ import {
   isUndefined,
   logPrefix,
   logger,
+  min,
+  once,
   roundTo,
   secureRandom,
   sleep,
   watchJsonFile,
 } from '../utils';
 
-export class ChargingStation {
+export class ChargingStation extends EventEmitter {
   public readonly index: number;
   public readonly templateFile: string;
-  public stationInfo!: ChargingStationInfo;
   public started: boolean;
   public starting: boolean;
   public idTagsCache: IdTagsCache;
   public automaticTransactionGenerator!: AutomaticTransactionGenerator | undefined;
   public ocppConfiguration!: ChargingStationOcppConfiguration | undefined;
-  public wsConnection!: WebSocket | null;
+  public wsConnection: WebSocket | null;
   public readonly connectors: Map<number, ConnectorStatus>;
   public readonly evses: Map<number, EvseStatus>;
   public readonly requests: Map<string, CachedRequest>;
@@ -172,6 +172,7 @@ export class ChargingStation {
   public bootNotificationRequest!: BootNotificationRequest;
   public bootNotificationResponse!: BootNotificationResponse | undefined;
   public powerDivider!: number;
+  private internalStationInfo!: ChargingStationInfo;
   private stopping: boolean;
   private configurationFile!: string;
   private configurationFileHash!: string;
@@ -191,9 +192,11 @@ export class ChargingStation {
   private reservationExpirationSetInterval?: NodeJS.Timeout;
 
   constructor(index: number, templateFile: string) {
+    super();
     this.started = false;
     this.starting = false;
     this.stopping = false;
+    this.wsConnection = null;
     this.wsConnectionRestarted = false;
     this.autoReconnectRetryCount = 0;
     this.index = index;
@@ -206,6 +209,16 @@ export class ChargingStation {
     this.idTagsCache = IdTagsCache.getInstance();
     this.chargingStationWorkerBroadcastChannel = new ChargingStationWorkerBroadcastChannel(this);
 
+    this.on(ChargingStationEvents.started, () => {
+      parentPort?.postMessage(buildStartedMessage(this));
+    });
+    this.on(ChargingStationEvents.stopped, () => {
+      parentPort?.postMessage(buildStoppedMessage(this));
+    });
+    this.on(ChargingStationEvents.updated, () => {
+      parentPort?.postMessage(buildUpdatedMessage(this));
+    });
+
     this.initialize();
   }
 
@@ -213,13 +226,42 @@ export class ChargingStation {
     return this.connectors.size === 0 && this.evses.size > 0;
   }
 
+  public get stationInfo(): ChargingStationInfo {
+    return {
+      ...this.internalStationInfo,
+      ...{
+        enableStatistics: false,
+        remoteAuthorization: true,
+        currentOutType: CurrentType.AC,
+        ocppStrictCompliance: true,
+        outOfOrderEndMeterValues: false,
+        beginEndMeterValues: false,
+        meteringPerTransaction: true,
+        transactionDataMeterValues: false,
+        mainVoltageMeterValues: true,
+        phaseLineToLineVoltageMeterValues: false,
+        customValueLimitationMeterValues: true,
+        supervisionUrlOcppConfiguration: false,
+        supervisionUrlOcppKey: VendorParametersKey.ConnectionUrl,
+        ocppVersion: OCPPVersion.VERSION_16,
+        ocppPersistentConfiguration: true,
+        stationInfoPersistentConfiguration: true,
+        automaticTransactionGeneratorPersistentConfiguration: true,
+        autoReconnectMaxRetries: -1,
+        registrationMaxRetries: -1,
+        reconnectExponentialDelay: false,
+        stopTransactionsOnStopped: true,
+      },
+    };
+  }
+
   private get wsConnectionUrl(): URL {
     return new URL(
       `${
-        this.getSupervisionUrlOcppConfiguration() &&
-        isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-        isNotEmptyString(getConfigurationKey(this, this.getSupervisionUrlOcppKey())?.value)
-          ? getConfigurationKey(this, this.getSupervisionUrlOcppKey())!.value
+        this.stationInfo?.supervisionUrlOcppConfiguration &&
+        isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
+        isNotEmptyString(getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)?.value)
+          ? getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)!.value
           : this.configuredSupervisionUrl.href
       }/${this.stationInfo.chargingStationId}`,
     );
@@ -240,14 +282,6 @@ export class ChargingStation {
     return isNotEmptyArray(this.idTagsCache.getIdTags(getIdTagsFile(this.stationInfo)!));
   }
 
-  public getEnableStatistics(): boolean {
-    return this.stationInfo.enableStatistics ?? false;
-  }
-
-  public getMustAuthorizeAtRemoteStart(): boolean {
-    return this.stationInfo.mustAuthorizeAtRemoteStart ?? true;
-  }
-
   public getNumberOfPhases(stationInfo?: ChargingStationInfo): number {
     const localStationInfo: ChargingStationInfo = stationInfo ?? this.stationInfo;
     switch (this.getCurrentOutType(stationInfo)) {
@@ -341,32 +375,6 @@ export class ChargingStation {
     return this.connectors.get(connectorId);
   }
 
-  public getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
-    return (stationInfo ?? this.stationInfo)?.currentOutType ?? CurrentType.AC;
-  }
-
-  public getOcppStrictCompliance(): boolean {
-    return this.stationInfo?.ocppStrictCompliance ?? true;
-  }
-
-  public getVoltageOut(stationInfo?: ChargingStationInfo): number {
-    const defaultVoltageOut = getDefaultVoltageOut(
-      this.getCurrentOutType(stationInfo),
-      this.logPrefix(),
-      this.templateFile,
-    );
-    return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut;
-  }
-
-  public getMaximumPower(stationInfo?: ChargingStationInfo): number {
-    const localStationInfo = stationInfo ?? this.stationInfo;
-    // eslint-disable-next-line @typescript-eslint/dot-notation
-    return (
-      (localStationInfo['maxPower' as keyof ChargingStationInfo] as number) ??
-      localStationInfo.maximumPower
-    );
-  }
-
   public getConnectorMaximumAvailablePower(connectorId: number): number {
     let connectorAmperageLimitationPowerLimit: number | undefined;
     if (
@@ -374,20 +382,20 @@ export class ChargingStation {
       this.getAmperageLimitation()! < this.stationInfo.maximumAmperage!
     ) {
       connectorAmperageLimitationPowerLimit =
-        (this.getCurrentOutType() === CurrentType.AC
+        (this.stationInfo?.currentOutType === CurrentType.AC
           ? ACElectricUtils.powerTotal(
               this.getNumberOfPhases(),
-              this.getVoltageOut(),
+              this.stationInfo.voltageOut!,
               this.getAmperageLimitation()! *
                 (this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors()),
             )
-          : DCElectricUtils.power(this.getVoltageOut(), this.getAmperageLimitation()!)) /
+          : DCElectricUtils.power(this.stationInfo.voltageOut!, this.getAmperageLimitation()!)) /
         this.powerDivider;
     }
-    const connectorMaximumPower = this.getMaximumPower() / this.powerDivider;
+    const connectorMaximumPower = this.stationInfo.maximumPower! / this.powerDivider;
     const connectorChargingProfilesPowerLimit =
       getChargingStationConnectorChargingProfilesPowerLimit(this, connectorId);
-    return Math.min(
+    return min(
       isNaN(connectorMaximumPower) ? Infinity : connectorMaximumPower,
       isNaN(connectorAmperageLimitationPowerLimit!)
         ? Infinity
@@ -415,7 +423,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) {
@@ -423,46 +431,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;
-  }
-
-  public getOutOfOrderEndMeterValues(): boolean {
-    return this.stationInfo?.outOfOrderEndMeterValues ?? false;
-  }
-
-  public getBeginEndMeterValues(): boolean {
-    return this.stationInfo?.beginEndMeterValues ?? false;
-  }
-
-  public getMeteringPerTransaction(): boolean {
-    return this.stationInfo?.meteringPerTransaction ?? true;
-  }
-
-  public getTransactionDataMeterValues(): boolean {
-    return this.stationInfo?.transactionDataMeterValues ?? false;
-  }
-
-  public getMainVoltageMeterValues(): boolean {
-    return this.stationInfo?.mainVoltageMeterValues ?? true;
-  }
-
-  public getPhaseLineToLineVoltageMeterValues(): boolean {
-    return this.stationInfo?.phaseLineToLineVoltageMeterValues ?? false;
-  }
-
-  public getCustomValueLimitationMeterValues(): boolean {
-    return this.stationInfo?.customValueLimitationMeterValues ?? true;
+    return numberOfRunningTransactions;
   }
 
   public getConnectorIdByTransactionId(transactionId: number): number | undefined {
@@ -516,11 +496,11 @@ export class ChargingStation {
   public getHeartbeatInterval(): number {
     const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval);
     if (HeartbeatInterval) {
-      return convertToInt(HeartbeatInterval.value) * 1000;
+      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value));
     }
     const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval);
     if (HeartBeatInterval) {
-      return convertToInt(HeartBeatInterval.value) * 1000;
+      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value));
     }
     this.stationInfo?.autoRegister === false &&
       logger.warn(
@@ -533,10 +513,10 @@ export class ChargingStation {
 
   public setSupervisionUrl(url: string): void {
     if (
-      this.getSupervisionUrlOcppConfiguration() &&
-      isNotEmptyString(this.getSupervisionUrlOcppKey())
+      this.stationInfo?.supervisionUrlOcppConfiguration &&
+      isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey)
     ) {
-      setConfigurationKeyValue(this, this.getSupervisionUrlOcppKey(), url);
+      setConfigurationKeyValue(this, this.stationInfo.supervisionUrlOcppKey!, url);
     } else {
       this.stationInfo.supervisionUrls = url;
       this.saveStationInfo();
@@ -569,8 +549,7 @@ export class ChargingStation {
       );
     } else {
       logger.error(
-        `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()},
-          not starting the heartbeat`,
+        `${this.logPrefix()} Heartbeat interval set to ${this.getHeartbeatInterval()}, not starting the heartbeat`,
       );
     }
   }
@@ -592,21 +571,20 @@ export class ChargingStation {
   public startMeterValues(connectorId: number, interval: number): void {
     if (connectorId === 0) {
       logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId.toString()}`,
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}`,
       );
       return;
     }
     if (!this.getConnectorStatus(connectorId)) {
       logger.error(
         `${this.logPrefix()} Trying to start MeterValues on non existing connector id
-          ${connectorId.toString()}`,
+          ${connectorId}`,
       );
       return;
     }
     if (this.getConnectorStatus(connectorId)?.transactionStarted === false) {
       logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}
-          with no transaction started`,
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction started`,
       );
       return;
     } else if (
@@ -614,8 +592,7 @@ export class ChargingStation {
       isNullOrUndefined(this.getConnectorStatus(connectorId)?.transactionId)
     ) {
       logger.error(
-        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId}
-          with no transaction id`,
+        `${this.logPrefix()} Trying to start MeterValues on connector id ${connectorId} with no transaction id`,
       );
       return;
     }
@@ -664,10 +641,10 @@ export class ChargingStation {
     if (this.started === false) {
       if (this.starting === false) {
         this.starting = true;
-        if (this.getEnableStatistics() === true) {
+        if (this.stationInfo?.enableStatistics === true) {
           this.performanceStatistics?.start();
         }
-        if (this.hasFeatureProfile(SupportedFeatureProfiles.Reservation)) {
+        if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
           this.startReservationExpirationSetInterval();
         }
         this.openWSConnection();
@@ -692,10 +669,10 @@ export class ChargingStation {
                 // Restart the ATG
                 this.stopAutomaticTransactionGenerator();
                 delete this.automaticTransactionGeneratorConfiguration;
-                if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
+                if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) {
                   this.startAutomaticTransactionGenerator();
                 }
-                if (this.getEnableStatistics() === true) {
+                if (this.stationInfo?.enableStatistics === true) {
                   this.performanceStatistics?.restart();
                 } else {
                   this.performanceStatistics?.stop();
@@ -711,7 +688,7 @@ export class ChargingStation {
           },
         );
         this.started = true;
-        parentPort?.postMessage(buildStartedMessage(this));
+        this.emit(ChargingStationEvents.started);
         this.starting = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already starting...`);
@@ -721,22 +698,25 @@ export class ChargingStation {
     }
   }
 
-  public async stop(reason?: StopTransactionReason): Promise<void> {
+  public async stop(reason?: StopTransactionReason, stopTransactions?: boolean): Promise<void> {
     if (this.started === true) {
       if (this.stopping === false) {
         this.stopping = true;
-        await this.stopMessageSequence(reason);
+        await this.stopMessageSequence(reason, stopTransactions);
         this.closeWSConnection();
-        if (this.getEnableStatistics() === true) {
+        if (this.stationInfo?.enableStatistics === true) {
           this.performanceStatistics?.stop();
         }
+        if (hasFeatureProfile(this, SupportedFeatureProfiles.Reservation)) {
+          this.stopReservationExpirationSetInterval();
+        }
         this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
         this.templateFileWatcher?.close();
         this.sharedLRUCache.deleteChargingStationTemplate(this.templateFileHash);
         delete this.bootNotificationResponse;
         this.started = false;
         this.saveConfiguration();
-        parentPort?.postMessage(buildStoppedMessage(this));
+        this.emit(ChargingStationEvents.stopped);
         this.stopping = false;
       } else {
         logger.warn(`${this.logPrefix()} Charging station is already stopping...`);
@@ -754,36 +734,26 @@ export class ChargingStation {
   }
 
   public saveOcppConfiguration(): void {
-    if (this.getOcppPersistentConfiguration()) {
+    if (this.stationInfo?.ocppPersistentConfiguration === true) {
       this.saveConfiguration();
     }
   }
 
-  public hasFeatureProfile(featureProfile: SupportedFeatureProfiles): boolean | undefined {
-    return getConfigurationKey(
-      this,
-      StandardParametersKey.SupportedFeatureProfiles,
-    )?.value?.includes(featureProfile);
-  }
-
   public bufferMessage(message: string): void {
     this.messageBuffer.add(message);
   }
 
   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: this.getConnectionTimeout() * 1000, ...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 (
@@ -801,8 +771,7 @@ export class ChargingStation {
 
     if (this.isWebSocketConnectionOpened() === true) {
       logger.warn(
-        `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()}
-          is already opened`,
+        `${this.logPrefix()} OCPP connection to URL ${this.wsConnectionUrl.toString()} is already opened`,
       );
       return;
     }
@@ -813,7 +782,7 @@ export class ChargingStation {
 
     this.wsConnection = new WebSocket(
       this.wsConnectionUrl,
-      `ocpp${this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16}`,
+      `ocpp${this.stationInfo.ocppVersion}`,
       options,
     );
 
@@ -852,17 +821,17 @@ export class ChargingStation {
       let automaticTransactionGeneratorConfiguration:
         | AutomaticTransactionGeneratorConfiguration
         | undefined;
-      const automaticTransactionGeneratorConfigurationFromFile =
-        this.getConfigurationFromFile()?.automaticTransactionGenerator;
+      const stationTemplate = this.getTemplateFromFile();
+      const stationConfiguration = this.getConfigurationFromFile();
       if (
-        this.getAutomaticTransactionGeneratorPersistentConfiguration() &&
-        automaticTransactionGeneratorConfigurationFromFile
+        this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true &&
+        stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+        stationConfiguration?.automaticTransactionGenerator
       ) {
         automaticTransactionGeneratorConfiguration =
-          automaticTransactionGeneratorConfigurationFromFile;
+          stationConfiguration?.automaticTransactionGenerator;
       } else {
-        automaticTransactionGeneratorConfiguration =
-          this.getTemplateFromFile()?.AutomaticTransactionGenerator;
+        automaticTransactionGeneratorConfiguration = stationTemplate?.AutomaticTransactionGenerator;
       }
       this.automaticTransactionGeneratorConfiguration = {
         ...Constants.DEFAULT_ATG_CONFIGURATION,
@@ -886,7 +855,7 @@ export class ChargingStation {
       this.automaticTransactionGenerator?.start();
     }
     this.saveAutomaticTransactionGeneratorConfiguration();
-    parentPort?.postMessage(buildUpdatedMessage(this));
+    this.emit(ChargingStationEvents.updated);
   }
 
   public stopAutomaticTransactionGenerator(connectorIds?: number[]): void {
@@ -898,18 +867,18 @@ export class ChargingStation {
       this.automaticTransactionGenerator?.stop();
     }
     this.saveAutomaticTransactionGeneratorConfiguration();
-    parentPort?.postMessage(buildUpdatedMessage(this));
+    this.emit(ChargingStationEvents.updated);
   }
 
   public async stopTransactionOnConnector(
     connectorId: number,
-    reason = StopTransactionReason.NONE,
+    reason?: StopTransactionReason,
   ): Promise<StopTransactionResponse> {
     const transactionId = this.getConnectorStatus(connectorId)?.transactionId;
     if (
-      this.getBeginEndMeterValues() === true &&
-      this.getOcppStrictCompliance() === true &&
-      this.getOutOfOrderEndMeterValues() === false
+      this.stationInfo?.beginEndMeterValues === true &&
+      this.stationInfo?.ocppStrictCompliance === true &&
+      this.stationInfo?.outOfOrderEndMeterValues === false
     ) {
       // FIXME: Implement OCPP version agnostic helpers
       const transactionEndMeterValue = OCPP16ServiceUtils.buildTransactionEndMeterValue(
@@ -933,20 +902,20 @@ export class ChargingStation {
       {
         transactionId,
         meterStop: this.getEnergyActiveImportRegisterByTransactionId(transactionId!, true),
-        reason,
+        ...(isNullOrUndefined(reason) && { reason }),
       },
     );
   }
 
-  public getReservationOnConnectorId0Enabled(): boolean {
+  public getReserveConnectorZeroSupported(): boolean {
     return convertToBoolean(
       getConfigurationKey(this, StandardParametersKey.ReserveConnectorZeroSupported)!.value,
     );
   }
 
   public async addReservation(reservation: Reservation): Promise<void> {
-    const [exists, reservationFound] = this.doesReservationExists(reservation);
-    if (exists) {
+    const reservationFound = this.getReservationBy('reservationId', reservation.reservationId);
+    if (!isUndefined(reservationFound)) {
       await this.removeReservation(
         reservationFound!,
         ReservationTerminationReason.REPLACE_EXISTING,
@@ -964,13 +933,11 @@ export class ChargingStation {
 
   public async removeReservation(
     reservation: Reservation,
-    reason?: ReservationTerminationReason,
+    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;
@@ -987,130 +954,102 @@ 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) {
       for (const evseStatus of this.evses.values()) {
         for (const connectorStatus of evseStatus.connectors.values()) {
-          if (connectorStatus?.reservation?.[filterKey as keyof Reservation] === value) {
+          if (connectorStatus?.reservation?.[filterKey] === value) {
             return connectorStatus.reservation;
           }
         }
       }
     } else {
       for (const connectorStatus of this.connectors.values()) {
-        if (connectorStatus?.reservation?.[filterKey as keyof Reservation] === value) {
+        if (connectorStatus?.reservation?.[filterKey] === value) {
           return connectorStatus.reservation;
         }
       }
     }
   }
 
-  public doesReservationExists(
-    reservation: Partial<Reservation>,
-  ): [boolean, Reservation | undefined] {
-    const foundReservation = this.getReservationBy(
-      ReservationFilterKey.RESERVATION_ID,
-      reservation.id!,
-    );
-    return isUndefined(foundReservation) ? [false, undefined] : [true, foundReservation];
+  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;
   }
 
-  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`,
-    );
+  private startReservationExpirationSetInterval(customInterval?: number): void {
+    const interval = customInterval ?? Constants.DEFAULT_RESERVATION_EXPIRATION_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);
-            }
-          }
-        }
+        removeExpiredReservations(this).catch(Constants.EMPTY_FUNCTION);
       }, interval);
     }
   }
 
-  public restartReservationExpiryDateSetInterval(): void {
-    this.stopReservationExpirationSetInterval();
-    this.startReservationExpirationSetInterval();
-  }
-
-  public validateIncomingRequestWithReservation(connectorId: number, idTag: string): boolean {
-    return this.getReservationBy(ReservationFilterKey.CONNECTOR_ID, connectorId)?.idTag === idTag;
-  }
-
-  public isConnectorReservable(
-    reservationId: number,
-    idTag?: string,
-    connectorId?: number,
-  ): boolean {
-    const [alreadyExists] = this.doesReservationExists({ id: reservationId });
-    if (alreadyExists) {
-      return alreadyExists;
+  private stopReservationExpirationSetInterval(): void {
+    if (!isNullOrUndefined(this.reservationExpirationSetInterval)) {
+      clearInterval(this.reservationExpirationSetInterval);
     }
-    const userReservedAlready = isUndefined(
-      this.getReservationBy(ReservationFilterKey.ID_TAG, idTag!),
-    )
-      ? false
-      : true;
-    const notConnectorZero = isUndefined(connectorId) ? true : connectorId! > 0;
-    const freeConnectorsAvailable = this.getNumberOfReservableConnectors() > 0;
-    return !alreadyExists && !userReservedAlready && notConnectorZero && freeConnectorsAvailable;
   }
 
+  // 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 (this.hasEvses && this.evses.get(0)?.connectors.get(0)?.reservation) {
-      ++numberOfReservations;
-    } else if (this.connectors.get(0)?.reservation) {
-      ++numberOfReservations;
+    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 numberOfReservations;
+    return 0;
   }
 
   private flushMessageBuffer(): void {
@@ -1136,20 +1075,6 @@ export class ChargingStation {
     }
   }
 
-  private getSupervisionUrlOcppConfiguration(): boolean {
-    return this.stationInfo.supervisionUrlOcppConfiguration ?? false;
-  }
-
-  private stopReservationExpirationSetInterval(): void {
-    if (this.reservationExpirationSetInterval) {
-      clearInterval(this.reservationExpirationSetInterval);
-    }
-  }
-
-  private getSupervisionUrlOcppKey(): string {
-    return this.stationInfo.supervisionUrlOcppKey ?? VendorParametersKey.ConnectionUrl;
-  }
-
   private getTemplateFromFile(): ChargingStationTemplate | undefined {
     let template: ChargingStationTemplate | undefined;
     try {
@@ -1180,7 +1105,8 @@ export class ChargingStation {
   private getStationInfoFromTemplate(): ChargingStationInfo {
     const stationTemplate: ChargingStationTemplate = this.getTemplateFromFile()!;
     checkTemplate(stationTemplate, this.logPrefix(), this.templateFile);
-    warnTemplateKeysDeprecation(stationTemplate, this.logPrefix(), this.templateFile);
+    const warnTemplateKeysDeprecationOnce = once(warnTemplateKeysDeprecation, this);
+    warnTemplateKeysDeprecationOnce(stationTemplate, this.logPrefix(), this.templateFile);
     if (stationTemplate?.Connectors) {
       checkConnectorsConfiguration(stationTemplate, this.logPrefix(), this.templateFile);
     }
@@ -1225,7 +1151,7 @@ export class ChargingStation {
       stationTemplate?.firmwareUpgrade ?? {},
     );
     stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime)
-      ? stationTemplate.resetTime! * 1000
+      ? secondsToMilliseconds(stationTemplate.resetTime!)
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
     stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
     return stationInfo;
@@ -1233,7 +1159,7 @@ export class ChargingStation {
 
   private getStationInfoFromFile(): ChargingStationInfo | undefined {
     let stationInfo: ChargingStationInfo | undefined;
-    if (this.getStationInfoPersistentConfiguration()) {
+    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
       stationInfo = this.getConfigurationFromFile()?.stationInfo;
       if (stationInfo) {
         delete stationInfo?.infoHash;
@@ -1261,26 +1187,13 @@ export class ChargingStation {
   }
 
   private saveStationInfo(): void {
-    if (this.getStationInfoPersistentConfiguration()) {
+    if (this.stationInfo?.stationInfoPersistentConfiguration === true) {
       this.saveConfiguration();
     }
   }
 
-  private getOcppPersistentConfiguration(): boolean {
-    return this.stationInfo?.ocppPersistentConfiguration ?? true;
-  }
-
-  private getStationInfoPersistentConfiguration(): boolean {
-    return this.stationInfo?.stationInfoPersistentConfiguration ?? true;
-  }
-
-  private getAutomaticTransactionGeneratorPersistentConfiguration(): boolean {
-    return this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ?? true;
-  }
-
   private handleUnsupportedVersion(version: OCPPVersion) {
-    const errorMsg = `Unsupported protocol version '${version}' configured
-      in template file ${this.templateFile}`;
+    const errorMsg = `Unsupported protocol version '${version}' configured in template file ${this.templateFile}`;
     logger.error(`${this.logPrefix()} ${errorMsg}`);
     throw new BaseError(errorMsg);
   }
@@ -1292,17 +1205,17 @@ export class ChargingStation {
       dirname(this.templateFile.replace('station-templates', 'configurations')),
       `${getHashId(this.index, stationTemplate)}.json`,
     );
-    const chargingStationConfiguration = this.getConfigurationFromFile();
+    const stationConfiguration = this.getConfigurationFromFile();
     if (
-      chargingStationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
+      stationConfiguration?.stationInfo?.templateHash === stationTemplate?.templateHash &&
       // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-      (chargingStationConfiguration?.connectorsStatus || chargingStationConfiguration?.evsesStatus)
+      (stationConfiguration?.connectorsStatus || stationConfiguration?.evsesStatus)
     ) {
-      this.initializeConnectorsOrEvsesFromFile(chargingStationConfiguration);
+      this.initializeConnectorsOrEvsesFromFile(stationConfiguration);
     } else {
       this.initializeConnectorsOrEvsesFromTemplate(stationTemplate);
     }
-    this.stationInfo = this.getStationInfo();
+    this.internalStationInfo = this.getStationInfo();
     if (
       this.stationInfo.firmwareStatus === FirmwareStatus.Installing &&
       isNotEmptyString(this.stationInfo.firmwareVersion) &&
@@ -1323,7 +1236,7 @@ export class ChargingStation {
     }
     this.saveStationInfo();
     this.configuredSupervisionUrl = this.getConfiguredSupervisionUrl();
-    if (this.getEnableStatistics() === true) {
+    if (this.stationInfo?.enableStatistics === true) {
       this.performanceStatistics = PerformanceStatistics.getInstance(
         this.stationInfo.hashId,
         this.stationInfo.chargingStationId!,
@@ -1339,14 +1252,14 @@ export class ChargingStation {
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
         currentTime: new Date(),
-        interval: this.getHeartbeatInterval() / 1000,
+        interval: millisecondsToSeconds(this.getHeartbeatInterval()),
         status: RegistrationStatusEnumType.ACCEPTED,
       };
     }
   }
 
   private initializeOcppServices(): void {
-    const ocppVersion = this.stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
+    const ocppVersion = this.stationInfo.ocppVersion;
     switch (ocppVersion) {
       case OCPPVersion.VERSION_16:
         this.ocppIncomingRequestService =
@@ -1377,22 +1290,22 @@ export class ChargingStation {
       addConfigurationKey(this, StandardParametersKey.HeartBeatInterval, '0', { visible: false });
     }
     if (
-      this.getSupervisionUrlOcppConfiguration() &&
-      isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-      !getConfigurationKey(this, this.getSupervisionUrlOcppKey())
+      this.stationInfo?.supervisionUrlOcppConfiguration &&
+      isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
+      !getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)
     ) {
       addConfigurationKey(
         this,
-        this.getSupervisionUrlOcppKey(),
+        this.stationInfo.supervisionUrlOcppKey!,
         this.configuredSupervisionUrl.href,
         { reboot: true },
       );
     } else if (
-      !this.getSupervisionUrlOcppConfiguration() &&
-      isNotEmptyString(this.getSupervisionUrlOcppKey()) &&
-      getConfigurationKey(this, this.getSupervisionUrlOcppKey())
+      !this.stationInfo?.supervisionUrlOcppConfiguration &&
+      isNotEmptyString(this.stationInfo?.supervisionUrlOcppKey) &&
+      getConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!)
     ) {
-      deleteConfigurationKey(this, this.getSupervisionUrlOcppKey(), { save: false });
+      deleteConfigurationKey(this, this.stationInfo.supervisionUrlOcppKey!, { save: false });
     }
     if (
       isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
@@ -1547,7 +1460,7 @@ export class ChargingStation {
           for (let connectorId = 0; connectorId <= configuredMaxConnectors; connectorId++) {
             if (
               connectorId === 0 &&
-              (!stationTemplate?.Connectors[connectorId] ||
+              (!stationTemplate?.Connectors?.[connectorId] ||
                 this.getUseConnectorId0(stationTemplate) === false)
             ) {
               continue;
@@ -1604,6 +1517,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))
@@ -1615,11 +1535,11 @@ export class ChargingStation {
         this.evsesConfigurationHash = evsesConfigHash;
         const templateMaxEvses = getMaxNumberOfEvses(stationTemplate?.Evses);
         if (templateMaxEvses > 0) {
-          for (const evse in stationTemplate.Evses) {
-            const evseId = convertToInt(evse);
+          for (const evseKey in stationTemplate.Evses) {
+            const evseId = convertToInt(evseKey);
             this.evses.set(evseId, {
               connectors: buildConnectorsMap(
-                stationTemplate?.Evses[evse]?.Connectors,
+                stationTemplate?.Evses[evseKey]?.Connectors,
                 this.logPrefix(),
                 this.templateFile,
               ),
@@ -1676,7 +1596,7 @@ export class ChargingStation {
   }
 
   private saveAutomaticTransactionGeneratorConfiguration(): void {
-    if (this.getAutomaticTransactionGeneratorPersistentConfiguration()) {
+    if (this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration === true) {
       this.saveConfiguration();
     }
   }
@@ -1698,12 +1618,15 @@ export class ChargingStation {
         let configurationData: ChargingStationConfiguration = this.getConfigurationFromFile()
           ? cloneObject<ChargingStationConfiguration>(this.getConfigurationFromFile()!)
           : {};
-        if (this.getStationInfoPersistentConfiguration() && this.stationInfo) {
+        if (this.stationInfo?.stationInfoPersistentConfiguration === true && this.stationInfo) {
           configurationData.stationInfo = this.stationInfo;
         } else {
           delete configurationData.stationInfo;
         }
-        if (this.getOcppPersistentConfiguration() && this.ocppConfiguration?.configurationKey) {
+        if (
+          this.stationInfo?.ocppPersistentConfiguration === true &&
+          this.ocppConfiguration?.configurationKey
+        ) {
           configurationData.configurationKey = this.ocppConfiguration.configurationKey;
         } else {
           delete configurationData.configurationKey;
@@ -1713,7 +1636,7 @@ export class ChargingStation {
           buildChargingStationAutomaticTransactionGeneratorConfiguration(this),
         );
         if (
-          !this.getAutomaticTransactionGeneratorPersistentConfiguration() ||
+          !this.stationInfo?.automaticTransactionGeneratorPersistentConfiguration ||
           !this.getAutomaticTransactionGeneratorConfiguration()
         ) {
           delete configurationData.automaticTransactionGenerator;
@@ -1735,34 +1658,35 @@ export class ChargingStation {
               stationInfo: configurationData.stationInfo,
               configurationKey: configurationData.configurationKey,
               automaticTransactionGenerator: configurationData.automaticTransactionGenerator,
+              ...(this.connectors.size > 0 && {
+                connectorsStatus: configurationData.connectorsStatus,
+              }),
+              ...(this.evses.size > 0 && { evsesStatus: configurationData.evsesStatus }),
             } as ChargingStationConfiguration),
           )
           .digest('hex');
         if (this.configurationFileHash !== configurationHash) {
-          AsyncLock.acquire(AsyncLockType.configuration)
-            .then(() => {
-              configurationData.configurationHash = configurationHash;
-              const measureId = `${FileType.ChargingStationConfiguration} write`;
-              const beginId = PerformanceStatistics.beginMeasure(measureId);
-              const fileDescriptor = openSync(this.configurationFile, 'w');
-              writeFileSync(fileDescriptor, JSON.stringify(configurationData, null, 2), 'utf8');
-              closeSync(fileDescriptor);
-              PerformanceStatistics.endMeasure(measureId, beginId);
-              this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
-              this.sharedLRUCache.setChargingStationConfiguration(configurationData);
-              this.configurationFileHash = configurationHash;
-            })
-            .catch((error) => {
-              handleFileException(
-                this.configurationFile,
-                FileType.ChargingStationConfiguration,
-                error as NodeJS.ErrnoException,
-                this.logPrefix(),
-              );
-            })
-            .finally(() => {
-              AsyncLock.release(AsyncLockType.configuration).catch(Constants.EMPTY_FUNCTION);
-            });
+          AsyncLock.runExclusive(AsyncLockType.configuration, () => {
+            configurationData.configurationHash = configurationHash;
+            const measureId = `${FileType.ChargingStationConfiguration} write`;
+            const beginId = PerformanceStatistics.beginMeasure(measureId);
+            writeFileSync(
+              this.configurationFile,
+              JSON.stringify(configurationData, undefined, 2),
+              'utf8',
+            );
+            PerformanceStatistics.endMeasure(measureId, beginId);
+            this.sharedLRUCache.deleteChargingStationConfiguration(this.configurationFileHash);
+            this.sharedLRUCache.setChargingStationConfiguration(configurationData);
+            this.configurationFileHash = configurationHash;
+          }).catch((error) => {
+            handleFileException(
+              this.configurationFile,
+              FileType.ChargingStationConfiguration,
+              error as NodeJS.ErrnoException,
+              this.logPrefix(),
+            );
+          });
         } else {
           logger.debug(
             `${this.logPrefix()} Not saving unchanged charging station configuration file ${
@@ -1791,7 +1715,7 @@ export class ChargingStation {
 
   private getOcppConfigurationFromFile(): ChargingStationOcppConfiguration | undefined {
     const configurationKey = this.getConfigurationFromFile()?.configurationKey;
-    if (this.getOcppPersistentConfiguration() === true && configurationKey) {
+    if (this.stationInfo?.ocppPersistentConfiguration === true && configurationKey) {
       return { configurationKey };
     }
     return undefined;
@@ -1822,31 +1746,34 @@ export class ChargingStation {
             skipBufferingOnError: true,
           });
           if (this.isRegistered() === false) {
-            this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount;
+            this.stationInfo?.registrationMaxRetries !== -1 && ++registrationRetryCount;
             await sleep(
               this?.bootNotificationResponse?.interval
-                ? this.bootNotificationResponse.interval * 1000
+                ? secondsToMilliseconds(this.bootNotificationResponse.interval)
                 : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL,
             );
           }
         } while (
           this.isRegistered() === false &&
-          (registrationRetryCount <= this.getRegistrationMaxRetries()! ||
-            this.getRegistrationMaxRetries() === -1)
+          (registrationRetryCount <= this.stationInfo.registrationMaxRetries! ||
+            this.stationInfo?.registrationMaxRetries) === -1
         );
       }
       if (this.isRegistered() === true) {
+        this.emit(ChargingStationEvents.registered);
         if (this.inAcceptedState() === true) {
+          this.emit(ChargingStationEvents.accepted);
           await this.startMessageSequence();
         }
       } else {
         logger.error(
-          `${this.logPrefix()} Registration failure: max retries reached (${this.getRegistrationMaxRetries()}) or retry disabled (${this.getRegistrationMaxRetries()})`,
+          `${this.logPrefix()} Registration failure: max retries reached or retry disabled (${this
+            .stationInfo?.registrationMaxRetries})`,
         );
       }
       this.wsConnectionRestarted = false;
       this.autoReconnectRetryCount = 0;
-      parentPort?.postMessage(buildUpdatedMessage(this));
+      this.emit(ChargingStationEvents.updated);
     } else {
       logger.warn(
         `${this.logPrefix()} Connection to OCPP server through ${this.wsConnectionUrl.toString()} failed`,
@@ -1876,7 +1803,7 @@ export class ChargingStation {
         this.started === true && (await this.reconnect());
         break;
     }
-    parentPort?.postMessage(buildUpdatedMessage(this));
+    this.emit(ChargingStationEvents.updated);
   }
 
   private getCachedRequest(messageType: MessageType, messageId: string): CachedRequest | undefined {
@@ -1896,7 +1823,7 @@ export class ChargingStation {
 
   private async handleIncomingMessage(request: IncomingRequest): Promise<void> {
     const [messageType, messageId, commandName, commandPayload] = request;
-    if (this.getEnableStatistics() === true) {
+    if (this.stationInfo.enableStatistics === true) {
       this.performanceStatistics?.addRequestStatistic(commandName, messageType);
     }
     logger.debug(
@@ -1987,7 +1914,7 @@ export class ChargingStation {
             logger.error(`${this.logPrefix()} ${errorMsg}`);
             throw new OCPPError(ErrorType.PROTOCOL_ERROR, errorMsg);
         }
-        parentPort?.postMessage(buildUpdatedMessage(this));
+        this.emit(ChargingStationEvents.updated);
       } else {
         throw new OCPPError(
           ErrorType.PROTOCOL_ERROR,
@@ -2058,7 +1985,7 @@ export class ChargingStation {
   }
 
   private getEnergyActiveImportRegister(connectorStatus: ConnectorStatus, rounded = false): number {
-    if (this.getMeteringPerTransaction() === true) {
+    if (this.stationInfo?.meteringPerTransaction === true) {
       return (
         (rounded === true
           ? Math.round(connectorStatus.transactionEnergyActiveImportRegisterValue!)
@@ -2076,7 +2003,7 @@ export class ChargingStation {
     return stationTemplate?.useConnectorId0 ?? true;
   }
 
-  private async stopRunningTransactions(reason = StopTransactionReason.NONE): Promise<void> {
+  private async stopRunningTransactions(reason?: StopTransactionReason): Promise<void> {
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
         if (evseId === 0) {
@@ -2108,18 +2035,6 @@ export class ChargingStation {
     return Constants.DEFAULT_CONNECTION_TIMEOUT;
   }
 
-  // -1 for unlimited, 0 for disabling
-  private getAutoReconnectMaxRetries(): number | undefined {
-    return (
-      this.stationInfo.autoReconnectMaxRetries ?? Configuration.getAutoReconnectMaxRetries() ?? -1
-    );
-  }
-
-  // 0 for disabling
-  private getRegistrationMaxRetries(): number | undefined {
-    return this.stationInfo.registrationMaxRetries ?? -1;
-  }
-
   private getPowerDivider(): number {
     let powerDivider = this.hasEvses ? this.getNumberOfEvses() : this.getNumberOfConnectors();
     if (this.stationInfo?.powerSharedByConnectors) {
@@ -2142,6 +2057,23 @@ export class ChargingStation {
     }
   }
 
+  private getMaximumPower(stationInfo?: ChargingStationInfo): number {
+    return (stationInfo ?? this.stationInfo).maximumPower!;
+  }
+
+  private getCurrentOutType(stationInfo?: ChargingStationInfo): CurrentType {
+    return (stationInfo ?? this.stationInfo).currentOutType!;
+  }
+
+  private getVoltageOut(stationInfo?: ChargingStationInfo): number {
+    const defaultVoltageOut = getDefaultVoltageOut(
+      this.getCurrentOutType(stationInfo),
+      this.logPrefix(),
+      this.templateFile,
+    );
+    return (stationInfo ?? this.stationInfo).voltageOut ?? defaultVoltageOut;
+  }
+
   private getAmperageLimitation(): number | undefined {
     if (
       isNotEmptyString(this.stationInfo?.amperageLimitationOcppKey) &&
@@ -2206,24 +2138,25 @@ export class ChargingStation {
     }
 
     // Start the ATG
-    if (this.getAutomaticTransactionGeneratorConfiguration()?.enable === true) {
+    if (this.getAutomaticTransactionGeneratorConfiguration().enable === true) {
       this.startAutomaticTransactionGenerator();
     }
     this.wsConnectionRestarted === true && this.flushMessageBuffer();
   }
 
   private async stopMessageSequence(
-    reason: StopTransactionReason = StopTransactionReason.NONE,
+    reason?: StopTransactionReason,
+    stopTransactions = this.stationInfo?.stopTransactionsOnStopped,
   ): Promise<void> {
     // Stop WebSocket ping
     this.stopWebSocketPing();
     // Stop heartbeat
     this.stopHeartbeat();
     // Stop ongoing transactions
+    stopTransactions && (await this.stopRunningTransactions(reason));
+    // Stop the ATG
     if (this.automaticTransactionGenerator?.started === true) {
       this.stopAutomaticTransactionGenerator();
-    } else {
-      await this.stopRunningTransactions(reason);
     }
     if (this.hasEvses) {
       for (const [evseId, evseStatus] of this.evses) {
@@ -2279,7 +2212,7 @@ export class ChargingStation {
         if (this.isWebSocketConnectionOpened() === true) {
           this.wsConnection?.ping();
         }
-      }, webSocketPingInterval * 1000);
+      }, secondsToMilliseconds(webSocketPingInterval));
       logger.info(
         `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
           webSocketPingInterval,
@@ -2357,10 +2290,6 @@ export class ChargingStation {
     }
   }
 
-  private getReconnectExponentialDelay(): boolean {
-    return this.stationInfo?.reconnectExponentialDelay ?? false;
-  }
-
   private async reconnect(): Promise<void> {
     // Stop WebSocket ping
     this.stopWebSocketPing();
@@ -2371,13 +2300,14 @@ export class ChargingStation {
       this.stopAutomaticTransactionGenerator();
     }
     if (
-      this.autoReconnectRetryCount < this.getAutoReconnectMaxRetries()! ||
-      this.getAutoReconnectMaxRetries() === -1
+      this.autoReconnectRetryCount < this.stationInfo.autoReconnectMaxRetries! ||
+      this.stationInfo?.autoReconnectMaxRetries === -1
     ) {
       ++this.autoReconnectRetryCount;
-      const reconnectDelay = this.getReconnectExponentialDelay()
-        ? exponentialDelay(this.autoReconnectRetryCount)
-        : this.getConnectionTimeout() * 1000;
+      const reconnectDelay =
+        this.stationInfo?.reconnectExponentialDelay === true
+          ? exponentialDelay(this.autoReconnectRetryCount)
+          : secondsToMilliseconds(this.getConnectionTimeout());
       const reconnectDelayWithdraw = 1000;
       const reconnectTimeout =
         reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0
@@ -2395,17 +2325,16 @@ export class ChargingStation {
       );
       this.openWSConnection(
         {
-          ...(this.stationInfo?.wsOptions ?? {}),
           handshakeTimeout: reconnectTimeout,
         },
         { closeOpened: true },
       );
       this.wsConnectionRestarted = true;
-    } else if (this.getAutoReconnectMaxRetries() !== -1) {
+    } else if (this.stationInfo?.autoReconnectMaxRetries !== -1) {
       logger.error(
         `${this.logPrefix()} WebSocket connection retries failure: maximum retries reached (${
           this.autoReconnectRetryCount
-        }) or retries disabled (${this.getAutoReconnectMaxRetries()})`,
+        }) or retries disabled (${this.stationInfo?.autoReconnectMaxRetries})`,
       );
     }
   }